ELF文件——映射

前言

主要描述的是动态库的映射。

动态库映射

动态库加载时会根据动态库的program header table进行内存映射,主要是对具有LOAD属性的segment进行映射,以一例说明:
通过readelf得到的其program header的内容为:

Elf file type is DYN (Shared object file)
Entry point 0x1ab19
There are 10 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align  
  EXIDX          0x0dcf90 0x000dcf90 0x000dcf90 0x04a30 0x04a30 R   0x4    
  PHDR           0x000034 0x00000034 0x00000034 0x00140 0x00140 R   0x4    
  INTERP         0x0dbcb8 0x000dbcb8 0x000dbcb8 0x00019 0x00019 R   0x4    
      [Requesting program interpreter: /lib/ld-linux-armhf.so.3]
  LOAD           0x000000 0x00000000 0x00000000 0xe19c4 0xe19c4 R E 0x10000
  LOAD           0x0e1b90 0x000f1b90 0x000f1b90 0x026d8 0x04b64 RW  0x10000
  DYNAMIC        0x0e2d44 0x000f2d44 0x000f2d44 0x000f8 0x000f8 RW  0x4
  NOTE           0x000174 0x00000174 0x00000174 0x00044 0x00044 R   0x4
  TLS            0x0e1b90 0x000f1b90 0x000f1b90 0x00008 0x00054 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x0e1b90 0x000f1b90 0x000f1b90 0x01470 0x01470 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00     .ARM.exidx
   01
   02     .interp
   03     .note.gnu.build-id .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_d .gnu.version_r .rel.dyn .rel.plt .plt 
.text __libc_freeres_fn .rodata .interp .ARM.extab .ARM.exidx .eh_frame
   04     .tdata .init_array .data.rel.ro .dynamic .got .data __libc_subfreeres __libc_IO_vtables __libc_atexit .bss __libc_freeres_ptrs     
   05     .dynamic
   06     .note.gnu.build-id .note.ABI-tag
   07     .tdata .tbss
   08
   09     .tdata .init_array .data.rel.ro .dynamic .got

由上可知,其具有两个需要映射的segment:

  LOAD           0x000000 0x00000000 0x00000000 0xe19c4 0xe19c4 R E 0x10000
  LOAD           0x0e1b90 0x000f1b90 0x000f1b90 0x026d8 0x04b64 RW  0x10000

但是通过cat /proc/pid/maps查看到其占有4个VM area:

f747c000-f755e000 r-xp 00000000 b3:13 915                                /lib/libc-2.31.so
f755e000-f756d000 ---p 000e2000 b3:13 915                                /lib/libc-2.31.so
f756d000-f756f000 r--p 000e1000 b3:13 915                                /lib/libc-2.31.so
f756f000-f7571000 rw-p 000e3000 b3:13 915                                /lib/libc-2.31.so
f7571000-f7573000 rw-p 00000000 00:00 0

根据属性判断可知,第一个f747c000-f755e000区间的vm area对应于动态库的代码段,即第一个需要load的segment。而第二个vm area,即f755e000-f756d000区间的来源是第一个load segment 和第二个load segment的虚拟地址之间形成的空洞:

第一个load的虚拟地址结尾(4k对齐向上取整):0xe2000 (0xe19c4得来)
第二个load的虚拟地址其实(4k对齐向下取整):0xf1000(0xf1b90的来)
两者之间的空洞为:0xf1000 - 0xe2000 = f756d000 - f755e000

该空洞内没有任何内容,可以认为没有内容。对于第二个load segment,其映射地址应该是:

f756d000 - f757000 (f756d000 + 0x026d8 4k对齐向上取整)

其中,映射地址的结尾按照file_size而不是mem_size计算,mem_size比file_size大的原因是包含bss段,而bss段不用映射,可以在此后发生读写操作的时候产生pagefault再建立映射关系。所以其实从maps中可以看到其后面还跟了一个rw-p的vm area。而在加载动态库时,动态库解释器还对GNU_RELRO segment进行了判断,该segment为只读区域,所以对第二个load segment又划分成了两段,其中一段为GNU_RELRO segment,其映射的vm area应该为:

f756f000-f7571000(mem_size为0x01470 4k对齐向上取整)

第二段即为第二个load segment剩余没有映射的区间,为:

0x026d8 - 0x01470 ,4k对齐向上取整为0x2000

所以才得到最终的四个vm area。解释器代码路径如果不清楚可以通过objdump解释器获取,对共享库进行映射主要集中在函数_dl_map_object_from_fd中。

此外,文件的映射是指将ELF文件load segment中的virtual_addr映射到进程的虚拟地址空间(并且还要page页对齐,向下取整),而load segment中的virtul_addr和offset并不匹配,此时如果已知一个符号在进程中的虚拟地址为p,假设该地址所属动态库在进程中的地址区间(可执行段,即text段所在的区间)为:

f747c000-f755e000 r-xp 00000000 b3:13 915                                /lib/lib_random.so

该段的加载地址为:

 LOAD           0x000000 0x00001100 0x00000000 0xe19c4 0xe19c4 R E 0x10000

那么该地址在ELF文件中的实际偏移为:

(p - 0xf747c000) + (0x00001100 & ~(PAGE_SIZE - 1))

即,要0xf747c000是在对load segment的virtual_addr进行page对齐,向下取整之后得到的地址值,在计算的时候需要将这部分地址空间加回来。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值