go 闭包实现机制
最近在 hack viewcore,需要了解闭包的实现机制,来完成逆向的类型推导,所以搞了几个小例子,分析了闭包的实现机制,简单记录一下的。
运行时表示
在运行时,闭包是一个 GC 对象,可以用下面的结构来表示。
type closure struct {
pc func
arg1
arg2
...
}
pc 比较好理解,就是对应函数的入口地址。arg1, arg2, ... 则是闭包函数引用的上层局部变量。
如下示例:
func genClosure(a int, b int) func(int) bool {
return func(n int) bool {
return n*a-b > 0
}
}
生成的汇编如下,核心就是构造一个新的 GC 对象:
LEAQ runtime.rodata+58080(SB), AX ## 0x1099b00 <_type.*+0xe2e0>
CALL runtime.newobject(SB)
LEAQ main.genClosure.func1(SB), CX
MOVQ CX, 0(AX)
MOVQ 0x20(SP), CX
MOVQ CX, 0x8(AX)
MOVQ 0x28(SP), CX
MOVQ CX, 0x10(AX)此时生成的闭包则等效于这样的 struct 对象:
type closure struct {
pc func
a int
b int
}
调用过程
调用闭包,跟普通的函数调用基本类似,只是把闭包对象放到 DX 寄存器。
例如下面的示例:
f := genClosure(10, 100)
v := f(10)
生成如下汇编:
MOVL $0xa, AX
MOVL $0x64, BX
CALL main.genClosure(SB)
MOVQ 0(AX), CX
MOVQ AX, DX
MOVL $0xa, AX
CALL CX我们可以看到:
先从闭包对象中取出函数入口地址,写入 CX 寄存器 将闭包对象写入 DX 寄存器 CALL CX,调用闭包函数
总结
简单的来说,闭包的实现也比较简单,通过一个 GC 对象,将函数入口地址,以及引用的局部变量,都装进来,就是一个闭包对象了。调用的时候,将闭包对象,作为函数的第四个参数,也就是使用 DX 寄存器传参。