iOS要分析函数的执行时间,一种办法是hook住objc_msgSend方法,实现比较简单,但是有个局限性,只对objective-c方法有效,对c函数和block就不行了。还有一种办法是插桩,可以统计所有的函数,但是,实现比较复杂。
比如我们要统计main函数的执行时间,可以创建两个函数_ly_fun_b, _ly_fun_e,然后插入到main函数的开始和结束的位置
long _ly_fun_b(){
struct timeval star;
gettimeofday(&star, NULL);
long b = star.tv_sec * 1000000 + star.tv_usec;
return b;
}
void _ly_fun_e(char *name, long b){
struct timeval end;
gettimeofday(&end, NULL);
long e = end.tv_sec * 1000000 + end.tv_usec;
long t = e - b;
printf("%s %ld us\n",name, t);
}
int main(){
printf("hello world!");
return 0;
}
那么怎么写这个Pass。
首先创建Pass的工程
由于LLVM使用CMake构建的,所以我们要创建CMake的工程。比如工程名叫FunTime,可以模仿LLVM的例子Hello,在llvm的源码目录中,llvm/lib/Transforms目录创建目录,FunTime,添加FunTime.cpp和CMakeLists.txt文件。然后把目录添加到Transforms中的CMakeLists.txt中。
CMakeLists.txt可以拷贝LLVM的例子Hello中,修改文件为要编译文件为FunTime.cpp,FunTime.cpp中创建类FunTime继承自FunctionPass。实现runOnFunction函数。
struct FunTime : public FunctionPass{
static char ID;
FunTime() : FunctionPass(ID){}
bool runOnFunction(Function &F) override{
return false;
}
};
cmake一下,这样我们工程就创建成功了,可以编译输出FunTime.dylib文件了。下面我们将完善工程。
插入开始函数
- 找到开始函数插入的位置,就是在函数第一条指令之前。
- 得到_ly_fun_b函数,先得到LLVM的Context,然后创建函数Type,包括返回值和参数。
然后把函数的定义插入到模块中。函数中就能使用了。 - 插入_ly_fun_b函数,在第一条指令之前插入上面得到的开始函数。
LLVMContext &context = F.getParent()->getContext();
BasicBlock &bb = F.getEntryBlock();
Instruction *beginInst = dyn_cast<Instruction>(bb.begin());
FunctionType *type = FunctionType::get(Type::getInt64Ty(context), {}, false);
Constant *beginFun = F.getParent()->getOrInsertFunction("_ly_fun_b", type);
Value *beginTime = nullptr;
if (Function *fun = dyn_cast<Function>(beginFun)) {
CallInst *inst = CallInst::Create(fun);
inst->insertBefore(beginInst);
beginTime = inst;
}
插入结束函数
结束指令要遍历函数中每一条指令,判断是否是ReturnInst类表示的返回指令,在这条指令前插入结束函数。这个函数有两个参数,开始函数传来的时间和当前函数名。
for (Function::iterator I = F.begin(), E = F.end(); I != E; ++I) {
BasicBlock &BB = *I;
for (BasicBlock::iterator I = BB.begin(), E = BB.end(); I != E; ++I){
ReturnInst *IST = dyn_cast<ReturnInst>(I);
if (IST){
FunctionType *type = FunctionType::get(Type::getVoidTy(context), {Type::getInt8PtrTy(context),Type::getInt64Ty(context)}, false);
Constant *s = BB.getModule()->getOrInsertFunction("_ly_fun_e", type);
if (Function *fun = dyn_cast<Function>(s)) {
IRBuilder<> builder(&BB);
CallInst *inst = CallInst::Create(fun, {builder.CreateGlobalStringPtr(BB.getParent()->getName()), beginTime});
inst->insertBefore(IST);
}
}
}
}
为了防止死循环,在_ly_fun_b和_ly_fun_e中就不用插装了,完整代码如下
struct FunTime : public FunctionPass{
static char ID;
FunTime() : FunctionPass(ID){}
bool runOnFunction(Function &F) override{
if (F.getName().startswith("_ly_fun")) {
return false;
}
LLVMContext &context = F.getParent()->getContext();
BasicBlock &bb = F.getEntryBlock();
Instruction *beginInst = dyn_cast<Instruction>(bb.begin());
FunctionType *type = FunctionType::get(Type::getInt64Ty(context), {}, false);
Constant *beginFun = F.getParent()->getOrInsertFunction("_ly_fun_b", type);
Value *beginTime = nullptr;
if (Function *fun = dyn_cast<Function>(beginFun)) {
CallInst *inst = CallInst::Create(fun);
inst->insertBefore(beginInst);
beginTime = inst;
}
for (Function::iterator I = F.begin(), E = F.end(); I != E; ++I) {
BasicBlock &BB = *I;
for (BasicBlock::iterator I = BB.begin(), E = BB.end(); I != E; ++I){
ReturnInst *IST = dyn_cast<ReturnInst>(I);
if (IST){
FunctionType *type = FunctionType::get(Type::getVoidTy(context), {Type::getInt8PtrTy(context),Type::getInt64Ty(context)}, false);
Constant *s = BB.getModule()->getOrInsertFunction("_ly_fun_e", type);
if (Function *fun = dyn_cast<Function>(s)) {
IRBuilder<> builder(&BB);
CallInst *inst = CallInst::Create(fun, {builder.CreateGlobalStringPtr(BB.getParent()->getName()), beginTime});
inst->insertBefore(IST);
}
}
}
}
return false;
}
};
集成Xcode
因为Xcode的限制不能加载插件,所用只能使用自己编译的clang,在设置中增加CC=/paht/clang自定义。
使用pod
我封装了pod库,直接运行就可以,简化了设置下,不需要的时候去掉去掉pod就行了。
pod 'LYFunTime', :configurations => ['Debug'], :git=>'https://github.com/lyleyang/LYFunTime.git'