Skip to content

回调原函数原理

Jakegogo edited this page Oct 16, 2024 · 2 revisions

trampoline模型

image


【原函数】: 需要切面函数, 或者叫需要被代理的函数,function_A 【代理函数】: 实现AOP逻辑地方, 代理函数内部有可能会调用原函数, function_B 【修复的原函数】: 原函数的拷贝, 没有被替换因此是正确的, function_A_gate 【占位函数】: 用来存放未被被替换字节码的原函数的字节码

参考开源框架gomonkey进行改造升级。

image

图左:gomonkey实现原理 图右:go-instrument实现原理

优化工作:

  • 从简单的JMP实现改为trampoline模式
  • 实现动态hook,无需指定函数类型和签名(采用类型注册的模式)
  • hook状态自检,更加安全可靠
  • 对私有函数或方法的hook支持
  • 静态hook对函数签名校验,兼容依赖库的版本升级

GO语言AOP实现具体步骤:

  1. 获取任意函数(【原函数】)的地址:
    1. 读取go的moduledata, 使用golink访问go语言库的内置私有变量moduledata
    2. 维护函数{函数名称:函数地址}的mapping
  2. 将【原函数】的调用跳转到【代理函数】: 将gomonkey升级: 改造为trampoline方式
    1. 定义【占位函数】预留空间用来存放的原函数字节码
    2. 必须有字节码替换的基础: 将原函数开头替换JMP到【代理函数】的指令
    3. 原函数被破坏需要修复: 字节码替换之前先将【原函数】字节码拷贝到另外一块区域
    4. 相对地址修复: 拷贝之后相对地址的偏移量需要修正
    5. 因此解决了并发调用问题: gomonkey原先的实现方式需要还原字节码之后才能回调原函数
  3. 构造替换函数 - 【代理函数】(采用wrap方式) >
    1. 使用go的reflect.makeFunc对【原函数】进行wrap, 这样就能用wrap的函数等价替换掉【被代理函数】
  4. 调用【修复的原函数】 - 动态反射 : 因为原函数的类型或签名是任意的
    1. 获取【原函数】参数签名: (调用【修复的原函数】时传递参数)
      1. 获取函数类型信息: 编写函数扫描注册工具,扫描源代码内的函数定义, 生成注册到类型表代码: 维护{函数地址:函数签名对象}的mapping
      2. 获取方法类型信息: 读取go编译期内置的变量.rodata里面的方法类型表
    2. 动态调用【修复的原函数】: 使用go的reflect.Call(args)

使用案例

静态代理代码调用

image

动态代理代码调用

image 执行结果: image

golang函数拦截原理介绍:

https://onedrive.live.com/View.aspx?resid=7804A3BDAEB13A9F!58083&authkey=!AKVlLS9s9KYh07s (由共建成员@Miliao提供)