CSAPP-note-3
x86-64 Stack
Region of memory managed with stack discipline.Grows toward lower address.Register %rsp contains lowest stack address.(address of “top” element)
Push
pushq Src
- Fetch operand at Src
- Decrement %rsp by 8
- Write operand at address given by %rsp
Pop
popq Dest
- Read Value at address given by %rsp
- Increment %rsp by 8
- Store value at Dest (must be register)
Calling Conventions
Passing control
在下面的C程序中,我们在main函数调用了multsotre,在multsotre中调用了mult2。我们通过这个例子来观察函数调用的过程。
1 | #include <stdio.h> |
mult2
1 | movq %rdi, %rax # a |
multstore
1 | pushq %rbx # Save %rbx |
我们使用栈来支持过程的调用与返回。
过程调用:call label
- Push return address on stack
- Jump to label
返回地址:
- 函数调用的下一条指令的地址。
- 在我们这个例子中,multstore进行call时,会把movq %rax, (%rbx)这条指令的地址压入栈中。
过程返回:ret
- Pop address from stack(此前压入的返回地址)
- Jump to adress
我们使用gdb来更加直观地查看具体的调用过程与指令的跳转。
1 | 0x0000000000001159 <+4>: push %rbx |
当我们执行到<+8>call时,程序首先会将rsp-8,也就是申请8个字节的栈空间(因为64位下,指令地址的大小就是8个字节),用来保存call的下一条指令的地址,在本例子中是0x0000000000001162<+13> mov,接着将%rip(保存了当前待执行指令的地址)设置为mult2首条指令的地址。
1 | 0x0000000000001149 <+0>: endbr64 |
在本例子中%rip被设置成0x0000000000001149。然后随着程序的执行%rip跳转到0x0000000000001154 <+11> ret时,代表过程结束,我们将%rsp保存的地址pop出,并且赋值给%rip。这样程序的执行回到了multstore中的
0x0000000000001162 <+13>: mov %rax,(%rbx)
并且接着往下执行,这样就完成了一次简单的函数调用。
Passing data
Registers(only first 6 arguments):%rdi, %rsi, %rdx, %rcx, %r8, %r9
Return value:%rax
stack:Arg 7, Arg 8,…,Arg n.
还是以上面那个例子来说。
1 | multsotre: |
Managing local data
Linux Stack Frame
Example:incr
1 | long incr(long *p, long val) { |
暂时无法在飞书文档外展示此内容
下表给出了寄存器的保存规则。
寄存器 | 用途 | 状态 | 是否可被修改 | 备注 |
---|---|---|---|---|
%rax | 返回值 | 调用者保存 | 是 | |
%rdi | 第一个参数 | 调用者保存 | 是 | 后续参数 %rsi 到 %r9 也遵循相同的规则 |
%rsi | 第二个参数 | 调用者保存 | 是 | |
… | 后续参数 | 调用者保存 | 是 | 后续参数 %rdi 到 %r9 也遵循相同的规则 |
%r9 | 第九个参数 | 调用者保存 | 是 | |
%r10 | 通用寄存器 | 调用者保存 | 是 | |
%r11 | 通用寄存器 | 调用者保存 | 是 | |
%rbx | 通用寄存器 | 被调用者保存 | 否(需保存) | 被调用者必须在函数结束前恢复其原始值 |
%r12 | 通用寄存器 | 被调用者保存 | 否(需保存) | 被调用者必须在函数结束前恢复其原始值 |
%r13 | 通用寄存器 | 被调用者保存 | 否(需保存) | 被调用者必须在函数结束前恢复其原始值 |
%r14 | 通用寄存器 | 被调用者保存 | 否(需保存) | 被调用者必须在函数结束前恢复其原始值 |
%rbp | 帧指针 | 被调用者保存 | 否(需保存) | 可以作为帧指针使用,被调用者必须在函数结束前恢复其原始值 |
%rsp | 栈指针 | 特殊被调用者保存 | 否(需恢复) | 在程序退出时恢复到原始值 |
Recursion
- 无需特别考虑即可处理
- 栈帧意味着每个函数调用都有私有存储。
- 保存寄存器和局部变量。
- 保存返回指针。
- 寄存器保存约定可以防止一个函数调用破坏另一个函数的数据。
- 除非C代码明确这样做(例如,第9讲中的缓冲区溢出)。
- 栈的纪律遵循调用/返回模式。
- 如果P调用Q,那么Q在P之前返回。
- 后进先出(LIFO)。
- 栈帧意味着每个函数调用都有私有存储。
- 也适用于相互递归
- P调用Q;Q调用P。