CSAPP - 03.Machine-Level Programming I
对干机器级编程来说,其中两种抽象尤为重要。第一种是由指令集体系结构或指令集架构(Instruction Set Architecture,ISA)来定义机器级程序的格式和行为,它定义了处理器状态、指令的格式,以及每条指令对状态的影响。
机器级编程 I: (Machine-Level Programming I):
主要内容:
本章是机器级编程的入门,重点介绍 汇编语言的基础。包括:
- C 代码如何变成机器码:编译、汇编、链接的流程。
- 硬件的“程序员视角”:介绍了 CPU 中你可以直接操作的资源,特别是 16个通用寄存器(
%rax,%rbx等)。 - 基本指令:如何移动数据(
movq)、如何访问内存(寻址模式)、以及基本的算术运算(addq,leaq)。
Motivation:
计算机不认识 C 语言,只认识二进制指令。要理解程序的性能瓶颈、调试底层 Bug 或者理解指针的本质,你必须掌握 CPU 是如何通过寄存器和内存搬运数据的。这是理解计算机系统的基石。
1.1 Intel处理器与体系结构简史
- CISC vs. RISC:
- Intel x86 属于 复杂指令集计算机 (CISC)。其特点是指令集庞大,指令功能复杂多样,格式不一。
- 与之相对的是 精简指令集计算机 (RISC),例如 ARM 和 RISC-V。RISC的指令数量少,格式统一,易于优化。近年来,RISC在低功耗和移动设备领域复兴。
- x86 发展里程碑:
- 1978 (8086): 第一款16位Intel处理器,是IBM PC和DOS系统的基础。
- 1985 (386): 第一款32位Intel处理器 (IA32),引入了“平坦寻址模式”,能够运行Unix等现代操作系统。
- 2004 (Pentium 4E): 第一款支持64位扩展 (x86-64) 的Intel处理器。
- 2006 (Core 2): 第一款Intel多核处理器,标志着性能提升的思路从单纯提高主频转向增加核心数。
- 性能发展的趋势与挑战:
- CPU-内存性能差距 (The CPU-Memory Gap): 几十年来,处理器性能的提升速度远超内存(DRAM)和磁盘(Disk)。这导致CPU经常需要花费时间等待从内存中获取数据,内存访问延迟成为主要性能瓶颈。
- 功耗墙 (Power Wall): 大约在2005年之后,由于功耗和散热问题,单纯提高CPU时钟频率变得不再可行。这促使整个行业转向多核架构来继续提升计算性能。
1.2 从C代码到可执行文件
将C源代码文件(.c)编译成一个可执行程序,主要经历以下步骤:
- 编译 (Compilation): 编译器(如
gcc -S)将C代码翻译成人类可读的汇编代码(.s文件)。 - 汇编 (Assembly): 汇编器(如
as)将汇编代码转换成二进制的机器指令,生成目标文件(.o文件)。此文件包含了代码的二进制表示,但外部函数的地址等尚未确定。 - 链接 (Linking): 链接器(如
ld)将多个目标文件和所需的库文件(如C标准库)合并起来,解析符号引用(如函数调用),最终生成一个完整的可执行文件。 - 反汇编 (Disassembly): 可以使用工具将目标文件或可执行文件“逆向”回汇编代码,这对于调试和理解底层代码非常有帮助。
- 命令行工具:
objdump -d <文件名> - GDB调试器内:
disassemble <函数名>
- 命令行工具:
1.3 汇编基础:程序员视角
在汇编层面,程序员直接与处理器的状态打交道。
-
程序员可见的状态 (Programmer-Visible State):
- PC Programme Counter (程序计数器): 在x86-64架构中是
%rip寄存器。它存放着下一条 将要执行的指令的内存地址。 - 寄存器文件 (Register File): 16个64位的通用整数寄存器(如
%rax,%rbx等),它们是CPU内部极快的小容量存储单元。 - 条件码 (Condition Codes): 一组特殊的标志位,用于存储最近一次算术或逻辑运算的结果状态(例如,结果是否为零、是否发生进位等)。它们是实现条件分支(如if-else)的基础。
- 内存 (Memory): 一个巨大的、按字节寻址的数组,用于存放代码、用户数据和程序运行栈。
- PC Programme Counter (程序计数器): 在x86-64架构中是
-
x86-64 整数寄存器:
x86-64提供了16个64位的通用寄存器。为了向后兼容,前8个寄存器还可以访问它们的低32位、16位和8位部分。
| 64位 | 32位 | 惯例用途 |
|---|---|---|
%rax |
%eax |
累加器 / 函数返回值 |
%rbx |
%ebx |
基址寄存器 |
%rcx |
%ecx |
计数器 |
%rdx |
%edx |
数据寄存器 |
%rsi |
%esi |
源变址寄存器 |
%rdi |
%edi |
目的变址寄存器 |
%rsp |
%esp |
栈指针 |
%rbp |
%ebp |
基址指针 |
%r8 - %r15 |
%r8d - %r15d |
通用寄存器 |
- AT&T vs. Intel 汇编语法: 本课程使用 AT&T 语法,这在Linux/GCC环境中是默认的。
| 特性 | AT&T 风格 (本课程使用) | Intel 风格 |
|---|---|---|
| 操作数顺序 | 指令 源, 目标 |
指令 目标, 源 |
| 寄存器 | 加%前缀, e.g., %rax |
无前缀, e.g., rax |
| 立即数 | 加$\text{前缀}, e.g., $0x104 |
直接书写 |
| 内存寻址 | 8(%rbp) |
[rbp+8] |
1.4 数据传送:mov 指令与寻址模式
-
指令格式:
movq Source, Dest(将8字节数据从源移动到目标)(q代表是64位操作)。 -
操作数类型 (Operand Types):
- 立即数 (Immediate): 常数值,以
$\text{开头,例如}$-533。 - 寄存器 (Register): 16个整数寄存器之一,例如
%rax。不能写给有专用用途的如%rsp - 内存 (Memory): 内存中的一个地址,由括号
()表示。(%rax)以寄存器中存的数据当作地址去内存中寻找。
- 立即数 (Immediate): 常数值,以
-
核心限制: 不可以在一条指令中完成从内存到内存的数据传送。必须先将数据从内存加载到寄存器,再从寄存器存回内存。
-
内存寻址模式 (Memory Addressing Modes): 用于灵活地指定内存地址。
- 普通模式 (Normal):
(%rcx),将寄存器%rcx中的值作为内存地址(等价于C语言中的指针解引用*rcx)。 - 偏移模式 (Displacement):
D(R),例如8(%rbp),表示访问内存地址为Reg[R] + D的位置(等价于*(rbp + 8))。 - 最通用形式:
D(Rb, Ri, S)- 地址计算公式为:
Mem[Reg[Rb] + S * Reg[Ri] + D]。 - D: 常量偏移量 (Displacement)。
- Rb: 基址寄存器 (Base register)。
- Ri: 变址寄存器 (Index register)。
- S: 比例因子 (Scale),值只能是1, 2, 4, 8。这个因子对于数组访问非常有用,可以方便地计算出数组元素的地址。
- 地址计算公式为:
- 普通模式 (Normal):
1.5 算术和逻辑运算
-
加载有效地址 (
leaq):leaq Src, Dst指令非常特殊。它使用内存寻址的语法来计算一个地址,但 并不会访问内存。- 相反,它将计算出的 地址值本身 存放到目标寄存器
Dst中。 - 编译器经常巧妙地利用它来完成一些高效的算术运算。例如,要计算
x * 12,可以分两步:leaq (%rdi, %rdi, 2), %rax#rax = x + 2*x = 3*xsalq $2, %rax#rax = rax << 2 = (3*x) * 4 = 12*x
-
常见的二元运算指令:
(注意:指令格式为op Src, Dest,运算结果存回Dest)
| 指令 | 运算 | 描述 |
|---|---|---|
addq Src, Dest |
Dest = Dest + Src |
加法 |
subq Src, Dest |
Dest = Dest - Src |
减法 |
imulq Src, Dest |
Dest = Dest * Src |
乘法 |
andq Src, Dest |
Dest = Dest & Src |
按位与 |
orq Src, Dest |
Dest = Dest | Src |
按位或 |
xorq Src, Dest |
Dest = Dest ^ Src |
按位异或 |
salq Src, Dest |
Dest = Dest << Src |
左移 |
sarq Src, Dest |
Dest = Dest >> Src |
算术右移 |
shrq Src, Dest |
Dest = Dest >> Src |
逻辑右移 |
- 常见的一元运算指令:
| 指令 | 运算 | 描述 |
|---|---|---|
incq Dest |
Dest = Dest + 1 |
加一 |
decq Dest |
Dest = Dest - 1 |
减一 |
negq Dest |
Dest = -Dest |
取负 (补码) |
notq Dest |
Dest = ~Dest |
按位取反 |
CSAPP - 03.Machine-Level Programming I
https://yima-gu.github.io/2026/01/14/CSAPP/03-machine-level-1-basics/