Skip to content
封幼麟 edited this page May 23, 2020 · 3 revisions

设计实现这个模块的初衷是为了在运行阶段实现golang函数钩子。实现的原理是基于运行阶段指令修改,将原本函数入口处的指令替换为一条跳转指令,跳转到钩子函数去执行。

至于函数入口处被覆盖的指令,会被拷贝到另外分配的可执行内存空间。如果这段指令中没有相对寻址操作的话,就能够在新的内存空间直接执行,也就意味着不需要拷贝回原地址;对于使用相对寻址操作的指令,无法在新的内存空间直接执行,需要对相对寻址的偏移量进行修正,或者拷贝回原地址才行。因为相对寻址的修正实现起来较为复杂,目前不能支持。

涉及到运行阶段代码修改,所以需要格外注意并发安全问题,最好在init中应用钩子。如果程序代码中需要禁用、启用、还原钩子,需要做好同步处理,以免当前协程对代码进行修改时,其他协程正在执行相应的代码,进而造成异常。

本模块用作研究工具的话,应该会比较适合。可以对第三方模块甚至是golang runtime的一些函数下钩子,打印一些调试信息,还是比较方便的。下面就来看两个关于golang特性研究的实际例子:

验证变量逃逸

通过对golang runtime的了解,我们得知运行阶段分配单个对象是通过runtime.newobject函数,代码中的注释也表明它就是内置的new函数的具体实现。函数原型如下所示:

func newobject(typ *_type) unsafe.Pointer

该函数需要一个typ参数,现阶段可以不用关心。我们只需要实现一个wrapper函数,通过hookingo用wrapper替换掉newobject,在wrapper中打印一条信息,然后再调用原newobject函数就行了。通过打印的信息,我们就能知道newobject函数被调用,从而可以验证发生了变量逃逸。

获取闭包类型

通过对可执行文件的反编译,我们知道golang的closure实际上就是编译器构造出来的一个struct,其第一个字段总是一个地址指向是闭包函数,后续字段就是闭包的捕获列表了。runtime中有个funcval结构,相当于每个closure结构的头部都是嵌入了一个funcval。

但是通过反编译来查看闭包结构太繁琐,我们希望找到更加简洁直观的方法。在上个示例中验证变量逃逸时,我们hook了runtime.newobject函数,实际上运行阶段分配闭包对象也要通过该函数。还记得那个不需要关心的typ参数吗?现在可以关心一下了,拿到这个参数再结合反射技术,就可以直接打印出闭包对象的类型信息了,不要太直观。

Clone this wiki locally