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(动态栈指针)的距离偏移,在堆栈上划出专属内存区域,这是高级语言存储局部变量的底层实现原理。