前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >LLVM-插桩

LLVM-插桩

作者头像
Helloted
发布2022-06-08 10:31:06
2K0
发布2022-06-08 10:31:06
举报
文章被收录于专栏:Helloted
0、Clang插桩原理

Clang在优化过程中,可以自己定义Pass来优化代码

1、编译插件的工具准备

1.1 新建文件夹llvm,下载LLVM(预计大小 648.2 M)

代码语言:javascript
复制
$ git clone https://git.llvm.org/git/llvm.git/

1.2 在llvm/tools文件夹下载clang(预计大小 240.6 M)

代码语言:javascript
复制
$ cd llvm/tools
$ git clone https://git.llvm.org/git/clang.git/

1.3 安装编译工具ninja和cmake

代码语言:javascript
复制
$ brew install cmake
$ brew install ninja

1.4 在llvm同级目录下新建llvm_build和llvm_release两个文件夹,llvm是编译起始文件夹,llvm_release则是编译结果文件夹

1.5 在llvm_build文件夹下设定编译结果路径

代码语言:javascript
复制
$ cd llvm_build
$ cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=’编译结果路径,也就是llvm_release文件夹’
(参考:cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=‘/Users/xxxx/LLVMProject/llvm_release’)

1.6 在llvm_build路径下依次执行编译和安装命令

代码语言:javascript
复制
$ ninja
代码语言:javascript
复制
$ ninja install

1.7 在llvm源码同级目录下新建文件夹llvm_xcode

1.8 在llvm_xcode路径下,编译xcode

代码语言:javascript
复制
$ cd llvm_xcode
$ cmake -G Xcode ../llvm

最终效果:

1.9 打开LLVM.xcodeproj并用Automaticallly Create Schemes

1.10 然后选择ALL_BUILD进行编译,编译过程需要几十分钟不等。

2、编写PASS插件

在$LLVM_SOURCE/lib/Transforms/ 目录下有一个Hello的自带Demo,可以仿照Hello编写我们自己的PASS

2.1 在Hello同级目录下创建文件夹MyPass,并在MyPass文件夹下创建CMakeLists.txt和MyPass.cpp两个文件

2.2 在$LLVM_SOURCE/lib/Transforms/MyPass/CMakeLists.txt内添加内容,需要注意的是add_llvm_library后面的MyPass是将要生成的Target的名称,自带的Hello文件夹内添加的是LLVMHello名称,所以Target是LLVMHello。

代码语言:javascript
复制
add_llvm_library( MyPass MODULE BUILDTREE_ONLY
  MyPass.cpp  
  DEPENDS
  intrinsics_gen
  PLUGIN_TOOL
  opt
)

2.3 在$LLVM_SOURCE/lib/Transforms/CMakeLists.txt内把我们的pass添加进去

代码语言:javascript
复制
add_subdirectory(MyPass)

2.4 回到llvm_xcode文件夹重新生成xcode

代码语言:javascript
复制
cmake -G Xcode ../llvm

2.5 再次打开LLVM.xcodeproj就能找到MyPass的Target。

2.6 在MyPass.cpp内编写PASS内容

代码语言:javascript
复制
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/DebugLoc.h"
#include "llvm/IR/DebugInfo.h"
#include <string>

using namespace llvm;

namespace {
    struct MyFunctionPass : public FunctionPass {
        static char ID;
        MyFunctionPass() : FunctionPass(ID) {}
        bool runOnFunction(Function &F) override {
            if (F.getName().startswith("my_mark_executed_func")){ // 如果已经插入则不用再次插入
                  return false;
            }
            LLVMContext &context = F.getParent()->getContext();
            BasicBlock &bb = F.getEntryBlock();
            IRBuilder<> bbBuilder(&bb);
            IRBuilder<> contextBuilder(context);
            
            Instruction *beginInst = dyn_cast<Instruction>(bb.begin()); // 所有函数的起始位置
            FunctionType *type = FunctionType::get(Type::getVoidTy(context), {Type::getInt8PtrTy(context),}, false); // 函数的返回类型和参数类型
            FunctionCallee beginFun = F.getParent()->getOrInsertFunction("my_mark_executed_func", type); // 获取函数
            CallInst *inst = contextBuilder.CreateCall(beginFun,{bbBuilder.CreateGlobalStringPtr(F.getName())}); // 构造函数
            inst->insertBefore(beginInst); // 插入标记函数
            
            auto SP = F.getSubprogram();
            DebugLoc DL = DebugLoc::get(SP->getScopeLine(), 0, SP);
            inst->setDebugLoc(DL); //设置DebugLoc,给debugger使用
            return false;
        }
    };
}

char MyFunctionPass::ID = 0;
static RegisterPass<MyFunctionPass> X("func-coverage", "A pass that can check function coverage.", false, false);

static RegisterStandardPasses Y(
    PassManagerBuilder::EP_EarlyAsPossible,
    [](const PassManagerBuilder &Builder,
       legacy::PassManagerBase &PM) { PM.add(new MyFunctionPass()); });

2.7 build MyPass可以得到Mypass.dylib

3、使用PASS

3.1 新建一个普通xcode project,并添加一个hook.c的文件,里面是刚才在pass里添加的标记函数

代码语言:javascript
复制
void my_mark_executed_func(char *name ) {
    printf("方法 %s \n",name);
}

3.2 将bulid system改为旧版构建方法,File-Project Setting-Build System

3.3 在build setting里添加MyPass.dylib。

代码语言:javascript
复制
-Xclang -load -Xclang Pass路径

3.4 在User-Defined内添加CC和CXX,值分别是刚刚构建的clang的路径,让xode使用clang的替代版本

3.5 将Enable Index-While-Building Functionality值改为NO,否则会报错

3.6 执行可以获取到log输出方法执行情况

4、插桩的其他思路:SanitizerCoverage

LLVM本身提供了一种覆盖函数的方案:SanitizerCoverage

在官方文档中就有说明,如果在编译配置里有-fsantize-cover等参数,就会在每个函数的边缘插入一个sanitizer-cov—trace-guard函数,也就是说,每个方法函数执行的时候,都会调用一次这个插入的函数,所以我们可以通过这个插入函数,来获取方法函数名,从而获取启动过程中的符号顺序。

5、插桩的其他思路:SanitizerCoverage

OC 的方法调用在底层都是objc_msgSend函数。所以,如果能够Hook它,获取每个调用objc_msgSend的方法名,也能够达到插桩效果。objc_msgSend是C函数而且是系统函数,C 函数在编译链接时就确定了函数指针的地址偏移量(Offset),虽然这个偏移量在编译好的可执行文件中是固定的,但是可执行文件每次被重新装载到内存中时被系统分配的起始地址(在 lldb 中用命令image List获取)是不断变化的。所以,我们就可以借助facebook公司的一个开源库fishhook来达到系统函数与自己定义的函数进行了交换;

苹果采用了PIC(Position-independent code)技术成功让 C 的底层也能有动态的表现:

  • 编译时在 Mach-O 文件 _DATA 段的符号表中为每一个被引用的系统 C 函数建立一个指针(8字节的数据,放的全是0),这个指针用于动态绑定时重定位到共享库中的函数实现。
  • 在运行时当系统 C 函数被第一次调用时会动态绑定一次,然后将 Mach-O 中的 _DATA 段符号表中对应的指针,指向外部函数(其在共享库中的实际内存地址)。

fishhook 正是利用了 PIC 技术做了这么两个操作:

  • 将指向系统方法(外部函数)的指针重新进行绑定指向内部函数/自定义 C 函数。
  • 将内部函数的指针在动态链接时指向系统方法的地址。

这样就把系统方法与自己定义的方法进行了交换,达到 HOOK 系统 C 函数(共享库中的)的目的。

下面是是我们的hook大概过程

代码语言:javascript
复制
static void hook_objc_msgSend() {
    /// begin之前保存objc_msgSend的参数
    save()
    /// 将objc_msgSend执行的下一个函数地址传递给beginHook的第二个参数x0 self, x1 _cmd, x2: lr address
    __asm volatile ("mov x2, lr\n");
    /// 执行beginHook
    call(blr, & beginHook)
    /// 恢复objc_msgSend参数
    load()
    // 执行
    call(blr, orig_objc_msgSend)
    /// after之前保存objc_msgSend执行完成的参数----------
    save()
    /// 调用 endHook----------
    call(blr, & endHook)
    /// 将endHook返回的参数放入lr,恢复调用beginHook前的lr地址
    __asm volatile ("mov lr, x0\n");
    /// 恢复objc_msgSend执行完成的参数
    load()
    /// 方法结束,继续执行lr
    ret()
}

我们需要使用汇编指令对寄存器进行存取和执行,stp存储原参数,blr调用执行,ldp加载指令。这个过程与OC方法的Method-Swilling类似。最终可以达到hook_objc_msgSend函数与原来的objc_msgsend函数进行调换的目的。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0、Clang插桩原理
  • 1、编译插件的工具准备
  • 2、编写PASS插件
  • 3、使用PASS
  • 4、插桩的其他思路:SanitizerCoverage
  • 5、插桩的其他思路:SanitizerCoverage
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档