On this page

Process Memory Layout

Coverage: Interpreting /proc/pid/maps → stack/heap/mmap/vDSO/vvar/vsyscall → segments (text/data/bss) → Memory layout and ASLR → mmap mapping types Applicable to: Linux x86-64/ARM64 user space

Overview

Each Linux process sees a 48-bit (or 57-bit) virtual address space. Understanding the layout of this space is crucial for debugging out-of-bounds memory access, analyzing core dumps, and optimizing memory usage. /proc/pid/maps is the "map" of this layout.

Typical 64-bit Layout

64-bit Virtual Address Space Layout (48-bit effective, 128TB user + 128TB kernel) Low High 0x0000_0000_0000_0000 NULL page (unmapped, catches NULL dereferences) 0x0000_0000_0040_0000 PIE Binary Segments .text(R+X) · .rodata · .plt/.got · .data · .bss 0x0000_0000_0060_0000 heap (brk/sbrk) ↑ Grows upward ... Large unmapped gap ... 0x0000_7f00_0000_0000 mmap Region (mmap base) Shared libraries (.so) · Anonymous mappings (large malloc/thread stacks) · File mmap 0x0000_7fff_ffff_ffff [stack] Main thread stack ↓ Grows downward ≈0x7fff_ffff_ffff [vvar] vDSO data page (kernel timekeeper, read-only) [vdso] vDSO code page (clock_gettime, etc., avoids syscalls) [vsyscall] Deprecated fast-call page (fixed address, security risk, default emulate) 0xffff_ffff_ffff_ffff Kernel space (inaccessible from user space) The heap grows upward from the end of the binary segments, while the mmap region (including the stack) grows downward from high addresses— They move toward each other, with a huge unmapped gap in between serving as a safety buffer.

/proc/pid/maps Detailed Explanation

$ cat /proc/self/maps
# Format: address perms offset dev inode pathname

# === Binary (PIE) ===
55a8e3c00000-55a8e3c01000 r--p 00000000 08:02 1234567 /bin/sleep
55a8e3c01000-55a8e3c03000 r-xp 00001000 08:02 1234567 /bin/sleep  ← .text (R+X)
55a8e3c03000-55a8e3c04000 r--p 00003000 08:02 1234567 /bin/sleep  ← .rodata
55a8e3c04000-55a8e3c05000 r--p 00003000 08:02 1234567 /bin/sleep
55a8e3c05000-55a8e3c06000 rw-p 00004000 08:02 1234567 /bin/sleep  ← .data+.bss

# === Heap ===
55a8e4c00000-55a8e4c21000 rw-p 00000000 00:00 0       [heap]

# === Shared libraries ===
7f1234000000-7f1234001000 r--p 00000000 08:02 9999 /lib64/ld-linux-x86-64.so.2
7f1234001000-7f123402a000 r-xp 00001000 08:02 9999 /lib64/ld-linux-x86-64.so.2
7f123402a000-7f1234034000 r--p 0002a000 08:02 9999 /lib64/ld-linux-x86-64.so.2
7f1234035000-7f1234037000 rw-p 00034000 08:02 9999 /lib64/ld-linux-x86-64.so.2

# === Thread stack (anonymous mmap) ===
7f1233e00000-7f1234000000 rw-p 00000000 00:00 0       ← Thread stack

# === Stack ===
7ffc12345000-7ffc12366000 rw-p 00000000 00:00 0       [stack]
7ffc123de000-7ffc123e2000 r--p 00000000 00:00 0       [vvar]
7ffc123e2000-7ffc123e4000 r-xp 00000000 00:00 0       [vdso]

Permission Bits Explanation

r--p: read-only, private              ← .rodata, read-only segments
r-xp: read+execute, private           ← .text
rw-p: read+write, private             ← .data, .bss, heap, stack
r--s: read-only, shared               ← rarely used
rw-s: read+write, shared              ← MAP_SHARED mappings
---p: no access, private              ← guard pages (stack/heap guard)

Anonymous Mappings vs. File Mappings

[heap], [stack], [vdso]:              Anonymous mappings (no filename, created by kernel)
/lib64/libc.so.6:                     File mappings (has filename, mmap from file)
7f1233e00000 (no [name]):             Anonymous mapping (malloc, thread stack)
                                        → Not [heap]! malloc uses mmap

Detailed Explanation of Each Region

Stack: Automatic Stack Growth

// Stack is allocated using mmap or brk (depending on kernel config)
// VM_GROWSDOWN: push beyond current stack bottom → page fault → kernel automatically expands stack
//   → Expansion is limited by RLIMIT_STACK (default 8MB)
//   → There is a guard page (---p) below the stack top to prevent overflow

// Main thread stack: located at [stack]
// Child thread stacks: pthread_create → mmap (usually 8MB, located in the mmap region)

Heap: brk vs mmap

// Traditional brk/sbrk: expands the data segment (directly following .bss)
//   → User space: brk(addr) → changes program break
//   → Problem: Fragmentation, cannot be released independently

// malloc's current strategy:
//   1. Small allocations (< 128KB default): uses brk → placed in [heap] region
//   2. Large allocations (>= 128KB): uses mmap → independent anonymous mapping
//   3. mmap threshold: can be adjusted via mallopt(M_MMAP_THRESHOLD)

vDSO / vvar / vsyscall: Kernel Injection

[vdso]:   .so injected by the kernel into each process address space
          Provides: __vdso_clock_gettime, __vdso_getcpu, __vdso_time, __vdso_getrandom
          → Executed in user space, no syscall → Extremely fast

[vvar]:   vDSO data page (kernel timekeeper structure, per-CPU data)
          → R/O for userland

[vsyscall]: Deprecated fast system call page (x86 only)
            → Fixed address: 0xffffffffff600000 (security risk)
            → Now default emulate (page fault → kernel simulation → return)

Tools

# Process memory layout
cat /proc/self/maps
cat /proc/self/smaps | head -50        # Detailed stats per mapping (RSS/PSS/swap)

# Memory mapping summary
pmap -x <pid>

# Memory usage summary
cat /proc/self/status | grep Vm
# VmPeak: Historical maximum virtual memory
# VmSize: Current virtual memory
# VmRSS:  Physical memory (Resident)
# VmData/VmStk/VmExe/VmLib: Data/Stack/Code/Libraries

# ASLR settings
cat /proc/sys/kernel/randomize_va_space  # 0=off, 1=partial, 2=full (default)

References

  • Kernel Documentation: Documentation/filesystems/proc.rst (maps/smaps)
  • Source Code: fs/proc/task_mmu.c (/proc/pid/maps implementation), arch/x86/entry/vdso/
  • LWN: "The vDSO and vvar", "ASLR for 64-bit Linux"

Keywords: /proc/pid/maps, stack, heap, vDSO, vvar, ASLR, PIE, smaps, brk, mmap