iOS要分析函數(shù)的執(zhí)行時(shí)間,一種辦法是hook住objc_msgSend方法,實(shí)現(xiàn)比較簡(jiǎn)單,但是有個(gè)局限性,只對(duì)objective-c方法有效,對(duì)c函數(shù)和block就不行了。還有一種辦法是插樁,可以統(tǒng)計(jì)所有的函數(shù),但是,實(shí)現(xiàn)比較復(fù)雜。
比如我們要統(tǒng)計(jì)main函數(shù)的執(zhí)行時(shí)間,可以創(chuàng)建兩個(gè)函數(shù)_ly_fun_b, _ly_fun_e,然后插入到main函數(shù)的開始和結(jié)束的位置
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;
}
那么怎么寫這個(gè)Pass。
首先創(chuàng)建Pass的工程
由于LLVM使用CMake構(gòu)建的,所以我們要?jiǎng)?chuàng)建CMake的工程。比如工程名叫FunTime,可以模仿LLVM的例子Hello,在llvm的源碼目錄中,llvm/lib/Transforms目錄創(chuàng)建目錄,F(xiàn)unTime,添加FunTime.cpp和CMakeLists.txt文件。然后把目錄添加到Transforms中的CMakeLists.txt中。
CMakeLists.txt可以拷貝LLVM的例子Hello中,修改文件為要編譯文件為FunTime.cpp,F(xiàn)unTime.cpp中創(chuàng)建類FunTime繼承自FunctionPass。實(shí)現(xiàn)runOnFunction函數(shù)。
struct FunTime : public FunctionPass{
static char ID;
FunTime() : FunctionPass(ID){}
bool runOnFunction(Function &F) override{
return false;
}
};
cmake一下,這樣我們工程就創(chuàng)建成功了,可以編譯輸出FunTime.dylib文件了。下面我們將完善工程。
插入開始函數(shù)
- 找到開始函數(shù)插入的位置,就是在函數(shù)第一條指令之前。
- 得到_ly_fun_b函數(shù),先得到LLVM的Context,然后創(chuàng)建函數(shù)Type,包括返回值和參數(shù)。
然后把函數(shù)的定義插入到模塊中。函數(shù)中就能使用了。 - 插入_ly_fun_b函數(shù),在第一條指令之前插入上面得到的開始函數(shù)。
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;
}
插入結(jié)束函數(shù)
結(jié)束指令要遍歷函數(shù)中每一條指令,判斷是否是ReturnInst類表示的返回指令,在這條指令前插入結(jié)束函數(shù)。這個(gè)函數(shù)有兩個(gè)參數(shù),開始函數(shù)傳來的時(shí)間和當(dāng)前函數(shù)名。
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);
}
}
}
}
為了防止死循環(huán),在_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
因?yàn)閄code的限制不能加載插件,所用只能使用自己編譯的clang,在設(shè)置中增加CC=/paht/clang自定義。
使用pod
我封裝了pod庫,直接運(yùn)行就可以,簡(jiǎn)化了設(shè)置下,不需要的時(shí)候去掉去掉pod就行了。
pod 'LYFunTime', :configurations => ['Debug'], :git=>'https://github.com/lyleyang/LYFunTime.git'