Appearance
程序的机器级表示
考情分析
2014、2017、2019、2023、2024 年均以综合题形式考查了 C 代码到汇编的翻译。高频考点:循环语句的汇编实现、过程调用的栈帧布局、call/ret 的操作细节。
选择语句的机器级表示
if-else 语句的通用翻译模式(goto 形式):
t = test_expr;
if (!t) goto false;
then_statement
goto done;
false:
else_statement
done:编译器使用 cmp/test 设置标志位,再用条件转移指令实现分支选择。
示例:
c
int get_cont(int *p1, int *p2) {
if (p1 > p2) return *p2;
else return *p1;
}参数 p1 在 [ebp+8],p2 在 [ebp+12],返回值放 eax。
asm
mov eax, dword ptr [ebp+8] ; 加载 p1
mov edx, dword ptr [ebp+12] ; 加载 p2
cmp eax, edx ; 比较 p1 和 p2
jbe .L1 ; 若 p1 <= p2,跳到 L1(无符号比较)
mov eax, dword ptr [edx] ; 取 *p2 作为返回值
jmp .L2
.L1:
mov eax, dword ptr [eax] ; 取 *p1 作为返回值
.L2:指针比较按无符号整数处理,因此使用 jbe(无符号"小于等于")。
循环语句的机器级表示
三种循环都可以转换为"条件测试 + 条件跳转"的形式。编译器通常统一转换为 do-while 结构。
do-while
loop:
body_statement
t = test_expr;
if (t) goto loop;循环体至少执行一次。
while
先判断再进入循环体,转换为带前置判断的 do-while:
t = test_expr;
if (!t) goto done;
loop:
body_statement
t = test_expr;
if (t) goto loop;
done:for
for (init; test; update) body 等价于:
init;
t = test;
if (!t) goto done;
loop:
body;
update;
t = test;
if (t) goto loop;
done:示例:
c
int nsum_for(int n) {
int i, result = 0;
for (i = 1; i <= n; i++)
result += i;
return result;
}asm
mov ecx, dword ptr [ebp+8] ; 加载参数 n
mov eax, 0 ; result = 0
mov edx, 1 ; i = 1
cmp edx, ecx ; 比较 i 与 n
jg .L2 ; 若 i > n,跳过循环
.L1:
add eax, edx ; result += i
add edx, 1 ; i++
cmp edx, ecx ; 比较 i 与 n
jle .L1 ; 若 i <= n,继续循环
.L2:result 分配到 eax(同时是返回值),i 分配到 edx。int 型比较属于有符号,使用 jg/jle。
过程调用的机器级表示
调用过程
假设过程 P(调用者)调用过程 Q(被调用者):
- P 将参数放到 Q 能访问的位置(压入栈中)
- P 执行
call Q:将返回地址(call 的下一条指令地址)压栈,跳转到 Q - Q 建立栈帧:
push ebp; mov ebp, esp; sub esp, N - Q 执行函数体
- Q 将返回值放入 eax,执行
leave(或等价操作)恢复栈帧 - Q 执行
ret:弹出返回地址,跳回 P
栈帧结构
栈从高地址向低地址增长。EBP 指向当前栈帧底部(保存旧 EBP 的位置),ESP 指向栈顶。
高地址
┌─────────────────┐
│ 参数 n │ [ebp+12]
│ 参数 1 │ [ebp+8]
│ 返回地址 │ [ebp+4] ← call 指令压入
├─────────────────┤
│ 旧 EBP │ [ebp] ← push ebp
├─────────────────┤
│ 局部变量 1 │ [ebp-4]
│ 局部变量 2 │ [ebp-8]
│ ... │
│ 被调用者保存寄存器│
│ 为下次 call 准备 │
│ 的参数区 │
└─────────────────┘ ← ESP(栈顶)
低地址寄存器保存约定
| 类别 | 寄存器 | 谁负责保存 |
|---|---|---|
| 调用者保存 | EAX, ECX, EDX | P 在 call 前保存(如果之后还要用) |
| 被调用者保存 | EBX, ESI, EDI | Q 使用前先 push,返回前 pop 恢复 |
完整示例
c
int add(int x, int y) { return x + y; }
int caller() {
int temp1 = 125, temp2 = 80;
int sum = add(temp1, temp2);
return sum;
}caller 的汇编代码:
asm
push ebp ; 保存调用者的 EBP
mov ebp, esp ; 建立新栈帧
sub esp, 24 ; 分配局部变量和参数空间
mov dword ptr [ebp-12], 125 ; temp1 = 125
mov dword ptr [ebp-8], 80 ; temp2 = 80
mov eax, dword ptr [ebp-8] ; 加载 temp2
mov [esp+4], eax ; 参数 2 入栈(高地址)
mov eax, dword ptr [ebp-12] ; 加载 temp1
mov [esp], eax ; 参数 1 入栈(低地址)
call add ; 调用 add,返回值在 eax
mov dword ptr [ebp-4], eax ; sum = 返回值
mov eax, dword ptr [ebp-4] ; 将 sum 作为 caller 的返回值
leave ; mov esp,ebp + pop ebp
ret ; 弹出返回地址,返回add 的汇编代码(含机器码):
asm
8048469: 55 push ebp
804846a: 89 e5 mov ebp, esp
804846c: 8b 45 0c mov eax, dword ptr [ebp+12] ; 加载 y
804846f: 8b 55 08 mov edx, dword ptr [ebp+8] ; 加载 x
8048472: 8d 04 02 lea eax, [edx+eax] ; eax = x + y
8048475: 5d pop ebp
8048476: c3 retadd 函数用 lea 指令完成加法运算(lea 计算地址但不访存,这里利用地址计算实现 x+y)。
leave 指令
等价于:
asm
mov esp, ebp ; 释放局部变量空间,ESP 回到保存旧 EBP 的位置
pop ebp ; 恢复调用者的 EBP考点清单
- if-else:cmp + 条件跳转(真分支顺序执行,假分支 goto)
- 循环:for/while 都可以转换为 do-while + 前置判断
- call = push 返回地址 + jmp;ret = pop 返回地址 + jmp
- 栈帧:EBP 指向底部(固定),ESP 指向栈顶(动态变化)
- 参数通过
[ebp+8]、[ebp+12]等访问(32 位系统中参数每个占 4 字节) - 返回值放在 EAX 中
- EAX/ECX/EDX 为调用者保存,EBX/ESI/EDI 为被调用者保存
- leave = mov esp,ebp + pop ebp