Linux – Purpose of Seemingly Unusable Memory Mappings

linuxprocessvirtual-memory

It seems that every process has private memory mappings that are neither readable nor writeable nor executable (whose flags are "—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

returns some in shared libraries and doing that for java (JVM) processes even returns a dozen of anonymous mappings with hundreds of megabytes.

Edit: If these mappings are placeholders, who will be using these kept places at which events and from which other activity are they protected – in other words: What is the wrong behavior that could happen if these placeholders were not there ?

2nd edit: Given these holes in shared libraries are in fact helpful for some purpose of the compiler and/or dynamic linker, there must be some other purpose for these holes in anonymous mappings visible in JVM processes. Sorted the anonymous mappings of a tomcat JVM process by size:

 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

Best Answer

Note that there are two memory regions of 2044KB with null permissions. As mentioned earlier, the ELF's 'execution view' is concerned with how to load an executable binary into memory. When ld.so brings in the dynamic libraries, it looks at the segments labelled as LOAD (look at "Program Headers" and "Section to Segment mapping" from readelf -a xxx.so command.) Usually there are two LOAD segments, and there is a "hole" between the two segments (look at the VirtAddr and MemSiz of these two segments), so ld.so will make this hole inaccessible deliberately: Look for the PROT_NONE symbol in _dl_map_object_from_fd in elf/dl-load.c

http://www.cs.stevens.edu/~jschauma/810/elf.html

It's also easy to see this happening as a mprotect call, using strace e.g. 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

There are mirrors of the glibc repo on github, so it wasn't hard to search for PROT_NONE...

/* This implementation assumes (as does the corresponding implementation of _dl_unmap_segments, in dl-unmap-segments.h) that shared objects are always laid out with all segments contiguous (or with gaps between them small enough that it's preferable to reserve all whole pages inside the gaps with PROT_NONE mappings rather than permitting other use of those parts of the address space). */

https://github.com/bminor/glibc/blob/73dfd088936b9237599e4ab737c7ae2ea7d710e1/elf/dl-map-segments.h#L21

/* _dl_map_segments ensures that any whole pages in gaps between segments are filled in with PROT_NONE mappings. So we can just unmap the whole range in one fell swoop. */

https://github.com/bminor/glibc/blob/73dfd088936b9237599e4ab737c7ae2ea7d710e1/elf/dl-unmap-segments.h#L25

Java

OpenJDK ... uses PROT_NONE mappings to reserve uncommitted address space (which is then committed with mprotect calls, as needed).

The natural assumption is that it wishes to have contiguous heap memory for some reason.

It uses PROT_NONE to reserve space, until it actually needs it. The original context of this comment is a discussion about Linux VM overcommit: using unaccessible mappings avoids requiring any commitment from the kernel (until the mapping is needed & made accessible), in case the kernel is configure for strict commit mode.

If you're wondering why it needs to make this reservation in advance in the context of the JVM, consider that native code linked in using JNI or equivalent might also be using mmap.

https://lwn.net/Articles/627728/

Related Question