ELF File Details

ELF file details

ELF的全称是Executable and Linking Format,一般linux系统中的可执行文件都是这种格式的,本文将详细介绍此类文件的构成以及用途

What is ELF?

在linux系统中常见的ELF文件主要有三类:目标文件,可执行文件,动态链接库文件(注意静态链接库文件不是ELF),这三类文件内容结构大致相似,用途却不同。

目标文件,经由源码编译和汇编后生成的含有机器代码的文件,例:gcc -c test.c会生成目标文件test.o

可执行文件,链接器将目标代码链接后生成的文件,例:gcc test.o将生成可执行文件a.out

动态链接库文件,该类文件也是经由源码编译而成的,不过不是直接用来执行而是作为库来使用的,例:gcc -shared -fPCI test.o -o libtest.so

ELF的构成

可以使用readelf -a filename来查看elf文件的内容,上面的三类ELF文件的结构是相似的,主要内容为:

    ELF Header

    Program Headers 和 Segment Sections

    Section Headers

    Section1

    Section2

    ...

    SectionN
  1. ELF Header主要记录了文件适用的OS以及硬件架构和文件各个组成部分的偏移量等,查看命令: readelf -h a.out

  2. Program Headers是可执行文件独有的(目标文件中没有),主要记录了加载到内存运行时所需要的信息,Segment Sections也是可执行文件独有的又可称作Segment描述,其实就是section到segment的映射表(一个Segment通常有若干相关sections构成),Program Headers和Segment Sections内容的查看命令: readelf -l a.out,这里有必要仔细研究一下Segments,在可执行文件载入内存时是以segment为单位进行的,并且OS会将一个segment映射到Program Headers指定的虚拟地址,常见的Segment到section的映射有: .text section和.rodata section会合并为text segment,.data section和.bss section会合并为data segment,symbol information以及debugging information被放置在不可载入内存的segment

  3. Sectons Header,记录了各个section名称以及偏移量等信息,查看命令: readelf -S a.out,elf文件中一般来说包含text section,data section,dynamic information,debugging data,symbol table,relocation information,string tables,其中有的内容仅仅用作调试,有的信息用于程序载入内存时使用,还有的section只存在于目标文件中,总之要记得并不是存在于elf文件中的内容最终都会被载入内存的

  4. SectionN,各个section的内容,这些内容是elf文件的主要构成部分,要显示所有section的详细内容: readelf -t a.out

从目标文件到可执行文件

我们知道目标文件和可执行文件都是elf文件,它们的大致内容构成是类似的,其主要的区别是section内容的变化,section的本质是elf文件对相关内容的一块逻辑划分,那为什么要进行section划分呢?

理由就是elf文件中的不同内容有不同用途,分为多个section是为了方便数据管理,将文件内容分为多个section的好处很多,例如:指令码是不希望被改写的,所以我们将其单独划分出来,这样在可执行文件加载到内存中时操作系统会将text section放入只读区域,还有将初始化和未初始化数据隔离开来,bss section在可执行文件中并没有为数据分配空间,当可执行文件加载到内存时才会为bss section分配空间,这样就节省了磁盘空间

目标文件的主要sections:

    .text       主要内容是指令码,该section具有read,execute权限

    .data       放置初始化(含有非空值)的global和static变量,该section具有read,write权限

    .bss        全称叫作Block Started by Symbol,用于放置未初始化的global和static变量,

                其实由于变量是未初始化的(即值为空),所以在目标文件中其实并没有为这些变量分配空间

    .rodata     放置只读数据,其实就是constants和string literals

    .rel.text   用于text section的relocation信息(记录目标文件依赖的外部符号)

    .rel.bss    用于bss section的relocation信息

    .rel.data   用于data section的relocation信息

    .rel.got    用于got section的relocation信息

    .symtab     源码中标志符到text/data section地址的映射表,反汇编工具可以根据目标文件提供的symbol table来生成源码

    .comment    版本控制信息

    .debug_*    供调试器使用的信息,不过在用gcc编译时需要指定-g或-ggdb选项才会出现该section

可执行文件的主要sections:

    .init       存放用于进程初始化的指令

    .fini       存放用于终止进程的指令

    .text       指令码

    .rodata     放置只读数据,其实就是constants和string literals

    .data       initialized data

    .tdata      initialized thread data

    .tbss       uninitialized thread data

    .ctors      constructors

    .dtors      destructors

    .bss        uninitialized data

    .dynamic    存放动态链接信息

    .dynstr     存放需要被动态链接的字符串

    .dynsym     存放动态链接符号表

    .got        global offset table,存放系统函数的地址

    .comment    版本控制信息

    .debug_*    供调试器使用的信息,不过在用gcc编译时需要指定-g或-ggdb选项才会出现该section

我们看到可执行文件大致包含目标文件中sections,同时可执行文件又多了许多自己独有的sections,这些多出的sections一般是为程序加载到内存提供的信息,那么从目标文件到可执行文件到底发生了什么呢?

目标文件到可执行文件的过程叫作linking,所谓linking即使将多个目标文件以及静态/动态库整合为一个可执行文件的过程,linking是可执行文件生成的一个必经步骤,其主要任务是将不同目标文件的相应section进行合并,并解决目标文件中对外部标识符依赖的问题(即根据relocation信息改写相应section),至于对静态/动态链接库的链接,则更复杂些,被依赖的静态链接库会写入可执行文件的text section中,而动态链接库则会写入可执行文件的dynamic section以及相关区域,最终生成可执行文件,也就是说从目标文件到可执行文件linker将某些信息合并(不同目标文件的相同section合并),某些内容去除(目标文件中的部分relocation信息),并添加了某些内容(dynamic section),同时可执行文件中多了Program Headers 和 Segment Sections以供加载到内存中时使用

符号表

每个生成目标文件(可执行文件中也有符号表,不过它的内容来自目标文件以及依赖的库)的源码中都使用了各种各样的变量名,函数名等标识符,在词法分析和语法分析后会将这些符号的信息提取出来,放在目标文件的特定区域(.symtab section)中,每条信息记录了符号名,符号类型(变量还是函数),作用域,默认值等内容,符号表对于目标代码的链接以及程序的调试都很重要,同时linux有工具(strip)允许你手动移除elf文件的符号表

relocation信息

生成目标文件时,对于文件使用的外部标识符(即非本文件定义的变量,函数等)汇编器的处理是,将外部标识符信息记录在目标文件的特定区域(.rel.text,.rel.data等section)中,每条记录的内容包含: 外部标识符在符号表中的索引,外部标识符在目标文件相应section中的地址,标识符的类型等,有了这些内容,linker在进行目标文件链接时,可以根据查找到的外部标识符实际地址改写对应section的内容,也就是说有了relocation信息我们才能够解决文件中使用外部标识符的问题

Previous
Next