基础
NASM
- 开源、跨平台汇编器(支持
Windows
、Linux
、macOS
) - 特点
- 语法简洁严格(如内存访问必须用
[ ]
包裹) - 无自动类型推断(需显式指定操作数大小,如
mov word [var], 1
) - 支持直接生成纯二进制文件(如引导扇区程序)
- 语法简洁严格(如内存访问必须用
- 应用
- 操作系统开发、嵌入式系统、跨平台项目
MASM
- 微软商业汇编器,深度集成于
Windows
生态(如Visual Studio
) - 特点
- 语法宽松(如内存访问可省略
[ ]
,支持mov ax, table[di]
) - 自动类型推断(如
mov [var], 1
能识别var
类型) - 提供高级伪指令(如
ASSUME
关联段寄存器)
- 语法宽松(如内存访问可省略
- 应用
Windows
驱动开发、与C/C++
混合编程
对比
- 不同的汇编器,它们的核心目标相同:将人类可读的汇编源代码(
.asm
文件)翻译成处理器可执行的机器码(包含在.obj
或特定格式文件中)- 一条
MOV EAX, EBX
在正确的上下文中,经过任何一个汇编器都会产生相同的机器码(如89 D8
for 32-bit
)
- 一条
NASM
和MASM
在语法层面存在差异
其他汇编
FASM(Flat Assembler)
- 轻量、跨平台(
Windows/Linux
)、支持直接生成二进制文件(如引导扇区)
- 轻量、跨平台(
GoASM
- 专为
Windows
设计,简化Win32 API
调用,语法接近MASM
- 专为
GAS
Linux
生态默认汇编器,采用AT&T
语法(与Intel
语法差异大)
MASM
环境配置
参考
masm32
- 下载
- https://www.masm32.com/download.htm
Australia 1
- 安装
- 解压安装程序,点击地球上的
Install
- 解压安装程序,点击地球上的
- 后续
- 经过一系列点击,在控制台编译二进制文件
- 将
masm32
下的bin目录添加到path
- 新建
include
,将masm32
目录下的inclcude
目录添加进去 - 新建
lib
,将masm32
目录下的lib
目录添加进去 - 实践:
include
和lib
添加到了用户环境变量
- 新建
测试代码
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.386 .model flat, stdcall option casemap:none include windows.inc include user32.inc includelib user32.lib .data g_sz db "hello world", 0 .code START: invoke MessageBoxA, NULL, offset g_sz, NULL, MB_OK end START |
编译1
ml
MASM
编译器的可执行文件名- 调用
Microsoft
的汇编器
负责将人类可读的汇编语言源代码(.asm
文件)翻译成机器可读的目标文件(.obj
文件)
/c
- 只编译(
Assemble Only
),不链接(Do Not Link
) - 为什么
/c
?
开发较大项目时,程序通常由多个模块(源文件)组成。需要先单独编译每个模块(/c
为每个.asm
文件生成.obj
文件)
或者,当需要链接多个目标文件或链接外部库时
或者,只想确认汇编代码语法是否正确,而不需要生成最终的可执行文件
- 只编译(
MASM
的ml
程序实际上既包含汇编器也包含链接器的功能- 默认情况下,如果你只给
ml
一个.asm
文件(像ml test.asm
),它会: - 将
test.asm
汇编成test.obj
(目标文件) - 然后自动调用内置的链接器(
link.exe
),尝试将test.obj
链接成可执行的.exe
文件(或.dll
) - 在这个命令
ml /c /coff test.asm
中,使用/c
明确告诉MASM
:“只需把test.asm
编译成目标文件test.obj
,不要尝试链接生成.exe
或.dll
”
- 默认情况下,如果你只给
/coff
- 指定目标文件的格式为
COFF
(Common Object File Format
) - 目标文件(
.obj
)需要遵循特定的二进制格式标准。/coff
选择Microsoft
的COFF
格式
COFF
格式是生成PE
(Portable Executable
) 文件(即Windows
操作系统使用的.exe
、.dll
、.sys
等可执行或库文件格式)的基础
链接器(link.exe
)需要COFF
格式的目标文件才能正确地将其组装成PE
文件
- 指定目标文件的格式为
- 可选格式
MASM
以前支持/omf
(OMF - Object Module Format
)目标文件格式,主要用于一些较旧的DOS/Win16
开发环境- 现代
32
位(x86
)及64
位(x64
)Windows
开发都应使用/coff
1 |
ml /c /coff test.asm(coff--PE) |
1 2 3 |
// 只编译,不链接 ml /c /coff file1.asm file2.asm file3.asm |
编译2
(拓展)
- 编译并直接生成可执行文件(一步到位)
MASM
会先汇编test.asm
生成test.obj
(这一步隐式包含了/c
的行为)- 然后
MASM
会自动调用链接器link.exe
,传递给它生成的test.obj
文件、指定的库文件(kernel32.lib
,user32.lib
)和链接选项(/subsystem:console
) - 最终会生成一个
test.exe
可执行文件(控制台应用程序)
1 |
ml /coff test.asm kernel32.lib user32.lib /link /subsystem:console |
链接
- 使用
Microsoft Linker
(link.exe
) 将 目标文件(.obj
) 链接成Windows
可执行文件(.exe
) 的关键步骤 link
- Microsoft 链接器 (
link.exe
) 的可执行文件名 - 它的核心任务是将一个或多个目标文件(
.obj
) 以及库文件(.lib
) “粘合”在一起,解决程序各部分之间的引用关系(比如函数调用、全局变量的访问),并最终输出一个符合特定操作系统格式(如~
格式)的可执行文件(.exe
) 或动态链接库(.dll
)
- Microsoft 链接器 (
/subsystem:windows
- 明确指定要生成的可执行文件类型为图形用户界面 (
GUI
) 应用程序
- 明确指定要生成的可执行文件类型为图形用户界面 (
- Windows 程序主要有两种运行方式
/subsystem:console
/subsystem:windows
1 |
link /subsystem:windows test.obj |
16
位与32
位汇编
核心硬件差异
特性 | 16 位汇编 |
32 位汇编 |
寄存器宽度 | 16 位(如 AX , BX , CX , DX ) |
32 位(扩展为 EAX , EBX , ECX , EDX ) |
地址总线宽度 | 20 位(最大寻址 1MB ) |
32 位(最大寻址 4GB ) |
通用寄存器 | 8 个(AX , BX , CX , DX , SI , DI , BP , SP ) |
8 个扩展为32 位 + 新增8 个(如 EBP , ESP ) |
段寄存器 | 4 个(CS , DS , ES , SS ) |
6 个(新增 FS , GS ) |
内存寻址模式
16
位(实模式)
1 2 3 4 5 6 7 8 9 |
地址计算: 物理地址 = 段寄存器 × 16 + 偏移地址 示例:DS=0x1000, BX=0x200 → 物理地址 0x10200 // 限制 每个段最大 64KB(偏移地址 16 位) 全局内存空间仅 1MB(20 位地址总线) |
32
位(保护模式)
1 2 3 4 5 6 7 |
地址计算: 物理地址 = 段选择符 → GDT/LDT + 32位偏移地址 段寄存器(如 DS)存储 选择子(指向全局描述符表 GDT 的索引) 偏移地址可达 4GB(32 位偏移寄存器如 EAX) |
工作模式差异
模式 | 16 位环境(实模式) |
32 位环境(保护模式) |
特权级 | 无特权级,直接访问硬件 | 4 级特权(Ring 0~3 ),限制硬件访问 |
内存保护 | ❌ 无隔离(所有程序可写任意内存) | ✅ 通过段描述符权限位保护内存 |
多任务支持 | ❌ 需自行切换任务 | ✅ 硬件级任务切换(TSS 任务状态段) |
中断处理 | 直接修改 IVT (中断向量表) |
通过 IDT (中断描述符表)及特权检查 |
指令集扩展
32
位指令在16
位基础上新增操作:- 扩展寄存器操作:
1 2 |
mov eax, 0x12345678 ; 32位立即数赋值(16位仅支持 mov ax, 0x1234) add ebx, ecx ; 32位寄存器运算 |
- 新寻址方式:
1 |
mov eax, [ebx + esi*4 + 0x10] ; 比例因子寻址(16位仅支持 BX+SI 等组合) |
- 系统指令:
1 2 |
lgdt [gdtr] ; 加载全局描述符表(启用保护模式) mov cr0, eax ; 控制寄存器操作(16位无此指令) |
MASM
语法
特点
- 开发环境
- 微软官方工具链成员,与
Visual Studio
无缝集成(项目类型.asm
可编译调试) - 通过
.inc
包含文件(如kernel32.inc
,user32.inc
)直接调用系统函数
- 微软官方工具链成员,与
- 语法宽松
- 变量定义后无需显式指定操作数大小(如
mov [var], 1
根据var
定义自动识别byte/word/dword
) - 写
mov ax, var
等效于mov ax, [var]
(NASM
强制要求[ ]
) - 不区分大小写,
MyLabel
与mylabel
视为同一符号
- 变量定义后无需显式指定操作数大小(如
基础语法
- 程序框架伪指令
1 2 3 4 5 6 7 8 9 10 11 |
.MODEL FLAT, STDCALL ; 内存模型(FLAT=32位平坦模式)及调用约定(STDCALL) .STACK 4096 ; 定义堆栈大小(字节) .DATA ; **数据段开始** myVar DB 10 ; 定义字节变量(初始值10) msg DB "Hello", 0 ; 字符串(以0结尾) .CODE ; **代码段开始** main PROC ; 过程(函数)定义 ; 代码逻辑... RET ; 返回 main ENDP ; 过程结束 END main ; 程序入口点标记 |
- 变量与数据定义
伪指令 | 含义 | 示例 |
DB |
定义字节 | age DB 25 |
DW |
定义字(2字节) | counter DW 1000 |
DD |
定义双字(4字节) | pointer DD 0x00400000 |
DQ |
定义四字(8字节) | bigNum DQ 1234567890 |
DT |
定义十字节 | float DT 3.1415926535 |
DUP |
重复初始化 | buffer DB 100 DUP(?) |
- 表达式与运算符
1 2 3 4 |
OFFSET myVar ; 获取变量偏移地址(等价于 NASM 的 `mov eax, myVar`) LENGTHOF array ; 数组元素个数(如 `array DD 10,20,30` → LENGTHOF=3) SIZEOF array ; 数组总字节数(此例 SIZEOF=12) TYPE myVar ; 返回变量类型大小(DB=1, DW=2, DD=4) |
核心语法
- 内存访问的宽松性
1 2 |
mov eax, myVar ; 直接读取变量(自动加 [ ]) mov [ebx], 100 ; 向 ebx 指向的地址写入100(自动推断为 dword) |
1 2 |
mov [ebx], 100 ; 可能误判大小 → 显式指定解决: mov byte ptr [ebx], 100 ; 明确写入1字节 |
ASSUME
伪指令(段关联)- 编译器据此生成正确的段前缀(如
mov eax, [ebx]
自动添加DS:
前缀)
- 编译器据此生成正确的段前缀(如
1 2 3 4 5 |
ASSUME DS:FLAT, CS:FLAT, SS:FLAT ; 告知编译器段寄存器的逻辑指向 ; ASSUME 不实际设置寄存器值!需手动加载: mov ax, @DATA ; @DATA 是 MASM 预定义符号(数据段选择子) mov ds, ax ; 实际加载 DS |
- 过程函数定义
1 2 3 4 5 6 7 8 9 |
; 定义带参数的函数(使用 STDCALL 调用约定) MyProc PROC param1:DWORD, param2:WORD push ebp mov ebp, esp ; 建立栈帧 mov eax, param1 ; 访问参数(ebp+8) ; ...函数逻辑... pop ebp RET 6 ; STDCALL 清理参数(2个参数共6字节:DWORD+WORD) MyProc ENDP |
1 2 |
;调用 PrintString myMessage |
- 结构体
1 2 3 4 5 6 7 8 |
Point STRUCT x DD ? y DD ? Point ENDS ; 使用 p1 Point <10, 20> ; 初始化结构体实例 mov eax, p1.x ; 访问成员 |
文档
固定头
8086
和286
都是16
位的,从386
开始是32
位,后缀+P
的指可以使用跳转指令的(适用于面向内核)
1 2 3 |
.386 ; 表示使用的是386的是指令集 .model flat, stdcall ; 指明内存模型和调用约定 option casemap:NONE ; 其它选项,和ml或link的命令选项等价,32位汇编一般只用casemap-NONE |
casemap
ALL
/Cu
Map all identifiers to upper case
- 所有标识符转大写-大小写不敏感
NONE
/Cp
Preserve case of user identifiers
- 所有标识符保持原有大小写-大小写敏感
NOTPULIC
/Cx
Preserve case in publice, externs
分段
32
位汇编取消 了分段,改用内存属性来划分,称作节(section
)、内存区或内存块- 编写时每种类型可以定义多个,编译期会自动被同类归置
.DATA
- 可读、可写
- 初始化的全局变量
.CONST
- 可读
- 只读数据区
.DATA?
- 可读、可写
- 未初始化的全局变量
.CODE
- 可读、可执行
- 代码
段寄存器
- 实模式(
16
位)- 段寄存器存储的是段基址的高
16
位,物理地址计算为: - 此时段寄存器仅作为地址计算组件,不参与内存保护
- 段寄存器存储的是段基址的高
1 2 |
物理地址 = (段寄存器 << 4) + 偏移地址 :DS=0x1000, BX=0x0020 → 物理地址 0x10020 |
- 保护模式(
32
位)- 段寄存器存储的是段选择子(
Selector
),本质是索引编号: - 段选择子 = 索引号(
13
位) +TI
标志(1
位) +RPL
权限(2
位) - 索引号:指向全局描述符表(
GDT
)或局部描述符表(LDT
)的条目 - 描述符:包含32位段基址、段界限、访问权限等
16
位段寄存器通过索引描述符表间接扩展为32
位段基址
- 段寄存器存储的是段选择子(
1 |
物理地址 = 段基址(32位) + 偏移地址(32位) |
- 长模式(
64
位)CS/SS/DS/ES
被强制设为0
(平坦内存模型,偏移地址直接寻址全64位空间)FS/GS
仍可自定义基址(通过MSR
寄存器加载64
位基址)- 除
FS/GS
外,其他段寄存器失效,FS/GS
通过64
位基址扩展寻址能力
1 |
物理地址 = FS/GS基址 + 偏移地址 |
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Linux » Linux 高性能服务器编程:信号12/12
- ♥ Windbg:命令总览学习一04/06
- ♥ Windows Dll自卸载相关10/19
- ♥ Macos蓝牙相关05/31
- ♥ Dump分析:重复释放堆内存,死锁03/17
- ♥ X86_64汇编学习记述三08/08