从零开始写一个操作系统 —— 3.从c语言到内核

655 阅读3分钟

1.链接的作用

通过前文从零开始写一个操作系统 —— 2.5 从c语言到机器码 - 掘金 (juejin.cn)我们已经知道如何把c文件编译成可以被cpu识别的机器码,但是这个机器码离真正能够运行还存在一个对变量的定位问题。如下列文件main.c

int i = 3;
const j = 4;

void func()
{
    //something here;
}

int main()
{
    func();
    return 0;
}

对上述代码进行编译gcc -c main.c之后,生成的main.o二进制文件如下:

$objdump -d main.o

main.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <func>:
   0:   f3 0f 1e fa             endbr64
   4:   55                      push   %rbp
   5:   48 89 e5                mov    %rsp,%rbp
   8:   90                      nop
   9:   5d                      pop    %rbp
   a:   c3                      retq

000000000000000b <main>:
   b:   f3 0f 1e fa             endbr64
   f:   55                      push   %rbp
  10:   48 89 e5                mov    %rsp,%rbp
  13:   b8 00 00 00 00          mov    $0x0,%eax
  18:   e8 00 00 00 00          callq  1d <main+0x12>
  1d:   b8 00 00 00 00          mov    $0x0,%eax
  22:   5d                      pop    %rbp
  23:   c3                      retq

我们可以看到在18行中调用func函数的二进制代码是e8 00 00 00 00,也就是call了一个保留的地址,并未指向func函数,而这个地址的确认实际上是在链接过程中。

2.链接脚本

在日常的程序编译过程中,c源文件经过gcc编译之后生成了ELF(Executable and Linkable Format)文件。在上述文件中,二进制文件采用分段储存。比如代码相关数据,全部储存至名为.text的section中。定义的全局变量,则储存至名为.data的section中。而链接的过程,就是给各个section一个确定的地址,这样的话就可以让程序在运行的过程中正确定位到函数以及变量。至于这个确定的地址,则是由链接脚本给出,如main.lds

SECTIONS
{
    . = 0x1000;
    .text : { *(.text) }
    .data : { *(.data) }
    .rodata : { *(.rodata) }
}

.代表着对当前地址的定位,上述例子将section分布的起始位置定位为0x1000,因为.text是第一个section,则.textsection的第一个数据地址为0x1000.text : { *(.text) }它代表着链接过程将会把所有目标文件的.text段融合到输出文件的.text段中。紧接着的是.datasection,因为没有再次设定地址,则当最后一个属于.textsection的数据结束后,下一个数据就是属于.datasection的了,以此类推。 对于刚刚我们提到的main.c文件进行特定编译脚本的编译后gcc -c main.c && ld main.o -T main.lds -o main && objdump -s main

gcc -c main.c && ld main.o -T main.lds -o main && objdump -s main

main:     file format elf64-x86-64

Contents of section .text:
 1000 f30f1efa 554889e5 905dc3f3 0f1efa55  ....UH...].....U
 1010 4889e5b8 00000000 e8e3ffff ffb80000  H...............
 1020 00005dc3                             ..].
Contents of section .data:
 1024 03000000                             ....
Contents of section .rodata:
 1028 04000000                             ....
 ......

可以看到contents of section .text就是从0x1000开始,并且后面紧接着contents of section .data,起始于0x1024,然后是.rodata。这与我们在链接脚本中规定的排列方式是一样的。接下来要做的就是提取完整的二进制文件了,如我们之前提到的那样从零开始写一个操作系统 —— 2.5 从c语言到机器码 - 掘金 (juejin.cn),到目前我们已经解决了从c源文件到内核文件的问题了。