模块加载器
众所周知,Linux 的一大特色就是它的内核模块,那么,作为 OS 开发者的我们,我们应如何为自己的内核加入这个功能呢?
首先,我们需要知道内核模块的格式。Linux 的格式是 REL,但我们最容易想到的是 DYN 类型。为什么选 REL 呢?因为 DYN 的重定位更复杂。
然后我们就开始吧! PS: 本教程以 64 位为例,32 位同理
XIAOYI12: 该教程对动态链接的讲解较为笼统,建议转到 动态链接器 熟悉后再来观看。
Step 1
模块加载,首先需要一个模块。由于模块有多个文件,所以会用到链接器。但链接器显然不会输出 REL,怎么办呢?
好吧,反直觉的一点是,链接器会输出未链接好的 REL 文件,只需要-r 选项即可
同时,请使用 code-model=large 进行编译,这可以简化我们的工作(这意味着只有 R_X86_64_64 重定位项)
Step 2
模块准备好了,接下来开始加载
首先,我默认你们有办法在启动内核后把模块加载入内核,实在不行把文件嵌入内核也是可以的,毕竟先把实验做好嘛
把模块加载入内存后,由于它是 ELF 格式,因此需要 ELF 解析,这部分左转去OS Dev,这里不再赘述。 \
Step 3
整个加载过程最难的是重定位
REL 不同于其他的格式,它没有自己的虚拟地址分配,因此你需要从零开始
首先,在你的虚拟地址空间中分配一块内存,然后遍历每一个需要加载到内存中的节,为它们分配地址
然后遍历符号表,对于每一个已经定义的符号,计算它们的节内偏移,再拿到之前分配好的地址,相加得到符号的最终地址
未定义的符号,先检查是不是内核已经有的符号,有就把内核的符号地址填进去,没有直接报错
这样,我们就已经做好了准备工作
Step 4
正式重定位 !!!!!!
首先,遍历每一个重定位节,遍历其中的每一个重定位项
对于每一个重定位项,我们这样来称呼:重定位项中有符号表索引,索引指向的符号,就是重定位项所需要重定位到的地址,这里叫它 A
重定位项本身所在的地址,即我们要将 A 写到的地址,称之为 O
则重定位的过程就是data[O] = A(data 就是模块的 ELF 文件)
难点在于理清如何获得 A 和 O
A 只要去之前准备好的符号地址表去查,那么 O 呢?
我们只要拿到重定位项的重定位节,也就是前面的循环中的第一层,然后拿到它的sh_info
这就是data[O]所在的节的索引
然后根据索引,查找之前做好的节地址表,拿到节的地址,将其与重定位项中的节内偏移r_offset相加,这就是 O
根据data[O] = A写就完事了
Step 5
终于,你完成了最难的部分,只要 map 就可以了 \
完成以上步骤后,恭喜你,你已经完成了一个基本的模块加载器!!!!