本文汇编语言均使用AT&T风格。
1. 栈
栈是一种线性数据结构,它遵循后进先出(LIFO,Last In First Out)原则。
栈是处理器执行汇编代码时使用的特殊内存区域,是函数调用的重要组成部分。
栈是内存中的一段连续空间,由RSP/RBP寄存器标记,硬件指令PUSH/POP直接操作栈,增长方向从内存高地址到低地址。栈内存由操作系统为程序分配。
RBP(Base Pointer)栈帧基指针,指向栈帧的“底部”(实际上是栈帧中最高的内存地址)。用作访问函数参数和局部变量的参考点。
RSP(Stack Pointer)栈指针,指向栈的“顶部”,这里是栈的最低的内存地址,随着栈的增长,RSP会被递减。
通过以下代码示例说明栈和栈帧,C代码如下:
int sum(int a1,int a2,int a3, int a4, int a5,int a6,int a7,int a8) {
return a1+a2+a3+a4+a5+a6+a7+a8;
}
int sumcalc() {
return sum(1,2,3,4,5,6,7,8);
}通过gcc命令编译:“`gcc -g -o test test.c“`
再通过GDB进行反汇编函数 sumcalc和sum:
(gdb) disassemble/r sumcalc
Dump of assembler code for function sumcalc:
0x000000000000116d <+0>: f3 0f 1e fa endbr64
0x0000000000001171 <+4>: 55 push %rbp
0x0000000000001172 <+5>: 48 89 e5 mov %rsp,%rbp
0x0000000000001175 <+8>: 6a 08 push $0x8
0x0000000000001177 <+10>: 6a 07 push $0x7
0x0000000000001179 <+12>: 41 b9 06 00 00 00 mov $0x6,%r9d
0x000000000000117f <+18>: 41 b8 05 00 00 00 mov $0x5,%r8d
0x0000000000001185 <+24>: b9 04 00 00 00 mov $0x4,%ecx
0x000000000000118a <+29>: ba 03 00 00 00 mov $0x3,%edx
0x000000000000118f <+34>: be 02 00 00 00 mov $0x2,%esi
0x0000000000001194 <+39>: bf 01 00 00 00 mov $0x1,%edi
0x0000000000001199 <+44>: e8 8b ff ff ff call 0x1129 <sum>
0x000000000000119e <+49>: 48 83 c4 10 add $0x10,%rsp
0x00000000000011a2 <+53>: c9 leave
0x00000000000011a3 <+54>: c3 ret
(gdb) disassemble/r sum
Dump of assembler code for function sum:
0x0000000000001129 <+0>: f3 0f 1e fa endbr64
0x000000000000112d <+4>: 55 push %rbp
0x000000000000112e <+5>: 48 89 e5 mov %rsp,%rbp
0x0000000000001131 <+8>: 89 7d fc mov %edi,-0x4(%rbp)
0x0000000000001134 <+11>: 89 75 f8 mov %esi,-0x8(%rbp)
0x0000000000001137 <+14>: 89 55 f4 mov %edx,-0xc(%rbp)
0x000000000000113a <+17>: 89 4d f0 mov %ecx,-0x10(%rbp)
0x000000000000113d <+20>: 44 89 45 ec mov %r8d,-0x14(%rbp)
0x0000000000001141 <+24>: 44 89 4d e8 mov %r9d,-0x18(%rbp)
0x0000000000001145 <+28>: 8b 55 fc mov -0x4(%rbp),%edx
0x0000000000001148 <+31>: 8b 45 f8 mov -0x8(%rbp),%eax
0x000000000000114b <+34>: 01 c2 add %eax,%edx
0x000000000000114d <+36>: 8b 45 f4 mov -0xc(%rbp),%eax
0x0000000000001150 <+39>: 01 c2 add %eax,%edx
0x0000000000001152 <+41>: 8b 45 f0 mov -0x10(%rbp),%eax
0x0000000000001155 <+44>: 01 c2 add %eax,%edx
0x0000000000001157 <+46>: 8b 45 ec mov -0x14(%rbp),%eax
0x000000000000115a <+49>: 01 c2 add %eax,%edx
0x000000000000115c <+51>: 8b 45 e8 mov -0x18(%rbp),%eax
0x000000000000115f <+54>: 01 c2 add %eax,%edx
0x0000000000001161 <+56>: 8b 45 10 mov 0x10(%rbp),%eax
0x0000000000001164 <+59>: 01 c2 add %eax,%edx
0x0000000000001166 <+61>: 8b 45 18 mov 0x18(%rbp),%eax
0x0000000000001169 <+64>: 01 d0 add %edx,%eax
0x000000000000116b <+66>: 5d pop %rbp
0x000000000000116c <+67>: c3 ret通过上述反汇编代码可以得出如图所示的栈&帧结构如下:

2. 栈帧
当调用函数时会建立一个栈帧以支持该函数。栈帧由函数序言(prologue)建立,通过函数后记(epilogue)进行清理。
函数序言和后记均由编译器生成,固定模板如下:
函数序言&后记:
sum:
# 函数序言
push %rbp # 保存caller函数的rbp
mov %rsp, %rbp # 生成当前函数callee的帧基址,callee函数帧的起始位置
# 函数后记
mov %rbp, %rsp # 释放当前函数callee的栈帧
pop %rbp # 恢复caller的栈帧
ret # 从栈中弹出返回地址并跳到该地址,控制权返回给caller如上图所示,栈帧中通常包含以下组成部分:
1)返回地址
2)保存的基指针(RBP)
3)局部变量
4)函数参数(当函数参数的数量大于6个时使用栈传递)