当前位置: 首页 > Linux

ELF文件解析(二):ELF头详解

时间:2023-04-06 22:08:45 Linux

上一篇讲了ELF文件的整体布局,以及section和segment的概念。按照计划,今天继续讲ELF头。在说新内容之前,先纠正一个错误:在上一篇文章中,说到节头表中的条目和文件中的节是一一对应的。其实这样说是错误的。一个section必须有一个sectionheader来描述它,但是一个sectionheader在文件中不一定有相应的section,因为有些section并不占用文件字节。段也是如此。这篇文章本来应该是上周写的,但是上周忙于突击考试,周末放纵自己。看了两天《相声有新人》,所以今天赶紧补上。可以在/usr/include/elf.h中找到ELF标头定义。Elf32_Ehdr是32位ELF头的结构。Elf64_Ehdr是64位ELF头的结构。typedefstruct{unsignedchare_ident[EI_NIDENT];/*幻数及其信息*/Elf32_Halfe_type;/*目标文件类型*/Elf32_Halfe_machine;/*架构*/Elf32_Worde_version;/*目标文件版本*/Elf32_Addre_entry;/*入口点虚拟地址*/Elf32_Offe_phoff;/*程序头表文件偏移*/Elf32_Offe_shoff;/*节头表文件偏移量*/Elf32_Worde_flags;/*处理器特定的标志*/Elf32_Halfe_ehsize;/*以字节为单位的ELF头大小*/Elf32_Halfe_phentsize;/*程序头表条目大小*/Elf32_Halfe_phnum;/*程序头表项计数*/Elf32_Halfe_shentsize;/*节头表条目大小*/Elf32_Halfe_shnum;/*节头表条目计数*/Elf32_Halfe_shstrndx;/*节头字符串表索引*/}Elf32_Ehdr;typedefstruct{unsignedchare_ident[EI_NIDENT];/*幻数和其他信息*/Elf64_Halfe_type;/*目标文件类型*/Elf64_Halfe_machine;/*架构*/Elf64_Worde_version;/*目标文件版本*/Elf64_Addre_entry;/*入口点虚拟地址*/Elf64_Offe_phoff;/*程序头表文件偏移*/Elf64_Offe_shoff;/*节头表文件偏移量*/Elf64_Worde_flags;/*处理器特定的标志*/Elf64_Halfe_ehsize;/*以字节为单位的ELF头大小*/Elf64_Halfe_phentsize;/*程序头表条目大小*/Elf64_Halfe_phnum;/*程序头表项计数*/Elf64_Halfe_shentsize;/*节头表条目大小*/Elf64_Halfe_shnum;/*节头表条目计数*/Elf64_Halfe_shstrndx;/*节头字符串表索引*/}Elf64_Ehdr;64位和32位只是一个别字段长度不同,例如Elf64_Addr和Elf64_Off都是64位无符号整数,而Elf32_Addr和Elf32_Off是32位无符号整数。这会导致ELF标头的字节数不同。32位的ELF头占52字节,64位的ELF头占64字节。ELFheader详细e_ident占用16个字节。前四个字节称为ELF的幻数。下面的字节描述了如何解码ELF文件的内容和其他信息。等等细节。e_type,2字节,描述ELF文件的类型。以下值是有意义的:ET_NONE,0,无文件类型ET_REL,1,Relocatablefile(可重定位文件,通常文件名以.o结尾,目标文件)ET_EXEC,2,Executablefile(可执行文件)ET_DYN,3,Sharedobjectfile(动态库文件,你经常用gcc编译的binary也属于这种类型,是不是很奇怪?)ET_CORE,4,Corefile(核心文件,是coredump生成的吗?)ET_NUM,5,表示即5个文件类型ET_LOPROC,0xff00,Processor-specificET_HIPROC,0xffff,从ET_LOPROC到ET_HIPROC的Processor-specificvalues已经被定义,包括processor-specific语义。e_machine,2个字节。描述文件的体系结构,可能取值如下(因为文档比较老,现在取值较多,见/usr/include/elf.h中以EM_开头的宏定义):EM_NONE,0,没有机器EM_M32,1,AT&TWE32100EM_SPARC,2,SPARCEM_386,3,Intel80386EM_68K,4,Motorola68000EM_88K,5,Motorola88000EM_860,7,Intel80860EM_MIPS,8,MIPSRS30002...字节,描述ELF文件的版本号,合法取值如下:EV_NONE,0,InvalidversionEV_CURRENT,1,Currentversion,通常是这个值。EV_NUM,2,表示定义了2种版本号e_entry,(32位4字节,64位8字节),执行入口点,如果文件没有入口点,该字段保持0。e_phoff,(32位4字节,64位8字节),程序头表的偏移量,如果文件没有PH,这个值为0。e_shoff,(32位4字节,64位8字节),sectionheadertable的偏移量,如果文件没有SH,这个值为0。e_flags,4字节,处理器特定的flags,32位和64位Intel架构没有定义flags,所以eflags的值为0。e_ehsize,2字节,ELF头的大小,32位的ELF是52字节,64位的是64字节。e_phentsize,2个字节。程序头表中每个条目的大小。e_phnum,2个字节。如果文件没有程序头表,则e_phnum的值为0。将e_phentsize乘以e_phnum得到整个程序头表的大小。e_shentsize,2字节,节头表中表项的大小,即每个节头占多少字节。e_shnum,2字节,节头表中的头数。如果文件没有节头表,则e_shnum的值为0。将e_shentsize乘以e_shnum得到整个节头表的大小。e_shstrndx,2个字节。节头字符串表索引。在节头表中包含节名字符串表。如果没有段名字符串表,则e_shstrndx的值为SHN_UNDEF。注意:programheadertable一般翻译成programheadertable,sectionheadertable一般翻译成sectionheadertable,因为这样翻译对理解没有帮助,所以我倾向于不翻译。e_ident回过头来,我们仔细看一下文件的前16个字节,也是e_ident。如图,前4个字节为ELF的MagicNumber,固定为7f454c46。第5个字节表示ELF文件是32位还是64位。第六个字节指定了数据的编码方式,也就是我们通常所说的littleendian或者bigendian。Littleendian我喜欢叫小端在前,低字节在前,或者直接说低字节在低地址,比如0x7f454c46,存储顺序是464c457f。bigendian的意思是大端在前,高位字节在前,直接说就是高位字节在低位地址,比如0x7f454c46,在文件中的存储顺序是7f454c46.第七个字节表示ELF头的版本号,当前值为1。第8-16字节全部用0填充。readelf读取ELF头我们使用readelf-h读取ELF头信息的文件。比起我本地有执行文件hello,我执行reaelf-hhello,结果如下:ELFHeader:Magic:7f454c46020101000000000000000000Class:ELF64Data:2'scomplement,littleendianVersion:1(当前)操作系统/ABI:UNIX-系统VABI版本:0类型:DYN(共享目标文件)机器:AdvancedMicroDevicesX86-64版本:0x1入口点地址:0x1050程序头开始:64(字节到文件)节头开始:14768(字节到文件)标志:0x0这个头的大小:64(字节)程序头的大小:56(字节)程序头的数量:11节头的大小:64(字节)数量章节标题数:29章节标题stringtableindex:28这是我用gcc生成的可执行文件,但是注意它的Type是DYN(Sharedobjectfile),这可能是因为这个文件不能直接执行,需要依赖解释器和c库来运行真正的可执行文件是一个解释器,hello也是一个相对于解释器的共享库文件。这是我的推论,需要后面深入研究来验证。今天就到此为止。这周结束前,我打算写这个系列的第三篇文章,关于target文件中section的知识。2018-10-22星期一