
0. 简介
在Linux系统的可执行文件(ELF文件)中,开头是一个文件头,用来描述程序的布局,整个文件的属性等信息,包括文件是否可执行、静态还是动态链接及入口地址等信息;如下图所示:


程序文件中包含了程序头,程序的入口地址等信息不需要写死,调用代码可以通用,根据实际情况加载;此时的文件不是纯碎的二进制可执行文件了,因为包含的程序头不是可执行代码;将这种包含程序头的文件读入到内存后,从程序头中读取入口地址,跳转到入口地址执行;
0.1 文件格式
Linux环境中,目标文件是源代码编译后未链接的中间文件,如:gcc编译生成的.o文件;可执行文件(.o)、动态链接库(.so)、静态链接库(.a)文件都是按照ELF可执行文件格式存储的;
ELF指:Executable and Linkable Format,可执行链接格式;本文中的目标文件指各种类型符合ELF规范的我呢见,如:二进制可执行文件、Linux下的.o目标文件和.so动态库文件;
可执行文件(Executable file):经过编译链接后,可以直接执行的程序文件,如:ELF文件;
共享目标文件(Shared object file):动态链接库,在可执行文件被加载的过程中动态链接,成为程序代码的一部分;
可重定位文件(Relocatable file):可重定位文件即目标文件和静态库文件,是源文件编译后但未完成链接的半成品,被用于与其他目标文件合并链接,以构建出二进制可执行文件;
核心转储文件(Core dump file):当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些信息转储到核心转储文件;
0.2 段和节
程序中的段(Segment)和节(Secton)是真正的程序体;段包括代码段和数据段等,段是由节组成的,多个节经过链接后被合并为一个段;
段和节的信息用header描述,程序头是program header,节头是section header;段和节的大小和数量都是不固定的,需要用专门的数据结构来描述,即程序头表(program header table)和节头表(section header table),这是两个数组,元素分别是程序头(program header)和节头(section header);程序头表(program header table)中的元素全是程序头(program header),节头表(section header table)中的元素全是节头(section header);程序头表是用来描述段(Segment)的,成为段头表;段是程序本身的组成部分;
由于段和节的大小和数量都是不固定的,程序头表和节头表的大小也不固定,两个表在程序文件中的位置也不固定;需要在一个固定的位置,用一个固定大小的数据结构来描述程序头表和节头表的大小和位置信息,即位于文件最开始部分的ELF header;
1. ELF header
ELF目标文件的最开始是ELF文件头(ELF Header),包含了描述整个文件的基本属性;ELF文件分为文件头和文件体两部分;先用ELF header从文件全局概要出程序中程序头表、节头表的位置和大小等信息;然后从程序头表和节头表中分别解析出各个段和节的位置和大小等信息;
可执行文件和待重定位文件,文件最开头的部分是ELF header;程序头表对于可执行文件是必须的,而对于待重定位文件是可选的;
ELF文件头在32位系统中用Elf32_Ehdr结构体表示,在64位系统中用Elf64_Ehdr结构体表示;两者内容是一样的,区别是有些成员的大小不同;
// include/uapi/linux/elf.h

e_ident[EI_NIDENT]是16字节大小的数组,用来表示ELF字符等信息,开头四个字节是固定不变的elf文件魔数,0x7f 0x45 0x4c 0x46;

e_type:2字节,用来指定ELF目标文件的类型;
// include/uapi/linux/elf.h
e_machine:2字节,用来描述ELF目标文件的体系结构类型,即要在哪种硬件平台运行;
// include/uapi/linux/elf-em.h
2. 程序头表
程序头表(也称为段表)是一个描述文件中各个段的数组,程序头表描述了文件中各个段在文件中的偏移位置及段的属性等信息;从程序头表里可以得到每个段的所有信息,包括代码段和数据段等;各个段的内容紧跟ELF文件头保存;程序头表中各个段用Elf32_Phdr或Elf64_Phdr结构体表示;
// include/uapi/linux/elf.h
Elf32_Phdr或Elf64_Phdr结构体用来描述位于磁盘上的程序中的一个段;

p_type:4字节,用来指明程序中该段的类型;
// include/uapi/linux/elf.h
p_flags:4字节,用来指明与本段相关的标志;
// include/uapi/linux/elf.h
3. 节头表
节头表中各个节用Elf32_Shdr或Elf64_Shdr结构体表示;
// include/uapi/linux/elf.h
Elf32_Shdr或Elf64_Shdr结构体用来描述位于磁盘上的程序中的一个节;
成员32位64位说明sh_name44节名称,值是字符串的一个索引;节名称字符串以'0'结尾,统一存储在字符串表中,使用该字段存储节名称字符串在字符串表中的索引位置;sh_type44节的类型;sh_flags48节的标志;sh_addr48节在内存中的起始地址,指定节映射到虚拟地址空间中的位置;sh_offset48节在文件中的起始位置;sh_size48节的大小;sh_link44引用另一个节头表项,根据节类型有不同的解释;sh_info44节的附加信息,与sh_link联合使用;sh_addralign48节数据在内存中的对齐方式;sh_entsize48指定节中各数据项的长度,各数据项长度要相同;

sh_name
节名称sh_name的值是字符串的一个索引,节名称字符串以'0'结尾,字符串统一存放在字符串表中,使用sh_name的值作为字符串表的索引,找到对应的字符串即为节名称;
字符串表中包含多个以'0'结尾的字符串;在目标文件中,这些字符串通常是符号的名字或节的名字,需要引用某些字符串时,只需要提供该字符串在字符串表中的序号即可;
字符串表中的第一个字符串(序号为0)是空串,即'0',可以用于表示没有名字或一个空的名字;如果字符串表为空,节头中的sh_size值为0;
sh_type:节的类型;
// include/uapi/linux/elf.h
sh_flags:节的标志;
// include/uapi/linux/elf.h
4. readelf命令
readelf命令用于查看ELF格式的文件信息;
$ 命令如下:
$ 5. xxd命令
由于ELF文件是二进制文件,和普通文件不同,不能使用一般的vim等编辑工具直接打开;而xxd命令主要用来查看文件的十六进制格式,同样也能够以二进制格式查看文件;
$ xxd命令常用的选项:
-6. 总结
在Linux系统的可执行文件(ELF文件)中,开头是一个文件头,用来描述程序的布局,整个文件的属性等信息,包括文件是否可执行、静态还是动态链接及入口地址等信息;生成的文件不是纯碎的二进制可执行文件了,因为包含的程序头不是可执行代码;将这种包含程序头的文件读入到内存后,从程序头中读取入口地址,跳转到入口地址执行;可以参考《linux中ELF二进制程序解析》,使用中的实例可以参考《linux中ELF二进制文件解析----ELF文件实例解析》;
参考资料
《操作系统真象还原》
程序编译-汇编-链接的理解!—03-ELF头和节头表
ELF文件-节和节头
回到目录
本文介绍了Linux系统中可执行文件(ELF)的基本结构,包括文件头、程序头表、节头表等关键部分。ELF文件的开头包含文件头,用于描述文件属性,如是否可执行、入口地址等。程序头表和节头表则分别描述段和节的信息,帮助系统在内存中正确加载和执行程序。此外,文章还提到了readelf和xxd命令用于查看ELF文件信息。
1534

被折叠的 条评论
为什么被折叠?



