似乎每个进程都有私有内存映射,这些映射既不可读,也不可写,也不可执行(其标志是“---p”):
grep -- --- /proc/self/maps
7f2bd9bf7000-7f2bd9df6000 ---p 001be000 fc:00 3733 /lib/x86_64-linux-gnu/libc-2.19.so
7f2bd9e04000-7f2bda003000 ---p 00003000 fc:00 3743 /lib/x86_64-linux-gnu/libdl-2.19.so
7f2bda042000-7f2bda241000 ---p 0003d000 fc:00 36067 /lib/x86_64-linux-gnu/libpcre.so.3.13.1
返回一些共享库中的内容,对于 java (JVM) 进程来说,甚至会返回几十个数百兆字节的匿名映射。
编辑:如果这些映射是占位符,谁将在哪些事件和其他活动受到保护时使用这些保留的位置 - 换句话说:如果这些占位符不存在,可能会发生什么错误行为?
第二次编辑:鉴于共享库中的这些漏洞实际上对编译器和/或动态链接器的某些目的有帮助,因此在 JVM 进程中可见的匿名映射中的这些漏洞肯定还有其他目的。按大小对 tomcat JVM 进程的匿名映射进行排序:
20 MB 00007FA0AAB52000-00007FA0AC000000 ---p 00000000 00:00 0
41 MB 00007FA0B1603000-00007FA0B4000000 ---p 00000000 00:00 0
50 MB 00007FA090D04000-00007FA094000000 ---p 00000000 00:00 0
53 MB 00007FA0F8A40000-00007FA0FC000000 ---p 00000000 00:00 0
61 MB 00007FA0C42C5000-00007FA0C8000000 ---p 00000000 00:00 0
61 MB 00007FA0CC29A000-00007FA0D0000000 ---p 00000000 00:00 0
61 MB 00007FA0D0293000-00007FA0D4000000 ---p 00000000 00:00 0
62 MB 00007FA0D814C000-00007FA0DC000000 ---p 00000000 00:00 0
62 MB 00007FA0E017E000-00007FA0E4000000 ---p 00000000 00:00 0
63 MB 00007FA0B803B000-00007FA0BC000000 ---p 00000000 00:00 0
63 MB 00007FA0BC021000-00007FA0C0000000 ---p 00000000 00:00 0
63 MB 00007FA0C0021000-00007FA0C4000000 ---p 00000000 00:00 0
63 MB 00007FA0D4075000-00007FA0D8000000 ---p 00000000 00:00 0
63 MB 00007FA0DC040000-00007FA0E0000000 ---p 00000000 00:00 0
63 MB 00007FA0E4067000-00007FA0E8000000 ---p 00000000 00:00 0
189 MB 00007FA0EC300000-00007FA0F8000000 ---p 00000000 00:00 0
1008 MB 0000000100FF5000-0000000140000000 ---p 00000000 00:00 0
答案1
请注意,有两个 2044KB 的内存区域具有空权限。如前所述,ELF 的“执行视图”关注如何将可执行二进制文件加载到内存中。当 ld.so 引入动态库时,它会查看标记为 LOAD 的段(查看 readelf -a xxx.so 命令中的“Program Headers”和“Section to Segment Mapping”)。通常有两个 LOAD 段,并且两个段之间有一个“空洞”(查看这两个段的 VirtAddr 和 MemSiz),因此 ld.so 会故意使这个空洞无法访问:在 elf/dl-load.c 中的 _dl_map_object_from_fd 中查找 PROT_NONE 符号
http://www.cs.stevens.edu/~jschauma/810/elf.html
mprotect
使用 strace eg也可以很容易地将这种情况视为调用发生strace -f grep -- . /proc/self/maps 2>&1 |less
。
open("/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300\25\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=471728, ...}) = 0
mmap(NULL, 2564360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0e3ad2a000
mprotect(0x7f0e3ad9c000, 2093056, PROT_NONE) = 0
mmap(0x7f0e3af9b000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x71000) = 0x7f0e3af9b000
close(3) = 0
github上有glibc repo的镜像,所以搜索PROT_NONE并不难...
/* 此实现假设(与 dl-unmap-segments.h 中 _dl_unmap_segments 的相应实现一样)共享对象始终布置为所有段都是连续的(或者它们之间的间隙足够小,最好保留所有整个页面)在 PROT_NONE 映射的间隙内,而不是允许对地址空间的这些部分进行其他使用)。 */
/* _dl_map_segments 确保段之间间隙中的任何整个页面都用 PROT_NONE 映射填充。所以我们可以一举取消整个范围的映射。 */
爪哇
OpenJDK ...使用 PROT_NONE 映射来保留未提交的地址空间(然后根据需要通过 mprotect 调用进行提交)。
自然的假设是,由于某种原因,它希望拥有连续的堆内存。
它使用 PROT_NONE 来保留空间,直到它真正需要为止。此评论的原始上下文是关于 Linux VM 过度使用的讨论:使用不可访问的映射可以避免需要内核的任何承诺(直到需要映射并使其可访问),以防内核配置为严格提交模式。
如果您想知道为什么需要在 JVM 上下文中提前进行此保留,请考虑使用链接的本机代码JNI或等效的也可能使用 mmap。