基础
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 D8for 32-bit)
- 一条
NASM和MASM在语法层面存在差异
其他汇编
FASM(Flat Assembler)- 轻量、跨平台(
Windows/Linux)、支持直接生成二进制文件(如引导扇区)
- 轻量、跨平台(
GoASM- 专为
Windows设计,简化Win32 API调用,语法接近MASM
- 专为
GASLinux生态默认汇编器,采用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
mlMASM编译器的可执行文件名- 调用
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/CuMap all identifiers to upper case- 所有标识符转大写-大小写不敏感
NONE/CpPreserve case of user identifiers- 所有标识符保持原有大小写-大小写敏感
NOTPULIC/CxPreserve 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所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Macos自动更新相关06/01
- ♥ Soui一03/17
- ♥ 一些问题:vs相关、dll相关、编译相关10/27
- ♥ Macos编译x86_64相关一04/25
- ♥ Linux下修改用户密码记录08/08
- ♥ Windows 核心编程 _ 进程二06/19
热评文章
- x86_64汇编学习记述一 0
- 汇编记述二 0
- 汇编记述三 0
- 标志寄存器 0
- x86_64汇编学习记述二 0
- 相关指令 0