从源码到进程

点一个框看它做什么、在哪篇讲过;选一条路径,一步一步看一个 .cpp 怎么变成 .o、链接时报的错从哪来、./a.out 之后发生了什么。蓝字可以直接点开对应的文章或地图。

从源码到进程:编译、链接、加载 上面是编译:每个 .cpp 各自变成一个 .o。中间是链接:所有 .o 和库拼成一个可执行文件。下面是运行:内核和动态链接器把它变成进程地图上的那几段。 橙线 = 代码拷进输出文件;蓝色虚线 = 只记一个名字,运行时再找;绿色虚线 = 加载时 mmap 进地址空间;灰线 = 下一步。 蓝字 = 在哪里讲过,进程地图会打开对应的那张图。 编译:一次一个翻译单元 链接:把所有 .o 和库拼成一个文件 加载和运行 源文件和头文件 a.cpp、b.cpp:各自单独编译 a.h:放声明,被 #include 进去 一个 .cpp 加上它 include 的 全部内容 = 一个翻译单元 编译器一次只看一个翻译单元, 不知道别的 .cpp 里写了什么 ① 预处理:g++ -E #include:把头文件原样粘进来 #define:宏按文本替换 #if、#ifdef:删掉不要的分支 产物 a.ii:纯 C++ 文本, 常常有几万行 ② 编译:g++ -S 语法、类型检查:编译错误多在这 模板在这里按具体类型实例化 优化:内联、常量传播…… 名字修饰:f(int) → _Z1fi 产物 a.s:汇编文本 ③ 汇编:g++ -c 汇编指令 → 机器码 产物 a.o:可重定位目标文件 地址还没定:调别的文件里的 函数,地址先填 0, 旁边记一条重定位项 a.o 里有什么(nm、readelf) 段:.text 机器码,.data,.bss,.rodata 符号表:T f = 这里定义了 f U g = 用了 g,定义不在这里 重定位表:.text+0x1c 处要填 g 的地址 每个 .o 都从地址 0 排起,互相不知道对方 进程地图:这些段最后落在哪 静态库 libx.a 一堆 .o 打成的包(ar) 链接器只挑用得上的 .o, 把代码拷进可执行文件 动态库 libc.so.6 链接时只检查符号在不在, 记下“运行时要 libc.so.6”, 代码不拷进来 ④ 链接:ld(g++ 最后一步替你调) 1. 符号解析:每个 U 都要找到唯一的定义 一个都没有 → undefined reference 有两个 → multiple definition 2. 合并:各个 .o 的 .text 拼成一个 .text, .data、.bss 同理;定下每个段、 每个符号的最终地址 3. 重定位:按重定位项把地址填进去 产物:可执行文件,或者一个 .so 可执行文件 a.out(readelf -l) ELF 头:入口地址 _start 程序头:加载时每一段 mmap 到哪 LOAD r-x:.text LOAD r--:.rodata、.eh_frame LOAD rw-:.data、.bss(.bss 只记大小) INTERP:/lib64/ld-linux-x86-64.so.2 DYNAMIC:需要 libc.so.6…… .plt、.got:调 .so 里函数的跳板 进程地图:.text、.data、.bss 链接报错,多半是这几种 undefined reference to f: · 只声明没定义,或那个 .cpp 没参与链接 · 少了 -l,或 -l 写在了前面 · 模板的定义写在 .cpp 里 · C 函数没加 extern "C" multiple definition of x: · 头文件里定义了普通函数或变量 可以多处定义:inline、模板 落到进程地图上 .text → 代码段(r-x) .rodata → 只读数据(r--) .data、.bss → 读写数据(rw-) libc.so、ld.so → mmap 区 栈:内核放好 argv、环境变量 进程地图 ⑥ 动态链接器 ld.so 按 DT_NEEDED 找 .so:rpath、 LD_LIBRARY_PATH、/etc/ld.so.cache 找到的库 mmap 进来,做重定位 跑全局对象的构造函数 最后跳到 _start,由它调 main ⑤ execve("./a.out") 内核读 ELF 头和程序头 按 LOAD 段 mmap,建好栈 有 INTERP:把 ld.so 也映射进来, 先从 ld.so 开始跑 静态链接的:直接从 _start 开始 进程和线程:fork 和 exec ⑦ 调 .so 里的函数:PLT 和 GOT call printf@plt:先跳到 PLT 的跳板 跳板去 GOT 里取真实地址,再跳过去 延迟绑定:第一次取到的是解析函数, 解析完把 printf 的地址写回 GOT -z now:启动时全填好,再设只读 所有 .o 一起交给链接器 拷进来 只记名字 ./a.out 运行时调库函数 跳过去 mmap 运行时 mmap 进来,进程间共享 图随文章更新:讲到模板实例化、内联和链接时优化(LTO)时再往上补。