课程主页:http://www.cs.cmu.edu/afs/cs/academic/class/15213-f15/www/schedule.html

课程资料:https://github.com/EugeneLiu/translationCSAPP

课程视频:https://www.bilibili.com/video/av31289365/

这一讲介绍了机器级编程3:过程。

栈结构

x86-64栈

  • 通过栈规范管理内存区域
  • 栈顶在低地址
  • 寄存器%rsp为最小的堆栈地址
    • 即“顶部”元素的地址

Push

  • pushq Src
  • 将%rsp减少8
  • 在给定的地址%rsp处写入操作数

Pop

  • popq Dest
  • 在%rsp指定的地址处读取值
  • 将%rsp增加8
  • 将值存储在Dest(必须是寄存器)

调用约定

传递控制

例子

考虑如下代码

void multstore
 (long x, long y, long *dest) {
    long t = mult2(x, y);
    *dest = t;
}
0000000000400540 <multstore>:
  # x in %rdi, y in %rsi, dest in %rdx
  400540: push   %rbx			# Save %rbx
  400541: mov    %rdx,%rbx		# Save dest
  400544: callq  400550 <mult2>	# mult2(x,y)
  # t in %rax
  400549: mov    %rax,(%rbx)	# Save at dest
  40054c: pop    %rbx			# Restore %rbx
  40054d: retq					# Return
long mult2
  (long a, long b)
{
  long s = a * b;
  return s;
}
0000000000400550 <mult2>:
  # a in %rdi, b in %rsi
  400550:  mov    %rdi,%rax	# a 
  400553:  imul   %rsi,%rax	# a * b
  # s in %rax
  400557:  retq				# Return
过程控制流
  • 使用栈来支持过程调用和返回
  • 程序调用:call label
    • 将返回地址推入堆栈
    • 跳转到标签
  • 返回地址:
    • call之后下一条指令的地址
  • 程序返回:ret
    • 从栈中的弹出地址
    • 跳转到地址

传递数据

过程数据流

6个以内的参数通过寄存器传递,6个以上的部分存储在栈中,返回值在%rax中。

管理局部数据

  • 支持递归的语言
    • 例如C,Pascal,Java
    • 代码必须为“Reentrant”
      • 单个过程的多个同时实例化
    • 需要一些地方来存储每个实例的状态
      • 参数
      • 局部变量
      • 返回指针
  • 栈规则
    • 在有限的时间内说明给定程序所需的状态
      • 从call到return的时间
    • callee先于回caller返回
  • 栈被分配在栈帧(Stack Frames)
    • 单过程实例化的状态
栈帧
  • 内容
    • 返回信息
    • 本地存储(如果需要)
    • 临时空间(如果需要)
  • 管理
    • 进入过程时分配空间
      • “Set-up”代码
      • 包括call指令
    • 返回时取消分配
      • “Finish”代码
      • 包括ret指令

x86-64/Linux栈帧
  • 当前栈帧(“顶部”至底部)
    • Argument build:要调用的函数的参数
    • Local Variables(如果没法存储在寄存器中)
    • Saved Registers
    • Old frame pointer(可选)
  • Caller栈帧
    • Return address
      • 由call指令推送
    • 此次call的参数

寄存器保存约定
  • 当过程yoo call who:
    • yoo是caller
    • who是callee
  • 可以将寄存器用于临时存储吗?
  • 约定
    • “Caller Saved”
      • caller在call之前将临时值保存在其帧中
    • “Callee Saved”
      • callee在使用前将临时值保存在其帧中
      • callee在返回到caller前将这些值恢复
x86-64/Linux寄存器用处
  • %rax
    • 返回值
    • 也是caller-saved
    • 可以通过过程修改
  • %rdi,…,%r9
    • 参数
    • caller-saved
    • 可以通过过程修改
  • %r10,%r11
    • caller-saved
    • 可以通过过程修改
  • %rbx,%r12,%r13,%r14
    • callee-saved
    • callee必须保存并还原
  • %rbp
    • callee-saved
    • callee必须保存并还原
    • 可用作帧指针
    • 可以混合搭配
  • %rsp
    • 特殊形式的callee-saved
    • 退出程序后恢复到原始值

递归过程

  • 无需特殊考虑即可处理
    • 栈帧意味着每个函数调用都有专用存储
      • 保存的寄存器和局部变量
      • 保存的返回指针
    • 寄存器保存约定可防止一个函数调用破坏另一个数据
      • 除非C代码明确这样做(例如,第9课中的缓冲区溢出)
    • 堆栈规则遵循调用/返回模式
      • 如果P呼叫Q,则Q在P之前返回
      • 后进先出
  • 也适用于相互递归
    • P call Q; Qcall P

x86-64过程总结

  • 重要事项
    • 栈是使得过程调用/返回正确的数据结构
    • 如果P call Q,则Q在P之前返回
  • 递归(和相互递归)由正常调用机制处理
    • 可以安全地将值存储在本地栈帧和被调用者保存的寄存器中
    • 将函数参数放在栈顶
    • 结果返回%rax
  • 指针是值的地址
    • 在栈上或全局