Skip to content

11_NASM

一、 汇编器指令与语法结构

NASM 在语法设计上比 MASM 更纯粹,去除了大量繁琐的伪指令和隐式操作。

1. 运行模式与内存起点

  • 物理地址重定位 (org): 在编写无操作系统的底层代码(如 Bootloader)时,NASM 使用 org 0x7C00 直接告知汇编器代码在内存中的绝对加载基址。MASM 通常依赖复杂的段定义(SEGMENT/ASSUME)和链接器来分配地址。

  • 架构模式控制 (bits): NASM 使用 bits 16、bits 32 或 bits 64 显式声明指令集架构模式。这对应 MASM 中的 .8086、.386 等处理器选择伪指令。

2. 标号与作用域 (Labels)

  • 全局标号: 如 _start:,用于标记函数或代码块入口。

  • 局部标号 (.): NASM 原生支持局部标号(如 .loop:、.end:)。只要在全局标号下使用点号前缀,汇编器就会将其归属到上一个全局标号的作用域内。这极大地避免了在 MASM 中因标号重名而导致的编译冲突。

3. 宏定义

  • NASM 使用 %define NAME VALUE 定义常量宏,类似于 C 语言的 #define。MASM 中等价的用法是 EQU 伪指令。

二、 数据定义与变量

NASM 对内存大小的控制更为直接,分为初始化与未初始化两类。

1. 初始化数据声明

NASM 与 MASM 均使用相同的助记符来定义自带初始值的数据,存放在 .data 段:

  • db (Define Byte, 1字节)、dw (Word, 2字节)、dd (Doubleword, 4字节)、dq (Quadword, 8字节)。

  • NASM 额外支持更宽的数据定义:dt (10字节)、do (16字节)、dy (32字节)、dz (64字节)。

  • 字符串定义: NASM 通常在字符串末尾手动追加 , 0 (Null-terminated) 或 , 0x0a, 0x0d (回车换行)。MASM 在 DOS 时代的特定中断下需用 $ 作为结束符。

2. 未初始化数据声明 (保留空间)

对于在 .bss 段声明的未初始化全局变量:

  • NASM: 拥有专用的 res (Reserve) 系列伪指令,如 resb 5(保留 5 个字节)、resw、resd。

  • MASM: 依赖 ? 操作符或 DUP 语法,如 var DB 5 DUP(?)。

三、 内存寻址与指针表示

这是 NASM 与 MASM 在语法上最核心的分水岭。NASM 奉行“所见即所得”,没有任何隐式的内存解引用。

1. 严格的解引用语法 ([])

  • NASM: 任何变量名或标号默认只代表内存地址(指针)。要获取地址里的值,必须严格使用中括号 [] 进行解引用。例如,mov al, [si] 表示将 si 寄存器指向的内存值读入 al。

  • MASM: 变量名默认代表值(隐式解引用 [var]),要取地址必须加 OFFSET 操作符(如 MOV EAX, OFFSET var)。

2. 内存宽度修饰符 (Size Specifiers)

当汇编器无法推断内存操作数的字节宽度时:

  • NASM: 使用 size [address],如 cmp byte [si], 0。丢弃了 MASM 中的 ptr 关键字。

  • MASM: 强制使用 PTR,如 cmp byte ptr [si], 0。

3. LEA 指令与 SIB 寻址

  • lea dest, [base + index * scale + offset]:加载有效地址 (Load Effective Address)。

  • 在 NASM 中,lea 常被用作“无副作用的算术运算”(Witchcraft)。它利用底层寻址电路直接计算括号内的结果并赋给目标寄存器,而不实际访问该内存,也不影响状态标志位(Flags)。

四、 寄存器与架构演进

1. 16 位基础架构

  • 通用寄存器: AX, BX, CX, DX。均可拆分为高位 (AH) 和低位 (AL) 两个 8 位独立寄存器。修改子寄存器会直接影响母寄存器。

  • 段寄存器: CS (代码段), DS (数据段), ES, FS, GS, SS (堆栈段)。

  • 指针寄存器: SP (堆栈指针), BP (基址指针), SI (源变址), DI (目标变址)。

2. 64 位长模式演进 (x64)

从 16/32 位升级至 64 位时,寄存器体系发生了两处关键变化:

  • 新增 R8-R15 通用寄存器: 这 8 个新寄存器同样拥有子宽度别名,例如 R8 的 32 位为 R8D,16 位为 R8W,8 位为 R8B。

  • 32 位操作的零扩展特性: 当操作 64 位寄存器(如 RAX)的低 32 位部分(EAX)时,例如执行 mov eax, 1,硬件会自动清零该寄存器的高 32 位。但操作 16 位 (AX) 或 8 位 (AL) 时,高位数据保持原样。

五、 核心运算与控制流

1. 硬件除法规则 (div)

除法指令是 x86 中较为特殊的运算,它隐式强绑定特定寄存器:

  • 操作模式: div bx 表示用隐式的 DX:AX 联合体除以 bx。

  • 除法前置要求: 执行前必须手动清零高位寄存器(如 xor dx, dx),否则残留的垃圾值会导致结果完全错误或触发除零异常。

  • 结果存储: 商存入 AX(或 RAX/EAX),余数存入 DX(或 RDX/EDX)。这一机制常被用于将数字转换为 ASCII 字符(模 10 取余)。

2. 比较与跳转 (cmp / jcc)

  • cmp 底层执行减法但不保存结果,仅影响 Flags 寄存器。

  • 根据 Flags 状态执行条件跳转:如 je/jz (相等/为零则跳)、jne/jnz (不等/非零则跳)。

  • hlt 指令用于让 CPU 停止执行指令并进入挂起状态,通常用于中断等待或系统死机 (Halt and catch fire)。

六、 堆栈控制与函数系统

x86 架构中,堆栈永远向低地址方向(向下)生长。

1. 基础栈操作

  • push reg:先将堆栈指针 SP 减去操作数宽度,然后将值写入内存。

  • pop reg:从 SP 处读取值,然后将堆栈指针 SP 加上操作数宽度 (First-In-Last-Out)。

2. 函数调用机制

  • call function:自动将当前的指令指针 (IP) 压入堆栈,然后跳转到目标地址。

  • ret:从堆栈弹出返回地址到 IP,恢复执行流。

  • 栈帧结构 (Stack Frame): 利用 BP(基址指针)保持稳定,结合 SP(动态栈指针)的距离偏移,在堆栈上划出专属内存区域,这是高级语言存储局部变量的底层实现原理。