在上一节中,我们讲述了elf格式的部分理论知识,为什么是部分呢?因为我们本着“够用”的原则,只把我们需要了解的部分说完啦。不过,我相信大部分同学仅仅凭上一节中的理论知识还是领悟不到elf本质,咱们在本节开始分析前面咱们写过的“内核”,让大家看清elf文件的每一个字节。
为了让大家看清楚elf文件内部,咱们要用之前的xxd命令,为了方便使用,如很久很久以前所述,已经将其封装成了xxd.sh脚本,参数1是待查看的文件名,参数2是文件内的起始字节,参数3是查看的连续字节数。脚本是逐字节输出文件的内容。脚本内容很简单,就是xxd命令而已:xxd -u -a -g 1 -s $2 -l $3 $1,您也看到了,参数比较多,弄成脚本完全是为了避免每次复杂的参数键入。为了让大家方便使用,我已经将其放到了tool目录下,脚本中有参数说明,这里不再列出。下面是用此脚本处理kernel.bin的输出,见图
之前我们就用过多次xxd命令啦,对于输出想必大家一定很熟悉啦。脚本的输出大概分了三部分,最左边的一列是16进制的地址,或者称为偏移量最为恰当。中间这一大块矩阵似的16进制数字是文件中的内容,每两位16进制数字为一字节,每行共16个字节。最右边那一列,含有点点的、偶尔伴有可读字符的部分是字符显示区,这部分将内容按照字符编码显示,当然,前提肯定得是可打印字符,控制字符肯定不行,所以只要不是可显示的字符便显示为‘.’。
为了方便大家查看elf文件中各部分属性,我在各属性下面用下划线予以区分。其中,细下划线属于elf header的范围,粗下划线属于program header table程序头表的范围。在各范围之中的各属性,又以明显的下划线分隔,相信大家一定能一目了然。
咱们按照从上到下的顺序,先从细下划线的elf header部分说起。
第一行是e_ident数组,前4字节是固定的elf魔数,正如您看到的,它们是0x7f、和字符ELF的ascii:0x45、0x4c、0x46。所以您在显示区看到了ELF的三个字符。紧跟其后的三个1分别是e_ident[4]、e_ident[5]、e_ident[6]三个成员,代表的意义是32位elf文件、小端字节序、当前版本。后面的9个00是e_ident[7]~ e_ident[15],这些确实都已经初始化为0。
现在看第二行。
第1个下划线处的内容是02 00,由于是小端字节序,所以其值为0x0002。以下为方便陈述,只说该字节序所表示的数值。这个是e_type属性,它占2字节,值为2表示类型为ET_EXEC,即可执行文件(有兴趣的同学可以自行查看linux下的.o目标文件,其e_type类型是值为1的ET_REL,即待重定位类型)。
本行的第2个下划线处的内容是0x0003,占2字节。该位置是e_machine属性,即EM_386,表示该elf文件是运行在Intel 80386平台。
第3个下划线处的内容是0x00000001,占4字节。该位置是e_version属性,即版本信息。
第4个下划线处的内容是0xc0001500,占4字节,该位置是e_entry属性,即程序的虚拟入口地址。
第5个下划线处的内容是0x00000034,占4字节,该位置是e_phoff属性,表示program header table程序头表在文件中的偏移量,这里的偏移量是0x34。
现在看第三行。
第1个下划线处的内容是0x0000055c,占4字节,该位置是e_shoff,表示section header table节头表在文件内的偏移量,这里的值为0x55c,表示在本文件偏移0x55c字节处为节头表。之前说过啦,若没有节头表,此处便为0。
第2个下划线处的内容是0x00000000,占4字节,该位置是e_flags属性。
第3个下划线处的内容是0x0034,占2字节,该位置是e_ehsize属性,表示elf header大小是0x34字节。这和前面e_phoff属性值大小一致,可见,程序头表紧跟着elf header之后。
第4个下划线处的内容是0x0020,占2字节,该位置是e_phentsize属性,即program header的结构:struct Elf32_Phdr的字节大小,值为0x20字节。
第5个下划线处的内容是0x0002,占2字节,该位置是e_phnum属性,即程序头表中段的个数,这里为2个段。
第6个下划线处的内容是0x0028,占2字节,该位置是e_shentsize属性,即节头表中各个节的大小。
现在看第四行。
第1个下划线处的内容是0x0006,占2字节,该位置是e_shnum属性,即节头表中节的个数,这里表示有6个节。
第2个下划线处的内容是0x0003,占2字节,该位置是e_shstrndx属性,即string name table在节头表中的索引为3。
现在开始分析粗下划线范围的程序头表部分。
从第4行到第8行是程序头表的范围,前面说过啦,程序头表中共有2个段,每个段大小是0x20字节。这有两个粗下划线,每个占0x20字节。大家注意图中,在两个粗下划线间有个小竖线,这是用来区分两个段的。竖线左右两边各是一个段。
下面咱们按照struct Elf32_Phdr结构来分析,该结构中每个属性都占4字节,不再赘述。现在还是继续说图的第4行。
第1个粗下划线值为0x00000001,该位置是p_type属性,值为1,即表示PT_LOAD类型,可加载程序段,由于kernel.bin已经是链接后的可执行程序啦,所以,这PT_LOAD类型符合我们的认知。
第2个粗下划线值为0x00000000,该位置是p_offset属性,表示本段在文件内的偏移量。这个偏移量为0,似乎很奇怪,这表示该段的起始是从文件头开始也算起啦,文件开头的部分不是elf header吗?不是代码啊,这是要闹哪样?好吧,到底是什么情况,一会咱们细说。
第3个粗下划线值为0xc0001000,该位置是p_vaddr属性,表示本段被加载到内存后的起始虚拟地址。看到这里,似乎觉得上面的p_offset为0有那么一点合理啦,结合elf header中的e_entry的值为0xc0101500,不知您想到了点什么。咱们先把剩下的说完。
第4个粗下划线值为0xc0001000,该位置是p_paddr属性,它通常和p_vaddr值一致,但该属性是保留项,咱们不用关注。
第5个粗下划线值为0x00000505,该位置是p_filesz属性,表示本段在文件中的字节大小。
第6个粗下划线值也应该是0x00000505,该位置是p_memsz属性,表示本段在内存中的大小,因为段无论在哪里,逻辑大小是不变的,故该值等于p_filesz。
第7个粗下划线值为0x00000005,该位置是p_flags属性,表示与本段相关的标志。5=4+1=PF_R+PF_X,在此表示可读,可执行,根据此属性,我们推测此段为代码段。
第8个粗下划线值为0x00001000,该位置是p_align属性,表示本段对齐的方式。
第一个段咱们说完了,第二个段这里就不解释啦,留着大家自己练手吧。