在做这个项目的过程中也参考了别人写的解析ELF文件解析程序,得知滕启明的ELF文件格式分析距今已经17年了,内容相较于现在使用的规范不免少了一些内容。当年ELF文件规范里的保留位现在都用上了不少,如果你用readelf命令读取的话,就会会发现多了好一些新名词,而这些是PDF里没有的。所以实践出真知!
由于这里我们需要用到头文件"elf.h",对这个头文件不了解的可以看以下这个网址:
elf.hsites.uclouvain.be从elf.h的总体结构加上之前讲过的ELF文件自身的结构,我们可以发现这个头文件把数据结构都帮我们封装好了,接下来只需要拿来用就好了。
在这个过程中准确定位是非常重要的。复习一下比较重要的几个c语言相关函数:
int fread(void *buffer,int size,int count,FILE *fp);
fread()──从fp所指向文件的当前位置开始,一次读入size个字节,重复count次,并将读入的数据存放到从buffer开始的内存中; buffer是存放读入数据的起始地址(即存放何处)。
int fwrite(void *buffer,int size,int count,FILE *fp);
fwrite()──从buffer开始,一次输出size个字节,重复count次, 并将输出的数据存放到fp所指向的文件中。buffer是要输出数据在 内存中的起始地址(即从何处开始输出)。
这两个函数一般用于二进制文件的处理。对应上了我们这里的ELF二进制文件。
int fseek(FILE *stream, long offset, int fromwhere);
第一个参数file指针 第二个参数移动的偏移量 第三个参数移动到哪里 分别用3个宏
SEEK_SET 既0 文件开头
SEEK_CUR 既1 文件当前位置
SEEK_END 既2 文件结尾
但不推荐用数字 最好用宏
简言之:
fseek(fp,100L,SEEK_SET);把fp指针移动到离文件开头100字节处;
fseek(fp,100L,SEEK_CUR);把fp指针移动到离文件当前位置100字节处;
fseek(fp,100L,SEEK_END);把fp指针退回到离文件结尾100字节处。
此函数常用来计算流的长度:
int filesize = fseek( fp, 0, SEEK_END );
fseek( fp, 0, SEEK_SET );
int rewind(FILE *stream);
将文件指针重新指向一个流的开头。
FILE *fs=fopen("C:1.txt","r");//创建文件流
long length=0;//声明文件长度
fseek(fs,0,SEEK_END);//将文件内部指针放到文件最后面
length=ftell(fs);//读取文件指针的位置,得到文件字符的个数
rewind(fs);//将文件指针重置到文件最前面
然后介绍编程思路:
(1)文件头部在ELF文件最开始的位置,可用Elf64_Ehdr数据结构概括。由于-file-headers就是为了得到ELF header的信息,所以这个部分就可以解决。
(2)在ELF 头部中,e_shoff 成员给出从文件头到节区头部表格的偏移字节数;e_shnum
给出表格中条目数目;e_shentsize 给出每个项目的字节数。从这些信息中可以确切地定
位节区的具体位置、长度。
(3)由于节区的位置与长度都可以确定,就可以讨论每个节区的内容。每个节区头部的数据结构都可以概括为Elf64_Shdr。其中sh_name给出节区名称。这样-section-headers的部分就可以解决,可以显示出所有的section headers.同时-section也可以通过名称判定确定。在此基础之上-full-contents指定的section的内容也可以实现完全显示。
(4)-all-headers涉及到很多内容。首先是Program header。程序头部的数据结构概括为Elf64_phdr。可执行目标文件在ELF头部的e_phentsize和e_phnum成员中给出其自身程序头部的大小。e_phoff为程序头部表格(Program Header Table)的偏移量(按字节计算)。e_phentsize为程序头部表格的表项大小。e_phnum为程序头部表格的表项数目。同理section header的处理,这里可以完成program header的处理。对于各个节区而言,节区头部的 sh_name 成员包含其对应的节区头部字符串表节区的索引,此节区由 ELF 头的 e_shstrndx 成员给出。符号表的打印放到下一部分讨论。
(5)符号表的数据类型概括为Elf64_sym。在section header中,sh_type为节区的内容和语义进行分类。其中SHT_SYMTAB代表此节区包含一个符号表。在滕启明的文件中说道目前目标文件对每种类型的节区都只能包含一个,不过这个限制将来可能发生变化。这样经过判定,可以使得符号表的提取成为可能。
(6)重定向信息的数据结构概括为。在section header中,sh_type若为SHT_RELA或者SHT_REL,那么代表此节区包含重定位表项。目标文件可能拥有多个重定位节区。这样经过判定,重定向信息的提取就完成了。
(7)接下来探讨可执行文件里的调试信息。这一部分PDF并没有讲得很清楚,所以我又查了一些资料。DWARF是ELF最常用的调试信息格式。它不一定与ELF相关,但两者是一起发展的,在开发中一起使用也非常好。DWARF 中的调试信息被放在一个叫作 .debug_info 的段中。该格式允许编译器告诉调试器程序源代码如何与将执行的二进制文件相互关系。一部分资料图片形式展示如下:
![36fd0d2ed6e913d5388ecfb688c79ff0.png](https://i-blog.csdnimg.cn/blog_migrate/bc6fd020b350cabd901cbda1355a0dde.jpeg)
![e3c84fdd7a6456231c03b26936ed35d7.png](https://i-blog.csdnimg.cn/blog_migrate/6a55076884550ce8aad902bdc60fd79c.jpeg)
调试信息被包含在某几个节中,如果是用dwarf2格式编译的,这些节的名字一般是以.debug开头,如.debug_info,.debug_line,.debug_frame等,如果是用dwarf1格式编译的,这些节的名字一般是.debug,.line等。现在的编译器默认大多数是dwarf2格式编译,当然可以通过gcc的编译选项改变。DWARF
是一种复杂的格式,它吸收了过去许多年各种不同的架构与操作系统的格式的经验。正是因为它解决了一个在任何平台与 ABI (应用二进制接口)上为任意高级语言产生调试信息这样棘手的难题,它也必须很复杂。想要透彻的讲解DWARF
仅仅是通过这单薄的一篇文章是远远不够的。所以感兴趣的话要自己下去看。
那么现在就明确了.debug_
开头的节就是dwarf调试信息的节。
(8)我在处理-g时输入命令行,下面的提示信息表示它打印出来的是.eh_frame section的内容。那么这一节到底是什么东西?后来查了一下,说法是.eh_frame 的格式与 .debug_frame 是很相似的(不完全相同),属于DWARF标准中的一部分。所有由 GCC 编译生成的需要支持异常处理的程序都包含了 DWARF 格式的数据与字节码,这些数据与字节码的主要作用有两个:
1)描述函数调用栈的结构(layout)
2)异常发生后,指导 unwinder 怎么进行 unwind。
DWARF 字节码功能很强大,它是图灵完备的,这意味着仅仅通过 DWARF 就可以做几乎任何事情(therotically)。但是从数据的组织上来看,DWARF 实在略显复杂晦涩,因此很少有人愿意去碰,本文也只是简单介绍其中与异常处理相关的东西。本质上来说,eh_frame 像是一张表,它用于描述怎样根据程序中某一条指令来设置相应的寄存器,从而返回到当前函数的调用函数中去,它的作用可以用如下表格来形象地描述。
![eed44e02d7b4932515964a4bd0d16dfe.png](https://i-blog.csdnimg.cn/blog_migrate/aab1f04ef882e79c16db780e54cd9298.png)
代码和运行结果就放到下一节去呈现啦。