dyp*_*dyp 5 linux glibc elf dynamic-loading
我想了解动态加载程序如何为 ELF 段创建映射的详细信息。
考虑一个与 GNU ld 链接的小型共享库。程序头是:
类型偏移 VirtAddr PhysAddr FileSiz MemSiz Flg Align 加载 0x000000 0x0000000000000000 0x0000000000000000 0x00095c 0x00095c RE 0x200000 负载 0x000df8 0x0000000000200df8 0x0000000000200df8 0x000250 0x000258 RW 0x200000 动态 0x000e08 0x0000000000200e08 0x0000000000200e08 0x0001d0 0x0001d0 RW 0x8 GNU_EH_FRAME 0x000890 0x0000000000000890 0x0000000000000890 0x00002c 0x00002c R 0x4 GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10 GNU_RELRO 0x000df8 0x0000000000200df8 0x0000000000200df8 0x000208 0x000208 R 0x1
这个共享对象可以打印它在 ( /proc/self/maps) 中加载的进程的映射,片段:
7fd1f057b000-7fd1f057c000 r-xp 00000000 fe:00 12090538 /path/libmy.so 7fd1f057c000-7fd1f077b000 ---p 00001000 fe:00 12090538 /path/libmy.so 7fd1f077b000-7fd1f077c000 r--p 00000000 fe:00 12090538 /path/libmy.so 7fd1f077c000-7fd1f077d000 rw-p 00001000 fe:00 12090538 /path/libmy.so
如果我打印可变全局变量的地址,则打印的地址在第四个映射中。
解构映射:
基址 == 7fd1f057b000 映射 1:虚拟偏移 0x000000,大小 0x001000,RX,从文件偏移 0x0000 映射 2:虚拟偏移 0x001000,大小 0x1ff000,---,从文件偏移 0x1000 映射 3:虚拟偏移 0x200000,大小 0x001000,R--,从文件偏移 0x0000 映射 4:虚拟偏移 0x201000,大小 0x001000,RW-,从文件偏移 0x1000
我目前的理解:
广告 1.
广告 2. 链接器不能只是在确切的虚拟地址 7fd1f077b000 上请求映射,从而创建一个洞吗?为什么要麻烦这个映射?
$ readelf -d libmy.so
Dynamic section at offset 0xe08 contains 25 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x5a8
0x000000000000000d (FINI) 0x848
0x0000000000000019 (INIT_ARRAY) 0x200df8
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x200e00
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x0000000000000004 (HASH) 0x190
0x000000006ffffef5 (GNU_HASH) 0x1e0
0x0000000000000005 (STRTAB) 0x380
0x0000000000000006 (SYMTAB) 0x218
0x000000000000000a (STRSZ) 172 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000003 (PLTGOT) 0x201000
0x0000000000000002 (PLTRELSZ) 120 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x530
0x0000000000000007 (RELA) 0x470
0x0000000000000008 (RELASZ) 192 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x450
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x42c
0x000000006ffffff9 (RELACOUNT) 3
0x0000000000000000 (NULL) 0x0
Run Code Online (Sandbox Code Playgroud)
$ readelf -Wl libmy.so
Elf file type is DYN (Shared object file)
Entry point 0x630
There are 6 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x00095c 0x00095c R E 0x200000
LOAD 0x000df8 0x0000000000200df8 0x0000000000200df8 0x000250 0x000258 RW 0x200000
DYNAMIC 0x000e08 0x0000000000200e08 0x0000000000200e08 0x0001d0 0x0001d0 RW 0x8
GNU_EH_FRAME 0x000890 0x0000000000000890 0x0000000000000890 0x00002c 0x00002c R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x000df8 0x0000000000200df8 0x0000000000200df8 0x000208 0x000208 R 0x1
Section to Segment mapping:
Segment Sections...
00 .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
01 .init_array .fini_array .dynamic .got .got.plt .data .bss
02 .dynamic
03 .eh_frame_hdr
04
05 .init_array .fini_array .dynamic .got
Run Code Online (Sandbox Code Playgroud)
这四个映射各自的目的是什么?
为什么动态加载器会创建一个没有权限的“填充”映射?
为了了解最终状态,我们需要跟踪动态链接器所采取的操作。它的“指示”是什么?它需要将ET_DYN对象加载到内存中的随机地址(由操作系统选择)。映射必须满足这些“命令”(我省略了 PhysAddr,因为它与 VirtAddr 相同):
Offset VirtAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000000000 0x00095c 0x00095c R E 0x200000
LOAD 0x000df8 0x0000000000200df8 0x000250 0x000258 RW 0x200000
Run Code Online (Sandbox Code Playgroud)
现在,对于所有 ELF 二进制文件来说,第一件重要的事情是,为了正确工作,两个LOAD段必须按相同的“基本偏移量”重新定位。例如,它不适用于处的mmap第一段和 处的第二段。LOAD0x10000000x2000000+0x200df8 == 0x2200df8
因此,动态链接器(我将rtld对其使用收缩)必须将两个mmap段作为单个段执行(否则,无法保证第二个映射不会干扰已映射到那里的其他内容)。所以它执行:mmap
size_t len = 0x200df8 + 0x258;
void *base = mmap(0, len, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0);
Run Code Online (Sandbox Code Playgroud)
在您的特定情况下,base == 0x7fd1f057b000,我们有一个映射,涵盖.text和.data:
7fd1f057b000-7fd1f077d000 r-xp 0 libmy.so
Run Code Online (Sandbox Code Playgroud)
但还rtld远未完成。现在它必须将(第二个)mmap段覆盖到正确的位置并具有所需的权限(省略错误检查):.dataLOAD
mmap(base + 0x200000, 0xdf8 + 0x258, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
Run Code Online (Sandbox Code Playgroud)
我们的映射现在看起来像这样:
7fd1f057b000-7fd1f077b000 r-xp 0 libmy.so
7fd1f077b000-7fd1f077d000 rw-p 0 libmy.so
Run Code Online (Sandbox Code Playgroud)
接下来,我们的文件非常短(小于 4K),当我们[0x7fd1f057c000, 0x7fd1f077b000)更SIGBUS喜欢简单的SIGSEGV.
我们可以munmap这个区域,但有缺点(其他一些小型库可能会落在那个几乎 2MiB 的区域,并混淆rtld寻找最近基础映射的其他部分)。相反,rtld以禁止访问的方式保护该区域,同时保持映射完好无损:
mprotect(0x7fd1f057c000, 0x1ff000, PROT_NONE);
Run Code Online (Sandbox Code Playgroud)
现在我们的内存映射看起来几乎就像您观察到的最终结果:
7fd1f057b000-7fd1f077b000 r-xp 0 libmy.so
7fd1f057c000-7fd1f077b000 ---p 0 libmy.so
7fd1f077b000-7fd1f077d000 rw-p 0 libmy.so
Run Code Online (Sandbox Code Playgroud)
但还有一件事要做rtld:您的对象请求(凭借拥有GNU_RELRO 段)其可写数据的一部分在重定位后受到保护以防止写入。因此rtld执行重定位,然后执行最后的mprotect:
mprotect(base + 0x200000, 0xdf8 + 0x208, PROT_READ);
Run Code Online (Sandbox Code Playgroud)
这会产生最终的内存映射(与您观察到的完全匹配):
7fd1f057b000-7fd1f077b000 r-xp 0 libmy.so
7fd1f057c000-7fd1f077b000 ---p 0 libmy.so
7fd1f077b000-7fd1f077c000 r--p 0 libmy.so
7fd1f077c000-7fd1f077d000 rw-p 0 libmy.so
Run Code Online (Sandbox Code Playgroud)
我在查找有关 GNU_RELRO 的文档时遇到一些问题。
这里有一个很好的讨论。
我猜它的 VirtAddr 和 FileSize 指定了哪些部分应该是只读的?
正确,但它是 the MemSize(但它应该始终匹配FileSize)。
那么节表没有被使用?
动态链接期间从不使用节表,它可以在删除了节表的完全剥离的二进制文件上工作。节表保留在二进制文件中(默认情况下)只是为了帮助调试。
| 归档时间: |
|
| 查看次数: |
300 次 |
| 最近记录: |