当前位置: 首页 > Linux

ELF文件分析(一):Segment和Section

时间:2023-04-06 11:53:02 Linux

ELF是ExecutableandLinkingFormat的缩写,即可执行和可链接的格式,是Unix/Linux系统ABI(ApplicationBinaryInterface)规范的一部分。Unix/Linux下的可执行二进制文件、目标代码文件、共享库文件、核心转储文件等都是ELF文件。下图来自文档可执行和可链接格式(ELF),它描述了ELF文件的一般布局。左边是ELF的链接视图,可以理解为目标代码文件的内容布局。右侧是ELF的执行视图,可以理解为可执行文件的内容布局。注意目标代码文件的内容是由节组成的,而可执行文件的内容是由段组成的。注意segment和section的概念,后面会经常提到。我们在编写汇编程序时,使用.text、.bss和.data等指令来引用段,例如.text,告诉汇编程序将以下代码放入.text段。目标代码文件中的节与节头表中的条目之间存在一一对应关系。链接器使用段信息来重新定位代码。当文件被加载到内存中执行时,它被组织成段,每个段对应ELF文件中程序头表中的一个条目,用于创建可执行文件的进程映像。例如,我们通常所说的代码段和数据段是段,目标代码中的段会被链接器组织成可执行文件的段。.text段的内容会被组装进代码段,.data、.bss等段的内容会被包含在数据段。在目标文件中,程序头不是必须的,我们用gcc生成的目标文件是不包含程序头的。解析ELF文件的有用工具是readelf。在我本地机器上的目标代码文件sleep.o上执行readelf-Ssleep.o,输出如下:Thereare12sectionheaders,startingatoffset0x270:SectionHeaders:[Nr]NameTypeAddressOffsetSizeEntSizeFlagsLinkInfoAlign[0]NULL00000000000000000000000000000000000000000000000000000000000[1].textPROGBITS00000000000000000000004000000000000000150000000000000000AX001[2].rela.textRELA0000000000000000000001e000000000000000180000000000000018I918[3].dataPROGBITS00000000000000000000005500000000000000000000000000000000WA001[4]。TAB00000000000000000000021000000000000000590000000000000000001标记键:W(写入)、A(分配)、X(执行)、M(合并)、S(字符串)、I(信息)、L(链接顺序)、O(OS需要处理)、G(组)、T(TLS)、C(压缩)、x(未知)、o(特定于操作系统)、E(排除)、l(大)、p(特定于处理器)readelf-S是显示文件中的节信息。sleep.o中有12个部分。我们省略了部分信息。可以看到除了大家熟悉的.text、.data、.bss之外,还有其他的Section,等我们以后展开section的时候会专门提到。我们还可以通过查看每个Section的Flags来获取一些信息。比如.text段的Flags是AX,表示需要分配内存,可执行。这部分无疑是代码。.data和.bss的Flags都是WA,表示可写,需要分配内存,这是数据段的特点。使用readelf-l显示文件的程序头信息。我们在sleep.o上执行readelf-lsleep.o。会输出Therearenoprogramheadersinthisfile..程序头与文件中的段一一对应,因为目标代码文件中没有段,所以程序头是不需要的。可执行文件的内容被组织成段,所以程序头表是必要的。节头不是必需的,但未分割的二进制文件包含此信息。对本地可执行文件sleep执行readelf-lsleep,输出如下:ElffiletypeisDYN(Sharedobjectfile)Entrypoint0x1040Thereare11programheaders,startingatoffset64ProgramHeaders:TypeOffsetVirtAddrPhysAddrFileSizMemSizFlagsAlignPHDR0x00000000000000400x00000000000000400x00000000000000400x00000000000002680x0000000000000268R0x8INTERP0x00000000000002a80x00000000000002a80x00000000000002a80x000000000000001c0x000000000000001cR0x1[Requestingprograminterpreter:/lib64/ld-linux-x86-64.so.2]LOAD0x00000000000000000x00000000000000000x00000000000000000x00000000000005600x0000000000000560R0x1000LOAD0x00000000000010000x00000000000010000x00000000000010000x00000000000001d50x00000000000001d5RE0x1000LOAD0x00000000000020000x00000000000020000x00000000000020000x00000000000001100x0000000000000110R0x1000LOAD0x0000000000002de80x0000000000003de80x0000000000003de80x00000000000002480x0000000000000250RW0x1000DYNAMIC0x0000000000002df80x0000000000003df80x0000000000003df80x00000000000001e00x00000000000001e0RW0x8NOTE0x00000000000002c40x00000000000002c40x00000000000002c40x00000000000000440x0000000000000044R0x4GNU_EH_FRAME0x00000000000020040x00000000000020040x00000000000020040x00000000000000340x0000000000000034R0x4GNU_STACK0x00000000000000000x00000000000000000x00000000000000000x00000000000000000x0000000000000000RW0x10GNU_RELRO0x0000000000002de80x0000000000003de80x0000000000003de80x00000000000002180x0000000000000218R0x1段到段nt映射:段部分...0001.interp02.interp.note.ABI-tag.note.gnu.build-id.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn。rela.plt03.init.plt.text.fini04.rodata.eh_frame_hdr.eh_frame05.init_array.fini_array.dynamic.got.got.plt.data.bss06.dynamic07.note.ABI-tag.note.gnu.build-id08.eh_frame_hdr0910.init_array.fini_array.dynamic.got如输出所示,文件中一共有11段,只有LOAD段是运行时真正需要的。除了段信息外,它还输出每个段包含哪些部分。例如,第二个LOAD段被标记为R(只读)和E(可执行),它的编号是03,表示它包含了哪些段。该行的内容是:03.init.plt.text.fini。可以发现其中包含了.text,这一段就是代码段。又如第三个LOAD段,索引为04,标志为R(只读),但没有可执行属性。它包含的sections包括.rodata.eh_frame_hdr.eh_frame,其中rodata表示只读数据,即程序中使用的String常量等。最后一个LOAD段,索引05,标志RW(可读可写),它的sections包含的是.init_array.fini_array.dynamic.got.got.plt.data.bss,可以看到.data和.bss都包含了,这一段无疑是数据段。今天就说到这里,下面的内容是这样组织的:首先说说精灵文件的文件头,因为文件的前几十个字节就是精灵文件头的数据。这个数据结构包含了很多信息,它还可以告诉我们关于程序头表的信息。,文件中的节头表在哪里。接下来,我将讨论如何解释节头表以及如何组织节数据。然后说一下程序头表和段的数据组织。section如何组织成segments,我们也需要对此提出要求。最后,我们将讨论如果程序被加载程序加载到内存中,如何生成进程映像。欢迎继续关注。