美文网首页LLVM
01 - LLVM Pass 实现 C函数 插桩

01 - LLVM Pass 实现 C函数 插桩

作者: xiongzh | 来源:发表于2019-10-10 21:57 被阅读0次

    01. llvm 在 iOS 插桩

    网上我就搜到这一篇文章介绍使用 LLVM Pass 的方式,来实现函数插桩。

    但是按照他文章的一步步坐下来,发现各种报错,要不就是 pass 无法被 xcode 加载,反正遇到了不少的问题。

    而且该作者很多核心的部分都没有写出来,不知道是因为太简单,还是不想让别人知道 ~

    经历了两周多左右的研究,以 LLVM 小白的身份,终于跑起来了 ~ 不容易 ~

    注意:如下内容,最好需要你已经有 LLVM 环境!!!

    02. C 函数 手动插桩

    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()
    {
      long b = _ly_fun_b();
      printf("hello world!");
      _ly_fun_e("main", b);
      return 0;
    }
    

    手动插桩实际上就是将预先写好的开始和结束两个函数的调用,手动写入到 main() 执行前 和 执行后。

    那么自动插桩了?
    就是不需要手动在 main() 中进行修改,而是在编译的过程中,自动将上面2个函数,插入到 main() 执行前 和 执行后。

    那么如何在编译的时候,在 main() 插入其他的 函数调用 了?

    03. 通过 LLVM Pass 自动插桩的过程

    001.jpg

    这里涉及一些 LLVM IR 的东西,在这里就不列举了,可以自己去学习下。
    总之,我们可以在如上 橘黄色 Optimizer (优化器)这部分,来完成 自动插桩 的目的。

    而 LLVM 留给开发者在 Optimizer (优化器)阶段 读写 IR 的方式也有很多,比如:Pass、ClangPlugin ..
    至于 Pass 和 ClangPlugin 对比,可以自己去看下,本文通过 Pass 来实现。

    04. 实现 函数插桩 Pass

    04-01. $LLVM_SOURCE/lib/Transforms/MyPlacement/MyPlacementPass.cpp

    #include <string>
    #include <system_error>
    #include <iostream>
    
    #include "llvm/Support/raw_ostream.h"
    #include "llvm/Pass.h"
    #include "llvm/IR/Module.h"
    #include "llvm/IR/Function.h"
    #include "llvm/IR/Instructions.h"
    #include "llvm/IR/LegacyPassManager.h"
    #include "llvm/IR/IRBuilder.h"
    //#include "llvm/IR/BasicBlock.h"
    //#include "llvm/IR/Constants.h"
    //#include "llvm/IR/LLVMContext.h"
    //#include "llvm/IR/Type.h"
    #include "llvm/Transforms/IPO/PassManagerBuilder.h"
    
    using namespace llvm;
    
    struct MyPlacementPass : public FunctionPass
    {
      static char ID;
      MyPlacementPass() : FunctionPass(ID){}
        
      bool runOnFunction(Function &F) override
      {
        printf("------------- runOnFunction --------------\n");
        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;
      }
    };
    
    char MyPlacementPass::ID = 0;
    
    // Automatically enable the pass.
    // http://adriansampson.net/blog/clangpass.html
    static void registerSkeletonPass(const PassManagerBuilder &, legacy::PassManagerBase &PM) 
    {
      PM.add(new MyPlacementPass());
    }
    
    static RegisterStandardPasses RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible, registerSkeletonPass);
    

    如上代码基本与前面的博客文章中的差不太多,但是底部的几句代码,那篇博客中并没有贴出来,这也是我经历了一两周摸索 ~

    不加上这几句,编译出来的 dylib ,总是无法被 clang 加载执行。

    并且注意如上代码是用的 LLVM 8.0.1 版本的代码,对应 LLVM github 源码仓库中的是 release_80 分支的代码。

    我当时使用的是 LLVM github 源码仓库中的是 release_90 分支代码,如上代码一直编译报错,发现很多的 API 都已经变了,搜搜了很多文档才找到新的 API 使用。

    这是 Pass 的毛病,因为依赖的是 libclang 库,而这个库 API 是不稳定的,经常会发生变化。

    04-02. $LLVM_SOURCE/lib/Transforms/MyPlacement/CMakeLists.txt

    add_llvm_library( MyPlacementPass MODULE
      MyPlacementPass.cpp
    )
    

    04-03. 构建生成 Pass dylib

    image.png
    ╭─xiongzenghui at xiongzenghui的MacBook Pro in ~/llvm-8.0.1.src/build/Debug/lib using ‹ruby-2.4.1›
    ╰─○ ll | grep -i 'MyPlacementPass'
    -rwxr-xr-x  1 xiongzenghui  staff   149K 10 10 22:52 MyPlacementPass.dylib
    

    MyPlacementPass.dylib 这个动态库就是要使用到的 LLVM Pass。

    05. 新建一个 Mac App 项目, 使用 Pass

    05-01. Build Settings 中新增 CC、CXX 两项用户定义设置, 指向自己编译的 clang

    image.png

    05-02. Build Settings 修改 OTHER_CFLAGS 加载 pass dylib

    image.png

    05-03. 如果编译报错: unknown argument index ...

    image.png

    Build Settings 栏目中搜索 index , 然后将 Enable Index-Wihle-Building Functionality 默认值 Default 改为 NO

    image.png

    05-04. 运行 mac app

    image.png

    🎉 🎉 🎉 成功自动插桩

    如上 Mac App 项目的代码: https://gitee.com/garywade/LLVMDemo.git

    参考的资料

    https://blog.csdn.net/qq_23599965/article/details/86620219
    https://www.leadroyal.cn/?p=647
    https://www.jianshu.com/p/4d392b16d831

    相关文章

      网友评论

        本文标题:01 - LLVM Pass 实现 C函数 插桩

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