加载ELF
本节介绍ELF格式的相关信息
Last updated
本节介绍ELF格式的相关信息
Last updated
在lab1中,我们说过,不管是ELF格式,还是EXE格式,这些格式的文件是为了提供关于程序尽可能多的信息,方便我们解析并加载到CPU上。所以,我们的实验也可以把Kernel和用户程序编译成各种不同的文件格式(比如exe,elf,macho......),甚至你可以自己发明一种文件格式和相应的编译器!
但是在这里,我们为了和ICS进行衔接,所以采用ELF文件格式,通过解析ELF文件,把Kernel和用户程序加载到内存中!
ELF(Executable Linkable Format,可执行可链接格式)文件是一种文件格式,在Linux系统上的目标文件都遵守这个格式。
下面是ELF的基本结构图:
左边是链接视图(Linkable),右边是执行视图(Executable)。链接视图是以节(section)为单位,执行视图是以段(segment)为单位的。链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。
在链接过程中,链接器会把不同的.o文件中的相同类型的section处理成一个segment。比如你要将多个.o文件链接起来,每个文件都会有.text这个节,链接时就会把这些.text节里面的地址重定向,变成.text段(如果还是想不起来,请回头看看ICS学过的内容)。
在链接过后,ELF文件会变成执行视图。这时,该ELF文件就可以被加载运行。
因为我们的任务是加载Kernel和用户程序来运行,所以忽略链接视图,感兴趣的同学可以从下面的参考资料去了解。
ELF文件的执行视图主要由三个部分构成:
ELF头(ELF header)
程序头表(Program Header Table)
各个段(segment)
ELF头在文件的最开始部分,用于找到ELF文件的其他部分,并且提供有关ELF文件的相关信息。
使用如下命令可以读取ELF头:
让我们随便找一个ELF可执行文件看看ELF头。
这其中的各个条目,实际上可以构成一个结构体(请查看bootloader/boot.h文件):
每个条目在固定的位置,所以可以通过结构体来进行解析。
其中比较重要的是跟程序头表有关的内容!!!
程序头表(Program header table) 列举了所有有效的段和他们的属性(执行视图)。
每一个段(比如.text,.data......)对应了一个程序头表项,表项里面包含了这些段的信息。如下图:
同样,我们可以在boot.h文件里找到程序头表表项对应的结构体:
其中,type为LOAD的段,需要被加载到内存里去。它相对于ELF起始位置的偏移量是off,应当被加载到PhysAddr的物理内存中去。文件的大小是filesz,在内存中占据的大小是memsz。
那么装载一个ELF可执行文件的过程就是:
先通过readSect函数把ELF文件整体读入固定的位置。
找到ELF可执行文件的ELF头。
通过ELF可执行文件的头,找到程序头表的位置,并获知表项的多少(实际上程序头表就是个结构数组)。
将type为LOAD的段装载到合适的位置上去。(注意,不要覆盖掉还未装载的段所占据的内存!!!)
在实验的后面,我将会提出一个非常奇怪的问题做challenge,来检测大家对ELF基础知识的掌握程度(可选,做不出来不扣分!)。🤣
对ELF格式感兴趣(或想了解)的同学可以参考ELF Specification。