1.背景
很长一段时间,我只知道ELF是一个广泛使用的文件格式规范,经常指动态库、bin等。而我也没有动力去深入研究。出于商业需要,我花了很多天仔细分析RTOS宾的截面和符号。也是借此机会查阅了很多资料,完善了知识脉络。
但可以预见的是,业务结束一段时间后,我必然会因为不常使用而逐渐淡忘。从入门到遗忘,相信只要经历过深度学习,都会有痛苦的挣扎。为了在以后需要的时候唤醒我的记忆,我决定通过这篇文章打下一个锚点,也希望这篇文章能帮助更多的人更快入门。
2.基本概念
2.1什么是ELF文件?
ELF的全称是可执行可链接格式,即“可执行可链接格式”,通俗地说就是二进制程序。ELF规定了这个二进制程序的组织规范,所有按照这个规范组织的文件都称为ELF文件。ELF文件有以下四类。
ELF文件类型的示例核心可重定位文件,如编译的进程文件。o '共享对象文件,如'so’库,以及动态链接的bin可执行文件,比如静态链接的文件,比如Linux上的coredump文件,我们可以通过file命令来识别。
# test . o:gcc-ctest . c-otest . o # test-static-link . bin:gcc-static test . c-otest # test-dynamic-link . bin:gcc test . c-otest $ filetest . otest-static-link . bin test-dynamic-link . bin test . o:ELF.可浮动,测试-动态-链接. bin:ELF.sharedobject,测试-静态-链接. bin:ELF.可执行文件,
此外,我们习惯称之为。o '文件作为目标文件,链接的可执行文件称为bin文件。
2.2 elf文件结构概述
ELF文件主要用于两个目的。
链接到动态库或库的构建器通常是一个目标文件。的
运行程序,一般指的是‘链接’。“so”或“bin”
当这个ELF文件用于不同的用途时,文件结构的解析角度会有一点不同。一般来说,不同的目的对需要什么数据有不同的要求。例如,节表头在构建(链接)时是必需的,但在运行时是可选的。比如操作需要小节信息,而链接只有小节信息。
上图中,程序头表跟随ELF文件头,节头表跟随节信息,但在实际文件中,这个顺序并不固定。在ELF文件的各个组成部分中,只有ELF文件头的位置是固定的,其他内容的位置都是可变的。
这里有一个潜在的概念,其他文章很少指出。节头表描述了所有的节信息表,而节目头表实际上描述了所有的节信息表。
下一节将介绍一些关键字段的含义。这里介绍这张图的主要目的是介绍两个重要的概念:Seciton和Segment。从上图可以看出,链接视图有大量的Section,而执行视图有Segment。那么什么是剖面,什么是剖面呢?
2.3部分)与分段
也许我们听说过bss段,文本段,或者数据段,但实际上我们口头交流的段都是更一般化的段。为什么?
我们就拿一个bin,列出程序头表:
$ readelf-l/bin/lsElffiletypeisDYN(shared object file)entry point0x 5850 here 9 program headers,startingatoffset 64 program headers:SectiontoSegmentmapping:
节目头下面列出了所有的片段信息,如下图所示,共有9个片段。
程序头:typeoffsetvirtaddrphysadrfilesizmesizflagsalignphdr0x 00000000000000040000000000000400000000000001 f 80x 000000000000000001 f 8re 0 x8 x8 x8 interp0x 000000000000002380 x 00000000001
部分到分段映射罗列了各个段(分段)包含了哪些节(节),是的,段是一个或者多个节的集合,详细如下文所示。
SectiontoSegmentmapping:分段分段.0001 .interp 02。interp。注意。ABI标签。注意。GNU。内部版本号。GNU。哈希。动态符号。dynstr。GNU。版本。GNU。rela版本。dyn。rela。PLT。PLT。明白了。文字。菲尼。罗达塔。嗯_帧_ HDR。eh _帧。初始化数组。fini _ array。数据。rel。ro。动态。数据。BSS 04。动态05。注意
总的来说,段是没有名字的,但我们往往把包含文本节的段叫做代码(正文)段,把含有数据节的段叫做数据(数据)段。一定程度上在口述时习惯也可以理解为'段=节,例如英国标准规格段,罗达塔段,实际他们指英国标准规格节,罗达塔节。甚至有时候我们说文本段就狭义的指文本节,完全不用太纠结。
3.关键的文件结构和节
3.1 一些关键的文件结构
对一个极低频格式的文件,有这么几个特殊的结构我们需要关注的。
极低频文件头(文件头):位于文件最开始,包含了整个文件的结构信息,例如是极低频幻数,是哪种极低频文件,程序头表、节头表的地址等。
程序头表(程序标题表):描述了所有段的信息
节头表(部分标题表):描述了所有节的信息
本文不会解释结构体每个元素,而是利用readelf工具解读。如果需要详细到每个字节的意义,可以查阅章节一提到的《UnderstandingELF.pdf》
3.1.1 文件头(文件头)
readelf -h命令可以查看文件头信息,例如:
readelf-h/bin/lsELFHeader:Magic:7f 454 c 4602010100000000000 class:elf 64 data:2 '补码,littleendianVersion:1(当前)OS/ABI:UNIX-SystemVABIVersion:0类型:DYN(共享对象文件)机器:高级微设备x86-64版本:0x 1入口点地址:0x 5850 startofprogramheaders:64(字节进
解读如下:
关于字段含义的说明:魔标是ELF文件类型,魔值只能是0x7F 'E' 'L' 'F'Class32Bit/64Bit类型ELF64/ELF32Data小头/大头编码版本文件头版本OS/ABI适用系统和ABIType什么类型的ELF文件DYN:共享目标文件,REL:可重定位文件,EXEC:可执行文件[1]机器处理器架构常见的如ARM,X86-64VersionELF文件的版本入口点地址指的是程序入口的虚拟地址一般它需要一系列的调用才能到达main()文件偏移量单位字节中的程序头表的开始段头的开始段头表文件偏移量单位字节中的段头表标志此头的处理器特定标志位大小ref文件头大小单位字节程序头的大小。ers节目头表中每个条目的大小单位是节目头表中条目数的字节数,它被理解为有多少个段。区段标题表中每个条目的大小单位是区段标题表中条目数量的字节数。它被理解为节标题表中对应于节名称表的条目的索引信息[2][1]。有关文件类型,请参见第2.1节[2]。该节没有名称,但该节有一个名称,因此该节的名称需要单独保存,这对应于。shstrtab部分。
程序标题表
readelf -l命令可以查看程序头中的信息。应该指出的是。o '文件通常没有程序头。例子如下:
$ readelf-l/bin/lsElffiletypeisDYN(shared object file)entry point0x 5850 there are 9 program headers,startingatoffset 64 program headers:typeoffsetvirtaddrphysadrfilesizmesizmizflagsalignphdr0x 00000000000040000000000000000000004000000000000000000000000000000000000000001 f 80.0001 . interp 02 . interp . note . ABI-tag . note . GNU . build-id . GNU . hash . dyn sym . dynstr . GNU . version . GNU . version _ r . rela . dyn . rela . PLT . init . PLT . PLT . got . text . f ini . rodata . eh _ frame _ HDR . eh _ frame 03 . init _ array . fini _ array . data . rel . ro . dynamic . data . BSS 04 . dynamic 05 . note
节目头列出了所有片段的信息,含义如下:
该字段的类型意味着备注类型段,意味着如何解析该段的内容[1]偏移该段在文件中的位置单位ByteFileSiz该段在文件中的大小单位ByteVirtAddr该段在进程空间中的起始位置是虚拟地址MemSiz该段在进程空间中的大小单位是BytePhysAddr由于MMU的存在,该段在进程空间中的起始位置的物理地址是未知的。大多数时候,右R/W/E等于虚拟地址的Flags段分别指示读/写/可执行对齐大小对齐信息[3] [1]。类型的值和含义如下。
PHDR:这种类型的程序头,如果存在的话,表示它自己的程序头表在文件或内存中的位置和大小。文件中可能不存在这样的段。只有当程序头表覆盖的段只是整个程序的一部分时,这样的条目才会出现一次,而且这样的条目必须出现在其他可加载段的条目之前。
INTERP:这一段指向一个以“null”结尾的字符串,这是一个ELF解析器的路径。这种段类型只对可执行程序有意义,当它出现在共享目标文件中时,就是一个无意义的冗余项。它在一个ELF文件中最多只能出现一次,并且必须出现在其他可加载段的条目之前。
LOAD:这种类型表示程序头指向一个可加载的段。该段的内容将从文件复制到内存中。
动态:该类型表示该段表示动态连接的信息。
注意:这一段指向一个以“null”结尾的字符串,它包含一些附加信息。
[3].对于可加载段,其虚拟地址和文件地址的值至少应该与内存的页面大小对齐。该数据成员指示该段落的内容如何在内存和文件中对齐。如果值为0或1,则没有对齐要求;否则,该值应为正整数和2的幂。对该值取模后,虚拟地址和文件地址应该相等。
段到段的映射显示了一个段包含哪些段,通常侧重于代码段和数据段。下表只是一个典型的例子。实际上更复杂的代码段可能包含更多的部分。
代码段数据段. text . data . rodata . dynamic . hash . got . dyn sym . BSS . dynstr . PLT . rel . got 3 . 1 . 3节头表。
readelf -S命令可以查看节头中的信息。例子如下:
$ read elf-S/bin/lstherare 28 section headers,startingatoffset0x 203 A0:section headers:[Nr]NameTypeAddressOffsetSizeEntSizeFlagsLinkInfoAlign[0]null 00000000000000000000000000000000000000000000000000000000000000000[1]. interpprogbits 0000000000000000000.00000000000000001800000000000000000008 ax 008[14]. text progbits 000000000000003 e 9000000000000000124d 9000000000000000000000000 ax 0016.0000000000000003 c 8000000000000000000008 wa 0008[24]。KeytoFlags:W(write),A(alloc),X(execute),M(merge),S(strings),I(info),L(linkorder),O(extroprocessingrequired),G(group),T(TLS),C(compressed),x(unknown),o(OSspecific),E(exclude),l(large),p(processorspecific)
字段含义备注名称本节名称大小本节大小单位为字节。如果section类型为NOBITS,这个值可能仍然是非0,但是没有意义。该段的类型[1]EntSize如果该段是表类型,则意味着表项大小单位ByteAddress需要映射到虚拟内存。它以字节偏移量表示起始地址单元。本节第一个字节所在文件的偏移单位ByteFlags权限等属性信息,请参考第一章提到的《UnderstandingELF.pdf》信息,参考第一章[1]提到的《UnderstandingELF.pdf》 Align大小对齐信息。截面有以下几种类型。
NULL:此部分标题是无效的(无效的)部分标题。
PROGBITS:该部分包含的信息由程序定义,该部分的格式和含义由程序决定。
SYMTAB: strip可以杀死符号表的所有调试信息,不影响操作。
STRTAB:这个部分是一个字符串表。
RELA:这部分是搬迁部分。
HASH/GNU_HASH:这个部分包含一个散列表。
动态:此部分包含动态连接信息。
注意:本节包含以某种方式标记本文档的信息。
NOBITS:该节内容为空,该节不占用实际空间。
REL:这部分是搬迁部分。
DYNSYM:动态链接符号表信息
符号表(。symtab部分)
符号表不属于文件结构,但是因为很常见,所以也单独提一下。
以一个简单的代码为例:
#includeintmain(intargc,char * * argv){ printf(' hello world ');return0}
编译好hello的bin后,我们就可以查看了。symtab和。通过下面的命令。
readelf-shelloSymboltable。“symtab”包含67个条目:Num:ValueSizeTypeBindVisNdxName.51:00000000000000000 funcglobaldefaultundprintf @ @ GLIBC _ 2 . 2 . 5.61:00000000000006 b039 func global default 14 main.
的。数据段主要存储初始化变量,通常是指一个程序中用来存储初始化全局变量和初始化静态变量的内存区域。
因为它已经被初始化,所以初始化的值必须记录在文件中,所以。数据段需要占用ROM空间,而。数据节与。rodata段,所以在运行之前需要将其复制到内存中的可写段。
Objdump对于查看数据类型的部分非常方便。让我们继续使用objdump。
//核心变量:int g _ int 2=1 $ objdump-d-j.data测试:文件格式elf 64-x86-64反汇编。数据:000000000201000:0000000000201008: 201008: 088.
代码int g_int2=1将一个非零初始值赋给变量,因此它不适用于。bss段,所以它被放在。数据段。还可以从dump的结果中看到变量g_int2和值0x1。
3.2.5 .罗达塔
的数据。rodata节会占用ROM空间,(大部分时间)运行时会复制到内存中。
Rodata存储只读数据,如字符串常量、全局常量变量和#define定义的常量。例如:char *p='123456 '123456 '123456的字符串存储在rodata节中。还有一个有趣的例子:
printf('thefuncis%s '_ _ func _ _);
func is %s '格式化打印字符串和__func__引用的函数名也是字符串常量,保存在。罗达塔区。
要查看字符串类型的部分,使用readelf要方便得多,如下面的代码:
intmain(intargc,char * * argv){ printf(' hello world ');printf('thisfuncis%s '_ _ func _ _);}
执行以下命令打印字符串数据。如果需要查看二进制数据,比如整数值,只需将-p改为-x.
$ readelf-p . rodatamainestringdumpofsection。rodata '[4]hello world[10]this func is % s[20]main
这里有一个小技巧,相同的字符串只会保留一个副本,所以下面的两个代码有相同的效果,只是保存了。rodata数据不同。适当的优化可以节省ROM空间。自己想想:)。
//code 1printf ('func% s: valis% d '_ _ func _ _,val);printf('func%s:openfailed '_ _ func _ _);//code 2 printf(' func main:valis % d 'val);printf(“func main:open failed”);
3.2.6 .symtab和。dynsym
都是。symtab和。dynsym是符号表,比如函数和变量,甚至。dynsym是。symtab,但是它们的功能不同。symtab俗称符号表,记录所有符号,无论是自定义变量、函数,还是需要动态库提供的未定义符号。它必须存在于”。o"链接,但链接到bin后,节省空间可以删除,如strip。去掉符号表后,程序可以定位到函数地址,但是函数名已经不知道了,这就是为什么有时候程序崩溃时打印出来的堆栈里只有地址,有时候有具体的函数名。
Dynsym是动态链接所需的符号列表,它可以包括用于外部调用的符号和用于外部实现的符号。'中需要Dynsym。so’或动态链接bin。symtab和。dynsym占用ROM空间。dynsym将被加载到内存中,但是。symtab不会。我们也可以剥离符号表,然后判断符号表是否已经被文件剥离,例如:
$ gcc test . c-otest $ filetesttest:elf 64-bitLSBsharedobject,not stripped $ strip test test:elf 64-bitLSBsharedobject,剥离
4.用工具分析ELF
在上面的例子中,readelf和objdump经常用于读取各种标题表和节内容。除了这两个,还有一个nm工具,它们的功能非常相似。吃多了也不能嚼太烂。下面我们来解释一下如何用以readelf为主,objdump为辅的工具来分析ELF。
上面有例子,本文主要做一个总结记录,以备参考。对于该字段的具体含义,可以参见第三章中的具体章节分析。
获取ELF文件头
readelf-h
获取程序头表(段表)
readelf-l
获取部分表(获取有哪些部分)
readelf-S
获取符号表(列出函数和变量的符号)
#获取所有符号表(包括。symtab和。获取动态符号表。
获取章节内容
#分段打印字符串,常用于字符串类型的分段,如。rodata section readelf-p#以二进制格式打印节,对于非字符串类型的节,如。bss和。数据段readelf-x#汇编并打印二进制代码objdump-d-j d-j。
5.ELF加载到磁盘上的内存中。
在文章《linux 目标文件(*.o) bss,data,text,rodata,堆,栈》(https://cloud . Tencent . com/developer/article/1835295)中,有一张图展示了虚拟内存的空间分布,如下:
我们关注最下面的三个音程,其实对应的是ELF的内容。使用新的图表来显示它们之间的关系:
可执行文件的代码段和数据段将被复制到内存中。BSS段虽然没有数据,但是也记录了哪些变量会被复制到内存的可写区,而动态库是从map到m map区。
审计刘清