第1章:预备知识

对齐原书第1章 1.1-1.3:使用 Compiler Explorer 建立源码—汇编—机器码证据链,追踪可执行文件的虚拟内存加载,并用寄存器和间接寻址读懂简单函数。

学习目标

  • 能复现 Compiler Explorer 实验并对齐 C/C++ source、assembly、machine code 和 runtime output
  • 能绘制 CPU眼里的程序运行路径,解释 executable、virtual memory、code/data/BSS、heap/stack 与 thread context
  • 能分析 CPU眼里的汇编语言中 rax/rip/rbp/rsp、push/pop 和 register-indirect addressing 的状态变化

从“CPU 不执行 C++ 源码”开始

CPU 取指、译码、执行的是 machine instructions。C/C++ source 表达程序员意图,compiler 把它转换为特定 ISA 的 instructions,再由 linker 组织成 executable image;operating system loader 建立进程地址空间和线程上下文,CPU 才获得第一条可执行指令地址。

先预测 return 0; 在目标平台会修改哪个 return-value register,再让工具给出 evidence;不要把某次 x86-64 输出误写成所有 ABI 的语言规则。

1.1 工具介绍:Compiler Explorer

Compiler Explorer 把 source line 与 generated assembly 用颜色关联,可切换 compiler/version、optimization flags、target architecture,并查看 binary bytes 或直接运行程序。

int add(int left, int right) {
    return left + right;
}

同一函数至少比较 -O0-O2:前者便于看 stack frame 和局部变量,后者展示 register allocation、dead-code elimination 与 inline 后的真实 optimized path。

工具输出不是语言规范

x86-64 SysV 常用 eax/rax 放整数 return value,但 Windows x64、ARM64 和不同类型有各自规则。实验记录必须包含 compiler、version、target、flags 和 source;否则 assembly screenshot 无法复现。

1.2 可执行文件保存了什么

compiler/linker 把 instructions、initialized data、symbols/relocations 与 metadata 组织到 executable。source 中硬编码的 plaintext secret 很可能直接出现在 binary data 中,删除 variable name 或 debug info 不会自动隐藏 value。

const char* password = "do-not-embed-secrets";

可用 strings/hex viewer 验证 binary 是否含 literal。真正 secret 应来自受控 runtime configuration/credential service,而不是依赖编译隐藏。

loader 建立虚拟地址空间

现代 OS 不要求把整个 executable 连续复制到一块 physical memory。loader 按 segment permissions 把 file-backed pages 映射进 process virtual address space;MMU/page tables 在访问时把 virtual addresses 转换到 physical frames。

code、data、BSS、heap 与 stack

functions 通常位于 executable code/text mapping;initialized globals 位于 data;zero/uninitialized globals 用 BSS 描述,文件不必存一大段零。heap 与 stack 是 runtime 可增长区域,局部变量、global、dynamic allocation 和 function address 因而相距很远。

int globalValue;
 
int main() {
    int localValue = 1;
    auto* heapValue = new int(2);
    delete heapValue;
}

地址实验只展示一次 process layout;ASLR 会改变绝对地址。应比较 region 和 mapping permissions,而不是背固定数值。

从 thread context 开始执行

OS scheduler 保存/恢复 thread context。x86-64 的 rip 指向下一条 instruction,rsp 指向当前 stack top;当 context 被恢复,CPU 从 rip 所在 virtual address fetch。

书中用 main 首地址解释入口很直观,但真实 native process 常先进入 runtime startup code,由它准备 arguments/runtime,再调用 main。教学模型与真实入口层次要区分。

1.3 汇编阅读:先追踪状态

不需要背完整 ISA。先把 instruction 翻译成“读哪个 register/memory、写哪个位置、哪些 flags 改变”。mov 搬运,add 做 read-modify-write,brackets 表示 dereference effective address。

QWORD PTR [rbp-8] 可读作:以 rbp-8 为 address,访问 8-byte object。它对应高级语言中基址加偏移后的 dereference,而不是“变量天生住在 rbp”。

push/pop 与 stack frame

在典型未优化 frame-pointer code 中,push rbp 保存 caller frame base 并移动 rsp,mov rbp,rsp 建立 current frame anchor;epilogue 恢复 rbp/rsp 并返回。

为什么简单加法仍有大量 memory traffic

-O0 为可调试性把 locals 频繁 spill 到 stack,一次 a += 2 可能包含多次 loads/stores;-O2 可把 a 保留 register,甚至 constant-fold。CPU 视角的重要洞察是 data movement 常比 arithmetic 更支配成本,但必须在 optimized representative code 上测量。

最小实验协议

  1. 固定 source、compiler version、target ABI 与 flags。
  2. 先预测 return register、memory regions 和 frame changes。
  3. 对比 -O0/-O2,标出被删除、合并、inline 的 operations。
  4. 用 binary/strings 检查 instructions 与 literals。
  5. 用 debugger/maps 验证 addresses 所属 region,不背绝对地址。
  6. 每条结论注明“language guarantee”或“implementation evidence”。

小结

  • Compiler Explorer 用 source-linked assembly 比较 compiler/version/flags,但输出不是语言规范
  • executable 保存 loadable instructions/data;hard-coded secret 可直接暴露在 binary
  • loader 建立 process virtual memory,code/data/BSS/heap/stack 具有不同来源与 lifetime
  • virtual address 空白不等于等量 physical memory 已占用,MMU/page tables 按需映射
  • thread context 的 instruction pointer/stack pointer 让 scheduler 恢复执行
  • 汇编先追踪 register/memory 状态;brackets 表示 indirect addressing
  • push/pop frame 是常见未优化模型,优化可省略 frame、spill 或整个 call
  • 所有实验同时保留 configuration 与 language/implementation 边界

资料与写作方式声明

本章以CPU眼里的C/C++,第1章 预备知识权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

名词解释

本章出现的专业名词,用大白话再讲一遍。

CPU 指令
处理器直接执行的机器编码。
Compiler Explorer

源码与生成代码关联实验工具。

ABI calling convention

平台二进制调用约定。

executable image

可被 loader 映射的代码数据映像。

虚拟内存
进程地址到物理资源的映射抽象。
program loader

建立进程映像与初始上下文的组件。

BSS
零初始化 static storage 描述区域。
线程上下文

恢复线程所需的寄存器与调度状态。

register-indirect addressing

寄存器计算地址后的间接访问。

stack frame
一次函数调用的栈上逻辑区域。
register spill
值从寄存器暂存到栈内存。
CPU 视角证据链

配置预测和多层输出组成的实验记录。

练习

  1. 问题 1:用 Compiler Explorer 比较 add 函数的 O0/O2。 记录哪些结论是 ABI evidence,哪些是 C++ semantics。
  1. 问题 2:global、local、new object 与 function address 为什么相距很远? 绘制 load/runtime 路径。
  1. 问题 3:解释 add QWORD PTR [rbp-8],2 写出状态变化并说明优化后为何可能消失。

讨论

评论区加载中…