llvm 在 iOS 插桩

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(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(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(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(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(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(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(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(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'

参考

  • https://www.cs.cornell.edu/~asampson/blog/clangpass.html

你可能感兴趣的:(llvm 在 iOS 插桩)