Linux 加载的ELF段的重叠映射
我想了解动态加载程序如何为ELF段创建映射的细节 考虑一个与GNULD链接的小型共享库。程序标题为: 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 如果打印可变全局变量的地址,则打印的地址位于第四个映射中Linux 加载的ELF段的重叠映射,linux,elf,glibc,dynamic-loading,Linux,Elf,Glibc,Dynamic Loading,我想了解动态加载程序如何为ELF段创建映射的细节 考虑一个与GNULD链接的小型共享库。程序标题为: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x00095c 0x00095c R E 0x200000 LOAD 0
解构映射: Base address == 7fd1f057b000 Mapping 1: virtual offset 0x000000, size 0x001000, R-X, from file offset 0x0000 Mapping 2: virtual offset 0x001000, size 0x1ff000, ---, from file offset 0x1000 Mapping 3: virtual offset 0x200000, size 0x001000, R--, from file offset 0x0000 Mapping 4: virtual offset 0x201000, size 0x001000, RW-, from file offset 0x1000
这四种映射的目的是什么?
为什么动态加载程序在没有权限的情况下创建“填充”映射 为了理解最终状态,我们需要跟踪动态链接器所采取的操作。它的“指示”是什么?它需要以随机地址(由操作系统选择)加载内存中的
ET_DYN
对象。映射必须满足这些“命令”(我省略了PhysAddr,因为它与VirtAddr相同):
现在,对于所有ELF二进制文件来说,第一件重要的事情是为了正确工作,两个LOAD
段必须通过相同的“基偏移”重新定位。例如,在0x1000000
处加载第一个mmap
段,在0x2000000+0x200df8==0x2200df8
处加载第二个
因此,动态链接器(我将使用它的rtld
压缩)必须将两个段的mmap
作为单个mmap
(否则,无法保证第二个映射不会干扰已经映射到那里的其他内容)。因此,它执行:
size_t len = 0x200df8 + 0x258;
void *base = mmap(0, len, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0);
在您的特定情况下,base==0x7fd1f057b000
,我们有一个映射,覆盖.text
和.data
:
7fd1f057b000-7fd1f077d000 r-xp 0 libmy.so
但是rtld
远未完成。现在,它必须在mmap
上加载段的.data
(第二个)将段加载到正确的位置并具有所需的权限(省略错误检查):
我们的映射现在如下所示:
7fd1f057b000-7fd1f077b000 r-xp 0 libmy.so
7fd1f077b000-7fd1f077d000 rw-p 0 libmy.so
其次,我们的文件非常短(小于4K),如果将地址保留在[0x7fd1f057c000,0x7fd1f077b000)
映射范围内,则当我们更喜欢简单的SIGSEGV
时,可能会产生SIGBUS
或其他令人困惑的错误
我们可以munmap
这个区域,但这是一个缺点(其他一些小型库可能会降落在这个几乎2MB的区域,并混淆了rtld
中寻找最近基映射的其他部分)。取而代之的是,rtld
保护该区域而不访问,同时保持映射完整:
mprotect(0x7fd1f057c000, 0x1ff000, PROT_NONE);
现在,我们的内存映射看起来几乎像您观察到的最终结果:
7fd1f057b000-7fd1f077b000 r-xp 0 libmy.so
7fd1f057c000-7fd1f077b000 ---p 0 libmy.so
7fd1f077b000-7fd1f077d000 rw-p 0 libmy.so
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
但是,rtld
还有一件事要做:您的对象请求(通过使用GNU RELRO
段)在重新定位后保护其部分可写数据不被写入。因此,rtld
执行重新定位,然后执行最终的mprotect
:
mprotect(base + 0x200000, 0xdf8 + 0x208, PROT_READ);
这将导致最终的内存映射(与您观察到的完全匹配):
我在查找GNU_RELRO的文档时遇到了一些问题
有一个很好的讨论
我猜它的VirtAddr和FileSize指定哪些部分应该是只读的
正确,除了它是MemSize
(但它应该始终匹配FileSize
)
所以不使用截面表
动态链接期间从不使用节表,它可以在删除节表的情况下处理完全剥离的二进制文件。节表保留在二进制文件中(默认情况下)只是为了帮助调试。有趣的是,内核映射动态加载程序而不使用“填充”映射。“文本”和“数据"动态加载程序映射的段。您的问题中有很多要解包的内容,而且答案很长,需要花费一些时间来编写。为了完整性,请编辑您的问题以添加来自readelf-d libmy的输出。因此
。我保证,它的输出将是相关的。哦,还请显示来自readelf-Wl libmy.s的完整输出o
。这也会相关。我在查找GNU_RELRO
上的文档时遇到一些问题。我猜它的VirtAddr和FileSize指定哪些部分应该是只读的?所以不使用section表?
7fd1f057b000-7fd1f077b000 r-xp 0 libmy.so
7fd1f077b000-7fd1f077d000 rw-p 0 libmy.so
mprotect(0x7fd1f057c000, 0x1ff000, PROT_NONE);
7fd1f057b000-7fd1f077b000 r-xp 0 libmy.so
7fd1f057c000-7fd1f077b000 ---p 0 libmy.so
7fd1f077b000-7fd1f077d000 rw-p 0 libmy.so
mprotect(base + 0x200000, 0xdf8 + 0x208, PROT_READ);
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