HNU2026-计算机系统-笔记 4 汇编初步

4 汇编初步

4.1 计算机系统的抽象层级与指令集架构

理解汇编语言,首要前提是建立计算机系统的抽象层次观。汇编语言作为高级语言与机器级二进制代码之间的桥梁,是理解程序底层行为、性能优化及系统安全不可或缺的基石。本讲重点剖析主流的 x86-64 体系结构。

4.1.1 抽象层级

现代计算系统通过层层抽象来屏蔽底层复杂性:

  1. 高级语言 (High-Level Language, 如 C/C++):提供变量、控制流、数据结构等高度抽象的逻辑表达。
  2. 汇编语言 (Assembly Language):高级语言经过编译器 (Compiler) 翻译后的文本表示,是机器指令的助记符形式。
  3. 机器语言 (Machine Code):由汇编器 (Assembler) 生成,CPU 可直接解码并执行的纯二进制序列。
  4. 微架构 (Microarchitecture):硬件层面对指令集架构的具体物理实现(如超标量流水线、分支预测器、多级缓存)。

4.1.2 指令集架构 (Instruction Set Architecture, ISA)

设计哲学 / 核心概念

ISA 是软件与硬件之间最关键的一份"契约"。它精确定义了处理器的状态(如寄存器、内存模型)、机器指令的格式、语义以及每条指令对处理器状态造成的影响。

在 x86-64 架构下,程序可见的状态主要包括:

  • 程序计数器 (PC, 在 x86 中称为 %rip):始终存储下一条将要执行的指令在内存中的地址。
  • 寄存器文件 (Register File):包含一组可以直接被指令访问的高速存储单元。
  • 条件码寄存器 (Condition Codes/Flags):存储最近执行的算术或逻辑指令的状态特征(如进位、溢出),是实现控制流分支的硬件基础。
  • 内存 (Memory):对汇编级程序而言,内存被抽象为一个巨大的字节数组。

4.2 x86-64 寄存器组织 (Register Organization)

寄存器是 CPU 内部访问速度最快的存储层级。x86-64 架构在传统的 32 位 x86 (IA32) 基础上,将通用寄存器的数量扩展至 16 个,位宽扩展至 64 位。

4.2.1 寄存器分类与系统级规约

每一个 64 位寄存器均可按需作为 32 位、16 位或 8 位来访问(例如 %rax 的低 32 位是 %eax,低 16 位是 %ax,低 8 位是 %al)。根据 System V AMD64 ABI (类 UNIX/Linux 系统标准的应用程序二进制接口),寄存器被赋予了特定的功能语义:

  • 函数参数传递%rdi (Arg 1), %rsi (Arg 2), %rdx (Arg 3), %rcx (Arg 4), %r8 (Arg 5), %r9 (Arg 6)。
  • 返回值%rax 专用于存储函数的返回值。
  • 栈与帧指针
    • %rsp (Stack Pointer):栈顶指针,动态指向当前运行时栈的顶部。在 x86-64 中,栈向低地址方向生长。
    • %rbp (Base Pointer):帧指针 ,常用于标识当前函数栈帧的基准位置(尽管在开启现代编译器优化如 -O1-O2 后,帧指针常被省略以释放作为通用寄存器使用)。
  • 被调用者保存 (Callee-saved) 寄存器%rbx, %rbp, %r12~%r15。函数在修改这些寄存器前必须将其原值压栈保护,并在返回前恢复。
  • 调用者保存 (Caller-saved) 寄存器:除上述外的其余通用寄存器,调用子函数期间可能会被破坏,调用者需自行负责保存。

4.3 寻址模式与内存访问 (Addressing Modes)

指令如何获取其操作数?x86-64 提供了高度灵活的寻址机制,其本质是有效地址 (Effective Address, EA) 的计算。

4.3.1 操作数的三种基本形态

  1. 立即数寻址 (Immediate) :操作数直接嵌在指令码中。在 AT&T 语法下,立即数以 $ 为前缀,如 $-577$0x1F
  2. 寄存器寻址 (Register) :操作数存储在寄存器中,直接通过寄存器名称访问,如 %rax
  3. 内存寻址 (Memory):最复杂的寻址方式,根据计算出的有效地址去内存中读取或写入数据。

4.3.2 通用内存寻址范式

在 x86-64 中,内存寻址的最一般化形式为:D(Rb, Ri, S)

其底层硬件逻辑对应的地址计算多项式为:

EA = Reg[Rb] + (Reg[Ri] × S) + D

  • Rb (Base Register):基址寄存器,可以是任意通用寄存器。
  • Ri (Index Register) :变址寄存器,除 %rsp 外的任意通用寄存器。
  • S (Scale Factor) :比例因子,受硬件位移逻辑限制,仅能取 1, 2, 4, 8(对应不同数据类型的大小,如 int 对应 4,double 对应 8)。
  • D (Displacement):位移量,通常为一个 8 位、16 位或 32 位的常量。

示例

%rdx 存储了数组基地址,%rcx 存储了数组索引,则 0x8(%rdx, %rcx, 4) 计算出的物理地址即为 Reg[%rdx] + Reg[%rcx] * 4 + 8。这在 C 语言中完美契合了结构体数组 struct_array[i].field 的访问逻辑。

4.4 数据传送指令 (Data Movement Instructions)

mov 指令族是汇编中最频繁使用的指令,用于在寄存器与内存之间搬运数据。

4.4.1 基本指令与位宽后缀

AT&T 语法采用后缀显式声明操作的数据位宽:

  • movb (byte, 8-bit)
  • movw (word, 16-bit)
  • movl (long word, 32-bit)
  • movq (quad word, 64-bit)

指令格式mov SRC, DST (将 SRC 传送到 DST)。
硬件级限制 :x86-64 架构不允许直接的内存到内存 (Memory-to-Memory) 的单一数据传送。必须先将源内存加载到寄存器,再从寄存器写入目的内存。

4.4.2 符号扩展与零扩展

当将一个较窄的数据类型复制到较宽的寄存器时,高位如何填充是一个关键问题:

  • 零扩展 (movz 族) :例如 movzbl (将 byte 零扩展至 long)。高位全部填充 0。主要用于无符号数 (Unsigned)。
  • 符号扩展 (movs 族) :例如 movslq (将 long 符号扩展至 quad)。高位全部填充符号位(最高位)。主要用于有符号补码数 (Two's Complement)。
    (补充:在 x86-64 中,任何生成 32 位寄存器结果的指令,都会自动将该寄存器的高 32 位清零。)

4.5 算术与逻辑指令 (Arithmetic & Logical Operations)

4.5.1 LEA 指令 (Load Effective Address)

leaq S, D 是 x86 体系中最精妙的指令之一。

它在形式上类似 movq,接受一个内存寻址模式 S。但它绝不进行内存解引用 ,而是纯粹将计算出的有效地址 (EA) 作为数值存入目的寄存器 D

除了用于计算指针地址,现代编译器极度青睐用 leaq 来执行简单的算术运算。
优化示例

c 复制代码
// C 代码
long scale(long x, long y, long z) {
    return x + 4 * y + 12 * z;
}
assembly 复制代码
# 对应的汇编核心逻辑 (假设 x 在 %rdi, y 在 %rsi, z 在 %rdx)
leaq (%rdi, %rsi, 4), %rax   # %rax = x + 4*y
leaq (%rax, %rdx, 8), %rax   # %rax = %rax + 8*z
leaq (%rax, %rdx, 4), %rax   # %rax = %rax + 4*z  (相当于加了 12*z)
# leaq 既完成了乘法,又完成了加法,且不占用 ALU 的乘法器,执行速度极快。

4.5.2 基础算术运算

  • 一元操作inc D (加 1), dec D (减 1), neg D (取负补码), not D (按位取反)。
  • 二元操作add S, D (D = D + S), sub S, D (D = D - S), imul S, D (D = D * S)。
  • 移位操作
    • sal / shl S, D:左移,右端补零(算术与逻辑左移等价)。
    • sar S, D:算术右移,左端补符号位(保证负数仍为负)。
    • shr S, D:逻辑右移,左端补

4.6 控制流与状态标志 (Control Flow & Condition Codes)

顺序执行是程序的常态,但分支(if-else)与循环(for/while)构成了图灵完备的基础。硬件通过条件码 (Condition Codes)条件跳转指令协同实现控制流的转移。

4.6.1 核心条件码

处理器内部维护了一个特殊的 %eflags / %rflags 寄存器,每当算术/逻辑单元 (ALU) 运算完成时,硬件会自动更新以下标志位:

  • CF (Carry Flag, 进位标志):最高位发生进位或借位。用于探测无符号数溢出。
  • ZF (Zero Flag, 零标志):运算结果为 0。
  • SF (Sign Flag, 符号标志):运算结果为负(即最高位为 1)。
  • OF (Overflow Flag, 溢出标志):发生有符号补码溢出(正正得负,或负负得正)。

4.6.2 显式状态设置:CMP 与 TEST

为了专门设置条件码而不污染寄存器,指令集提供了两种探测指令:

  • cmp S1, S2 (Compare) :计算 S2 - S1,丢弃结果,仅更新条件码。是实现 if (a < b) 的基石。
  • test S1, S2 (Test) :计算 S2 & S1,丢弃结果,仅更新条件码。常用于掩码测试,或 test %rax, %rax 来判断 %rax 是正、负还是零。

4.6.3 条件流转:跳转指令族

  • 无条件跳转jmp L(直接将 %rip 设置为标签 L 的地址)。
  • 条件跳转 (jX) :依据条件码的状态组合进行判断。
    • 相等测试je (Equal, ZF=1), jne (Not Equal, ZF=0)。
    • 有符号比较jg (Greater, 大于), jl (Less, 小于), jge (大于等于), jle (小于等于)。
      (以 jl 为例,其触发条件为 SF ^ OF = 1,即符号位与溢出位异或为 1。因为若产生正向溢出,结果虽为负但实际是 S2 > S1;若无溢出,SF=1 则确切表明 S2 < S1。硬件设计极度严密)
    • 无符号比较ja (Above, 高于), jb (Below, 低于)。依靠 CF 和 ZF 判断。

通过 cmp 配合 jX,编译器可将高级语言的结构化控制流完全展平为带状态测试的 goto 语句。

c 复制代码
// C 语言 if (x > y)
if (x > y) {
    x = x - y;
}
assembly 复制代码
# 对应的核心汇编逻辑
    cmpq %rsi, %rdi    # 比较 x (%rdi) 和 y (%rsi)
    jle  .L2           # 若 x <= y,跳转跳过 if 块
    subq %rsi, %rdi    # x = x - y
.L2:
相关推荐
FakeEnd1 小时前
Unity开发笔记6
笔记·unity·游戏引擎
Hello_Embed2 小时前
libmodbus 源码分析
笔记·stm32·单片机·嵌入式·ai编程
05候补工程师2 小时前
【408考研】数据结构核心笔记:单链表与栈操作精髓总结
数据结构·笔记·考研·链表·c#
kdxiaojie2 小时前
U-Boot分析【学习笔记】(7)
linux·笔记·学习
Huanzhi_Lin2 小时前
skynet笔记
笔记·lua·skynet·actor·actor模型
爱看大明王朝156610 小时前
磁件学习-磁性元器件的极限计算
笔记·学习
问心无愧051310 小时前
ctf show web入门 40
笔记
@蓝莓果粒茶12 小时前
【Unity笔记】保姆级AssetBundle详解(含代码+避坑指南)
笔记·游戏·unity
kobesdu13 小时前
【ROS2实战笔记-20】ROS2 bag 录播与时间模拟:从基础操作到高级调试技巧
笔记·机器人·ros·ros2