---
title: Process Memory Layout
url: https://doc.liz6.com/en/systems-programming/03-processes-and-memory/01-process-memory-layout
locale: en
area: systems-programming
tags:
- systems-programming
- processes-and-memory
date: 2026-06-30
modified: 2026-07-16
description: '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'
---

# 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

<svg viewBox="0 0 720 415" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="64-bit virtual address space layout from low to high: NULL page, binary segments, heap, mmap region, stack, vDSO/vvar/vsyscall, kernel space">
  <defs>
    <marker id="memlayout-ah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto">
      <path d="M0,0 L8,3 L0,6 Z" fill="#475569"/>
    </marker>
  </defs>
  <rect width="720" height="415" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">64-bit Virtual Address Space Layout (48-bit effective, 128TB user + 128TB kernel)</text>

  <!-- growth direction guide -->
  <text x="18" y="56" font-size="10" fill="#64748b">Low</text>
  <line x1="30" y1="50" x2="30" y2="326" stroke="#475569" stroke-width="1.6" marker-end="url(#memlayout-ah)"/>
  <text x="14" y="322" font-size="10" fill="#64748b">High</text>

  <!-- 1. NULL page -->
  <text x="145" y="65" text-anchor="end" font-size="9.5" fill="#64748b">0x0000_0000_0000_0000</text>
  <rect x="150" y="50" width="500" height="22" rx="6" fill="#e2e8f0" stroke="#cbd5e1"/>
  <text x="400" y="65" text-anchor="middle" font-size="11" fill="#475569">NULL page (unmapped, catches NULL dereferences)</text>

  <!-- 2. binary segments -->
  <text x="145" y="88" text-anchor="end" font-size="9.5" fill="#64748b">0x0000_0000_0040_0000</text>
  <rect x="150" y="76" width="500" height="46" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="400" y="94" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">PIE Binary Segments</text>
  <text x="400" y="110" text-anchor="middle" font-size="10.5" fill="#4f46e5">.text(R+X) · .rodata · .plt/.got · .data · .bss</text>

  <!-- 3. heap -->
  <text x="145" y="142" text-anchor="end" font-size="9.5" fill="#64748b">0x0000_0000_0060_0000</text>
  <rect x="150" y="126" width="500" height="26" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="400" y="143" text-anchor="middle" font-size="11" font-weight="600" fill="#0f766e">heap (brk/sbrk) ↑ Grows upward</text>

  <!-- 4. gap -->
  <rect x="150" y="156" width="500" height="20" rx="4" fill="none" stroke="#cbd5e1" stroke-dasharray="4 3"/>
  <text x="400" y="170" text-anchor="middle" font-size="10" fill="#94a3b8">... Large unmapped gap ...</text>

  <!-- 5. mmap region -->
  <text x="145" y="192" text-anchor="end" font-size="9.5" fill="#64748b">0x0000_7f00_0000_0000</text>
  <rect x="150" y="180" width="500" height="46" rx="8" fill="#e0e7ff" stroke="#a5b4fc"/>
  <text x="400" y="198" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">mmap Region (mmap base)</text>
  <text x="400" y="214" text-anchor="middle" font-size="10" fill="#4338ca">Shared libraries (.so) · Anonymous mappings (large malloc/thread stacks) · File mmap</text>

  <!-- 6. stack -->
  <text x="145" y="246" text-anchor="end" font-size="9.5" fill="#64748b">0x0000_7fff_ffff_ffff</text>
  <rect x="150" y="230" width="500" height="26" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="400" y="247" text-anchor="middle" font-size="11" font-weight="600" fill="#0f766e">[stack] Main thread stack ↓ Grows downward</text>

  <!-- 7. vvar/vdso/vsyscall -->
  <text x="145" y="278" text-anchor="end" font-size="9.5" fill="#64748b">≈0x7fff_ffff_ffff</text>
  <rect x="150" y="260" width="500" height="44" rx="8" fill="#ffedd5" stroke="#fdba74"/>
  <text x="400" y="274" text-anchor="middle" font-size="10.3" fill="#9a3412">[vvar] vDSO data page (kernel timekeeper, read-only)</text>
  <text x="400" y="287" text-anchor="middle" font-size="10.3" fill="#9a3412">[vdso] vDSO code page (clock_gettime, etc., avoids syscalls)</text>
  <text x="400" y="300" text-anchor="middle" font-size="10.3" fill="#9a3412">[vsyscall] Deprecated fast-call page (fixed address, security risk, default emulate)</text>

  <!-- 8. kernel space -->
  <text x="145" y="322" text-anchor="end" font-size="9.5" fill="#64748b">0xffff_ffff_ffff_ffff</text>
  <rect x="150" y="308" width="500" height="24" rx="6" fill="#fef2f2" stroke="#ef4444"/>
  <text x="400" y="324" text-anchor="middle" font-size="11" font-weight="700" fill="#dc2626">Kernel space (inaccessible from user space)</text>

  <!-- insight -->
  <rect x="60" y="348" width="600" height="52" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="368" font-size="12.5" fill="#3730a3">The heap grows upward from the end of the binary segments, while the mmap region (including the stack) grows downward from high addresses—</text>
  <text x="76" y="388" font-size="12.5" fill="#3730a3">They move toward each other, with a huge unmapped gap in between serving as a safety buffer.</text>
</svg>

## /proc/pid/maps Detailed Explanation

```bash
$ 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

```c
// 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

```c
// 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

```bash
# 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*
