第 2 课3.2.1机器级代码最小前置知识1 byte 8 bits1 个十六进制数字表示 4 bits。指针保存地址*p表示通过该地址访问对象。CPU 执行机器指令而不是直接执行 C 源代码。本节暂不要求掌握补码细节、调用约定、栈帧和正式汇编语法。3.2 导入从 C 到可执行文件C 源文件 p1.c / p2.c预处理器展开 include 和宏编译器生成 p1.s / p2.s汇编器生成 p1.o / p2.o链接器合并目标文件和库代码可执行文件 p教材示例命令gcc-Og-op p1.c p2.c-Og让优化后的代码仍较容易对应回 C 源码适合学习它不表示实际项目中性能总是最佳。3.2.1 Machine-Level Code核心心智模型机器级程序可以理解为不断发生的状态转移机器状态 S {%rip, 整数寄存器, 条件码, 向量寄存器, 内存} | 执行一条指令 v 机器状态 S单条指令通常只完成很基本的操作例如寄存器加法、寄存器与内存之间的数据传送或根据条件跳转。编译器使用多条指令实现 C 中的表达式、循环和函数调用。ISA 与微体系结构ISAInstruction Set Architecture规定处理器的程序可见状态、指令格式以及每条指令对状态产生的效果。它是软件与处理器之间的合同。微体系结构microarchitecture芯片内部如何实现这份合同例如流水线、并行执行和乱序执行。ISA 通常把程序描述成顺序执行的指令流。真实硬件可以让多条指令重叠甚至乱序执行但必须让程序看到符合 ISA 规定的结果。因此不同内部设计的处理器仍可以运行同一份 x86-64 二进制代码。程序可见状态状态本节含义%rip程序计数器指出下一条将执行指令的地址。16 个 64 位整数寄存器保存整数、地址和临时结果寄存器不与某个 C 变量永久绑定。条件码保存最近算术或逻辑操作的某些结果特征用于条件跳转等操作。向量寄存器保存一个或多个整数或浮点数支持向量运算。内存程序通过虚拟地址访问的、按字节编址的大数组。%rip不属于上述 16 个整数寄存器。内存没有 C 类型标签C 源码中有long、数组、结构体和不同类型的指针机器级内存中保存的只是按地址排列的字节。字节本身不会声明“我是有符号整数”“我是指针”或“我是结构体成员”。数据的读取宽度和解释方式由具体指令及上下文决定。这不表示所有操作都没有类型差异某些指令会明确选择数据宽度或有符号、无符号语义。机器级程序使用虚拟地址。虚拟地址不是某一物理内存芯片位置的直接编号硬件与操作系统共同完成虚拟地址到实际存储位置的映射。状态追踪示例以下是帮助理解的伪指令不是正式 x86-64 语法R3 - M[R2] R3 - R3 R1 M[R2] - R3若初始状态为R1 4 R2 地址 A M[A] 9执行三条指令后R1 4 R2 地址 A R3 13 M[A] 13加法还可能更新条件码%rip也会移动到后续指令。由于这里没有真实指令编码、长度和起始地址不能计算%rip的确切最终值。常见误区与疑问ISA 是程序可见合同不是 CPU 电路图。乱序执行不表示程序可以得到违反 ISA 的乱序结果。寄存器不是“更快且永久绑定的 C 变量”。内存字节没有固有 C 类型但具体指令仍有宽度和解释语义。虚拟地址不等于物理地址。单条机器指令不一定只花一个时钟周期。第 3 课3.2.2代码示例C 源码longmult2(long,long);voidmultstore(longx,longy,long*dest){longtmult2(x,y);*destt;}函数的语义是调用mult2(x, y)再把返回值写入dest指向的内存位置。编译到汇编文本gcc-Og-Smstore.c-S让 gcc 在生成mstore.s后停止不再调用汇编器。multstore: pushq %rbx movq %rdx, %rbx call mult2 movq %rax, (%rbx) popq %rbx ret本节暂时只追踪数据流第三个参数dest到达函数时位于%rdx。movq %rdx, %rbx保存这个地址避免调用mult2时丢失。call mult2调用乘法函数返回值放入%rax。movq %rax, (%rbx)把返回值写入dest指向的内存。pushq/popq保存并恢复调用者原来的%rbxret返回。参数寄存器和保存规则属于调用约定将在 3.7 正式学习。编译为目标文件gcc-Og-cmstore.c-c会完成编译和汇编生成二进制目标文件mstore.o但不进行链接。其中multstore的 14 个机器码字节是53 48 89 d3 e8 00 00 00 00 48 89 03 5b c3偏移机器码字节反汇编长度053push %rbx1148 89 d3mov %rdx,%rbx34e8 00 00 00 00callq目标待链接5948 89 03mov %rax,(%rbx)3c5bpop %rbx1dc3retq1总长度为1 3 5 3 1 1 14字节。x86-64 指令是变长编码本书指出单条指令可以占 1-15 字节。原书表头写有 “function sum”但符号行和本节代码实际都是multstore学习时以multstore为准。为什么会出现这个差异原书在排版时可能沿用了早期草稿中的函数名sum而正文示例在定稿前改成了multstore表头却未同步更新。这类“表头与正文不一致”在教材中并不罕见遇到时以符号表、反汇编输出和实际代码为准不要被表头误导。反汇编objdump-dmstore.o反汇编器读取机器码字节把每组字节翻译成便于人阅读的汇编文本。它不需要原始 C 文件也不需要原始.s文件。但反汇编并不是“恢复原始 C”变量名和大部分 C 类型已经丢失。优化可能重排、合并或删除源码操作。多种不同的 C 写法可能产生相同机器码。函数名等信息可能来自目标文件的符号表而不是指令字节本身。为什么call后面暂时是零mstore.o尚未知道mult2在最终可执行文件中的地址因此目标文件中的调用位移暂时是e8 00 00 00 00目标文件还会保存一条重定位信息告诉链接器这里需要在链接时修补。链接生成prog后gcc-Og-oprog main.c mstore.c书中最终代码把multstore放到0x400540把mult2放到0x40058b。调用指令结束后的下一地址是0x400549所以相对距离为0x40058b - 0x400549 0x42链接器据此把调用位移填成42 00 00 00。它还改变代码的最终地址并可能加入nop做对齐。本节核心结论CPU 执行的是机器码字节不是汇编文本。汇编代码是机器码便于人阅读的文本表示。目标文件是包含机器码、符号和重定位等信息的容器。反汇编器可以从机器码恢复指令但不能恢复唯一的原始 C 程序。链接器负责确定最终地址并修补尚未确定的函数和全局对象引用。x86-64 指令长度不固定但从正确起始位置可以唯一解码。第 4 课3.2.3汇编文本格式GCC 生成的原始汇编.file 010-mstore.c .text .globl multstore .type multstore, function multstore: pushq %rbx movq %rdx, %rbx call mult2 movq %rax, (%rbx) popq %rbx ret .size multstore, .-multstore .ident GCC: (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1 .section .note.GNU-stack,,progbits一行汇编文本的四种类别类别例子用途CPU 是否直接执行标签multstore:给当前位置命名供函数入口、跳转或符号引用否汇编器指令.text、.globl、.type、.size指导汇编器和链接器生成节、符号及元数据否机器指令pushq、movq、call、ret被编码成 CPU 执行的机器码字节是注释# Save %rbx供人阅读汇编器忽略否注意汇编器指令不由 CPU 执行但可能影响目标文件结构、符号、布局或元数据所以“读代码时暂时忽略”不等于“可以从真实文件随意删除”。有些以点开头且末尾带冒号的名字例如新版本 GCC 可能生成的.L...:是局部标签不是汇编器指令。标签只是某个地址位置的名字本身不占一条 CPU 指令。教材添加的行号只是讲解索引不属于汇编语法。教材整理后的注释版# void multstore(long x, long y, long *dest) # x in %rdi, y in %rsi, dest in %rdx multstore: 1 pushq %rbx # 保存原来的 %rbx 2 movq %rdx, %rbx # 暂存 dest 3 call mult2 # t mult2(x, y) 4 movq %rax, (%rbx) # *dest t 5 popq %rbx # 恢复 %rbx 6 ret # 返回调用者整理原则保留当前分析所需的标签和真实机器指令。暂时省略与讨论无关的汇编器指令。添加行号方便引用但行号不能原样交给汇编器。注释说明指令对机器状态和 C 语义的作用而不只是翻译助记符。整理版只是教学展示不能冒充 GCC 输出的完整.s文件。C 与汇编是语义对应不是逐行对应。比如pushq、popq和ret服务于函数调用协议并没有各自对应的一条 C 语句。ATT 与 Intel 格式特征ATT本书使用Intel寄存器%raxrax内存解引用(%rax)[rax]双操作数顺序源目的目的源8 字节宽度常用后缀q如movq常写QWORD PTR指令名可写mov同一条内存写入# ATT源在前目的在后 movq %rax, (%rbx) ; Intel目的在前源在后 mov QWORD PTR [rbx], rax两者都表示M[RBX] - RAX因此ATT 的%rax与 Intel 的rax都表示寄存器中的值。ATT 的(%rax)与 Intel 的[rax]都表示以寄存器值为地址访问内存。本节核心结论原始.s文件不只包含机器指令还包含标签和给汇编器、链接器使用的指令。CPU 最终执行的是机器指令编码出的字节不执行标签、注释或汇编器指令。教材为了可读性省略大多数元数据并补充行号和 C 语义注释。C 与汇编通常不是一行对一行。阅读汇编前必须先确认语法格式ATT 与 Intel 的操作数顺序相反。