说明
计算机中的栈一个很大的应用场合使用在函数调用中。我们这里简单说说golang的协程栈布局,学过计算机的应该都不会陌生。
程序事例
package main
func f(a, b int) int {
sum := 0
sum = a + b
for i := 0; i < 1000; i++ {
println("sum is:", sum)
}
return sum
}
func main() {
f(1, 2)
}
汇编代码
(gdb) disas
Dump of assembler code for function main.main:
0x00000000004010b0 <main.main+0>: mov %fs:0xfffffffffffffff8,%rcx
0x00000000004010b9 <main.main+9>: cmp 0x10(%rcx),%rsp
0x00000000004010bd <main.main+13>: jbe 0x4010de <main.main+46>
0x00000000004010bf <main.main+15>: sub $0x18,%rsp
0x00000000004010c3 <main.main+19>: movq $0x1,(%rsp)
0x00000000004010cb <main.main+27>: movq $0x2,0x8(%rsp)
0x00000000004010d4 <main.main+36>: callq 0x401000 <main.f>
0x00000000004010d9 <main.main+41>: add $0x18,%rsp
0x00000000004010dd <main.main+45>: retq
0x00000000004010de <main.main+46>: callq 0x44abd0 <runtime.morestack_noctxt>
0x00000000004010e3 <main.main+51>: jmp 0x4010b0 <main.main>
0x00000000004010e5 <main.main+53>: add %al,(%rax)
0x00000000004010e7 <main.main+55>: add %al,(%rax)
0x00000000004010e9 <main.main+57>: add %al,(%rax)
0x00000000004010eb <main.main+59>: add %al,(%rax)
0x00000000004010ed <main.main+61>: add %al,(%rax)
0x00000000004010ef <main.main+63>: add %ah,-0x75(%rax,%rcx,2)
End of assembler dump.
(gdb) disas
Dump of assembler code for function main.f:
0x0000000000401000 <main.f+0>: mov %fs:0xfffffffffffffff8,%rcx
0x0000000000401009 <main.f+9>: cmp 0x10(%rcx),%rsp
0x000000000040100d <main.f+13>: jbe 0x401097 <main.f+151>
0x0000000000401013 <main.f+19>: sub $0x20,%rsp
0x0000000000401017 <main.f+23>: mov 0x28(%rsp),%rbx
0x000000000040101c <main.f+28>: mov 0x30(%rsp),%rbp
0x0000000000401021 <main.f+33>: add %rbp,%rbx
0x0000000000401024 <main.f+36>: mov %rbx,0x10(%rsp)
0x0000000000401029 <main.f+41>: xor %eax,%eax
0x000000000040102b <main.f+43>: mov %rax,0x18(%rsp)
0x0000000000401030 <main.f+48>: cmp $0x3e8,%rax
0x0000000000401036 <main.f+54>: jge 0x401088 <main.f+136>
......
0x0000000000401088 <main.f+136>: mov 0x10(%rsp),%rbx
0x000000000040108d <main.f+141>: mov %rbx,0x38(%rsp)
0x0000000000401092 <main.f+146>: add $0x20,%rsp
执行过程中stack变化情况
在main调用f()时,协程堆栈情况:
注意:这里的返回地址是由call指令自动push至esp所指向内存,而参数内容则是由调用者main函数设置的,如下代码:
// we have 2 argument and 1 return value
// so must reserve 24 bytes in amd64(0x18)
0x00000000004010bf <main.main+15>: sub $0x18,%rsp
0x00000000004010c3 <main.main+19>: movq $0x1,(%rsp)
0x00000000004010cb <main.main+27>: movq $0x2,0x8(%rsp)
0x00000000004010d4 <main.main+36>: callq 0x401000 <main.f>
在f函数内部执行时,会扩充当前stack,为了临时存储一些本地变量,如sum等,则f执行时堆栈情况如下:
可以看到为本地变量sum和i自动在栈上分配了存储空间,计算sum,然后将sum的值存储到f()返回值该去的地方((esp) + 0x38)
可以简单看看main.f()的主要汇编代码
// sub esp to allocate space for local variable
0x0000000000401013 <main.f+19>: sub $0x20,%rsp
// get parameters, compute and store sum
0x0000000000401017 <main.f+23>: mov 0x28(%rsp),%rbx
0x000000000040101c <main.f+28>: mov 0x30(%rsp),%rbp
0x0000000000401021 <main.f+33>: add %rbp,%rbx
// store sum in (esp) + 0x10
0x0000000000401024 <main.f+36>: mov %rbx,0x10(%rsp)
// for loop assemble code
0x0000000000401029 <main.f+41>: xor %eax,%eax
0x000000000040102b <main.f+43>: mov %rax,0x18(%rsp)
0x0000000000401030 <main.f+48>: cmp $0x3e8,%rax
0x0000000000401036 <main.f+54>: jge 0x401088 <main.f+136>
......
// store sum into return value address(esp + 0x38)
// and shrink stack((%esp) + 0x20) and return to main
0x0000000000401088 <main.f+136>: mov 0x10(%rsp),%rbx
0x000000000040108d <main.f+141>: mov %rbx,0x38(%rsp)
0x0000000000401092 <main.f+146>: add $0x20,%rsp
参考资料
http://www.cs.nyu.edu/courses/fall04/V22.0201-003/ia32_chap_03.pdf有疑问加站长微信联系(非本文作者)