Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Assembly (x86-64, AArch64, RISC-V)  Module 3 – How a CPU runs your program

Memory and a process's address space

What memory a running program sees: virtual addresses, the regions of an address space, pages and page size, and why addresses change on every run from ASLR.

  • Beginner
  • 10 minutes
  • Examples run with Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Describe the regions of a Linux process: text, rodata, data, bss, heap, mappings and stack
  • Explain virtual addresses, pages and the job of the memory-management unit
  • Find the page size of the machine you are on instead of hard-coding it
  • Explain why addresses change between runs and what ASLR and position-independent code do
  • Calculate the next page boundary for any size on any page size

Before you start

What you’ll learn · Before you start

What you will learn

  • Describe the regions of a Linux process: text, rodata, data, bss, heap, mappings and stack
  • Explain virtual addresses, pages and the job of the memory-management unit
  • Find the page size of the machine you are on instead of hard-coding it
  • Explain why addresses change between runs and what ASLR and position-independent code do
  • Calculate the next page boundary for any size on any page size

Before you start

On this page

Print the address of a local variable, run the program twice, and you will usually see two different numbers. Nothing is wrong: a running program never touches physical memory. It sees an address space: The set of virtual addresses a process can use, divided into regions (code, read-only data, writable data, heap, mapped libraries and the stack), each with its own read, write and execute permissions. of virtual address: An address a program uses, which the memory-management unit translates to a physical address through per-process page tables. Two processes can use the same virtual address and map to different physical memory., which the memory-management unit: The hardware that translates a process's virtual addresses to physical addresses a page at a time, using page tables, and enforces each page's read, write and execute permissions, faulting on a violation. maps to real memory one page: The fixed-size unit in which the memory-management unit maps and protects memory. Common sizes are 4 KiB, 16 KiB (Apple silicon) and 64 KiB; a request is always rounded up to whole pages. at a time, checking read, write and execute permission on every access.

That space is split into regions: code in .text, constants in .rodata, writable globals in .data and .bss, the heap that malloc grows, shared libraries and large allocations in the middle, and the stack near the top. By the end you can name each region, say why two processes can use the same address, read the page size from the machine (16384 bytes on an Apple-silicon Mac, 4096 on most x86-64 Linux) instead of assuming it, explain why addresses move between runs, and round a size up to whole pages.

A process sees regions, each with its own permissions

When the kernel starts a program it maps the file’s sections into memory with the right permissions and sets up a stack. From low addresses to high, a Linux process holds: .text (your machine code, read and execute), .rodata (constants, read-only), .data (initialised globals, read and write), .bss (globals that start at zero), the heap (which malloc grows upward), a middle region of shared libraries and mmap allocations, and the stack (which grows downward from near the top). Walk down them:

Interactive · step by step The regions of a process's address space Walk down a Linux process's virtual memory from the high addresses to the low, seeing what lives in each region.
The regions of a process's address space, step 6 of 6. Showing Stack (grows down) (grows towards the heap), Libraries and mmap (moved by ASLR each run), Heap (grows up) (malloc lives here), .bss: zeroed data (zero-filled), .data: initialised (read + write), .rodata: read-only (read-only), .text: your code (read + execute). In focus: Stack (grows down).Stack (grows down)grows towards the heapLibraries and mmapmoved by ASLR each runHeap (grows up)malloc lives here.bss: zeroed datazero-filled.data: initialisedread + write.rodata: read-onlyread-only.text: your coderead + execute

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Every address is virtual. The program sees virtual addresses. The memory-management unit maps each page to real memory and checks its permissions. Two programs can both use address 0x400000 and never collide.
  2. Code and constants at the bottom. .text holds the machine code, mapped read and execute. .rodata holds constants such as strings, read-only. A write to either faults, which is how the hardware stops code from rewriting itself.
  3. Writable static data. .data holds initialised globals and .bss holds those that start at zero. .bss takes no space in the file; the loader just maps zeroed pages for it.
  4. The heap grows up. malloc hands out memory from the heap, which grows towards higher addresses. Under the hood it comes from the kernel through brk or mmap, a page at a time.
  5. Libraries and mappings. Shared libraries and large allocations are mapped in this middle region. Their addresses change every run because of address-space layout randomisation.
  6. The stack grows down. The stack starts near the top and grows towards lower addresses as functions call. The system caps its size with a limit (RLIMIT_STACK on Linux); growing past the cap faults and ends the program.

Each region has its own permissions, which the hardware enforces: a write to .text or .rodata faults, which is how the machine stops code from rewriting itself and a string constant from being overwritten. On Linux a process can read its own map; the script below prints /proc/self/maps, one line per region, in the format that proc(5) documents:

maps-in-the-lab.sh: a process's own memory map Shell · maps-in-the-lab.sh
#!/bin/sh
# See a process's own address space on Linux. /proc/self/maps lists every mapped region of the process that reads
# it, with its address range, permissions (r, w, x and p for private or s for shared), file offset, device, inode
# and the file behind it, or a name such as [heap], [stack] or [vdso]. On macOS the nearest tool is "vmmap <pid>".
# Addresses change on every run because of ASLR, so no two runs print the same numbers.
cat /proc/self/maps
The map of cat, in a Linux lab on an AArch64 machine Python · maps_lab_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import linux_lab

# In the Linux lab, an AArch64 machine, cat prints the map of its own process. Addresses, device and inode numbers
# change from run to run, so they are shown as …; the permissions, offsets and names are as printed.
linux_lab.run("""
uname -m
getconf PAGESIZE
sh maps-in-the-lab.sh
""", maps=True)

Output

$ uname -m
aarch64
$ getconf PAGESIZE
4096
$ sh maps-in-the-lab.sh
…-… r-xp 00000000 …     …  /usr/bin/cat
…-… r--p 0000f000 …     …  /usr/bin/cat
…-… rw-p 00010000 …     …  /usr/bin/cat
…-… rw-p 00000000 00:00 0  [heap]
…-… rw-p 00000000 00:00 0
…-… r-xp 00000000 …     …  /usr/lib/aarch64-linux-gnu/libc.so.6
…-… ---p 0019c000 …     …  /usr/lib/aarch64-linux-gnu/libc.so.6
…-… r--p 0019d000 …     …  /usr/lib/aarch64-linux-gnu/libc.so.6
…-… rw-p 001a0000 …     …  /usr/lib/aarch64-linux-gnu/libc.so.6
…-… rw-p 00000000 00:00 0
…-… r-xp 00000000 …     …  /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
…-… rw-p 00000000 00:00 0
…-… rw-p 00000000 00:00 0
…-… r--p 00000000 00:00 0  [vvar]
…-… r-xp 00000000 00:00 0  [vdso]
…-… r--p 0002e000 …     …  /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
…-… rw-p 00030000 …     …  /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
…-… rw-p 00000000 00:00 0
…-… rw-p 00000000 00:00 0  [stack]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 maps_lab_session.py

The map belongs to cat itself. Its first three lines are /usr/bin/cat: the start of the file with its code and, in this AArch64 build, its constants, readable and executable; a page of data the dynamic loader filled in at start-up and then made read-only; and .data with .bss, readable and writable. Above them come the heap, then the C library and the dynamic loader, a line for each part with its own permissions, the kernel’s [vvar] and [vdso] pages, and the stack at the top. Each range shows as …-… because ASLR moves it on every run.

Key idea

A program’s memory is a set of regions with separate permissions, not one flat array. Code is executable but not writable, constants are read-only, and the stack and heap grow towards each other.

Checkpoint

Check: Regions and their permissions

  1. Question 1 of 2 What happens when a program tries to write to its .text (code) region?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It faults: .text is mapped read and execute, not write

    .text is mapped read and execute, .rodata read-only, .data and .bss read and write. A write to a non-writable region faults, which is how the hardware stops code from rewriting itself and constants from being changed.

  2. Question 2 of 2 In which direction do the stack and the heap grow in a typical Linux process?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The stack grows down from high addresses; the heap grows up from low addresses

    The stack grows towards lower addresses from near the top, and the heap grows towards higher addresses from the end of the static data. The figure walks down from the stack at the top to .text at the bottom.

Each process gets private memory, protected page by page

The addresses a program uses are virtual: the memory-management unit translates each one to a physical address through page tables, in units of a page. Because the mapping is per process, two programs can both use the virtual address 0x400000 and never interfere, since each maps to different physical memory. Permissions are held per page, so a single page can be read-only while the next is writable.

This is also why a null-pointer dereference faults cleanly: the page at virtual address 0 is left unmapped, so any access to it traps at once instead of corrupting something. The whole scheme lets the operating system give every process its own private, protected view of memory on shared hardware. The table separates what belongs to the process from what belongs to the page:

Question about a virtual address What answers it Scope
Which physical memory does it reach? the process’s page table per process: another process can use the same address for different memory
May it be read, written or executed? the permission bits of its page per page: neighbouring pages can differ
Does it reach anything at all? whether its page is mapped per page: address 0 is unmapped, so a null access faults

The first row is why two processes can use the same address and never meet; the second is why one page can be read-only while the next is writable.

Checkpoint

Check: Private memory, page by page

  1. Question 1 of 2 Two processes both read from virtual address 0x400000. Do they see the same bytes?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Not necessarily: each process has its own page tables, so 0x400000 maps to different physical memory

    Each process has its own page tables, so the same virtual address maps to different physical memory in each. That private, protected mapping is why one process cannot read or corrupt another's memory.

  2. Question 2 of 2 Why does dereferencing a null pointer usually crash cleanly instead of corrupting memory?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The page at virtual address 0 is left unmapped, so any access to it faults

    The page at virtual address 0 is deliberately left unmapped, so the memory-management unit faults on any access to it. That turns a null dereference into an immediate, clean crash rather than silent corruption.

Page size is not universal

The page size is a property of the machine and the kernel, not a fixed 4096. Ask for it. The run below asks three ways: getconf PAGESIZE, which is the shell’s face of sysconf(_SC_PAGESIZE); sysctl hw.pagesize; and a tiny program that calls getpagesize() through libSystem and exits 0 only when it gets 16384, the Apple-silicon value.

The page size on Apple silicon, from getconf, sysctl and code Python · pagesize_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

# The page size is a property of the machine and the kernel, not a constant. Ask for it instead of hard-coding it.
# getconf PAGESIZE is the shell face of sysconf(_SC_PAGESIZE) and sysctl reports the macOS value; then a tiny
# program calls getpagesize() through libSystem and exits 0 only if the value is 16384, the Apple-silicon page size.
session.run("""
getconf PAGESIZE
/usr/sbin/sysctl hw.pagesize hw.cachelinesize
cat getpagesize.s
clang -o getpagesize getpagesize.s
./getpagesize; echo "page-is-16384 -> exit=$?"
""")

Output

$ getconf PAGESIZE
16384
$ /usr/sbin/sysctl hw.pagesize hw.cachelinesize
hw.pagesize: 16384
hw.cachelinesize: 128
$ cat getpagesize.s
.text
.globl _main
.align 2
_main:
    stp     x29, x30, [sp, #-16]!
    mov     x29, sp
    bl      _getpagesize           // w0 = the page size in bytes (takes no arguments, returns an int in w0)
    mov     w1, #0x4000            // 16384, the page size of Apple silicon
    cmp     w0, w1
    cset    w0, ne                 // exit 0 if the page size is 16384, else 1
    ldp     x29, x30, [sp], #16
    ret
$ clang -o getpagesize getpagesize.s
$ ./getpagesize; echo "page-is-16384 -> exit=$?"
page-is-16384 -> exit=0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pagesize_session.py

Linux on x86-64 uses 4096, while Linux on arm64 is built for 4 KiB, 16 KiB or 64 KiB pages. So portable code asks: sysconf(_SC_PAGESIZE), AT_PAGESZ from the auxiliary vector, or getpagesize(), whose manual page says a program should not hard-code a page size; on macOS, Apple documents the vm_page_size variable. The page size matters because the kernel maps and protects memory a whole page at a time: a one-byte mmap still costs a page, and mmap always returns page-aligned memory.

Hard-coding 4096

Code that writes #define PAGE_SIZE 4096 is right on most x86-64 Linux machines and wrong on a Mac with 16 KiB pages or a server with 64 KiB pages. Read the page size at run time and derive every alignment and rounding from it.

Checkpoint

Check: Page size is not universal

  1. Question 1 of 3 What do sysctl hw.pagesize and getpagesize() report on an Apple-silicon Mac?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: 16384 bytes (16 KiB)

    sysctl printed hw.pagesize 16384 and the getpagesize() program exited 0, confirming 16384. Page size varies by machine and kernel, so read it rather than hard-coding 4096.

  2. Question 2 of 3 Which call lets a C program on Linux or macOS learn the page size at run time instead of assuming it?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: sysconf(_SC_PAGESIZE)

    The page size belongs to the machine and the kernel, so ask the running system: sysconf(_SC_PAGESIZE), the AT_PAGESZ entry of the auxiliary vector, or getpagesize(). Derive every alignment and rounding from that value.

  3. Question 3 of 3 Why does a one-byte allocation from mmap still consume a whole page?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: The kernel maps and protects memory a whole page at a time, so mmap always returns page-aligned, page-sized memory

    The memory-management unit maps and protects memory a page at a time, so mmap returns page-aligned memory rounded up to whole pages. A one-byte request costs a page: 4096 bytes on a 4 KiB system, 16384 on Apple silicon.

Why addresses change between runs

Run the same program twice and its stack, heap, libraries and often its code sit at different addresses each time. That is address-space layout randomisation: A defence that places a process's regions (stack, heap, libraries and position-independent code) at randomised addresses on each run, so code cannot rely on a fixed address. It raises attack cost but does not fix bugs. (ASLR): the kernel and loader place regions at randomised addresses so that an attacker cannot rely on a fixed address, a defence the security module returns to. Randomising the code too needs a position-independent executable: An executable whose code reaches other code and data through PC-relative addressing instead of fixed addresses, so it runs correctly at any load address. This is what lets the loader randomise its placement under ASLR.: its code uses PC-relative addressing (lea [rip+..] on x86-64, adr on AArch64, auipc on RISC-V) instead of fixed addresses, so it runs correctly wherever it is loaded.

The practical effect is that no program or test should contain a raw address. The examples on these pages that print an address show it as 0x…, because the real value differs on every run. Two runs are expected to agree on what a program computes, never on the addresses it happens to be loaded at.

Key idea

Addresses are not stable across runs. Position-independent code plus ASLR means the same program loads at different addresses each time, so never bake an address into a program or a test.

Checkpoint

Check: Why addresses change between runs

  1. Question 1 of 2 Why do a program's stack, heap and libraries sit at different addresses each time you run it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Address-space layout randomisation places the regions at randomised addresses each run

    ASLR randomises the load addresses of the stack, heap, libraries and often the code so that an attacker cannot rely on a fixed address. That is why the examples on these pages print addresses as 0x….

  2. Question 2 of 2 What makes it possible to load a program's code at a random address and still have it run correctly?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It is a position-independent executable: its code uses PC-relative addressing instead of fixed addresses

    A position-independent executable reaches code and data through PC-relative addressing (lea [rip+..], adr, auipc), so it runs correctly at any load address, which is what lets ASLR randomise the code too.

Practise: round up to whole pages

A mapping is a whole number of pages, so a request is rounded up to the next page boundary. page_round_up(size, pagesize) does it with one idiom: add pagesize - 1, then clear the low bits with a mask. Read the model, then answer the practice questions for 4 KiB, 16 KiB and 64 KiB pages.

Rounding a size up to whole pages for any page size Python · page_round.py
"""Rounding a size up to a whole number of pages, the way mmap does.

A page is the unit the memory-management unit maps and protects. A mapping is a whole number of pages, so the page
size decides how much a request really costs. page_round_up uses the standard idiom: add (pagesize - 1), then clear
the low bits with a mask. It works for any power-of-two page size.
"""


def page_round_up(size, pagesize):
    assert pagesize and (pagesize & (pagesize - 1)) == 0, "page size must be a power of two"
    return (size + pagesize - 1) & ~(pagesize - 1)


for pagesize in (4096, 16384, 65536):
    print(f"page size {pagesize} bytes ({pagesize // 1024} KiB):")
    for size in (1, 4095, 4096, 4097, 70000):
        up = page_round_up(size, pagesize)
        pages = up // pagesize
        print(f"  {size:>6} B -> {up:>6} B  ({pages} {'page' if pages == 1 else 'pages'})")

Output

page size 4096 bytes (4 KiB):
       1 B ->   4096 B  (1 page)
    4095 B ->   4096 B  (1 page)
    4096 B ->   4096 B  (1 page)
    4097 B ->   8192 B  (2 pages)
   70000 B ->  73728 B  (18 pages)
page size 16384 bytes (16 KiB):
       1 B ->  16384 B  (1 page)
    4095 B ->  16384 B  (1 page)
    4096 B ->  16384 B  (1 page)
    4097 B ->  16384 B  (1 page)
   70000 B ->  81920 B  (5 pages)
page size 65536 bytes (64 KiB):
       1 B ->  65536 B  (1 page)
    4095 B ->  65536 B  (1 page)
    4096 B ->  65536 B  (1 page)
    4097 B ->  65536 B  (1 page)
   70000 B -> 131072 B  (2 pages)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 page_round.py

Practice that adapts to you

Practise this lesson

10 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 Using page_round_up, how many bytes does a 70000-byte request round up to on a 4 KiB (4096-byte) page system?

    Type a number.

    A hint

    Round 70000 up to the next multiple of 4096.

    Show the answer: Question 1 of 10

    Answer: 73728 bytes

    70000 / 4096 is 17.1, so it rounds up to 18 pages: 18 × 4096 = 73728 bytes. The model prints "70000 B -> 73728 B (18 pages)" for the 4 KiB case.

  2. Question 2 of 10 The same 70000-byte request on 16 KiB (16384-byte) pages rounds up to how many bytes?

    Type a number.

    A hint

    Round 70000 up to the next multiple of 16384.

    Show the answer: Question 2 of 10

    Answer: 81920 bytes

    70000 / 16384 is 4.3, so it rounds up to 5 pages: 5 × 16384 = 81920 bytes. The model prints "70000 B -> 81920 B (5 pages)" for the 16 KiB case: fewer pages than the 4 KiB case because each page is larger, yet more bytes in total because the last page is bigger.

  3. Question 3 of 10 On a 16 KiB page system, how many bytes does a 1-byte request round up to?

    Type a number.

    A hint

    The smallest mapping is one page.

    Show the answer: Question 3 of 10

    Answer: 16384 bytes

    The smallest unit the kernel can map is one page, so a 1-byte request costs a whole 16384-byte page. The model shows "1 B -> 16384 B (1 page)".

  4. Question 4 of 10 Which region does memory from malloc normally come from?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: The heap (backed by brk or mmap from the kernel)

    malloc allocates from the heap, which grows upward and is backed by memory the kernel hands over through brk or mmap. The stack holds call frames; code and constants live in .text and .rodata.

  5. Question 5 of 10 On a system with 16 KiB pages, a program maps one readable byte with mmap and then reads the byte at offset 100 of the returned address. What happens, and why?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: The read succeeds, because the mapping is a whole 16384-byte page and offset 100 lies inside it

    A mapping is a whole number of pages, so a one-byte mmap reserves a full page and every byte in it is readable and writable with the page's permissions. A fault would come only from stepping past the end of the page into an address that is not mapped.

  6. Question 6 of 10 A junior engineer prints a pointer in two runs of the same program and sees different values, and worries the program is buggy. What do you tell them?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: That is expected: ASLR loads the program at a different address each run, so pointer values differ while the behaviour is the same

    Addresses are virtual and assigned at load time, and ASLR randomises them, so the same object has different addresses in different runs. The behaviour is unchanged; only the addresses move, which is why tests compare computed values, not addresses.

  7. Question 7 of 10 In one sentence, what does ASLR defend against, and what does it rely on to work?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: It stops an attacker relying on fixed addresses by randomising where regions load, and needs the program to be position-independent for its code to move too

    ASLR randomises the load addresses of a program's regions so an attacker cannot depend on a fixed address. The code moves too only when the program is position-independent (PC-relative code), so it runs correctly wherever it loads. The security module covers the attacks it raises the cost of.

  8. Question 8 of 10 A program stores a string literal in .rodata and then tries to write one of its bytes through a pointer. What happens, and why?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: The write faults, because the hardware enforces the read-only permission of the page the constant sits on

    An address space is a set of regions with separate permissions, not one flat writable array. The kernel maps .text read and execute, .rodata read-only, and .data and .bss read and write. A store to a read-only page is refused by the hardware and the process takes a fault, which is how a machine stops a constant from being overwritten and stops code from rewriting itself.

  9. Question 9 of 10 In a virtual-memory system, what is the unit that permissions such as read-only or no-execute are attached to?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: A page

    The memory-management unit translates virtual addresses to physical ones in units of a page, and the same page table entry carries that page's permissions. That is why regions line up with page boundaries and why a single page can be read-only while its neighbour is writable.

  10. Question 10 of 10 You are writing a test for a program that prints a heap address and a computed total. What should the test assert?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: The total exactly, and the address only as a masked placeholder or a property such as being page-aligned

    Address-space layout randomisation places regions at different addresses on each run, and position-independent code makes that possible. So two runs are expected to agree on what the program computes and never on the addresses it was loaded at. The examples on these pages print addresses as a placeholder for exactly this reason, and a test should assert the computed value or a property such as page alignment.

Interview questions

What is a virtual address, and why does every process get its own address space?

A virtual address is the address a program uses, which the memory-management unit translates to a physical address through page tables, in units of a page. Each process has its own page tables, so its virtual addresses are private: two processes can both use 0x400000 and map to different physical memory, and neither can read or write the other’s pages. This buys three things. It gives protection: permissions are held per page, so code pages are executable but not writable, constant pages are read-only, and the unmapped page at address 0 makes a null dereference fault at once instead of corrupting memory. It gives isolation: a bug or a crash in one process cannot touch another. And it gives flexibility: the kernel can place a program’s regions anywhere, back them with physical pages on demand, swap them out, and randomise their addresses with ASLR, which is why the same program loads at different addresses on each run. The program is built as a position-independent executable so its PC-relative code runs correctly wherever it lands.

Two more questions to practise, each with follow-ups:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    Code that aligns buffers to a hard-coded 4096-byte page works on a Linux x86-64 server but misbehaves on an Apple-silicon Mac. Explain the cause and the fix.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to staff

    Explain, defensively, what ASLR and position-independent executables protect against, what they rely on to work, and what they do not fix.

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What regions does a process see in its virtual address space?

    Code (.text), read-only constants (.rodata), initialised data (.data), zeroed data (.bss), the heap, shared libraries and other mmap mappings, and the stack. Each region carries its own read, write and execute permissions.

  2. How does the memory-management unit keep each process's memory private?

    It translates virtual addresses to physical memory one page at a time, through page tables that belong to each process. Two processes can use the same virtual address and reach different physical pages, and neither can touch the other's.

  3. Why does a null pointer dereference fault at once instead of reading garbage?

    The page at address 0 is left unmapped, so the memory-management unit has no translation for it and raises a fault on the first access.

  4. Is a page always 4096 bytes?

    No. An Apple silicon Mac reports 16384, and Linux on arm64 may use 4, 16 or 64 KiB pages. Ask at run time with sysconf(_SC_PAGESIZE) or the AT_PAGESZ entry instead of assuming 4 KiB.

  5. How do you round a size up to whole pages, and what does 70000 bytes become?

    Compute (size + pagesize - 1) and then clear the low bits with & ~(pagesize - 1). 70000 bytes becomes 73728 (18 pages) with 4 KiB pages and 81920 (5 pages) with 16 KiB pages.

  6. Why must a program or a test never rely on a fixed address?

    Address-space layout randomisation places the stack, heap and libraries at different addresses on each run, and a position-independent executable lets the code itself move too.

Key takeaways

  • A process sees a virtual address space with regions: .text, .rodata, .data, .bss, the heap, shared libraries and mmap mappings, and the stack; each region has its own read, write and execute permissions.
  • The memory-management unit maps virtual addresses to physical memory a page at a time, so each process is private and protected and a null dereference faults on the unmapped page at address 0.
  • Page size is not a fixed 4096: an Apple-silicon Mac reports 16384 through getconf, sysctl and getpagesize(), and Linux on arm64 can use 4, 16 or 64 KiB pages, so read it at run time with sysconf(_SC_PAGESIZE) or AT_PAGESZ.
  • ASLR places regions at randomised addresses each run, and a position-independent executable lets the code move too, so addresses are never stable and the examples mask them with 0x….
  • Round a size up to whole pages with (size + pagesize - 1) & ~(pagesize - 1); the model shows 70000 bytes becoming 73728 (18 pages) at 4 KiB and 81920 (5 pages) at 16 KiB.

Terms in this lesson

address space
The set of virtual addresses a process can use, divided into regions (code, read-only data, writable data, heap, mapped libraries and the stack), each with its own read, write and execute permissions.
address-space layout randomisation
A defence that places a process's regions (stack, heap, libraries and position-independent code) at randomised addresses on each run, so code cannot rely on a fixed address. It raises attack cost but does not fix bugs.
memory-management unit
The hardware that translates a process's virtual addresses to physical addresses a page at a time, using page tables, and enforces each page's read, write and execute permissions, faulting on a violation.
page
The fixed-size unit in which the memory-management unit maps and protects memory. Common sizes are 4 KiB, 16 KiB (Apple silicon) and 64 KiB; a request is always rounded up to whole pages.
position-independent executable
An executable whose code reaches other code and data through PC-relative addressing instead of fixed addresses, so it runs correctly at any load address. This is what lets the loader randomise its placement under ASLR.
virtual address
An address a program uses, which the memory-management unit translates to a physical address through per-process page tables. Two processes can use the same virtual address and map to different physical memory.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.