模块加载器
众所周知,Linux的一大特色就是它的内核模块,那么,作为OS开发者的我们,我们应如何为自己的内核加入这个功能呢?
首先,我们需要知道内核模块的格式。Linux的格式是REL,但我们最容易想到的是DYN类型。为什么选REL呢?因为DYN的重定位更复杂。
然后我们就开始吧! PS: 本教程以64位为例,32位同理
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就可以了 \
完成以上步骤后,恭喜你,你已经完成了一个基本的模块加载器!!!!