汇编语言速成(NASM语法)
本教程使用 NASM (Netwide Assembler) 编写汇编代码。你不需要精通汇编,但需要能看懂和编写简单的汇编程序。
1. 基本语法
; 这是注释(分号开头)
section .data ; 数据段 - 存放初始化的数据
msg db 'Hello', 0 ; 定义字节串,以0结尾
section .bss ; BSS段 - 存放未初始化的数据
buffer resb 64 ; 预留64字节空间
section .text ; 代码段 - 存放指令
global _start ; 导出符号,让链接器能找到
_start: ; 标签(代码的入口点)
mov eax, 1 ; 指令
; ...关键概念
- section — 划分代码段、数据段
- 标签 (label) — 一个名字加冒号,代表当前地址
- 指令 — CPU 执行的操作(mov, add, jmp…)
- 伪指令 — 给汇编器的指示(db, dw, dd, resb…)
2. 数据定义
; 定义数据
db 0x55 ; Define Byte - 1字节
dw 0xAA55 ; Define Word - 2字节
dd 0x12345678 ; Define Dword - 4字节
dq 0 ; Define Qword - 8字节
; 定义字符串
msg db 'Hello, World!', 0x0D, 0x0A, 0
; 回车 换行 结束符
; 预留空间(BSS段)
buffer resb 256 ; 预留256字节
count resd 1 ; 预留1个双字(4字节)
; 填充
times 510-($-$$) db 0 ; 用0填充到510字节
; $ = 当前地址
; $$ = 当前section起始地址3. 常用指令
3.1 数据传送
mov eax, 42 ; 寄存器 ← 立即数
mov eax, ebx ; 寄存器 ← 寄存器
mov eax, [0xB8000] ; 寄存器 ← 内存(方括号=取内容)
mov [0xB8000], eax ; 内存 ← 寄存器
push eax ; 压栈
pop ebx ; 出栈
xchg eax, ebx ; 交换两个寄存器的值3.2 算术运算
add eax, 5 ; eax = eax + 5
sub eax, 3 ; eax = eax - 3
inc eax ; eax++
dec eax ; eax--
mul ebx ; edx:eax = eax * ebx(无符号)
div ebx ; eax = edx:eax / ebx, edx = 余数3.3 逻辑运算
and eax, 0xFF ; 按位与
or eax, 0x01 ; 按位或
xor eax, eax ; 按位异或(这里是清零技巧)
not eax ; 按位取反
shl eax, 4 ; 左移4位(×16)
shr eax, 4 ; 右移4位(÷16)3.4 比较和跳转
cmp eax, 10 ; 比较(做减法但不保存结果,设置标志位)
; 条件跳转(基于标志位)
je label ; Jump if Equal (ZF=1)
jne label ; Jump if Not Equal (ZF=0)
jg label ; Jump if Greater (有符号)
jl label ; Jump if Less (有符号)
ja label ; Jump if Above (无符号)
jb label ; Jump if Below (无符号)
jge label ; Jump if Greater or Equal
jle label ; Jump if Less or Equal
; 无条件跳转
jmp label ; 总是跳转
jmp $ ; 跳转到当前位置(无限循环)3.5 函数调用
; 调用函数
call my_function ; 压入返回地址,跳转到 my_function
; 函数定义
my_function:
push ebp ; 保存旧的基址指针
mov ebp, esp ; 建立新的栈帧
; 函数体...
mov esp, ebp ; 恢复栈指针
pop ebp ; 恢复基址指针
ret ; 弹出返回地址,跳转回去3.6 字符串操作
; SI/DI 用于字符串操作
lodsb ; AL = [SI], SI++ (加载一个字节)
stosb ; [DI] = AL, DI++ (存储一个字节)
movsb ; [DI] = [SI], SI++, DI++ (复制一个字节)
rep movsb ; 重复执行 movsb,CX次3.7 中断调用
int 0x10 ; 调用 BIOS 视频服务
int 0x13 ; 调用 BIOS 磁盘服务
int 0x80 ; 调用 Linux 系统调用(Linux环境)
; 中断控制
cli ; 关闭中断(Clear Interrupt Flag)
sti ; 开启中断(Set Interrupt Flag)4. NASM 特有指令
[BITS 16] ; 生成16位代码
[BITS 32] ; 生成32位代码
[BITS 64] ; 生成64位代码
[ORG 0x7C00] ; 设置代码起始地址(影响标签的地址计算)
%include "file.asm" ; 包含另一个文件
%define SIZE 512 ; 定义常量
equ ; 定义常量: OFFSET equ 0x7C005. 实例:Hello World(引导扇区版)
; hello.asm - 在BIOS中打印 Hello World
[BITS 16]
[ORG 0x7C00]
start:
; 设置段寄存器
xor ax, ax
mov ds, ax
; 打印字符串
mov si, message
call print_string
; 无限循环
jmp $
; 打印字符串函数
; 输入: SI = 字符串地址
print_string:
mov ah, 0x0E ; BIOS功能:teletype输出
.loop:
lodsb ; AL = [SI], SI++
cmp al, 0 ; 字符串结束?
je .done
int 0x10 ; 打印字符
jmp .loop
.done:
ret
message db 'Hello, World!', 0x0D, 0x0A, 0
; 填充到510字节,然后加引导签名
times 510-($-$$) db 0
dw 0xAA55编译和运行:
nasm -f bin hello.asm -o hello.bin
qemu-system-x86_64 -drive format=raw,file=hello.bin6. 阅读汇编的技巧
- 看标签 — 标签就是函数名或跳转目标
- 看 mov — 数据从哪来到哪去
- 看 cmp + jxx — 这就是 if 判断
- 看 call/ret — 函数调用和返回
- 看 int — 中断调用(类似系统调用)
- 方括号 [] — 表示内存访问(取地址的内容)