汇编语言速成(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 0x7C00

5. 实例: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.bin

6. 阅读汇编的技巧

  1. 看标签 — 标签就是函数名或跳转目标
  2. 看 mov — 数据从哪来到哪去
  3. 看 cmp + jxx — 这就是 if 判断
  4. 看 call/ret — 函数调用和返回
  5. 看 int — 中断调用(类似系统调用)
  6. 方括号 [] — 表示内存访问(取地址的内容)