汇编语言
《汇编语言》全课程精讲
本笔记覆盖大学本科计算机专业《汇编语言》一整个学期的课程内容,以 x86 为主线。先给概要大纲,再逐章详细讲解。学长考前串讲风格:不堆砌指令,每条指令都讲"硬件层面做了什么"。
一、课程大纲(全书 10 章)
| 模块 | 章节 | 内容 |
|---|---|---|
| 基础 | 第1章 | 计算机基础与汇编入门(冯·诺依曼结构、总线、指令周期、补码、字符编码) |
| 硬件核心 | 第2章 | CPU 内部结构与寄存器(8086 寄存器组、分段、物理地址计算) |
| 寻址 | 第3章 | 寻址方式(立即数/寄存器/直接/间接/变址/基址变址/相对) |
| 指令系统 | 第4章 | 基本指令系统(MOV、算术、逻辑、移位、标志位) |
| 控制流 | 第5章 | 控制转移与分支循环(JMP/Jcc/CMP/LOOP、switch 思想) |
| 程序机制 | 第6章 | 子程序与栈帧(PUSH/POP、CALL/RET、调用约定、递归) |
| 数据块处理 | 第7章 | 字符串操作与数据块处理(串指令、REP 前缀、DF) |
| 与外设交互 | 第8章 | 中断与系统调用(中断向量表、INT 21h、BIOS 中断、键盘显示) |
| 工程化 | 第9章 | 宏汇编与模块化编程(段定义、伪指令、宏、多模块连接) |
| 进阶 | 第10章 | 汇编与高级语言混合及逆向(C 混合编程、反汇编、保护模式、x86-64) |
二、一条主线(先抓住这个,整门课就通了)
汇编语言 = 机器指令的可读助记形式,是"软件与硬件的唯一分界线"。
整门课围绕 CPU 执行指令的本质展开:
寄存器(CPU内部的高速存储)
→ 寻址方式(如何在内存里"找数")
→ 指令系统(数据搬运、运算、控制转移)
→ 栈与调用(子程序/函数机制)
→ 中断(CPU与外界的交互通道)
学会汇编 = 真正理解程序在机器里每一步在干什么。
这根主线由三个根本事实支撑,任何时候忘了就从这里往回推:
事实 1:冯·诺依曼结构——"存储程序"。 程序和数据一样,都放在内存里。CPU 自己不带程序,它只是"一格格地"把内存里的指令取出来执行。所以指令能存在哪、内存怎么组织,决定了整个汇编语言长什么样。
事实 2:数据在三处流动——内存、寄存器、栈。 所有计算都必须经过寄存器(CPU 里最快的存储),所有大批量数据住在内存里,所有"临时倒手"的数据放在栈上。汇编程序写来写去,本质就是在指挥数据在这三个仓库之间搬家。
事实 3:指令 = 操作码 + 操作数。 一条指令由两部分组成:做"什么动作"(操作码,如 MOV/ADD)和"对谁做"(操作数,如 AX、[BX])。汇编的每一章,都是在问同一个问题:操作数怎么去指定? 这就是寻址方式的本质。
一句话:全课程 = 一台冯·诺依曼机器 + 一套搬运/运算/跳转指令 + 一个人(你)把数据在寄存器/内存/栈之间指挥得当。
三、逐章精讲
第1章 计算机基础与汇编入门 —— 全课程的总纲
这一章在干什么
你第一次问:"程序到底在机器里是怎么运行的?"这一章把运行环境交代清楚:计算机长什么样(硬件框架)、指令怎么被取出来执行(指令周期)、数据怎么存(二进制与编码)。它不教你写指令,而是先让你看清"指令将要面对的世界"。
核心内容:冯·诺依曼结构
一切现代通用计算机的骨架:
- 运算器:做算术逻辑运算(ALU,是 CPU 的一部分)。
- 控制器:取指令、解释指令、指挥其它部件(也是 CPU 的一部分)。
- 存储器:内存,存放指令和数据——关键点是"指令和数据共用一块内存",这是"存储程序"思想的核心。
- 输入设备 / 输出设备:键盘、显示器等,通过 I/O 与外界打交道。
- 总线:连接各部件的"公路",分三条:地址总线(CPU 说"我要访问哪个位置")、数据总线("数据传过去")、控制总线("是读还是写")。
由此立刻得到两个工程数字(8086 时期):
- 地址总线宽度 = 20 根 → 可寻址 \(2^{20}=1MB\) 内存。
- 数据总线宽度 = 16 根 → 一次最多搬 16 位数据。
- 地址总线决定"内存有多大",数据总线决定"一次搬多宽",别混。
核心内容:指令与指令周期
一条指令从内存到执行,经历经典的三步(常考流程题):
- 取指(Fetch):控制器把"指令地址"放到地址总线,把内存里那条指令取到 CPU 内。
- 译码(Decode):控制器分析这条指令是"干什么的、对谁干"。
- 执行(Execute):运算器动手,结果写回寄存器或内存。
这里有一个贯穿全书的认识:CPU 不认识"if、while、函数",它只认识这"取指—译码—执行"三步循环。 高级语言里的一切花哨,最终都被翻译成这种最朴素的节奏。而寄存器里的 IP(指令指针)就是"现在执行到哪了"的指针——后面讲 JMP、CALL,全是它在被修改。
核心内容:原码、反码、补码
计算机里只有 0 和 1,负数怎么表示?三种方案,只有补码最终胜出。考计算必考,先背公式再记理由:
- 原码:最高位为符号位(0 正 1 负),其余是数值。缺点:+0 和 −0 有两个表示,且加减法电路要分符号处理,太笨。
- 反码:正数同原码,负数是原码各位取反。缺点:还是有两个 0。
- 补码(计算机实际使用的):正数同原码;负数 = 其绝对值的原码各位取反再加 1。数学上等价于"模运算":对 n 位而言,\([x]_{补} = (x + 2^n) \bmod 2^n\)。
补码胜出的三个硬道理(为什么工业界最终选了它):
- 只有一个 0:\(1000...00\) 让给了最负的数(8 位是 −128)。
- 加减法统一:\(A - B = A + [-B]_{补}\),减法电路直接复用加法器,不需要判断符号。
- 溢出判断自然:符号位参与运算,结果由硬件自动判断 OF。
典型坑:8 位补码能表示的范围是 −128 ~ +127,不是 −127~127。计算口诀:"取反加一"求负数的补码;"取反加一"再把补码变回十进制真值——正反同一个操作,所以补码的加一个数减一个数是同一个电路。
核心内容:字符编码
- ASCII:7 位二进制编码 128 个字符。'0'=48(0x30)、'A'=65(0x41)、'a'=97(0x61)。数字字符和其数值之间差 30h,这是后面做字符转数字的常用操作。
- 中文语境:GBK(双字节)、Unicode(统一编码),一个汉字两个字节,后续字符串指令要按字节还是按字处理就靠这个意识。
这一章的考点
- 补码计算与范围判断:\(-5\) 的 8 位补码、补码 \(0xE3\) 的真值、溢出判断;
- 冯·诺依曼结构图填空(运算器/控制器/存储器/输入输出);
- 20 根地址总线为什么对应 1MB;总线三条线各干什么;
- 指令周期三步的名称与顺序。
为什么这一章重要
它是全部后续章节的坐标系。后面讲寄存器、寻址、指令,全在这个框架里;讲中断,是"指令周期"的扩展;讲栈,是"存储器"的一个特殊区域。操作系统课的进程、内存管理,编译原理的指令生成,也都站在"存储程序 + 指令周期"这个地基上。这一章没懂,后面全是空中楼阁。
第2章 CPU 内部结构与寄存器 —— 机器里的"办公桌"
这一章在干什么
CPU 内部有哪些"抽屉"能存东西?这些抽屉互相什么关系?这是学指令前必须认识的"硬件清单"。因为汇编语言能操作的,其实只有这么几个地方:寄存器、内存、栈(I/O 端口暂不论)。 这一章先把寄存器这个家底摸清。
核心内容:8086 寄存器组(三大类 + 一组)
-
通用寄存器(16 位,8 个):
- AX(累加器)、BX(基址)、CX(计数)、DX(数据)——这四个都有对应的 8 位半字节:AH/AL、BH/BL、CH/CL、DH/DL,见下面"高低位关系"。
- SP(栈指针)、BP(基址指针)——专管栈的,后面第6章的主角。
- SI(源变址)、DI(目的变址)——专管串操作的,第7章主角。
- 记忆法:"AX 算术、BX 基址、CX 计数(LOOP 用)、DX 数据(乘除法也用)",特殊用途在第4、6、7章反复出现。
-
段寄存器(4 个):
- CS(代码段)、DS(数据段)、SS(栈段)、ES(附加段)。
- 每个段寄存器都指向内存里某个"区域的地基",配合偏移量才得到实际物理地址。
-
指针寄存器(IP):指令指针,指向下一条要执行的指令。它由 CPU 自动修改,普通指令改不了它——只有 JMP/CALL 等转移指令能间接控制它。它是"控制流"的物理载体。
-
标志寄存器 FLAGS(重点,第4章详讲):一组 1 位的开关,记录上一条指令执行的结果特征,如 ZF(结果为零?)、CF(进位?)、SF(符号?)、OF(溢出?)。
核心内容:16 位与 32 位寄存器的关系
从 8086(16 位)升级到 80386(32 位)后,寄存器变宽,但为了向后兼容,保留了 16 位时代的名字:
寄存器分层(以 AX 为例):
32位:EAX(Extended AX)
16位:AX ← 它等于 EAX 的低 16 位
8位: AH AL ← 分别等于 AX 的高、低 8 位
用 AH/AL 只改半字节时,EAX 的高 16 位不受影响;但用 AX 会连 EAX 高 16 位一起被清零吗?——不会。 这是经典的"部分写"陷阱:mov ax, 0 只改 EAX 的低 16 位,高 16 位保持原样;而 mov eax, 0 才整个清零。考试与调试常考这个。
核心内容:存储器的分段结构与物理地址
8086 只有 20 根地址线(能寻址 1MB),但寄存器只有 16 位(最大表示 64KB)。怎么用 16 位去访问 20 位空间? 答案:分段。
- 内存被逻辑上切成若干段,每段最大 64KB,段内用 16 位偏移量寻址。
- 任何地址都由两个部分表达:段基址(16 位)+ 偏移量(16 位),写作
段寄存器:偏移量,如DS:BX。 - 物理地址 = 段基址 × 16 + 偏移量(即段基址左移 4 位再相加)。
例:DS = 0x1000,BX = 0x1234
物理地址 = 0x1000 × 16 + 0x1234 = 0x10000 + 0x1234 = 0x11234
为什么乘 16? 因为段寄存器存的是"段基址的高 16 位",物理地址的低 4 位恒为 0——所以段基址必须是 16 的倍数。这也意味着同一物理地址可以由多组"段:偏移"表示(如 0x1000:0x1234 和 0x1001:0x1134 指向同一处),这是后面分析程序时的常见小陷阱。
CPU 取指令默认用 CS:IP,访问数据默认用 DS:偏移,访问栈默认用 SS:SP——"默认段"的规则要记死,否则后面 [BP] 为什么配 SS 就不知道了。
这一章的考点
- 写物理地址:给段基址和偏移求物理地址,或反求;
- 判断合法:偏移量超过 FFFFH(段溢出)为什么错;
- AX/AH/AL 的关系:给 EAX 一个值,求 AL 是多少;
- 段寄存器的"默认配对"(取指配 CS、数据配 DS、栈配 SS)。
为什么这一章重要
寄存器是汇编语言的"变量名"。第3章寻址方式全在"用寄存器指内存",第4章每条指令都在操作寄存器,第6章栈操作靠 SP/BP。而"段 ×16 + 偏移"这个公式是实模式内存模型的命根子,第10章讲保护模式时,你会发现它被改造成 GDT/LDT,但"段+偏移"的思想一脉相承。
第3章 寻址方式 —— 如何在内存里"找数"
这一章在干什么
指令要操作数据,数据住在哪?这一章回答唯一的考纲问题:一条 MOV 指令里,操作数可以以哪几种方式给出? 寻址方式就是"怎么从一条指令里找到操作数"的规范。它是全课程最容易拿分的纯记忆章,也是后面每章读指令的"解码器"。
核心内容:七种寻址方式一网打尽
先立一个总框架:寻址方式按"操作数在不在指令里、在寄存器里还是在内存里"分三大类:
A. 数据直接在指令里——立即数寻址
MOV AX, 1234h ; 把立即数 1234h 直接送 AX
- 操作数就写在指令里,跟指令一起从内存取来,不访问内存、不占寄存器。
- 用途:给寄存器/变量赋初值。注意只能做源操作数,不能
MOV 1234h, AX(立即数没有"地址",当不了目的)。
B. 数据在寄存器里——寄存器寻址
MOV AX, BX ; 把 BX 的内容送 AX
- 操作数就是寄存器名,执行时直接读写寄存器,速度最快(不用访存)。
- 用途:一切中间计算都在寄存器里完成。注意 8 位和 16 位不能混:
MOV AL, BX是非法的。
C. 数据在内存里——根据"内存地址怎么算"细分为四种
所有内存寻址的关键是算出一个"有效地址(EA)",即段内偏移量。给一个万能公式:
有效地址 EA = 基址寄存器 + 变址寄存器 × 比例因子(286后才有多) + 位移量
8086 里没有比例因子,于是:
-
直接寻址:地址就是位移量(一个常数)。
MOV AX, [2000h] ; 把段内偏移 2000h 处的字送 AX(段默认 DS)用途:访问明确地址的全局变量。注意方括号
[]表示"这里装的是地址",与立即数区分。 -
寄存器间接寻址:地址存在寄存器里。
MOV AX, [BX] ; 有效地址 = BX 的内容 MOV AX, [DI] ; 可用 BX/SI/DI,注意不能用 AX/CX 等当地址用途:遍历数组、访问动态数据——地址是"算出来的",随循环改变。这是四种里最常考、也最重要的。
-
变址寻址:
[SI+位移]、[DI+位移]。MOV AX, [SI+4] ; 有效地址 = SI + 4用途:数组下标访问——把基地址放位移量,把下标放在 SI/DI 里变。
-
基址变址寻址:
[BX+SI+位移]等,一个基址 + 一个变址。MOV AX, [BX][SI+2] ; 有效地址 = BX + SI + 2用途:二维数组/结构体数组——BX 放行号,SI 放列号。这是 8086 时代"矩阵访问"的标准姿势。
D. 相对寻址(为控制流服务,不是数据寻址)
JMP short label ; 转移地址 = 当前 IP + 指令中给出的相对位移量
- 地址 = (IP) + 位移量,相对于当前指令位置来跳转。
- 用途:所有条件转移(Jcc)和 LOOP 只能用相对寻址——这就是为什么循环跳转的范围有限(±128 字节短转移)。后续第5章的核心。
一张表记住(考前必背)
| 寻址方式 | 写法示例 | 有效地址 | 典型用途 |
|---|---|---|---|
| 立即数 | MOV AX, 5 |
无(数据在指令里) | 赋初值 |
| 寄存器 | MOV AX, BX |
无(数据在寄存器里) | 高速中间计算 |
| 直接 | MOV AX, [2000h] |
位移量 | 访问全局变量 |
| 寄存器间接 | MOV AX, [BX] |
BX | 遍历数组 |
| 变址 | MOV AX, [SI+4] |
SI+4 | 数组下标访问 |
| 基址变址 | MOV AX, [BX+SI+4] |
BX+SI+4 | 二维数组 |
| 相对 | JMP label |
IP+位移 | 分支跳转 |
这一章的考点
- 判断题:指出某条指令的寻址方式(最经典的送分题);
- 区分"是值还是地址":
MOV AX, BX与MOV AX, [BX]的区别; - 求物理地址:给 DS、BX 算
[BX]访问的内存位置; - 非法指令判断:
MOV [BX], [SI]为什么错(两个操作数不能都是内存,只能"内存⇄寄存器"或"寄存器⇄寄存器"); MOV AL, 1000h为什么错(8/16 位不匹配)。
为什么这一章重要
寻址方式 = 读汇编程序的解码表。第4章所有指令的操作数、第5章条件跳转的位移、第6章栈帧里 [BP+4] 访问参数、第7章串指令的 [SI]/[DI]——全部长在这棵树上。而且它是编译原理的入口:编译器把 a[i] 翻译成什么,本质就是一次基址变址寻址。
第4章 基本指令系统 —— 指令 = 操作码 + 操作数
这一章在干什么
认识了寄存器(第2章)和寻址方式(第3章)这两套"语法词汇",这一章开始学真正的句子:MOV、ADD、AND、SHL……每条指令在硬件层面都对应 ALU 的一次具体动作。这一章是全书题量最大的内容,但逻辑很简单——先学"搬运",再学"计算",最后学"算完留下了什么痕迹(标志位)"。
核心内容:数据传送 MOV 及其限制
MOV 目的, 源
在硬件层面:数据总线把源搬到目的地(寄存器/内存),源不变、目的被覆盖。三条铁律(必考):
- 不能"内存→内存":
MOV [BX], [SI]非法,必须借道寄存器(MOV AL, [SI]+MOV [BX], AL)。硬件原因:一条指令只有一个内存操作数位。 - 不能把立即数直接送段寄存器:
MOV DS, 1000h非法,要MOV AX, 1000h+MOV DS, AX。硬件原因:指令编码里没有"段寄存器+立即数"的组合。 - 宽度必须一致:8 位对 8 位、16 位对 16 位。
MOV AL, 1000h非法。
变体 XCHG(交换)、LEA(取有效地址,注意 LEA 取的是地址不是内容:LEA AX, [BX+SI] 得到的是地址值,而 MOV AX, [BX+SI] 得到的是那个地址里的数据——这是经典易错点)。
核心内容:算术指令(重点:标志位怎么变)
- ADD/SUB(加/减):
ADD AX, BX即 AX = AX + BX。硬件层面就是 ALU 做加法,同时把结果特征写入标志寄存器。 - INC/DEC(加一/减一):注意它们不影响 CF——因为"自增"不产生进位概念,这是常考的细节。
- MUL/IMUL(无符号/有符号乘法):
MUL BL:隐含 AL × BL → AX;MUL BX:隐含 AX × BX → DX:AX(乘积 32 位分两半,DX 存高 16 位)。- IMUL 是有符号版,操作数按补码解释。
- 为什么乘积要把 DX 也用上?因为 16 位 × 16 位最多是 32 位结果,一个寄存器装不下。这暴露了指令系统的根本约束:运算宽度 = 寄存器宽度。
- DIV/IDIV(无符号/有符号除法):
DIV BL用 AX ÷ BL → 商在 AL、余数在 AH;DIV BX用 DX:AX ÷ BX → 商在 AX、余数在 DX。除数是 0 或商溢出 → CPU 产生除法错中断(内部中断,第8章再讲)。 - 关键认识:加减乘除的操作数组合里,隐含寄存器(AX、DX)几乎处处在场——这就是为什么 AX 叫"累加器"。
核心内容:逻辑与移位指令
- AND/OR/XOR/NOT(逻辑运算):按位操作。
AND常用来屏蔽(清零特定位):AND AL, 0FH保留低 4 位、高 4 位清零。OR常用来置位:OR AL, 80H把最高位置 1。XOR AL, AL是快速清零的惯用法(比MOV AL, 0短,且把 OF/CF 清零)。NOT取反,唯一不影响任何标志位的指令。
- 移位指令:
SHL/SHR(逻辑左移/右移):空出的位补 0。SHL 相当于乘 2,SHR 相当于除以 2(无符号)。SAL/SAR(算术左移/右移):SAL 同 SHL;SAR 右移时空出的高位保持符号位不变(相当于带符号除以 2,向下取整)。所以负数右移要用 SAR,否则符号位被 0 填掉就变正数了。ROL/ROR(循环左移/右移):从一头移出的位循环回到另一头,不丢位。- 硬件真相:移位指令内部就是移位寄存器(一个时钟沿把每位往邻居移动一次),移 n 位就动 n 个时钟周期——所以早期 CPU 上移位比乘法便宜得多,编译器常用"左移 2 位"替代"乘 4"。
核心内容:标志位(全课程的"状态记录仪")
执行完一条指令后,FLAGS 里几个 1 位开关记录了结果特征,条件转移指令(第5章)全靠它们决策:
| 标志 | 名称 | 什么时候置 1 | 典型用途 |
|---|---|---|---|
| ZF | 零标志 | 结果 = 0 | 判断两数相等(CMP 后) |
| SF | 符号标志 | 结果最高位 = 1(负数) | 判断正负 |
| CF | 进位/借位 | 无符号运算产生进位/借位 | 无符号数比较、多字节加法 |
| OF | 溢出标志 | 有符号运算结果超出范围 | 有符号数比较、判断溢出 |
CF 和 OF 的分工是本章最重要的一句话:CF 管无符号数的"出错",OF 管有符号数的"出错"。 同一个 ADD,可能 CF=1 而 OF=0(无符号溢出但有符号正常),也可能反过来。判断哪一个是真错误,取决于你把这数据当有符号还是无符号看。
例:8 位 ADD AL, 7Fh + 1:AL = 80h。CF = 0(没产生进位),但若按有符号看是 +127+1 = −128 正好成立也没溢出——但 ADD AL, 7Fh + 2 结果为 81h,按有符号 +127+2 = −127 就溢出了(OF=1)。溢出不是"结果超范围",而是"符号位被污染"——这是硬件的本质理解。
这一章的考点
- 补码 + MOV/ADD 混合计算:执行后 AX、各标志位分别是多少(必考大题);
- INC/DEC 影响哪些标志(不碰 CF);
- MUL 与 DIV 的隐含寄存器配对;
- 用 AND/OR/XOR/SHL 实现"取位、置位、清零、乘2";
- SAR 与 SHR 在负数上的区别。
为什么这一章重要
指令系统是程序的原材料,后面所有章节(循环、子程序、串操作)都是在"用这些指令拼程序"。它还是计算机组成原理(ALU 设计)与编译原理(指令选择、代码生成)的直接接口。你还会发现第10章看反汇编时,90% 的代码就是 MOV/ADD/CMP/Jcc 这几条在循环往复。
第5章 控制转移与分支循环 —— 让 CPU 学会"拐弯"
这一章在干什么
第4章的程序只能一条线往下走。真实程序需要"如果……就……否则……"和"循环"。这一章学改变 IP 的指令:无条件转移、条件转移、循环控制。硬件本质只有一个:改掉 IP 的值,CPU 下一条取指的地址就变了——所谓控制流,就是"给指令指针改值"。
核心内容:无条件转移 JMP
JMP 目标 ; IP = 目标,下一条从目标开始执行
三种形式要分清(决定"目标怎么写"):
JMP short label:短转移,相对寻址,目标在当前 IP ±127 字节内。JMP near ptr label:近转移,相对寻址,段内 ±32K 范围。JMP far ptr label:远转移,直接给出完整地址(段+偏移),连 CS 一起改,实现跨段跳转。
关键区别:短/近转移的指令里存的是"相对位移量",而远转移存的是"绝对地址"。 反汇编时,短/近跳转的"目标地址"是反汇编器帮你算出来的,实际指令里只是一个补码位移。
核心内容:比较指令 CMP 与条件转移 Jcc
CMP = 减法但不存结果:CMP AX, BX 执行 AX − BX,结果丢弃,只留下标志位。为什么先减一下?因为后面的 Jcc 只看标志位决策,所以 CMP 就是"为决策而进行的减法"。
条件转移指令 Jcc 分为三组,按"数据怎么看"划分:
1. 无符号数比较(用 CF 和 ZF)
JB/JC(below,低于/进位):CF=1 → AX < BXJAE/JNC(above or equal):CF=0 → AX ≥ BXJA(above):CF=0 且 ZF=0 → AX > BXJBE(below or equal):CF=1 或 ZF=1 → AX ≤ BX
2. 有符号数比较(用 SF、OF、ZF)
JL(less):SF≠OF → AX < BXJGE(greater or equal):SF=OF → AX ≥ BXJG(greater):SF=OF 且 ZF=0 → AX > BXJLE(less or equal):SF≠OF 或 ZF=1 → AX ≤ BX
3. 单标志判断
JZ/JE(ZF=1)、JNZ/JNE(ZF=0)、JS/JNS(SF)、JO/JNO(OF)、JC/JNC(CF)。
为什么 CMP 后必须成对记 CMP AX, BX 是"AX−BX"? 因为 Jcc 的名字是从"左操作数 vs 右操作数"的角度命名的:CMP AX, BX 后 JB 表示"AX 低于 BX";若写成 CMP BX, AX,结论就反了。比较方向是本章最常错点。
; 经典 if 结构(判断 AX 是否大于 5)
CMP AX, 5
JLE else_part ; AX <= 5 则跳转
... ; AX > 5 的执行体
JMP end
else_part:
...
end:
核心内容:LOOP 循环指令
MOV CX, 100 ; 循环次数放 CX
again:
... ; 循环体
LOOP again ; CX = CX - 1,若 CX != 0 继续,否则跳出
硬件本质:LOOP 内部做两件事——CX 减 1,然后看 ZF 决定要不要跳。 所以 CX 是 CPU 钦定的"计数器",这就是第2章说的"CX 计数"用途。
三个坑(必考):
- 循环次数先放 CX,循环体内绝不能再动 CX,否则次数错乱;
- LOOP 用的是相对寻址,循环体长度受限;
- CX=0 时循环体会执行 65536 次(减到 0 再借位才停)——所以"循环 N 次"要先判断 CX=0。
核心内容:多分支结构与 switch 思想
多个分支(switch/case)在汇编里有两种翻译策略,这正是编译原理考点:
- 一连串 CMP + JE/JNE:
CMP AX, 1 / JE case1 / CMP AX, 2 / JE case2 ...。分支少时用它,直观但判断次数多。 - 跳转表(jump table):把各 case 的地址放进一张表,用
JMP [表首+AX×2]基址变址寻址一次跳到位。分支多时用它,O(1) 跳转。
跳转表把"分支"变成了"查表 + 间接跳转"——这是理解 switch 在机器层面为何高效的钥匙,也是后面逆向工程里识别"跳转表"的依据。
这一章的考点
- 给定 CMP 和 Jcc,判断程序流向(配标志位表);
- 画流程图写双分支/嵌套分支汇编;
- LOOP 循环结果分析(注意 CX 被改的陷阱);
- 区分 JB 与 JL 在什么数据下结论不同(无符号 vs 有符号);
- 分析"CMP 方向写反"的 bug。
为什么这一章重要
控制流是程序结构的骨架:三种基本结构(顺序、分支、循环)全部由本章指令实现。它是第6章子程序、第7章串循环的基础;也是编译原理里"条件表达式→分支代码"、操作系统里"进程调度"和逆向工程里"控制流还原(CALL 图、基本块)"的硬件起点。看反汇编时,你学会的第一件事就是"把 Jcc 还原成 if/while"。
第6章 子程序与栈帧 —— 函数机制的全部秘密
这一章在干什么
高级语言里一个函数调用,在机器层面发生了什么?这一章是整个课程含金量最高的章节:引入栈这个全新数据结构,讲清 CALL/RET 如何实现"跳过去还能跳回来",以及参数、局部变量、返回值是如何在栈上"排座位"的。学完这章,你才真正理解"调用栈"是什么。
核心内容:栈的原理与 PUSH/POP
- 栈是内存里一块专门区域(由 SS:SP 管理),后进先出(LIFO),从高地址向低地址增长。
- 硬件真相:
PUSH AX= SP 先减 2,再把 AX 写入 [SP];POP AX= 先把 [SP] 读到 AX,再 SP 加 2。 - 两条规则死记:SP 永远指向栈顶;栈向下长,SP 只会减少(PUSH)和增加(POP)。
- 栈的用途三件套:① CALL 前保护现场(寄存器压栈,返回前弹回);② CALL 时保存返回地址;③ 函数参数与局部变量(见下)。
; 栈向下生长的直观图(高地址在上)
高地址
| ... |
| 局部变量 |
| 保存的BP |
| 返回地址 | ← 返回地址永远在栈顶附近
| 参数 |
SP → 当前栈顶
低地址
核心内容:CALL 与 RET 的完整配对
CALL 子程序做两件事:① 把下一条指令的地址(返回地址)压栈;② 跳转子程序。RET做一件事:从栈顶弹出返回地址,送回 IP。- 所以"能跳过去又能跳回来"的秘密就在栈——栈存的是"回家的路"。这也解释了嵌套调用为什么必须用栈:每个 CALL 压一层返回地址,RET 一层层弹,正好对应嵌套的深度。
递归的汇编视角(考试爱考):递归调用 = 子程序调自己。每次调用都压一份返回地址、保存一份寄存器、分配一份局部变量——所以递归的每一层都在栈上"新开一个房间",深度 n 就要 n 份空间。栈溢出就是房间开太多。 而递归的"终止条件",翻译过来就是"那个不递归调用的 RET"。
核心内容:栈帧(Stack Frame)结构
这是全课程最值得画图记忆的内容。一个被调用的函数,其"房间"叫栈帧:
高地址 参数2 ← [BP+6]
参数1 ← [BP+4]
返回地址 ← [BP+2]
保存的BP ← [BP] ← BP 固定指向这里(栈帧基准)
局部变量 ← [BP-2], [BP-4]...
SP → 栈顶
低地址
规则:BP 指向帧底(保存的 BP 位置),帧内所有东西都用 BP+偏移来访问。 为什么用 BP 不用 SP?因为 SP 会随 PUSH/POP 变动,用它作基准地址就全乱了;BP 在函数体内保持不变,是"帧的定海神针"。
; 典型子程序开头(进入子程序时的"开场白")
PUSH BP ; 保存调用者的 BP
MOV BP, SP ; 新帧的基准 = 当前栈顶
SUB SP, 10 ; 为 10 字节局部变量腾空间
PUSH AX ; 保存用到的寄存器(保护现场)
... ; 函数体,用 [BP+4] 取参数1、[BP-2] 用局部变量
POP AX ; 恢复现场
MOV SP, BP ; 撤销局部变量空间(等价于 ADD SP, n)
POP BP ; 恢复调用者的 BP
RET ; 弹返回地址,回调用处
核心内容:参数传递方式与返回值
- 寄存器传参:参数少的快函数用,如把参数放 AX/BX。缺点:寄存器数量有限。
- 栈传参:标准做法。调用者把参数从右往左(或从左往右)压栈,子程序用
[BP+偏移]取。 - 返回值:约定用 AX 返回 16 位结果(32 位结果用 DX:AX)。
- 清栈责任(调用约定的核心,见下)。
核心内容:常见调用约定
| 约定 | 参数入栈顺序 | 谁清栈 | 特点/使用 |
|---|---|---|---|
| cdecl(C) | 从右到左 | 调用者 | C 语言默认;可变参数必须靠调用者清 |
| stdcall(Pascal/Win32 API) | 从右到左 | 被调者 | 省空间(RET n 指令弹参数);Windows API 用它 |
| fastcall | 前两个参数走 ECX/EDX | 被调者 | 更快,参数少时 |
为什么必须规定"谁清栈"?因为栈空间要恢复平衡——谁清,谁负责把参数弹出或 SP 加回去。不统一就会栈失衡,返回地址取错,程序崩溃。这在逆向工程里是识别函数调用约定的指纹。
这一章的考点
- 手写/补全标准栈帧代码(PUSH BP / MOV BP,SP / ... / POP BP / RET);
- 分析递归程序:给一个递归子程序,问执行到某步时栈里有几个返回地址;
- 传参方式判断:寄存器传参 vs 栈传参;
- 栈图分析:给调用序列,画 SP/BP 的变化;
- cdecl 与 stdcall 下"谁清栈、怎么清"。
为什么这一章重要
栈帧 = 函数调用的物理实现。操作系统(进程内核栈、线程切换的上下文保存)、编译原理(活动记录/栈式存储分配)、逆向工程(看懂函数边界、恢复局部变量)全都建立在这个模型上。学完本章,gdb 里的 backtrace、异常崩溃的"调用栈"、缓冲区溢出漏洞(覆盖返回地址)都变得一目了然。
第7章 字符串操作与数据块处理 —— 让 CPU 自动搬运
这一章在干什么
第4章教你一条条搬数据,但"把一块数据从一个地方搬到另一个地方"需要循环写很多条。这一章介绍专门的串指令:一条指令完成"一次搬运 + 两个指针自动前进",再配上 REP 前缀实现"自动重复 N 次"。它训练的是"用一条指令做一批事"的思维。
核心内容:五条串指令(先背 SI/DI 分工)
串指令统一约定(务必背死):
- SI 是源指针(配 DS)、DI 是目的指针(配 ES);
- 每执行一条,SI/DI 自动 ±1(字节)或 ±2(字),方向由 DF 标志决定:
- DF=0 → 地址增加(向前),用
CLD清除; - DF=1 → 地址减少(向后),用
STD设置。
- DF=0 → 地址增加(向前),用
| 指令 | 动作 | 用途 |
|---|---|---|
MOVSB/MOVSW |
把 [SI] 的内容搬到 [DI],两指针自动走 | 数据块拷贝 |
CMPSB/CMPSW |
比较 [SI] 和 [DI] 的内容(只置标志,不写回) | 内存区比较 |
SCASB/SCASW |
用 AL/AX 与 [DI] 比较 | 在串中查找字符 |
LODSB/LODSW |
把 [SI] 读入 AL/AX,SI 自动走 | 把串取进寄存器 |
STOSB/STOSW |
把 AL/AX 写入 [DI],DI 自动走 | 填充串 |
核心内容:REP 前缀与它的"变体"
- REP:重复执行后面的串指令,次数由 CX 控制——每执行一次 CX 减 1,到 0 为止。典型应用:
REP MOVSB一条指令拷贝整个缓冲区(等价于一个循环)。 - REPE/REPZ(相等时重复):配 CMPS/SCAS 用,CX≠0 且 ZF=1 才继续。用于"比较直到找到第一个不同处"。
- REPNE/REPNZ(不等时重复):CX≠0 且 ZF=0 才继续。用于"查找直到找到目标字符"。
; 示例:把 100 字节从 src 拷到 dst
MOV SI, OFFSET src
MOV DI, OFFSET dst
MOV CX, 100
CLD
REP MOVSB ; 一条指令完成 100 字节搬运
硬件真相:REP 前缀是在取指/译码环节做的重复——CPU 内部就是把这个"重复"翻译成微操作循环。所以 REP 串指令的速度瓶颈在内存带宽,这也是"内存拷贝优化"(如 SSE 的 movntdq)的出发点。
OFFSET 伪指令第一次出现:OFFSET 变量 取变量的段内偏移(不是内容),配合 SI/DI 使用——这是"把变量地址交给串指令"的标准写法。
这一章的考点
- REP MOVSB 后 SI/DI/CX 各是多少(考 DF 方向和次数);
- 给 CLD/STD 判断数据从哪头开始拷;
- 用 SCAS 查找字符并定位(注意退出时 DI 指向哪);
- 比较两个数据块是否相同(CMPS + REPE);
- 手工模拟串指令的指针移动(每次 ±1 还是 ±2 看是 B 还是 W)。
为什么这一章重要
串指令是"批量数据操作"的第一课。它与 strcpy/memcpy/strcmp 的底层实现一一对应(标准库这些函数用 REP MOVSB/CMPSB 实现),也是理解DMA、缓存、现代 SIMD 向量化(AVX 一条指令处理 32 字节)的原型。逆向工程里,rep movsd 是识别"结构体赋值/数组拷贝"的极强特征。
第8章 中断与系统调用 —— CPU 与外界的交互通道
这一章在干什么
到目前为止,程序一直"自娱自乐"。真实程序要和键盘、屏幕、磁盘打交道。这一章讲中断机制:CPU 怎样响应外部事件,以及如何通过 INT 指令"主动麻烦"操作系统。这是汇编语言里最接近"操作系统"的一章。
核心内容:中断的分类与中断向量表
- 内中断(异常):CPU 内部产生,如除零、单步、断点。
- 外中断:外部硬件(键盘、定时器)发信号给 CPU。
- 软中断:程序用
INT n主动触发——这是系统调用的入口。
中断向量表(IVT):实模式下位于内存最低端 00000H~003FFH(1KB),共 256 个表项,每项 4 字节(段基址+偏移 = 一个完整的 32 位地址)。INT n 会去查表项 n,得到中断处理程序的入口。
中断处理完整流程(必考流程题,五步):
- 标志寄存器压栈(保存现场);2. IF 和 TF 清零(关中断,防重入);3. CS 压栈;4. IP 压栈(保存返回地址);5. 从向量表取新 CS:IP,转入中断处理程序。
对比一下 CALL:CALL 只压返回地址(2 步),INT 压 6 个字节(FLAGS+CS+IP),因为它还要恢复标志位、且能"打断任何地方"。这是中断与子程序最本质的区别。
返回用 IRET:弹回 IP、CS、FLAGS,恢复被中断前的完整状态。
核心内容:DOS 功能调用 INT 21h(考试主力)
DOS 把常用服务编成号,调用约定三件事:功能号放 AH、参数放指定寄存器、执行 INT 21h:
| AH | 功能 | 参数 | 说明 |
|---|---|---|---|
| 01h | 读一个字符并回显 | 返回值在 AL | 键盘输入 |
| 02h | 显示一个字符 | DL = 字符 | 屏幕输出 |
| 09h | 显示字符串 | DS:DX = 字符串首址(以 ` | AH |
| --- | --- | --- | --- |
| 01h | 读一个字符并回显 | 返回值在 AL | 键盘输入 |
| 02h | 显示一个字符 | DL = 字符 | 屏幕输出 |
| 结尾) | 输出整串 | ||
| 0Ah | 读字符串(带回车) | DS:DX = 缓冲区 | 输入整串 |
| 4Ch | 程序终止返回 DOS | AL = 返回码 | 程序结束标配 |
; 经典"Hello World"骨架
DATA SEGMENT
MSG DB 'Hello World', 0Dh, 0Ah, '$' ; $ 是 09h 的串终止符
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
MOV DX, OFFSET MSG
MOV AH, 09H
INT 21H ; 系统调用:显示字符串
MOV AH, 4CH
INT 21H ; 程序终止
CODE ENDS
END START
为什么 DS 要自己赋值? 这是 DOS 的约定——程序一开始 DS 不一定指向我们的数据段,所以 MOV AX, DATA; MOV DS, AX 是每个 DOS 程序的开场白。对比现代操作系统,这个"进入程序先建段"的步骤被内核接管了。
核心内容:BIOS 中断与键盘显示
BIOS 中断(更底层,直接访问硬件):
- INT 10h:显示服务。AH=0Eh 写字符到光标处、AH=00h 设置显示模式。
- INT 16h:键盘服务。AH=00h 读一个键(AL 返回 ASCII 码、AH 返回扫描码)、AH=01h 判断有无按键。
- INT 13h:磁盘服务(读/写扇区)。
层次关系要清楚:应用层 → DOS 功能调用(INT 21h)→ BIOS 中断 → 硬件端口 → 设备。INT 21h 内部最终也调用 BIOS。学完这章,你会明白操作系统"系统调用接口"的本质:把功能编个号、定好参数寄存器的约定、留一个统一的入口指令(软中断)——Linux 的 int 0x80/syscall 是同一种设计。
核心内容:键盘与显示的缓冲区机制
- 键盘中断(INT 09h,硬件触发)把扫描码放进键盘缓冲区(一个环形队列);程序用 INT 16h/21h 时从缓冲区取。"按键"和"读键"是两个时刻的事件——这就是为什么程序运行时按的键不会丢,会被缓冲。
- 显示原理:显存映射到内存(如文本模式 B8000h),往那个内存地址写字符,屏幕就显示出来——"输出到屏幕"在硬件层面其实是"写一段特定内存"。这是内存映射 I/O 思想的最早接触。
这一章的考点
- 画中断处理五步流程图;IRET 与 RET 的区别;
- 用 INT 21h 实现输入输出小程序(读键、显示字符串);
- 中断向量表项地址计算(n 号中断的处理程序地址在
4×n处); - 键盘扫描码与 ASCII 码的区别;
- 给"除零"场景判断会触发哪个中断。
为什么这一章重要
中断 = 操作系统"接电话"的机制。进程被抢占、系统调用、设备驱动、异常处理,全部建立在中断上;操作系统课讲"陷入(trap)/中断/异常"的分类,这里就是实物原型。逆向工程里 int 3(断点)是调试器一切断点功能的基石。这一章让你第一次看到"软件如何指挥硬件"。
第9章 宏汇编与模块化编程 —— 从"写代码"到"组织代码"
这一章在干什么
前面是"指令级"的世界。这一章跳到"源文件级":汇编器(MASM)除了翻译指令,还提供一批伪指令帮你组织程序——定义段、定义数据、定义宏、拆分模块。它是编程工程化(模块化、复用、可维护)在汇编时代的形态。
核心内容:程序的分段结构(一个完整程序的地基)
DATA SEGMENT
...
DATA ENDS
STACK SEGMENT PARA STACK
DW 100 DUP(0)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA, SS:STACK
START:
MOV AX, DATA
MOV DS, AX
...
CODE ENDS
END START
- SEGMENT/ENDS:划定一个段(代码段、数据段、栈段)。
- ASSUME:告诉汇编器"哪个段寄存器装哪个段"——它只是汇编时的约定声明,不是运行指令(所以 DS 还要用 MOV 亲手装,ASSUME 不替你干活,这是高频易错点)。
- END 标号:程序入口声明,告诉连接器从哪开始执行。
核心内容:数据定义与内存分配(伪指令 DB/DW/DD)
| 伪指令 | 单位 | 典型写法 | 分配 |
|---|---|---|---|
| DB | 字节 | NUM DB 5 / STR DB 'AB' / BUF DB 100 DUP(0) |
1 字节/个 |
| DW | 字 | WORD_VAL DW 1234h |
2 字节/个(小端存储:低字节在前) |
| DD | 双字 | DDVAL DD 12345678h |
4 字节/个 |
DUP是重复定义:DB 100 DUP(0)= 连续 100 个字节初值 0。数组、缓冲区全靠它。$运算符:当前地址。BUF_END = $常用于求数组长度。- 关键内存布局:小端存储——
DW 1234h在内存里是34h 12h(低字节在前)。这是逆向工程读内存的必备意识,也是"为什么读 [BX] 和 [BX+1] 得到不同字节"的原因。
核心内容:EQU 与宏(代码生成器)
- EQU:符号常量,
COUNT EQU 100,汇编时替换成 100。不占内存,纯粹文本替换。 - 宏(MACRO):把一段指令封装成一个"模板",调用时汇编器把模板展开到调用处。
; 宏定义
SHOW MACRO CHR
MOV DL, CHR
MOV AH, 02H
INT 21H
ENDM
; 调用(汇编时原地展开)
SHOW 'A' ; 等价于把上面三条指令写在这里
宏与子程序的本质区别(必考概念题):
| 宏 | 子程序 | |
|---|---|---|
| 机制 | 汇编时文本展开 | 运行时 CALL/RET |
| 代码量 | 调用多少次就复制多少份(代码膨胀) | 只有一份,反复 RET 回来 |
| 速度 | 快(无 CALL/RET 开销) | 有压栈/跳转开销 |
| 参数 | 灵活的形参 | 走寄存器/栈 |
展开 vs 调用:宏牺牲空间换时间,子程序反过来。这条权衡在 C++ 的 inline、模板里原样重现——宏就是汇编时代的"内联函数"。
核心内容:多模块连接(PUBLIC/EXTERN)
- PUBLIC 标号:声明"本模块导出这个符号,别的模块可用"。
- EXTERN 标号:声明"这个符号在别的模块,本模块要用"。
- 每个模块单独汇编成 .OBJ,由连接器(LINK)把多个 .OBJ 拼成一个 .EXE,并解决跨模块符号的地址。
这就是"编译 + 链接"的雏形:编译是翻译,链接是拼装。gcc 里多个 .c 文件各自编译成 .o 再链接,机制完全一致。逆向工程里"哪些函数是外部导入的"看导入表,就是 EXTERN 的运行时版本。
这一章的考点
- 写完整的分段程序骨架(段定义 + ASSUME + END 入口);
- DB/DW/DD 分配多少字节、内存布局(小端怎么排);
- 宏展开:手写展开结果并判断宏 vs 子程序的代码量;
- PUBLIC/EXTERN 跨模块访问;
- 解释 ASSUME 与 MOV DS, AX 的区别。
为什么这一章重要
这一章是从"会用指令"到"会组织程序"的质变。它直接对应编译原理的"符号表/作用域/链接",和操作系统的"程序加载(.EXE 的段重定位)"。现代编程里的头文件、extern、inline、多文件工程,源头都在这一章。
第10章 汇编与高级语言混合及逆向 —— 看穿编译器
这一章在干什么
最后一章跳出"用汇编写程序",回答两个高阶问题:① 汇编怎么和 C 互相调用(实践);② 反过来,高级语言程序在内存里到底长什么样(逆向视角)。同时把视野扩到 x86-64 和浮点指令。这章是通往"真正理解程序"的最后一公里。
核心内容:汇编与 C 混合编程
三种方式:
- C 内嵌汇编:
_asm { ... }(老 VS 语法)或asm("...")(GCC 扩展)。适合性能热点、原子操作、读写硬件寄存器。 - C 调用汇编子程序:关键在遵守 C 的调用约定(cdecl)——参数从右往左压栈、返回值在 AX(EAX)里。汇编子程序按第6章的标准栈帧写,就能被 C 直接调用。
; C 调用汇编: int add(int a, int b);
PUBLIC _add
_add PROC
PUSH BP
MOV BP, SP
MOV AX, [BP+4] ; 第一个参数 a
ADD AX, [BP+6] ; 第二个参数 b
POP BP
RET ; cdecl:调用者负责清栈
_add ENDP
注意 C 编译器会把函数名加下划线前缀(老式 32 位约定),PUBLIC 时名字要写成 _add。
3. 汇编调用 C 函数:同样遵守约定——压参数、CALL、用完清栈。
混合编程的约定三件套:名字修饰(name mangling)、调用约定(cdecl/stdcall)、栈帧布局。这三点没对齐,程序就崩——这也正是第6章调用约定的实战意义。
核心内容:从反汇编理解程序(逆向基础)
给定一段高级语言,看它的汇编长什么样;或反过来,从汇编还原逻辑。识别常见代码模式的"指纹":
| 高级语言 | 汇编特征 |
|---|---|
a[i] |
基址变址寻址 [reg+reg*2](乘以元素大小) |
if/else |
CMP + Jcc 开头、JMP 收尾 |
for/while |
循环头标签 + CMP/Jcc + 回跳 |
switch |
CMP 链 或 跳转表 JMP [table+reg*4] |
| 函数调用 | PUSH 参数 / CALL 地址;进入先 PUSH EBP; MOV EBP, ESP |
strcpy |
REP MOVSB |
| 结构体字段 | [EBP+8]、[EBP-4] 等固定偏移 |
常见编译器优化(看懂优化后的代码):
- 常量折叠:
a = 5*8直接被替换成a = 40,不生成乘法。 - 强度削减:乘 4 变
SHL reg, 2(移位比乘法快);i*10变LEA reg,[reg+reg*4]再 SHL。 - 寄存器分配:热点变量留在 EAX/ECX 等寄存器,不进内存。
- 消除公共子表达式:
x = a*b; y = a*b只算一次。
看反汇编时,先找"函数边界"(PUSH EBP / RET),再找"循环"(回跳的 Jcc),最后还原"数据访问"(偏移量)——这是 objdump -d、IDA、Ghidra 分析的标准套路。
核心内容:实模式与保护模式
| 实模式(8086) | 保护模式(80386 后) | |
|---|---|---|
| 地址寻址 | 段×16+偏移,1MB | 分段 + 分页,可达 4GB+ |
| 段 | 段寄存器直接存基址 | 段寄存器存段选择子,查 GDT/LDT 表 |
| 特权级 | 无 | 0/1/2/3 四级(OS=0,用户=3) |
| 内存保护 | 无 | 违反访问即产生保护异常 |
一句话:保护模式把"段+偏移"升级为"表+索引",加了权限和隔离。为什么必须升级?因为真实操作系统不能让用户程序随便写段寄存器去改自己的段——那是崩溃与病毒的大门。Linux 进程地址空间隔离、页表映射,都从这里来。
核心内容:x86-64 与浮点指令简介
- x86-64 扩展:寄存器 16 个、变 64 位(RAX 等,且地址能直接取 RIP 相对——
lea rax, [rip+offset]是位置无关代码的标准写法);参数传递改用寄存器(Windows x64 前 4 个参数走 RCX/RDX/R8/R9;System V 前 6 个走 RDI/RSI/RDX/RCX/R8/R9)——函数调用约定从栈为主变成了寄存器为主,因为寄存器变多了。16 个通用寄存器也让"寄存器传参"真正可行,这是 x64 比 x86 快的重要原因之一。 - 浮点指令:8087 协处理器时代是独立的 x87 栈寄存器(ST0~ST7);现代 CPU 用 SSE/AVX 的 XMM0~15(128/256 位向量寄存器),指令如
ADDSS(单精度标量)、MOVSS、CVTSI2SS(整数转浮点)。逆向里看到movsd/movss/addsd就知道是浮点运算。SSE 同时是 SIMD——一条指令对 4 个浮点数并行加,这就是现代 CPU 做科学计算、图像处理提速的物理来源。
这一章的考点
- C 函数对应的汇编栈帧分析(参数、返回值的寄存器约定);
- 识别优化后的常见模式(乘除 2 的幂 → 移位);
- 实模式/保护模式对比;
- x64 传参顺序(Windows 还是 System V 别混);
- 理解
rep movsb、lea在反汇编中的含义。
为什么这一章重要
这一章把整门课推向实际应用:混合编程让你写的汇编能进入真实工程;逆向让你能"读懂别人的程序"(调试器、IDA、漏洞分析);保护模式和 x86-64 让你从"古董 8086"走进现代 CPU。它是《操作系统》《编译原理》《计算机组成原理》和网络安全(逆向、漏洞利用)的直接接口——很多同学就是因为这一章找到了职业方向。
四、全书收尾:一张地图看明白所有内容的关系
┌────────────────────────── CPU 内部 ──────────────────────────┐
│ │
│ 通用寄存器 AX BX CX DX SI DI SP BP │
│ 段寄存器 CS DS SS ES │
│ 指针 IP(下一条指令在哪) │
│ 标志 FLAGS(ZF/SF/CF/OF …) │
│ │ 取指 / 译码 / 执行(第1章指令周期) │
│ │ 算术/逻辑/移位(第4章 ALU) │
│ │ 控制转移(第5章改 IP) │
│ │ CALL/RET 与栈帧(第6章) │
└────────┼────────────────────────────────────────────────────┘
│ 总线(地址线=20根→1MB,数据线=16根)
▼
┌────────────────────────── 内存(1MB,分四段)────────────────┐
│ 物理地址 = 段基址 × 16 + 偏移量(第2章) │
│ │
│ 高地址 ┌─────────────────────┐ │
│ │ 数据段 DS │ ← DB/DW 定义的变量(第9章) │
│ ├─────────────────────┤ │
│ │ 栈段 SS │ ← 栈向下增长(第6章) │
│ │ 参数/返回地址/局部变量 │
│ ├─────────────────────┤ │
│ │ 代码段 CS │ ← 指令按 IP 逐条执行(第1章) │
│ │ MOV/ADD/CMP/Jcc… │ │
│ ├─────────────────────┤ │
│ │ 附加段 ES │ ← 串指令的目的区(第7章) │
│ 低地址 └─────────────────────┘ │
│ │
│ 最低端:中断向量表 IVT(第8章,INT n 查表转去处理) │
└─────────────────────────────────────────────────────────────┘
栈的关系:SP 管栈顶、BP 管帧基准、CALL 压返回地址、RET 弹回来。
最后几句真言:
-
全课程一句话:汇编语言就是"数据在寄存器/内存/栈之间的搬运指令",外加"改 IP 的控制指令"和"查表的软中断",而这一切都遵循"物理地址=段×16+偏移"的内存模型。
-
复习权重从高到低:第6章栈帧与调用(最常考大题、也是理解一切函数机制的关键)> 第4章指令与标志位(计算题大户)> 第3章寻址方式(送分题+读题能力)> 第2章寄存器与物理地址(一切基础)> 第8章中断(流程题)> 第5章分支循环、第7章串指令、第9章宏汇编(中等)> 第1章概念、第10章拓展(背概念即可)。
-
如果只记住三句话:
- 数据在寄存器/内存/栈三处流动,指令负责把它们搬对地方;
- CMP 是"为决策而减",Jcc 是"看标志下决定",一切分支循环都是改 IP;
- 函数调用 = 压返回地址 + 建栈帧 + 取参数 + RET 恢复——栈,是程序"回家的路"。

浙公网安备 33010602011771号