elf文件格式_写一个工具来了解ELF文件(二)

转载请注明出处。 https:// rhirufxmbcyj.gitlab.io

上一篇中讲到了ELF文件的基本介绍、ELF中定义的数据类型以及ELF文件的ELF Header结构

接下来就按照官方给出的视图(链接视图和执行视图)来介绍program header

program header中存放的是系统加载可执行程序所需要的所有信息,包括解释器路径、需要映射到内存中的段、动态链接段等

多个program header组成了program header table,该表起始位置、每个表项的大小和表项的个数。(program header组成的表,表项当然是每一个program header)

所以,解析到ELF header后,就可以根据ELF header中的内容拿到program header table接着解析了

program header table的位置一般来说紧跟着ELF header后,但是也有可能被修改(暂且不提)

Program Header

program header的结构体描述:

bfd566cd0aecefd0e6cf5dbf1499e9f1.png

从上图可以看出,32位的program header结构和64位的还是有一些区别的,判断的时候就需要注意,仅仅是一个字段的位置变了,由于每个字段占用的size可能是不同的,所以两者就会有较大的差异了

  • p_type:用于描述该段的类型或解释该段的作用
  • p_offset:该段(数据)在文件中的位置
  • p_vaddr:该段在内存中的位置,也就是VA,如果是DYN类型的文件,这里就是RVA
  • p_paddr:官方的解释是在物理地址关联的操作系统中,该字段是保留给物理地址的。由于 System V忽略了应用程序的物理地址,这个字段在可执行文件与共享文件中是未指定的。貌似意思就是不用管,但是看到基本上所有的gcc编译出的文件这个字段和p_vaddr的数据相同
  • p_filesz:该段在文件中的大小
  • p_memsz:该段在内存中的大小
  • p_flags:段的标识,包括读、写、执行(PF_R、PF_W、PF_X)
  • p_align:对齐粒度,且该值为0或1时表示不需要对齐。

这个结构体相对来说较为简单,字段也易懂,这里多介绍一下p_type

6c028d1a4a32877f6e1a5991ae84198f.png
  • PT_NULL:代表这个program header没有用到
  • PT_LOAD:这个类型的program header 也就是常说的load segment,是需要加载到内存中的段,该段的file size不能比mem size大,而mem size多出file size的则初始化为0
  • PT_DYNAMIC:代表这个段是存放动态链接信息的段
  • PT_INTERP:该段存放的是解释器路径,是一个字符串。在程序执行期间,系统首先根据PT_INTERP拿到解释器的路径,然后创建解释器的进程映像,则程序启动时,控制权交给了解释器,解释器负责进行环境初始化工作(例如加载依赖的动态库),然后才会把控制权交给实际执行的程序进程。动态库被加载也需要解释器,并且解释器负责解决有可能出现的加载地址冲突的现象。
  • PT_NOTE:辅助信息,没有关注过
  • PT_SHLIB:保留
  • PT_PHDR:program header table 本身
  • PT_TLS:线程局部存储段,没有深入研究....

PC上常用的也就如上这些了,后边的一些定义貌似和特定处理器有关

Dynamic Program Header

Dynamic Program Header(Dynamic Segment)也是属于Program Header,为什么要单独拿出来说呢

因为这个段太特殊了,程序执行离不开他,简单来说,程序启动后,文件中的数据映射到内存,也就是PT_LOAD这个类型的段加载到了内存中去了,ELF header中有了入口点的地址,但是只有这些程序是不可能运行成功的

依赖的动态库还没有加载,重定位还没有处理,执行前的初始化代码还没有执行,如果是动态库,动态库是有导出符号的,但是连符号表在内存中哪个位置都不知道,也就无法导出 等等.....

所以这个就单独拿出来说一下

10e744b149318777f90678894e18da19.png

结构很简单,第一个字段是类型,第二个字段是联合体,说明这个字段有可能是个整型的value,有可能是个address。

类型有很多,很多我也没遇到过,挑些PC上常见的说

  • DT_NULL:这个是用来当结束符的,当遇到DT_NULL类型的项以后,就不继续往下解析
  • DT_NEEDED:依赖的动态库,值就是一个整型数值,是字符串表中的索引,根据这个索引就可以在字符串表对应的位置找到依赖动态库的名称
  • DT_PLTRELSZ:PLT类型重定位的大小(PLT类型重定位是啥,以后介绍)
  • DT_PLTGOT:.got.plt节的地址(.got.plt节是干啥的,以后介绍)
  • DT_HASH:符号哈希表的地址
  • DT_STRTAB:动态字符串表的地址,即.dynstr节在内存中的地址
  • DT_SYMTAB:动态符号表的地址,.dynsym
  • DT_RELA:RELA类型的重定位地址
  • DT_RELASZ:RELA类型重定位总大小
  • DT_RELAENT:RELA类型一个重定位的大小
  • DT_STRSZ:动态字符串表的大小
  • DT_SYMENT:动态符号表一个符号的大小
  • DT_INIT:初始化函数的地址,先于main执行(参考此处了解linux编程初始化函数与终止函数)
  • DT_FINI:终止函数的地址,main后执行
  • DT_SONAME:动态库专属,指明该动态库的名字
  • DT_RPATH:动态库专属,rpath,动态库搜索路径,linux程序员应该很清楚这个东西
  • DT_REL:同上DT_RELA,不过是结构体有差异
  • DT_RELSZ:REL类型重定位总大小
  • DT_RELENT:一个REL类型重定位的大小
  • DT_PLTREL:指明PLT重定位的类型,这一项的value就高级了,value的值是DT_REL(17)或DT_RELA(7)宏的值。
  • DT_TEXTREL:说是重定位可能会改变.text节,没遇到过
  • DT_JMPREL:PLT重定位的地址
  • DT_GNU_HASH:GNU类型的hash表
  • DT_VERSYM:.gnu.version节的地址
  • DT_VERNEED:.gnu.version_r节的地址
  • DT_VERNEEDNUM:”Number of needed versions“(暂时不了解)
  • DT_INIT_ARRAY 、DT_FINI_ARRAY、DT_INIT_ARRAYSZ、DT_FINI_ARRAYSZ:elf.h中的解释是”Array with addresses of init function“,猜测可能是编写程序的时候可能会把多个函数设为初始化函数,那这多个函数就保存到这个array里。(不一定对,抽时间验证)

根据这些文件格式的介绍,可以接着上次的工程向下继续解析了。


经过验证,DT_INIT_ARRAY 、DT_FINI_ARRAY、DT_INIT_ARRAYSZ、DT_FINI_ARRAYSZ和上次猜想的一样。

验证方法如下:

  • linux下gcc编译代码
#include <stdio.h>
void __attribute__((constructor)) test1()
{
	printf("hello test1n");
}
void __attribute__((constructor)) test2()
{
	printf("hello test2n");
}
int main()
{
	printf("hello mainn");
	return 0;
}

写了test1和test2两个初始化函数(没有在main中调用,但是先于main执行)

  • 将编译好的文件a.out使用readelf查看Dynamic Program Header。

5581f57fc80901b6019512132314178e.png

可以看到INIT_ARRAY的addr是0x600e00,换成文件偏移就是0xe00,大小是24个字节。

  • 使用十六进制查看工具看0xe00是什么

c992da5eead2da993bf2d24de72725cc.png

程序是64位,这里边的24个字节正好是三个8字节,也就是三个地址,分别是0x400500,0x400526和0x400537

  • 然后使用IDA看看这些分别是什么东西

5ea148b27f08aee06becb4568ce83a68.png

27ce9a8b7531c159d5e45ef08a8d6a7d.png
  • 挖一下第一个0x400500是什么鬼

可以看出后两个地址就是我们声明的初始化函数的地址,第一个是什么呢,名字那么长,怎么看怎么像系统函数,用readelf查看符号来看看

2fc96ac3b95641a03a320e5d286a0793.png

符号名少了个entry,估计是readelf的bug

看地址__frame_dummy_init_array_entry和__init_array_start的一样,看名字猜的话应该是init的起始函数

从stackoverflow上查到的答案,这些标记该.init_array节的结束和开始,其中包含指向所有程序级初始值设定项的指针,也就是为了方便系统找到我们写的初始化函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值