今天研究了下elf格式和链接过程,权且自己理解,以便备忘,诸多问题,还值得商榷。
目前自己用过的elf不外乎两种:编译生成的可重定位文件,连接生成的可执行文件。对于elf文件,结构轮廓为
不过就目前看来,似乎用ld连接出来的可执行文件都有节区头部表,应该是为了附带一些信息。对于elf文件可用工具对其结构进行分析,这样比直接分析其二进制码文件方便些。
(上图为可重定位文件结构)
(上图为可执行文件结构)
对于elf文件各个子项都有其含义,较为繁琐,可查看《ELF 文件格式分析》帮助理解。
接下来分析下关于对于目标文件为elf文件的连接过程:
对于连接器而言,其连接的基本单位为各个可重定位文件的节区,也就是说连接器可以为可重定位文件的节区(section)指定其在连接后生成的目标文件(可执行文件)中的位置,包括其在文件内的位置和当其载入到内存后的位置,当确定好各个节区的位置后,连接器便可根据其位置和符号表,重定位表等信息,填入确定的地址信息,变生成可执行文件。这也就是连接器的工作。
对于ld来说,在linux下可用lds文件来指定连接具体过程,例:
ENTRY(_start)
SECTIONS {
firtst 0x10000000 :{ leds.o }
second 0xB0004000 : AT(2048) { init.o head.o}
}
这里指定了文件的入口点为_start,它表示在这几个可重定位文件的源文件内有一个_start的标号,当载入生成的可执行文件后,其入口点便为_start处的地址,这一点体现在elf文件的elf header里,其表项有一个e_entry,该值便指定了入口地址,至于该值的的得出,是由于当指定好了各个区段的位置后,_start处代码载入到内存的位置可以计算得出,当解决好入口地址后,便是具体指定各个节区的位置了。
在SECTIONS里面,指定了两项,便是可执行文件一共有两个程序段(仍然可以有节表和节表头,往往一个程序段便是一个节表项,上图节表项1,2便是first,second,节表0是指elf头,无意义,后面的节表项,实际意义不大),并且也指定了,载入到内存后的地址和文件内部偏移,此处易误解的地方为后面是*.o文件,而不是节区,个人实验来看,当后面是文件,而不是节区时,意味着,把该文件的所有需要在“内存申请空间”的节区(个人理解,一般来说就是.text和.data)放在一起,接下来又放第二个文件的节区,这里需要注意的是,将各个文件的节区任意摆布,只要正确的设置了入口点,那么程序都会正确按照程序逻辑执行,这是因为无论节区如何摆布,连接器总会用根据指定的地址,计算出应该跳转到的地址,也即是说,只要按照程序段头的信息载入该程序,然后设置正确的入口点,那么程序便可以正确执行。
个人认为,若在pc上执行程序,并无必要自己来指定程序应摆布在何处,主要是由于在嵌入式开发时,需要在向ram中移动代码或进行mmu等操作时,才需要指定连接地址,以便容易直接操作。另外需要注意的是,对于ld连接器,没有提供lds文件链接文件时,ld有自己默认的lds,其中代码较为复杂,不作分析。