编译链接观察

数据

#include <unistd.h>
int a = 0; // Case 1
// extern int a; //Case 2
int main() {
    int x = a;
    return 0;
 
}

x86似乎不允许一条mov的源和目的都是内存地址

Case 1

mov    0x0(%rip),%eax //加载 a 到 %eax
mov    %eax,-0x4(%rbp) //读取 a 到 x

Case 2

mov    0x0(%rip),%rax   // 加载 optind 的地址到 %rax
mov    (%rax),%eax      // 读取 optind 的值到 %eax
mov    %eax,-0x4(%rbp)  // 存储到局部变量 x(栈位置 -0x4)

可以发现对于外部变量,会先加载地址再读取

函数

永远是call <?>

强制关闭位置无关代码后,唯一的变化就是函数调用变成绝对地址跳转

以上这些是编译器做好的,链接器之后能做的就是在空位上填值

jyy dlbox阅读

实现了call *sym(%rip)。动态链接库中的函数

call *sym(%rip)中sym这个位置存放的地址,被解读成真正的跳转地址

在每个(需要使用外部动态链接库)可执行文件中预留好一个区域,这个区域存放一系列“记录”,在这里添加sym:这个标记使得编译器能知道怎么编译call *sym(%rip),但是真正的跳转地址还得在加载的时候填入

加载器的职责就是在记录的位置填上运行时的真正地址

jyy的设计中记录分为三类:IMPORT,EXPORT和LOAD。IMPORT就是上面说的和sym:有关的,这些符号应当已经被EXPORT,从加载器全局维护的表里查到,填入sym位置就完成了。EXPORT暴露本地的函数(本地函数偏移量已确定 只需加上运行时的偏移)给加载器全局维护的符号列表,LOAD只是用来发起对依赖的动态库的递归解析

动态链接节约了内存,动态链接库的代码段和常量数据段都不需要被复制了。使用动态链接库的exe只需要多维护一个plt表和got表

启动

initramfs

文件树的打包

启动时加载initramfs并执行其中的init程序,以及init程序需要的文件描述符/dev/console

init程序可以使用mknode来创建一些对象,最终可以通过switch_root