llvm 在 iOS 插桩

作者: lyleyang | 来源:发表于2019-04-10 08:27 被阅读224次

    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'
    

    参考

    相关文章

      网友评论

        本文标题:llvm 在 iOS 插桩

        本文链接:https://www.haomeiwen.com/subject/headmqtx.html