-
Notifications
You must be signed in to change notification settings - Fork 2
回调原函数原理
Jakegogo edited this page Oct 16, 2024
·
2 revisions
【原函数】: 需要切面函数, 或者叫需要被代理的函数,function_A 【代理函数】: 实现AOP逻辑地方, 代理函数内部有可能会调用原函数, function_B 【修复的原函数】: 原函数的拷贝, 没有被替换因此是正确的, function_A_gate 【占位函数】: 用来存放未被被替换字节码的原函数的字节码
图左:gomonkey实现原理 图右:go-instrument实现原理
优化工作:
- 从简单的JMP实现改为trampoline模式
- 实现动态hook,无需指定函数类型和签名(采用类型注册的模式)
- hook状态自检,更加安全可靠
- 对私有函数或方法的hook支持
- 静态hook对函数签名校验,兼容依赖库的版本升级
- 获取任意函数(【原函数】)的地址:
- 读取go的moduledata, 使用golink访问go语言库的内置私有变量moduledata
- 维护函数{函数名称:函数地址}的mapping
- 将【原函数】的调用跳转到【代理函数】: 将gomonkey升级: 改造为trampoline方式
- 定义【占位函数】预留空间用来存放的原函数字节码
- 必须有字节码替换的基础: 将原函数开头替换JMP到【代理函数】的指令
- 原函数被破坏需要修复: 字节码替换之前先将【原函数】字节码拷贝到另外一块区域
- 相对地址修复: 拷贝之后相对地址的偏移量需要修正
- 因此解决了并发调用问题: gomonkey原先的实现方式需要还原字节码之后才能回调原函数
- 构造替换函数 - 【代理函数】(采用wrap方式) >
- 使用go的reflect.makeFunc对【原函数】进行wrap, 这样就能用wrap的函数等价替换掉【被代理函数】
- 调用【修复的原函数】 - 动态反射 : 因为原函数的类型或签名是任意的
- 获取【原函数】参数签名: (调用【修复的原函数】时传递参数)
- 获取函数类型信息: 编写函数扫描注册工具,扫描源代码内的函数定义, 生成注册到类型表代码: 维护{函数地址:函数签名对象}的mapping
- 获取方法类型信息: 读取go编译期内置的变量.rodata里面的方法类型表
- 动态调用【修复的原函数】: 使用go的reflect.Call(args)
- 获取【原函数】参数签名: (调用【修复的原函数】时传递参数)
执行结果:
https://onedrive.live.com/View.aspx?resid=7804A3BDAEB13A9F!58083&authkey=!AKVlLS9s9KYh07s (由共建成员@Miliao提供)