llvm 在 iOS 插樁

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'

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容