Lecture 16:知识 - 高级语言程序与机器级代码之间的对应
Chinese-language Computer Science 408 note: Lecture 16:知识 - 高级语言程序与机器级代码之间的对应. Original source available where applicable.
On this page
This note is currently available in Chinese. The original wording, formulas and diagrams are preserved to avoid an unverified technical translation.
描述:408 只要求结合 C 看懂关键 x86 汇编和相应机器码,不要求手工把 C 完整翻译成汇编。关键抓手是:Intel 语法“目的在左、源在右”、条件码、典型 32 位栈帧与
call/ret;具体寄存器保存、传参与对齐属于调用约定/ABI,不能当成所有 x86 程序都固定不变的 ISA 规则。
4.1 考查边界与 x86 基础
- 题目会给出简单 C、汇编或机器码,要求识别常见指令、选择结构、循环和函数调用;可结合给定编码分析汇编与机器码的对应关系,也会考查指令格式/寻址。这里不主张“同一功能只有唯一机器码”:不同编码或助记写法可能表达相同语义。
- 通常不要求人工完成 C
->汇编/机器码的逐行翻译;若出现非 x86 汇编,题目一般会提供充分注释。 - Intel 格式通常写作
op destination, source,第一个是目的操作数,第二个是源操作数,数据从右向左流动。操作数可在寄存器、主存或指令立即数中。
4.1.1 Intel 与 AT&T 语法边界
同一条 x86 机器指令可用不同汇编语法书写,语法差异不等于 ISA 不同:
| 项目 | Intel 语法 | AT&T 语法 |
|---|---|---|
| 操作数顺序 | 目的, 源 |
源, 目的 |
| 寄存器 | eax |
%eax |
| 立即数 | 10 或 0x0A |
$10 或 $0x0A |
| 内存 | [ebx+4] |
4(%ebx) |
| 宽度 | 常用 byte/word/dword ptr |
常在助记符后加 b/w/l 等后缀 |
本讲后文统一采用 Intel 语法;读题时先确认语法,不能只凭逗号两侧位置判断数据方向。
4.2 寄存器与标志
4.2.1 常用寄存器
| 类别 | 寄存器 | 典型作用 |
|---|---|---|
| 通用 | EAX、EBX、ECX、EDX | 计算、暂存、参数/返回值等;32 位寄存器名常以 E 开头 |
| AX 分割 | AH/AL,BH/BL,CH/CL,DH/DL | 16 位 AX/BX/CX/DX 的高/低 8 位可独立使用 |
| 指针/变址 | EBP、ESP、ESI、EDI | 典型帧指针代码中 EBP 作栈帧基址,ESP 指向栈顶,ESI/EDI 可作源/目的变址 |
| 指令 | EIP/IP | 下一条指令位置 |
| 段 | CS、DS、SS、ES | 代码、数据、栈和附加段 |
EBP与ESP也可作一般算术逻辑寄存器,但其常见约定用途是访问栈。ESP 动态指向栈顶;建立帧指针的函数中 EBP 通常在本次调用期间保持不变。优化编译器可以省略帧指针并把 EBP 当通用寄存器,此时改由 ESP 或其他方式定位栈内对象。- 以 EAX 为例:EAX 为 32 bit,AX 为低 16 bit,AH/AL 为 AX 的高/低 8 bit。
4.2.2 重要标志位
| 标志 | 含义 |
|---|---|
| OF | 有符号溢出 |
| SF | 结果符号,负为 1 |
| ZF | 结果为 0 时为 1 |
| CF | 最高位进位/无符号借位相关 |
| AF | 第 3 位向第 4 位的辅助进位 |
| PF | 结果低字节中 1 的数目为偶数时为 1 |
| DF | 串操作方向,1 时 SI/DI 递减,0 时递增 |
| IF | 1 允许可屏蔽中断 |
| TF | 调试单步陷阱 |
4.3 常用指令与寻址
4.3.1 传送与算术逻辑
| 指令 | 作用/约束 |
|---|---|
mov d,s |
复制 s 到 d;dword/word/byte ptr 指定内存访问宽度 32/16/8 bit |
add/sub d,s |
加/减,结果回写 d |
mul s |
32 位形式将无符号 EAX 与 s 相乘,64 位积放入 EDX:EAX;只有一个显式源操作数 |
imul s / imul d,s / imul d,s,imm |
有符号乘法有一、二、三操作数形式;单操作数 32 位形式的完整积也放入 EDX:EAX |
div/idiv s |
32 位形式用 EDX:EAX 除以 s,商入 EAX,余数入 EDX;分别按无符号/有符号解释 |
neg/inc/dec |
取负、自增、自减 |
and/or/not/xor |
逐位逻辑运算 |
shl/shr/sar |
左移、逻辑右移、算术右移;不发生有效位丢失时,左移 $k$ 位可对应乘 $2^k$ |
- 目的操作数可为寄存器或主存,不能是常量;源操作数可为寄存器、主存或立即数。对本讲常见的
mov和双操作数算术逻辑指令,通常不允许两个显式操作数同时来自主存;字符串指令等专用形式另有隐含内存操作数,不能把这条简化规则推广到全部 x86 指令。 mov eax, ebx是寄存器寻址;mov eax, dword ptr [0xaf996]是直接内存寻址;mov eax, dword ptr [ebx]是寄存器间接寻址。- 复杂地址
[base + index*scale + displacement]体现基址/变址/位移组合,硬件比例因子scale只能取 1、2、4、8。例如mov eax,[ebx+ecx*4+4]取以 EBX 为基址、ECX 按 4 B 元素缩放后的内存位置;*32不能直接编码成 x86 的 SIB 比例因子。
4.3.2 数组寻址例
int a[24][64] 采用行优先、元素 4 B 时,一行大小为 $64\times4=256$ B。指令:
mov [ecx + edx*4 + 0x00422000], 0x0A
0x0A是立即数源操作数;中括号说明目的为内存单元。0x00422000是数组基址;ecx可预先保存row*256,edx*4表示列号乘元素大小。因此有效地址为base + row*256 + col*4。行偏移 256 B 需由前序移位/乘法得到,不能在该内存操作数中写成硬件不支持的row*256比例因子。- 原题写法省略了目的内存操作数的宽度;真正汇编
mov [地址], 立即数时,单凭立即数0x0A通常无法判定写 8、16 还是 32 bit,须由题设或byte/word/dword ptr等声明确定。本题int元素占 4 B,若要写整个元素,应明确为mov dword ptr [ecx + edx*4 + 0x00422000], 0x0A。
4.4 选择与循环
4.4.1 选择结构
cmp a,b 本质执行比较并设置标志位,不保存算术结果;随后 jz/jne/jbe/... 根据 ZF、SF、OF、CF 等条件跳转。典型 if/else:比较 -> 条件跳转到 else/then -> 必要时无条件跳过另一分支 -> 汇合。
例如原页的函数比较两个指针 p1,p2:先从 [ebp+8]、[ebp+12] 取参数,cmp eax,edx,随后 jbe .L1 在按无符号地址比较得到 p1<=p2 时跳转(原页注释只写 <,遗漏等于)。两条路径分别执行 mov eax,[edx] 或 mov eax,[eax],EAX 承载返回值。若把原 C 代码当作严格 ISO C,p1>p2 的关系比较只在标准规定的相关对象之间有定义(例如同一数组中的元素及尾后位置、同一结构体的成员);任意两个无关对象的指针不能仅凭这段汇编反推为可移植的 C 语义。
4.4.2 循环结构
循环机器级结构为:初始化 -> 首次条件(可跳过)-> 循环体 -> 更新 -> 条件跳回。loop L1 的控制流语义是先将 ECX 减 1,若 ECX 不为 0 就跳到 L1;它本身不修改 EFLAGS。若只关心是否跳转,可近似理解为:
dec ecx
cmp ecx, 0
jne L1
- 上述三条伪代码会修改标志位,因而与真实
loop并非完全等价;后续代码若读取标志位,必须按真实指令语义分析。 loop默认以 ECX 为计数器;loopnz要求递减后的 ECX 非零且原有ZF=0,loopz要求递减后的 ECX 非零且原有ZF=1。这些loop指令不会替前面的比较重新计算 ZF。- 不要只看
jmp就断定无限循环,需结合跳转前的计数器/比较更新判断出口。
4.5 函数调用与栈帧
4.5.1 call/ret 与寄存器约定
call func的作用近似“把返回 IP 压栈,再跳转到 func”;ret从栈顶弹出返回 IP 并跳回调用点之后。- 在常见 32 位
cdecl类约定中,EAX、ECX、EDX 通常由调用者保存,EBX、ESI、EDI、EBP 通常由被调用者保护;内层函数若改动被调用者保护寄存器,返回前须恢复。stdcall、fastcall、System V、Windows x64 等约定的传参、清栈和保存集合可能不同,这些规则属于 ABI/调用约定而非 x86 ISA 本身。
4.5.2 栈帧布局与访问
在本讲采用的典型 32 位、基于帧指针的调用约定中,栈向低地址增长,常以 4 B 为基本压栈宽度。一个过程的栈帧可由稳定的 EBP 和变化的 ESP 定位;每个未被尾调用消除的普通嵌套调用形成自己的调用记录。以下偏移布局以常见的栈上传参方式为例,不是所有 x86 ABI 的唯一布局。
| 典型区域(相对 EBP) | 含义 |
|---|---|
[ebp] |
上一层 EBP(保存的旧帧基址) |
[ebp+4] |
返回地址 |
[ebp+8] 起 |
调用参数,靠前参数通常更靠近栈顶/低正偏移 |
[ebp-...] |
局部变量,通常在负偏移区域 |
push x可压入立即数、寄存器或内存值;pop d弹出到寄存器或内存。sub esp,n可一次为局部变量留空间,add esp,n回收。- 常见恢复序列:
mov esp, ebp后pop ebp。编译器可能把栈帧大小对齐到 16 B,因此会出现未使用填充区。
原页的 caller() 示例令 temp1=125、temp2=80,调用 add(temp1,temp2),其 return x+y 得到 $125+80=205$。在所示 32 位栈上传参及帧指针代码里,caller 用 [ebp-12]、[ebp-8] 保存两个局部变量,调用前把实参压栈;add 以 [ebp+8]、[ebp+12] 读取两个实参,结果由 EAX 返回。这里的负偏移和正偏移是该实例的布局,不是任意编译器都必须照用的数值。
4.5.3 进程地址空间中的栈
源 PDF 还给出了典型进程虚拟地址空间:只读代码/常量、可写数据、堆和栈属于不同区域,堆常向高地址扩展,栈常向低地址扩展。具体区域顺序、随机化地址、共享库映射和保护权限由操作系统、装载器与 ABI 决定;该图用于理解栈帧所处位置,不属于 x86 ISA 对地址布局的硬性规定。
题型:#函数代码阅读
第一部分:固定流程
- 若代码建立了帧指针,先找函数入口的保存 EBP/建立帧、
sub esp分配局部空间和结尾恢复序列,确定栈向下增长与参数偏移;若没有这套序言,考虑编译器省略帧指针、直接基于 ESP 寻址或进行了优化。 - 再把
[ebp+...]标成参数、[ebp-...]标成局部变量,追踪 EAX 返回值和条件跳转目标。 - 遇到
call记住返回地址压栈;遇到ret从栈顶取返回地址。若有嵌套调用,检查哪些寄存器需要保存恢复。
第二部分:易错点
- Intel 语法目的在左源在右,不能按 AT&T 语法反读。
mov只是复制,cmp主要改标志,jcc决定控制流;三者连读才知道 C 条件的方向。- 建立帧指针时,EBP 在帧内通常固定而 ESP 会随
push/pop/sub/add变化;若编译器省略 EBP,仍可在精确跟踪 ESP 变化后用其偏移访问参数和局部变量,不能机械套用[ebp+8]模板。
源 PDF 的递归函数与机器码例题(2019 年)
原页给出 f1(n):$n>1$ 时返回 $n\times f1(n-1)$,否则返回 $1$。题图列出部分 32 位 x86 机器码与地址,可把控制流直接读成下表:
| 地址与机器码 | 汇编 | 在该题中的作用 |
|---|---|---|
00401018: 83 7D 08 01 |
cmp dword ptr [ebp+8], 1 |
将参数 $n$ 与 1 比较,设置标志位 |
0040101C: 7E 17 |
jle 00401035 |
$n\le1$ 时跳到基本情形,属于条件转移 |
00401021: 83 E8 01、00401024: 50 |
sub eax,1、push eax |
形成并压入递归实参 $n-1$ |
00401025: E8 D6 FF FF FF |
call f1 (00401000) |
压入返回地址并调用同一函数,属于调用转移 |
00401033: EB 05 |
jmp 0040103A |
跳过基本情形,属于无条件转移 |
00401035: B8 01 00 00 00 |
mov eax,1 |
基本情形的返回值 |
0040104A: C3 |
ret |
从栈中取返回地址并返回 |
相对位移按指令末尾的顺序下一地址计算:jle 长 2 B,0040101E + 0x17 = 00401035;jmp 长 2 B,00401035 + 0x05 = 0040103A。call 长 5 B,下一地址为 0040102A,后四字节 D6 FF FF FF 在小端下是有符号 32 位位移 0xFFFFFFD6=-42,故 0040102A-0x2A=00401000。这些数值既说明控制流,也把前一节的 PC 相对寻址与本节机器码联系起来;未显示的指令不从截图臆测。
相关知识
- 科目导航:计算机组成原理导航
- 阅读顺序:上一篇:CISC 和 RISC · 下一篇:CPU 的功能与结构
4.6 本节复盘
- 408 要求会读 x86 的 C/汇编/机器码对应关系,不要求手译整段程序;Intel 语法为
目的,源。 - 寄存器、标志、
mov、算术逻辑、cmp+jcc是阅读选择和循环的基础;二维数组地址遵循基址加行列字节偏移。 - 普通
loop只递减计数寄存器 ECX/CX 并在其非零时跳转,不检查 ZF;loope/loopz、loopne/loopnz才把计数条件与 ZF 结合。选择与一般条件循环常通过比较后改变 IP。 call压返回地址并跳转,ret弹出返回地址;典型 32 位帧指针代码用 EBP/ESP 组织向低地址生长的栈帧,参数常在 EBP 正偏移、局部变量常在负偏移,但最终以题设调用约定和实际指令为准。