8086 CPU寄存器
均为16位。
寄存器名 | AX | BX | CX | DX | SP | BP | SI | DI | DS | ES | SS | CS | IP |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
作用 | 通用寄存器 | 栈区段地址寄存器 | 数据地址寄存器 | 栈区偏移地址寄存器 | 段地址寄存器 | 偏移地址寄存器 |
内存寻址
- 两个寄存器操作只能相加,不能相减,例如
mov ebx, [esi+eax] ; 有效
mov ebx, [esi - eax] ; 无效,不能做减法
- 最多只有两个寄存器参与地址计算
调用约定(Calling Convention)
调用约定是极为重要的部分。它关系到子函数调用的规则:如何调用及如何返回。
有很多种调用约定,以C语言调用约定为例:
C语言调用约定(C Calling Convention)
- 强依赖硬件提供的支持
- 基于push, pop, call, ret等指令
- 参数通过栈传递,子过程用到的变量也放在栈上。
调用约定通常可分为两个方面:调用方规则(Caller Rules)和被调方规则(Callee Ruler)。
调用方规则(Caller Rules)
C语言调用约定规定,在子过程(函数)调用之前,调用方应该:
- 保存应由调用方保存的寄存器(caller-saved registers):EAX, ECX, EDX。
这几个寄存器可能会被被调用方(callee)修改,所以先保存它们,以便调用结 束后恢复栈的状态。
- 将需要传给子过程的参数入栈(push onto stack)
参数按逆序 push 入栈(最后一个参数先入栈)。由于栈是向下生长的,第一个参数 会被存储在最低地址(这个特性使得变长参数列表成为可能)。此处存疑
- 使用
call
指令,调用子过程(函数)
call
先将返回地址 push 到栈上,然后开始执行子过程代码。子过程代码需要遵守的 callee rules。
子过程返回后(call
执行结束之后),被调用方会将返回值放到 EAX
寄存器,调用方 可以从中读取。为恢复机器状态,调用方需要做:
-
从栈上删除传递的参数
栈恢复到准备发起调用之前的状态。
-
恢复由调用方保存的寄存器(
EAX
,ECX
,EDX
)—— 从栈上 pop 出来调用方可以认为,除这三个之外,其他寄存器的值没有被修改过。
被调方规则(Callee Ruler)
-
将寄存器
EBP
的值入栈,然后 copyESP
toEBP
push ebp mov ebp, esp
-
在栈上为局部变量分配空间
栈自顶向下生长,故随着变量的分配,栈顶指针不断减小。
-
保存应有被调用方保存(
callee-saved
)的寄存器 —— 将他们压入栈。包括EBX
,EDI
,ESI
以上工作完成,就可以执行子过程的代码了。当子过程返回后,必须做以下工作:
-
将返回值保存在
EAX
-
恢复应由被调用方保存的寄存器(
EDI
,ESI
) —— 从栈上 pop 出来 -
释放局部变量
-
恢复调用方 base pointer
EBP
—— 从栈上 pop 出来 -
最后,执行
ret
,返回给调用方 (caller)
http://arthurchiao.art/blog/x86-asm-guide-zh/
http://www.cs.virginia.edu/~evans/cs216/guides/x86.html