关于 汇编语言:1. 汇编语言基础

一、汇编语言简介

1.1 什么是机器语言?

定义:机器语言是 CPU 唯一能够直接识别和执行的语言,由 0 和 1 组成的 二进制代码,每一条都是对硬件的直接控制。

特点:

特性 描述
形式 全部是二进制(0 和 1)
可读性 对人类极差
与 CPU 的关系 完全兼容,直接运行
表达能力 每一条机器语言对应一个 CPU 指令(如加法、跳转、内存访问等)
写作方式 极不方便,几乎没人手写机器码

示例:一条 x86 指令 mov al, 0x61(把数值 0x61 赋值给 AL 寄存器)在机器语言中的二进制可能是:

10110000 01100001

拆解:

  • 10110000 → 操作码(opcode),代表 mov al, imm8

  • 01100001 → 操作数 0x61

1.2 什么是汇编语言?

定义:汇编语言是用“助记符”(Mnemonic)表示机器指令的人类可读语言,每一条汇编语句通常对应一条机器指令。它是介于机器语言和高级语言之间的低级语言

特点:

特性 描述
形式 类似英语缩写(如 mov, add, jmp
可读性 相对较高,容易理解
可维护性 可以加标签、注释,更容易修改
转换方式 需要汇编器(如 NASM)转换为机器码
与硬件关系 紧密贴合每条机器指令

示例:

mov al, 0x61   ; 把 0x61 装入 AL 寄存器

这条语句写给人类看,汇编器会将其转换为上一节的二进制机器码。

1.3 汇编语言和机器语言的关系

汇编是机器语言的“符号化表达”:

汇编语言 机器语言(二进制)
mov al, 0x61 10110000 01100001
add eax, ebx 00000001 11000011(示例)
jmp label 11101001 00000010(示例)

编译过程:

汇编语言 (.asm)
   ↓  [汇编器,如 NASM/MASM]
机器语言(.obj/.bin)
   ↓  [链接器,生成可执行程序]
程序运行(.exe/.out)

1.4 对比:机器语言 vs 汇编语言

项目 机器语言 汇编语言
表示形式 二进制(如 10110000 助记符(如 mov al, 0x61
可读性 极低(人类难以理解) 中等(可读性强于机器码)
可维护性 不可维护 可添加标签、注释
执行效率 最高(直接被 CPU 执行) 同机器语言一样高
写作难度 极高 相对较低
转换过程 已是最终形式 需汇编器转换为机器码

1.5 汇编语言的用途

领域 具体用途
逆向工程 分析软件行为,破解或还原源码
安全分析 分析恶意代码、二进制漏洞
操作系统/驱动开发 操作寄存器、内存、中断
嵌入式开发 与硬件直接打交道,如 MCU、裸机编程
性能优化 在关键位置手写汇编提升运行效率

1.6 小结

机器语言是 CPU 的母语,汇编语言是人类写出来的“可读的机器语言”。理解汇编语言,就是你打开二进制世界大门的第一把钥匙。


二、指令集简介

2.1 什么是「指令集」

定义:指令集(Instruction Set Architecture,ISA)是CPU 能识别和执行的所有机器指令的集合

它定义了:

  • 可用的指令(如 movaddjmp

  • 使用的寄存器

  • 内存访问方式(寻址方式)

  • 数据宽度与数据结构

简单理解:可以把指令集看作是「CPU 的字典」,写汇编语言时,必须用它能“听懂”的单词(指令)。

2.2 主流指令集架构对比

指令集 开发者 位宽 应用领域 是否开源
x86 Intel 16/32-bit 老式 PC、嵌入式  封闭
x86_64 AMD/Intel 64-bit PC、服务器  封闭
ARM ARM 公司 32/64-bit 手机、嵌入式、IoT 部分开源
RISC-V 加州大学伯克利 32/64/128-bit 嵌入式、学术、开源芯片  完全开源

2.3 常见指令集详细分析

1)x86(32位)

  • 历史最悠久,最经典的 CISC(复杂指令集)架构

  • 拥有庞大的指令集,兼容性强

  • 指令长度不固定(1~15 字节)

  • 寄存器数量少(如 eax, ebx, ecx, edx

; x86 示例(32 位)
mov eax, 1      ; 把数值 1 赋给 eax
add ebx, eax    ; 把 eax 加到 ebx 上

用途:老式 Windows 程序、早期游戏、嵌入式控制系统

2)x86_64(64位)

  • 又叫 AMD64,是 x86 的扩展版本

  • 支持 64 位寻址,更大内存空间

  • 增加了更多寄存器(如 r8 ~ r15

  • 向后兼容 x86(32 位)

; x86_64 示例
mov rax, 0x10
add rbx, rax

用途:现代操作系统(Windows/Linux/macOS)桌面程序,服务器软件

3)ARM 架构(包括 ARMv7, ARMv8 等)

  • 典型的 RISC(精简指令集) 架构

  • 每条指令长度固定(通常为 4 字节)

  • 指令更简单,适合流水线执行

  • 功耗低、效率高,广泛用于移动设备

; ARM 汇编(AArch32)
MOV R0, #0x5      ; 把常量 0x5 放入 R0
ADD R1, R0, #3    ; R1 = R0 + 3
; ARM64 (AArch64)
MOV X0, #10       ; 64 位寄存器 X0 = 10
ADD X1, X0, #5    ; X1 = X0 + 5

用途:Android 手机、iOS、树莓派、嵌入式系统

4)RISC-V

  • 完全开源的 RISC 架构(由 UC Berkeley 发起)

  • 模块化设计:可按需扩展整数、多媒体、浮点、原子等指令

  • 社区活跃,逐步进入产业(比如华为、阿里都做了 RISC-V 芯片)

; RISC-V 汇编
li a0, 5         ; a0 寄存器载入数值 5
addi a1, a0, 3   ; a1 = a0 + 3

用途:物联网、国产芯片、教育研究、未来替代 ARM 的热门选择

2.4 CISC vs RISC 架构区别

比较项 CISC(x86) RISC(ARM、RISC-V)
指令数量 多,复杂(如字符串操作) 少,简单(每条一件事)
指令长度 可变(1~15 字节) 固定(ARM 为 4 字节)
执行效率 单条功能多,但硬件实现复杂 单条功能少,但更快、更高效
编译优化 编译器难优化 编译器易优化
能源效率 一般 高(移动设备更省电)

2.5 不同架构汇编

假设我们要做的操作是:把数值 3 加到寄存器 A 上。

架构 汇编代码示例
x86 add eax, 3
x86_64 add rax, 3
ARM ADD R0, R0, #3
RISC-V addi a0, a0, 3
  • x86/x86_64:常见于 Windows 桌面程序分析(用 IDA Pro 或 Ghidra)

  • ARM:在逆向 Android App 时会经常看到,尤其是 lib*.so

  • RISC-V:国产芯片、IoT 分析越来越多会用到

2.6 小结

你需要掌握的点 解释
什么是指令集 定义了 CPU 能识别的所有指令
x86/x86_64 是什么 用于 PC 的复杂指令集
ARM 是什么 移动设备主流架构,能效高
RISC-V 是什么 开源、模块化、未来趋势
CISC 与 RISC 的区别 是否复杂多功能 vs 精简高效
不同指令集汇编的差异 不同寄存器命名、语法略有差异

三、常见汇编工具:nasmmasmgas

3.1 NASM(Netwide Assembler)

简介:

  • NASM 是一款开源、跨平台的 x86 和 x86_64 汇编语言编译器(汇编器)。

  • 它支持 Intel 语法(Intel 风格),语法简单直观,易于学习。

  • 适合生成二进制文件(flat binary)、目标文件(.obj/.o),常用于操作系统开发、底层编程。

主要特点:

  • 语法:Intel 风格,跟大多数 Windows 汇编教程语法一致。

  • 平台支持:Windows、Linux、MacOS 都支持。

  • 输出格式:支持多种格式(ELF, COFF, Win32, Win64, Mach-O等)。

  • 用途:常用于 Linux 下的汇编项目,或者跨平台汇编开发。

使用示例:

section .data
    msg db 'Hello, NASM!', 0      ; 定义一个字符串 "Hello, NASM!",末尾加一个0作为字符串结束符

section .text
    global _start                 ; 声明程序入口点 _start,告诉链接器从这里开始执行

_start:
    mov edx, 12                  ; 将数字12放入edx寄存器,表示要写入的字节数("Hello, NASM!"长度为12)
    mov ecx, msg                 ; 将字符串 msg 的地址放入 ecx 寄存器,作为写入数据的内存起始地址
    mov ebx, 1                   ; 将数字1放入 ebx,表示输出目标为标准输出(stdout)
    mov eax, 4                   ; 将数字4放入 eax,表示调用 sys_write 系统调用(Linux下的写操作)
    int 0x80                     ; 触发中断 0x80,调用内核执行系统调用(写字符串到屏幕)

    mov eax, 1                   ; 将数字1放入 eax,表示调用 sys_exit 系统调用(程序退出)
    int 0x80                     ; 触发中断 0x80,调用内核退出程序

用命令汇编:

nasm -f elf32 hello.asm -o hello.o    # 用 NASM 汇编器把 hello.asm 编译成 32 位 ELF 格式的目标文件 hello.o
ld -m elf_i386 hello.o -o hello       # 用链接器 ld 把 hello.o 链接成 32 位 ELF 可执行文件 hello
./hello                              # 运行当前目录下生成的可执行文件 hello,输出程序结果

3.2 MASM(Microsoft Macro Assembler)

简介:

  • MASM 是微软官方出品的 x86/x86_64 汇编器,历史悠久,Windows 平台汇编的经典工具。

  • 支持丰富的宏功能、结构定义、数据类型,适合 Windows 应用程序和驱动程序开发。

  • 语法同样基于 Intel 风格。

主要特点:

  • 集成度高:和 Visual Studio 紧密集成,方便调试和编译。

  • 宏功能强大:支持高级宏和结构,便于大型项目。

  • 只支持 Windows 平台

  • 语法严格,支持结构化编程。

使用示例:

.386                    ; 指定使用 80386 及以上的 CPU 指令集
.model flat, stdcall    ; 指定内存模型为平坦模型,调用约定为 stdcall(Windows常用调用约定)
.stack 4096             ; 定义栈大小为4096字节(4KB)

.data                   ; 数据段开始,存放静态数据
    msg db 'Hello MASM!', 0    ; 定义字符串 "Hello MASM!",结尾以0结束(C风格字符串)

.code                   ; 代码段开始,存放程序指令
main PROC               ; 定义过程 main,程序入口点

    mov edx, LENGTHOF msg      ; 将字符串 msg 的长度(字节数)存入 edx 寄存器
    mov ecx, OFFSET msg        ; 将字符串 msg 的地址存入 ecx 寄存器
    mov ebx, 1                 ; 将数字1存入 ebx,表示标准输出(stdout) — 这是 Linux 系统调用习惯
    mov eax, 4                 ; 将数字4存入 eax,表示 sys_write 系统调用号 — Linux 下写操作调用号
    int 80h                   ; 触发中断 0x80 调用内核服务 — Linux 32位系统调用接口(注意:不适用于 Windows MASM)

    mov eax, 0                 ; 将0存入 eax,通常表示返回值0(正常退出)
    ret                       ; 返回调用者,结束过程

main ENDP                ; 过程结束
END main                 ; 程序结束,指定入口点为 main

通常在 Visual Studio 中编译,或者用 ml.exe 汇编:

ml /c /coff hello.asm    # 用微软汇编器 ml 编译 hello.asm,生成 COFF 格式的目标文件 hello.obj,不进行链接(/c 表示只编译)
link hello.obj           # 用微软链接器 link 将 hello.obj 链接成可执行文件(默认生成 hello.exe)

3.3 GAS(GNU Assembler)

简介:

  • GAS 是 GNU 工具链中的汇编器,是开源的、跨平台的。

  • 默认使用 AT&T 语法(与 Intel 语法不同,风格更接近 Unix 世界)。

  • 主要在 Linux、Unix 系统上使用,支持多种架构(x86/x86_64/ARM/RISC-V 等)。

  • 经常作为 GCC 编译器的后端,用于生成机器码。

主要特点:

  • 语法不同:AT&T 语法,指令顺序、寄存器前缀等有别于 Intel 语法。

  • 跨架构支持:支持多种 CPU 架构。

  • 结合 GCC:GAS 可以处理由 GCC 生成的汇编代码,也可以手写。

  • 调试支持良好

GAS 的 AT&T 语法特点:

  • 寄存器前面带 %,比如 %eax

  • 操作数顺序是 源操作数 -> 目标操作数(与 Intel 相反)

  • 立即数前面带 $,如 $0x10

  • 指令后缀表示操作数大小,如 movl(32位)、movb(8位)

使用示例:

.section .data             # 数据段开始,存放静态数据
msg:                      # 定义标签 msg,表示字符串的起始地址
    .ascii "Hello GAS!\n"  # 存储字符串 "Hello GAS!\n"(不以0结尾)

.section .text             # 代码段开始,存放指令
.globl _start              # 声明 _start 是全局符号,程序入口点
_start:                   # 程序入口标签

    movl $13, %edx        # 将数字13(字符串长度)加载到 edx 寄存器(写入长度)
    movl $msg, %ecx       # 将字符串 msg 的地址加载到 ecx 寄存器(写入缓冲区地址)
    movl $1, %ebx         # 将数字1加载到 ebx,表示标准输出(stdout)
    movl $4, %eax         # 将数字4加载到 eax,表示系统调用号 sys_write(写操作)
    int $0x80             # 触发中断 0x80,执行 Linux 32位系统调用

    movl $1, %eax         # 将数字1加载到 eax,表示系统调用号 sys_exit(退出程序)
    int $0x80             # 触发中断 0x80,调用内核退出程序

汇编链接:

as hello.s -o hello.o      # 用 GNU 汇编器 as 把 hello.s 汇编成目标文件 hello.o
ld hello.o -o hello        # 用链接器 ld 把目标文件 hello.o 链接成可执行文件 hello
./hello                    # 运行当前目录下的可执行文件 hello,执行程序输出内容

3.4 三者对比

特点 NASM MASM GAS
平台 跨平台(Linux/Windows/Mac) Windows 专用 跨平台(主要 Linux/Unix)
默认语法风格 Intel 风格 Intel 风格 AT&T 风格
语法复杂度 简洁直观 结构化和宏功能强 稍复杂,学习曲线陡峭
使用场景 系统编程、跨平台汇编项目 Windows 程序和驱动开发 Linux 内核、GCC 生成汇编、嵌入式
与IDE集成 一般手动编译 Visual Studio 集成 常与 GCC 集成

3.5 小结

  • 如果用 Windows,且做系统或驱动开发,MASM 是首选。

  • 如果跨平台开发,特别是 Linux 下,NASM 更灵活,且学习门槛低。

  • GAS 是 Linux 下的标准汇编器,但语法偏 Unix,初学时稍微难理解。


四、汇编的两种风格

不同的汇编器、不同平台、甚至不同社区会采用不同的汇编语法规范和格式,这被称为汇编风格。
最常见的两种风格是:

  • Intel 风格汇编语法(Intel Syntax)

  • AT&T 风格汇编语法(AT&T Syntax)

两者的历史背景

  • Intel 风格
    由 Intel 公司定义的汇编语言写法,是 Intel 官方文档、Windows 平台以及 NASM、MASM 汇编器采用的风格。
    易读,符合大多数人学习汇编的直觉。

  • AT&T 风格
    最早由美国加州大学伯克利分校的 AT&T 实验室设计,Linux 和 GNU 工具链(如 GAS)使用这种风格。
    主要在 Unix/Linux 及开源社区流行,语法更规范,适合写大型复杂程序。

4.1 两种风格的主要区别

方面 Intel 风格 AT&T 风格
操作数顺序 指令 目标操作数, 源操作数 指令 源操作数, 目标操作数
寄存器前缀 不加 % 寄存器名前加 %,如 %eax
立即数前缀 无前缀 立即数前加 $,如 $0x10
指令后缀 不用后缀(一般) 指令后面用大小写字母表示数据大小,如 movl(长字,32位),movb(字节,8位)
内存访问 方括号表示地址,如 [eax] 用括号表示间接寻址,如 (%eax)
段寄存器语法 ds: %%ds:
注释符号 ; #//

4.2 具体对比示例

假设我们要把数字 5 放入寄存器 eax,然后把 eax 的值加到 ebx

Intel 风格 AT&T 风格
```asm ```asm
mov eax, 5 movl $5, %eax
add ebx, eax addl %eax, %ebx
``` ```

4.3 详细说明

1)操作数顺序

  • Intel 风格是 目标,源。即
    mov eax, 5 是“将 5 移动到 eax 中”。

  • AT&T 风格是 源,目标。即
    movl $5, %eax 是“将立即数 5 移动到 %eax 中”。

2)寄存器表示

  • Intel 风格直接写寄存器名:eax, ebx, ecx

  • AT&T 风格寄存器名前加 %,比如 %eax, %ebx

3)立即数前缀

  • Intel 直接写数字:mov eax, 5

  • AT&T 前面要加 $movl $5, %eax

4)指令后缀(AT&T 独有)

AT&T 语法的指令后缀表示操作数大小:

  • b 表示字节操作(8 位),如 movb

  • w 表示字操作(16 位),如 movw

  • l 表示长操作(32 位),如 movl

  • q 表示四字操作(64 位),如 movq

Intel 语法没有强制后缀,大小由寄存器本身或操作数决定。

5)内存寻址方式

  • Intel:用方括号表示内存地址,例如:
mov eax, [ebx]    ; 把内存地址 ebx 指向的内容放入 eax
  • AT&T:用括号包住寄存器来表示间接寻址,且没有方括号:
movl (%ebx), %eax  ; 同上

4.4 更多示例对比

Intel AT&T
mov eax, [ebx + 4] movl 4(%ebx), %eax
add eax, 10 addl $10, %eax
push eax pushl %eax
jmp label jmp label
call printf call printf

4.5 实际应用场景

汇编风格 使用环境 / 工具
Intel 风格 Windows 平台(MASM、NASM)、Intel 官方文档、IDA 反汇编默认选项
AT&T 风格 Linux 下 GCC 生成的汇编代码、GAS 汇编器默认语法、Unix 系统内核源码

4.6 小结

项目 Intel 风格 AT&T 风格
操作数顺序 目标, 源 源, 目标
寄存器前缀 %
立即数前缀 $
指令后缀 无(一般) 必须,指明大小
内存寻址 [addr] (addr)
适用环境 Windows,Intel官方,NASM等 Linux,GAS,Unix

五、汇编的组成结构(段、伪指令、符号)

5.1 段(Segment)

1)什么是段?

是对程序结构的一种划分,汇编程序通常被划分为多个功能不同的区域,每个区域叫做一个「段」。

在早期的 16 位系统中(如 DOS),内存按段进行管理,而在现代系统中,段依然用于组织代码和数据,使程序结构更清晰。

2)常见的段类型

段名 作用说明
.textcode 存放程序的机器代码(即指令),是可执行的只读区域。
.data 存放已初始化的全局/静态变量(可读写)。
.bss 存放未初始化的全局/静态变量,程序运行时自动初始化为 0。
.rodata 只读数据,如字符串常量。
stack 用于程序运行时函数调用的局部变量、参数保存等。
heap 动态内存分配区域(malloc 等)。

3)示例(NASM Intel 风格)

section .data           ; 初始化数据段
    msg db 'Hello', 0

section .bss            ; 未初始化变量段
    buffer resb 64      ; 预留 64 字节

section .text           ; 代码段
    global _start

_start:
    ; 这里是程序执行起点

5.2 伪指令(Pseudo-instructions)

1)什么是伪指令?

伪指令不是 CPU 执行的机器指令,而是汇编器用来指导编译过程的命令。

它们用于定义变量、分配内存、声明段、导出符号等。

2)常见伪指令汇总

伪指令(NASM) 作用
sectionsegment 定义段,如 .data, .text
db, dw, dd 定义数据(byte/word/dword)
resb, resw, resd 保留内存空间(byte/word/dword)
equ 常量定义
global, extern 导出符号/引入外部符号(与链接器协作)
org 指定代码/数据起始地址(常用于裸机编程)

3)示例(定义常量和变量)

section .data
    hello_msg db 'Hello World', 0
    length equ 11                ; 定义常量,不占空间

section .bss
    input_buffer resb 128        ; 预留 128 字节空间

5.3 符号(Symbol)

1)什么是符号?

符号是程序中给某些内存位置或代码位置起的名字,本质是标签名字别名,用于表示地址或值。

包括:

  • 标签(Label):用于标识一段代码的位置(如函数、循环起点)

  • 变量名/常量名:为数据起的名字

  • 外部符号:跨模块调用或链接的符号(如 printf

2)符号使用示例

section .data
    msg db 'Hi!', 0         ; msg 是一个符号,地址指向字符串

section .text
    global _start

_start:                     ; _start 是一个代码标签(entry point)
    mov eax, 4              ; 系统调用号:write
    mov ebx, 1              ; 文件描述符:stdout
    mov ecx, msg            ; 指向 msg 符号
    mov edx, 3              ; 长度
    int 0x80

5.4 结合应用场景说明结构

一个完整汇编程序的组织结构大致如下:

; ---- 伪指令 ----
section .data              ; 数据段(已初始化变量)
    hello db 'Hello!', 0

section .bss               ; 数据段(未初始化)
    buffer resb 64

section .text              ; 代码段
    global _start          ; 向链接器声明 _start 是入口

; ---- 符号 / 标签 ----
_start:                    ; 程序入口点标签
    mov eax, 4             ; write 系统调用
    mov ebx, 1             ; 标准输出
    mov ecx, hello         ; hello 是一个符号,表示地址
    mov edx, 6
    int 0x80               ; 执行系统调用

5.5 小结

概念 定义 示例 作用
对程序结构的逻辑划分 .text, .data 区分代码、数据、未初始化变量等
伪指令 汇编器指令,不生成机器码 section, db, resb 定义数据、控制汇编行为
符号 地址或数值的别名(变量名/标签) msg, _start 提高可读性,方便定位

你可能感兴趣的:(汇编)