正如 WikiBooks 里面写的 “用汇编语言开发程序可能是一个非常耗时的过程”,汇编语言的编写难度比较大,因此我们更多是为了学会阅读它。MC 笑传之常常崩!神秘崩溃牵扯出了计算机底层原理呈现了从汇编层面找到导致 JVM 出现 SIGNSEGV 的原因的过程。
本文主要参考 Assembly Language for X86 Processors 7th Edition,采用了《32 位汇编语言程序设计 第 3 版》的一些说明。
Microsoft 于 1992 年出版的 Programmer’s Guide 的有些表述我认为是比课本要清晰的,但它正处于 16 位向 32 位过渡的阶段,导致有些描述需要兼顾 16 位的系统,但对于 32 位系统是不必要的。这份老资料的一些叙述是比课本更好的,但仍然建议先通过课本掌握基础。
0. 汇编语言概述
0.1 虚拟机理论
计算机可以执行原生的机器语言,机器语言的每条指令都简单到可以用较简单的电路实现,我们称为语言 L0。但程序员不可能使用机器语言编程 —— 想想你的数电 —— 于是需要设计一种更简单的语言 L1。反过来,计算机无法直接执行 L1 语言。为了让计算机执行 L1 程序,就有了两种方法:编译和解释。
任何层编写的程序都可以被下一层编译或解释。所以你知道第一个 C 编译器是怎么来的吧。
抽象地看,每一层语言都可以对应地属于一层虚拟机 (Virtual Machine)。此处定义虚拟机是一个硬件或软件构成,用于模拟其他物理机器或虚拟机的功能。虚拟机可以直接执行当前层语言程序(物理构成),或者将当前层语言程序编译/解释到下一层虚拟机执行(软件构成)。每层虚拟机之间不能有太大差异,否则编译/解释的成本就高了。如果这一层虚拟机的编码复杂度还是过高,可以再构建一层虚拟机,形成更友好的语言,直到某一层虚拟机的编码复杂度可接受为止。
(此处瞎写的)脉冲序列 ->
11100110->E6->open->激活,从脉冲序列到自然语言,编码的复杂程度在下降。
现代计算机就是由一层层虚拟机构成的。分为四层:数字逻辑硬件 (Level 1)、指令集架构 (ISA, Level 2)、汇编语言 (Level 3)、高级语言 (Level 4)。我们所说的机器语言处于第二层。
输入输出系统也有类似于虚拟机的分层设计:硬件 (Level 0)、BIOS (Basic Input-Output System, Level 1)、操作系统函数 (Level 2) 和应用库函数 (Level 3)。
0.2 汇编程序构建流程
Level 3 的汇编程序无法由 Level 1 的物理机器直接执行,因此我们需要将汇编向 Level 2 的机器语言转化,之后交由 CPU 向 Level 1 转化。
将汇编语句转化为机器语言的程序称为汇编器 (assembler)。输入汇编器的文件称为源文件 (source file)。汇编器将生成两个文件:目标文件 (object file) 和列表文件 (listing file)。目标文件保存了转化得到的机器指令和对应的汇编代码,列表文件提供的信息会更加详细,一般用于辅助分析调试。
生成的目标文件还需要经过链接器 (linker) 的处理才能生成可执行文件,这是因为较大规模程序往往会分割为多个模块,而每个模块的源文件都会生成对应的目标文件,所以需要将它们链接起来。
graph LR
A(源文件) --> B{汇编器}
B -->|汇编| C(目标文件)
B -->|汇编| D(列表文件)
C --> E{链接器}
H(链接库) --> E
E -->|链接| F(可执行文件)
F -->|系统装载| I(输出)
我们初学 C / C++ 时,会使用 “编译” 来描述将源代码转换成可执行文件的过程,这是不准确的。编译实际只负责整个过程中的语法检查和汇编代码生成,而目标文件到可执行文件还需要经过链接,故应该称为构建 (build),实际上开发工具确实也叫成 “构建”
0.3 x86 汇编概述
x86 架构以 1978 年的 Intel 8086 处理器为开端,覆盖 Intel 从 16 位到 64 位的处理器。由于汇编语言非常依赖硬件,因此当前将 x86 汇编语言分为 IA-32 (x86-32) 和 x86-64 两种[1],而 16 位版本已经不常见了。
2. 从 C 语言认知汇编
本节假设读者已经有 C 语言基础
你说读不懂汇编?没关系,我们就没想在这里就教会你汇编。第 2、3 节主要是将你从 C / C++ 引向纯汇编。随着深入学习,你就可以语义化地理解这些程序。
前文说过,源文件经由汇编器得到目标文件,里面就存放着我们想看到的汇编代码。
如果你的网络条件较好,那么有一个很好的在线编译器平台 Compiler Explorer。该平台提供了丰富的编译器选择,有助于学习和比较编译器对源码的编译方式。
如果你需要本地查看汇编,就要让构建工具生成目标文件。对于 GCC,输入:
1 | gcc –c example.c # -c 表示仅编译不链接 |
在当前目录下生成 example.o。此时的 example.o 不能直接查看,需要使用 objdump:
1 | objdump -h example.o # 查看文件段信息 |
对于一个简单的 Hello World:
1 |
|
Compiler Explorer 在 x86_64 GCC 15.2 下的输出:
1 | .LC0: |
通过 objdump 输出的汇编:
1 |
|
objdump 输出的汇编中,由于没有经过链接过程,一些地址和函数加载是以占位符形式出现的,比如 $0x0 和 call e(下一行指令)
Compiler Explorer 输出的是 Intel 语法,而 objdump 默认输出 AT & T 语法。
课程使用 Intel 语法。使用 -M 选项可以控制 objdump 的输出格式:
1 | objdump -s -d -M intel hello.o > hello.s # 以 intel 语法显示汇编指令 |
从 C 到汇编是另一个比较大的话题,涉及到编译原理。不过你也可以做一位肉编器 😉
3. 汇编语言本地开发环境搭建
如果不追求完全的汇编环境的话,可以直接使用 C / C++ 的内联汇编。
Assembly Environment Setup 记载了这位作者的开发环境搭建方案。
3.1 使用 Visual Studio 的 MASM
从 6.15 版本开始,MASM 被整合入微软的 C / C++ SDK 中。换句话说,现在的 Visual Studio 已经集成 MASM。使用 Visual Studio 的参考 Visual Studio 2022 配置汇编语言环境,使用此程序看看是否能跑成功:
1 |
|
在 VS 上的调试体验和你对 C / C++ 代码的调试体验差不多。
说实话,Assembly Language for x86 Processors 至少在十几年前就在使用这套环境并与时俱进,而我们在 2026 年还得用 2000 年的工具链呢!
3.2 使用独立版本的 MASM
对于独立版本的 MASM,6.15 版本可于 yumyumyum/MASM 获取(这应该是旧版 Assembly Language for x86 Processors 的附件,所以有示例程序。这也是课程所用的附件,偷懒!)
记得把
make32.bat里标着 “The following lines can be customized for your system” 的条目改成你的安装路径。
用 make32 尝试生成可执行文件,例如运行 make32 AddSub.asm
1 | TITLE Add and Subtract (AddSub.asm) |
我查了一下才发现原来
Irvine32.inc就是 Assembly Language for x86 Processors 作者写的汇编语言库(
3.3 使用 SASM + NASM
也有一个类似于 Dev-C++ 的轻量 IDE——SASM。它具有语法突出显示和调试器,且开箱即用,适合初学者学习汇编语言。
NASM 是基于 Linux 的跨平台汇编器。它的语法和 MASM 最相似,因此在此一并提及,但是不会深入使用。
SASM 附带一组汇编示例。例如,这是 NASM 汇编器的 Hello World:
1 | .data |
在继续之前,我们需要重申:汇编语言非常依赖于特定软硬件平台。在参考其他教程时,请先查看它用了什么汇编器。
本文将采用 MASM,以和本校课程同步。
3.4 汇编程序的调试
在 Windows 端,我们有一些强大的 GUI 调试器。除了使用 VS 的调试功能,对于使用独立版本 MASM 的同学,我们还有 Ollydbg 和 x64dbg 可以选择。
Ollydbg 和我们使用的 MASM 版本是同一时期的,社区资源非常丰富,积攒了许多优秀的脚本和插件。但 2014 年 Ollydbg 停更,停留在了 32 位时代。
之后 x64dbg 作为继任者崭露头角。它和 Ollydbg 的最大不同就是 x64dbg 开源,同时支持 x86 和 x86_64。x64dbg 的界面更加现代,但社区支持相比 Ollydbg 略显不足,这是资历带来的限制。
作者更喜欢 x64dbg,不过课程使用的是 Ollydbg。好在对于正向调试而言,两者都可堪此任。
使用这些调试器调试程序时,调试器高亮的指令表示调试器将要执行此指令。
Ollydbg 会直接运行到我们的代码。而 x86dbg 默认自动在系统 DLL 的入口处设置断点,为了尽快达到 OEP(程序入口点),可以使用它的 “运行到用户代码”。
接下来是两个重要的调试方式:步进 (Step into) 和步过 (Step over)。两者的区别在于 CALL 的时候是否进入所调用的函数。
如果误入了本来不想步近的函数,可以使用 “运行到返回”,调试器将运行到最近的可达 ret。
调试过程中,调试器会实时展示程序控制流、寄存器、内存变化情况。
4. x86 汇编的硬件基础
由于汇编语言允许更直接地与机器交流,因此我们需要非常熟悉计算机硬件。
4.1 基本微计算机架构
一个基本的计算机应包含:
- CPU:算数与逻辑单元,包含:
- 控制器 (CU),协调指令执行
- 运算器 (ALU),执行算数和逻辑运算
- 存储器:存放程序和数据。从快到慢分级为寄存器、缓存、内存、硬盘
- 外部设备:输入和输出设备
- 总线 (bus):实现各部件间的数据交换。

4.2 加载 - 存储架构
现代 CPU 的运行速度非常快,因此它希望存储设备的速度也可以跟上。在分级存储架构下,寄存器是最快的存储器。因此,硬件设计者是希望数据尽可能地由寄存器处理的。
加载 - 存储 (Load-Store) 架构是现代 CPU 设计中最核心、最基础的概念之一。在这种架构中,CPU 只能通过两种特定的指令来访问主内存:
-
加载指令:将数据从内存复制到 CPU 内部的寄存器中。
-
存储指令:将数据从 CPU 内部的寄存器复制到内存中。
最关键的限制是,所有算术运算(如 ADD、SUB、MUL)和逻辑运算(如 AND、OR、XOR)指令的操作数必须且只能来自寄存器,运算结果也只能写回寄存器。不能直接对内存中的数据进行运算。
加载 - 存储架构主要用于 ARM、MIPS、RISC - V 等 RISC 架构中,速度快、指令简单、容易优化。
在 x86 等 CISC 架构中,存在一些指令可以直接操作内存,这被称为寄存器 - 内存 (Register–Memory) 架构。例如 ADD [0x1000], AX 可以直接将寄存器 AX 中的值加到内存地址 0x1000 处的数据上。但在硬件底层实际上是将这种复杂指令解码并分解成多个微操作,其中就包括加载、存储和运算等简单操作。所以,在硬件执行层面,现代 x86 CPU 也遵循着加载 - 存储架构的思想。
在寄存器 - 内存架构中,指令的一些操作数可能在内存中,而其他一些在寄存器中。而加载 - 存储架构的操作数都必须先在指令之前出现在寄存器中。
寄存器 - 内存架构不允许操作数全部在内存中。x86 的指令虽然确实有支持内存 - 内存的,但十分少见;而更激进的寄存器加内存 (Register plus Memory) 架构现在也更少见了。即使是那些在单条指令中实现内存 - 内存操作的较少见 CPU 也被迫将指令实现为多个内存周期 —— 一个内存周期用于读取,第二个内存周期用于写入 —— 效率上并不会比加载 - 存储架构更快
可以看看 StackOverflow 的讨论:Are there memory-memory instructions?
4.3 x86 处理器的执行环境
这里只讲 x86 处理器两种主要的存储模型:平展内存模型 (Flat Memory Model) 和段式内存模型 (Segement Memory Model)
段式内存模型将存储器视为一组相互独立的地址空间集合,即内存段 (Segement)。程序的静态数据、代码和堆栈会分配到不同的段。对于 32 位处理器,每个段的最大寻址大小为 4GB (00000000H ~ FFFFFFFFH)。
平展内存模型将存储器视为一个连续的地址空间(即一个大段),程序的静态数据、代码和堆栈都包含在这个地址空间中,称为线性地址 (Linear Address)。
为了明确允许使用的指令和存储模型,x86 处理器提供三个操作模式:保护模式 (Protected Mode)、实地址模式 (Real-Address Mode) 和系统管理模式 (System Management Mode)。
早期处理器默认使用实地址模式。由于 CPU 寻址限制加上当时内存价格高昂,同一时间只能运行一个进程,一个进程占用 1MB 内存。程序可以直接访问物理地址,因此需要直接访问系统内存或硬件设备时,该模式会非常有用,但也容易导致软件甚至硬件问题。实地址模式可以切换到其他模式。现代机器一般在开机时先进入实模式,等待操作系统准备好了之后,再进入保护模式。
之后处理器默认使用保护模式。处理器为每个进程都分配了最多 4GB 的保留地址,实现了多任务处理。保护模式下,程序无法直接访问物理地址,而必须经过操作系统的处理,从而阻止程序非法访问其他进程的内存,因此得名 “保护模式”。
在实模式中,段内存是连续分配的,段和段之间紧靠在一起;在保护模式中,段可以分散分配,程序员不知道、也没法控制段分配到哪个地址上。
保护模式实现了特权管理,设置了 0 ~ 3 共 4 个特权层 (Privilege Level)。系统核心程序、操作系统、其他系统软件和用户软件可以根据需要分别处于不同的特权层并受保护。
系统管理模式为操作系统提供实现诸如电源管理与系统安全等机制。
无论是哪种操作模式或存储模型,程序员都使用逻辑地址进行程序设计。逻辑地址由段基地址 (Base) + 偏移地址 (Offset) 组成,在 MASM 中表示为 Base:Offset。段基地址存储在段寄存器中(见 6.3)。由于段基地址不同,一个存储单元可以有多个逻辑地址,但物理地址是唯一确定的。
4.4 x86 处理器的内存管理
这一节我真的查资料查疯了,我甚至都不敢明确这些资料所描述的 “平展”、“分段 / 分页” 概念是不是一样的 XwX
就这样吧,我什么时候有了新的清晰的认知再说
段基地址加上偏移地址就形成了线性地址,理论上这个线性地址就是物理地址。这是段式存储和实模式下的定义。段在物理内存中堆叠,就像 C 中的数组一样。段内内存连续,段间地址也连续。由于缺乏保护机制,一旦段基地址 / 偏移地址出现差错,就很容易访问到其他段上去。
分段分为固定段长和不固定段长。不论哪种,只要程序所占的内存不够整数倍段空间,就很可能出现内存的 “空隙”。这部分内存的大小或许很多,但就是分配不了。这就是内存的碎片化。固定段长段内出现的碎片化称为内部碎片化 (Internal Fragmetation),不固定段长段间出现的碎片化称为外部碎片化 (External Framentation)
随着程序规模的增大,一个物理段可能无法容纳所有属于此段的数据,因此需要更多的物理段,每个段都需要指定段基地址和偏移地址。而像文字处理软件,总有数据正好处于物理段的边界,增删就会涉及数据的段间移动,这是很耗时的。
Windows 等操作系统采用了分页 (Paging) 功能,使得程序可以使用比物理内存更大的线性内存空间,并且可以减少内存的碎片化。由于线性地址空间和物理地址分离,需要再将这个线性地址页转换 (Page Translation) 成真正的物理地址。
程序的局部性原理 (Principle of Locality) 是指在程序执行期间,数据和指令往往会以某种模式集中在某些特定的区域进行访问,而不是均匀分布在整个存储空间中。局部性原理有两个主要方面:时间局部性和空间局部性。
-
时间局部性 (Temporal Locality) 指的是一个数据项在一段时间内可能被多次访问。
-
空间局部性 (Spatial Locality) 指的是一个数据项被访问后,与它相邻的数据项也很可能在不久的将来被访问。
基于这个原理,处理器初始只需要将一部分程序加载到内存,而其他部分可以留在硬盘上。程序使用的线性内存被分割成若干小区域,称为页 (Page),大小必须为 2 的幂,一般在 4KB,对应到物理内存中有相同大小的页框 (Frame)。这些页使用页目录和页表维护,将页分配到页框。程序运行时,处理器会替换掉不活跃的页,将会被请求的页加载到内存中。
当程序试图访问线性地址空间内的一个地址时,处理器会将线性地址转换为物理地址,这个过程就是页转换。如果这个地址对应的页不在内存中,处理器会中断程序并产生一个页故障 (Page Fault),之后将所请求的页从硬盘复制到内存,程序继续运行。
现在打开你的任务管理器,转到 “内存” 分项。其中的 “已提交” 就是你设备的总虚拟内存,可见它是大于物理内存的。
现在的操作系统更多是两者综合利用,即段页式内存管理:面向用户程序仍采用分段形式,而在处理内存数据时使用分页形式。
现代操作系统将所有段的基址和大小都设为相同的,这样所有段是重叠在一起的,便于索引。故平展内存模型是在段式存储模型的基础上发展而成的。
5. 数据存储于内存之中
在此不再赘述数制和码制的内容 —— 你应该去看看数电和 C 语言的那些文章
在 x86 计算机中,所有数据存储的基本单位都是字节 (byte),其他单位还有字 (word)、双字 (doubleword) 和四字 (quadword)。一个字等于两个字节,即 16 bits。
字节、字和字长
1 个字节对应两位十六进制数字(因为 4 bit 最多表示 种状态)
“在计算机中,一个字是特定处理器设计所使用的自然数据单元。字是处理器指令集或硬件作为一个单元处理的固定大小数据。处理器中的大多数寄存器通常是字大小的,并且在许多架构中,单次操作中可以传输到和从工作内存传输的最大数据量是一个字。然而,最大的可能地址大小,通常是硬件字(这里 的 “硬件字” 指处理器的全尺寸自然字,而非其他定义)
…… 但市场压力在于在扩展处理器能力的同时,保持向后兼容性。因此,在新设计中的 CPU 字长,必须作为原始字长的替代尺寸以共存。…… 另一个例子是 x86 系列,该系列已发布三种不同字长的处理器(16 位,后来的 32 位和 64 位),而 WORD 仍然表示为 16 位…… 尽管 Windows API 可以在 32 位或 64 位的 x86 处理器上使用,但它仍然坚持将 WORD 在编程语言中定义为 16 位宽度,即使在 32 位或 64 位的 x86 处理器上,标准字长分别是 32 位或 64 位。包含这种不同大小字长的数据结构将它们称为:
WORD(16 位/ 2 字节)DWORD(32 位/ 4 字节)QWORD(64 位/ 8 字节)
英特尔的 x86 汇编语言也出现了类似的现象 —— 由于指令集中支持表示各种大小,一些指令助记符带有 d 或 q 标识符,表示 “双” 和 “四”,这是根据架构的原始 16 位字大小确定的。”
——Word (computer architecture) explained
“这不是设计。这是历史。”——Charles E. Leiserson from MIT 6.172
根据 IA-32 架构软件开发手册,x86 机器以 “小端序”(little endian) 存储数据。这意味着,对于一串连续字节,在内存中是从地址较小的那个字节(最低有效字节,LSB)开始存储和读取的。
例如,在内存地址 0x100 处存储一个 32 位(双字)的数据 0x12345678。根据小端序规则,它在内存中的实际存放方式为:
-
地址
0x100存放最低有效字节0x78 -
地址
0x101存放0x56 -
地址
0x102存放0x34 -
地址
0x103存放最高有效字节0x12
读取时也从 0x100 开始读取,因此你查看内存时,会看到数据显示为 0x78563412,但实际应为 0x12345678
6. 寄存器
寄存器 (register) 是 CPU 内部用来存储指令、数据和地址的存储器,在计算机的存储器体系中处于最高层次。寄存器的存储容量有限,但读写速度非常快。一般认为,访问寄存器中的数据不需要时间,是存储计算临时数据的绝佳位置。大多数寄存器都有特殊用途。
现代 CPU 有很多寄存器,我们主要了解这些种类:通用寄存器 (general purpose register)、指令指针寄存器 (instruction pointer)、段寄存器 (segement register) 和标志寄存器 (FLAGS)。前两种种寄存器长度为当前架构的字长,即 64 bits、32 bits 及 16 bits,而段寄存器和标志寄存器的长度固定为 16 bits。
我们建议,仅使用通用寄存器存储数据,因为其他类型的寄存器都是由处理器自动处理数值的。
6.1 通用寄存器
现代 CPU 有 16 个通用寄存器,前 8 个是最常用的:
| 64 位 | 32 位 | 16 位 | 16 位中的高 8 位 | 16 位中的低 8 位 | 描述 |
|---|---|---|---|---|---|
| RAX | EAX | AX | AH | AL | 主寄存器 / 累加器 (Accumulator) |
| RBX | EBX | BX | BH | BL | 基址寄存器 (Base address) |
| RCX | ECX | CX | CH | CL | 计数寄存器 (Counter) |
| RDX | EDX | DX | DH | DL | 数据寄存器 (Data) |
| RSI | ESI | SI | - | SIL | 源索引寄存器 (Source Index) |
| RDI | EDI | DI | - | DIL | 目标索引寄存器 (Destination Index) |
| RSP | ESP | SP | - | SPL | 堆栈指针寄存器 (Stack Pointer) |
| RBP | EBP | BP | - | BPL | 基址指针寄存器 (Base Pointer) |
| R8 | R8D | R8W | - | R8B | 通用 |
| R9 | R9D | R9W | - | R9B | 通用 |
| R10 | R10D | R10W | - | R10B | 通用 |
| R11 | R11D | R11W | - | R11B | 通用 |
| R12 | R12D | R12W | - | R12B | 通用 |
| R13 | R13D | R13W | - | R13B | 通用 |
| R14 | R14D | R14W | - | R14B | 通用 |
| R15 | R15D | R15W | - | R15B | 通用 |
AX、BX、CX、DX 虽然是 16 位寄存器,但也可以拆成两个 8 位寄存器使用,因为这四个通用寄存器本来就是两个独立的 8 位寄存器拼起来得到的。两个 8 位存储器中的 “H” 或 “L” 分别表示这个寄存器处于内存中的高位或低位。
6.2 指令指针寄存器
IP 寄存器指向下一条将要执行的指令(内存位置),一旦处理器执行完一条指令,则自动增加这条指令的字节长度,以实现指令的顺序执行。IP 寄存器需要通过控制转移指令或者出现中断和异常后由处理器更改数值,而非直接赋值。
| 64-bit | 32-bit | 16-bit |
|---|---|---|
| RIP | EIP | IP |
6.3 段寄存器
| 名称 | 描述 |
|---|---|
| CS | 代码段 (Code Segement) 寄存器,指向包含当前程序的段地址。 |
| DS | 数据段 (Data Segement) 寄存器,通常指向包含变量定义的段地址。 |
| SS | 堆栈段 (Stack Segement) 寄存器,指向包含堆栈的段地址。 |
| ES | 附加段 (Extra Segement) 寄存器(用于字符串操作) |
| FS | 通用段寄存器,同数据段 |
| GS | 通用段寄存器,同数据段 |
总结起来,段寄存器都有一个目的 —— 指向可访问的内存块。段寄存器加上一个 32 位的偏移量就是逻辑地址。
对于一般的程序员,其实不需要关注段寄存器,因为用户程序不会去修改它们。但它们包含了对内存段的直接引用。
6.4 标志寄存器
标志寄存器通常反映算术运算的结果以及有关当前时间对 CPU 作施加的限制的信息。用户程序不能直接修改标志寄存器,而应由 CPU 通过特殊指令修改或响应标志寄存器的值。
标志寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。
CPU 标志寄存器的结构:
1 | ... 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 |
CF标志
进位、借位标志位 (Carry Flag)。在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。PF标志
奇偶标志位 (Parity Flag)。记录相关指令执行后,其结果的所有 bit 位中 1 的个数是否为偶数。如果为偶数,PF= 1;如果为奇数,那么PF= 0。AF标志
辅助进位标志位 (Auxiliary Carry Flag)。记录在四个最低有效位或低四位中是否产生了进位或借位。它主要用于二进制编码的十进制 (BCD) 算术。ZF标志
零标志位 (Zero Flag)。记录相关指令执行后,其结果是否为 0。如果结果为 0,那么ZF= 1;如果结果不为 0,那么ZF= 0。SF标志
符号标志位 (Sign Flag)。在进行有符号数运算的时候,它记录相关指令执行后,其结果是否为负。如果结果为负,SF= 1;如果非负,SF= 0。TF标志
自陷标志位 (Trap Flag)。如果此标志可用,调试器可以使用它来逐步执行计算机程序。CPU 在执行完一条指令后,如果检测到标志寄存器的TF位为 1,则会产生一个 type-1 中断,然后再将TF置为 0,后进行 type-1 中断后继续执行。
8086 CPU 没有直接设置或重置自陷标志位的指令。这些操作可以通过将标志寄存器的内容压入堆栈,改变内存内容以设置或重置自陷标志,并将内存内容弹出堆栈以保存回标志寄存器来完成。用到的指令是pushf和popf。IF标志
中断标志位 (Interrupt Flag)。用于确定 CPU 是否将立即响应可屏蔽硬件中断。如果标志设置为 1 ,则中断被启用。如果清除(设置为 0 ),则中断被禁用。DF标志
方向标志位 (Direction Flag)。此标志用于确定将数据从内存的一个位置复制到另一个位置的方向(正向或反向)。
如果设置为 0(使用清除方向标志指令CLD),则表示从最低地址到最高地址处理数据。这种指令模式称为自动递增模式。
如果设置为 1(使用设置方向标志指令STD),则表示从最高地址到最低地址处理数据。这称为自动递减模式。OF标志
溢出标志位 (Overflow Flag)。对于有符号数运算,运算结果超出补码表示范围(8 位:-128~+127,16 位:-32768~+32767)就是溢出。若溢出,OF置 1,否则OF清 0。
这些标志是基本标志,其他标志等待后续学习~
ACTF 提供的 x86 汇编入门文件,包含文件内注释指导和课后作业 asm_tour_1
你也许还听说有 AMD64、Intel 64 和 IA-64 。首先,AMD64 就是 x86-64,Intel 64 是对 AMD64 的兼容,也可以视作属于 x86-64;其次,IA-64 是 Intel 推出的仅 64 位指令集,但没有流行开来。 ↩︎
