Project 2: Memory Map Detective

Query the BIOS for the system memory map using INT 15h E820, then display all memory regions with their addresses, sizes, and types - exactly what GRUB and Linux do during early boot.

Quick Reference

Attribute Value
Difficulty ★★☆☆☆ Beginner
Time Estimate 1 week (12-20 hours)
Language x86 Assembly (NASM)
Prerequisites Project 1 completed, basic hex arithmetic
Key Topics BIOS E820 memory detection, physical memory layout, 64-bit values in 16-bit mode, memory region types

1. Learning Objectives

After completing this project, you will be able to:

  • Explain why bootloaders cannot assume a flat, continuous memory space
  • Use the BIOS E820 interrupt service to query the physical memory map
  • Parse binary data structures returned by firmware services
  • Display 64-bit hexadecimal numbers in 16-bit real mode
  • Identify different memory region types (usable RAM, reserved, ACPI, etc.)
  • Handle stateful BIOS APIs with continuation values
  • Debug memory-related issues in bare-metal code

2. Theoretical Foundation

2.1 Core Concepts

Why Memory Detection Matters

At boot time, not all physical addresses are backed by usable RAM. The address space includes:

┌────────────────────────────────────────────────────────────────┐
│                    PHYSICAL ADDRESS SPACE                       │
├────────────────────────────────────────────────────────────────┤
│ 0x00000000 - 0x0009FFFF  │ Conventional Memory (~640KB)         │
│                          │ Usable, but contains BIOS structures │
├────────────────────────────────────────────────────────────────┤
│ 0x000A0000 - 0x000BFFFF  │ VGA Video Memory (128KB)             │
│                          │ Memory-mapped I/O, NOT RAM           │
├────────────────────────────────────────────────────────────────┤
│ 0x000C0000 - 0x000FFFFF  │ BIOS ROM, Option ROMs (256KB)        │
│                          │ Read-only, system firmware           │
├────────────────────────────────────────────────────────────────┤
│ 0x00100000 - 0x????????  │ Extended Memory (varies)             │
│                          │ Main RAM, kernel loads here          │
├────────────────────────────────────────────────────────────────┤
│ 0xFEC00000 - 0xFEDFFFFF  │ APIC (I/O and Local)                 │
│                          │ Interrupt controller registers       │
├────────────────────────────────────────────────────────────────┤
│ 0xFEE00000 - 0xFFFFFFFF  │ System ROM, PCI config space         │
│                          │ Hardware configuration               │
└────────────────────────────────────────────────────────────────┘

If your bootloader tries to load a kernel into a reserved region:

  • Best case: Immediate crash
  • Worst case: Corrupted BIOS data, hardware malfunction, silent data corruption

The E820 memory map tells you exactly which regions are safe to use.

E820 Memory Map BIOS Call

INT 15h with EAX=E820h is the standard way to detect memory on x86 systems:

INPUT:
  EAX = 0x0000E820        ; Function number
  EBX = Continuation      ; 0 for first call, returned value for subsequent
  ECX = Buffer size       ; At least 20 bytes (24 recommended)
  EDX = 0x534D4150        ; 'SMAP' signature
  ES:DI = Buffer address  ; Where to store result

OUTPUT:
  EAX = 0x534D4150        ; 'SMAP' if successful
  EBX = Continuation      ; 0 if last entry, else next value
  ECX = Bytes written     ; 20 or 24
  CF = Clear if success   ; Set if error
  ES:DI = Filled buffer   ; One memory region descriptor

E820 Entry Structure (20-24 bytes):

Offset  Size    Field
+0      8       Base Address (64-bit)
+8      8       Length (64-bit)
+16     4       Type (32-bit)
+20     4       Extended Attributes (optional, if ECX >= 24)

Memory Region Types: | Type | Name | Description | Can OS Use? | |——|——|————-|————-| | 1 | Usable RAM | Normal system memory | Yes | | 2 | Reserved | Reserved by BIOS/hardware | No | | 3 | ACPI Reclaimable | ACPI tables (can reclaim after reading) | After parsing | | 4 | ACPI NVS | ACPI Non-Volatile Storage | No | | 5 | Bad Memory | Defective RAM regions | No |

64-bit Addresses in 16-bit Mode

In 16-bit real mode, registers are only 16 bits. But E820 returns 64-bit addresses because modern systems can have >4GB RAM.

Solution: The BIOS stores 64-bit values in memory, and you access them as:

  • Two 32-bit values (high DWORD and low DWORD)
  • Or four 16-bit values
Memory layout of a 64-bit address (little-endian):
Offset: 0   1   2   3   4   5   6   7
Bytes:  [00][00][10][00][00][00][00][00]  = 0x0000000000100000 (1MB)
        └──Low DWORD──┘ └─High DWORD─┘

Continuation Values

E820 returns one region per call. To get all regions:

┌─────────────────────────────────────────────────────────────┐
│  First call: EBX = 0                                        │
│  ↓                                                          │
│  BIOS returns: Region 0, EBX = [some value]                 │
│  ↓                                                          │
│  Next call: EBX = [previous value]                          │
│  ↓                                                          │
│  BIOS returns: Region 1, EBX = [another value]              │
│  ↓                                                          │
│  ...continue until...                                       │
│  ↓                                                          │
│  BIOS returns: Last region, EBX = 0                         │
│  ↓                                                          │
│  DONE - no more regions                                     │
└─────────────────────────────────────────────────────────────┘

Critical Rule: Never modify the continuation value. It’s an opaque token managed by the BIOS.

2.2 Why This Matters

  1. Every Operating System Needs This: Linux’s early boot code (arch/x86/boot/memory.c) calls E820. Windows Boot Manager does the same. Without memory detection, no kernel can safely allocate memory.

  2. Debugging Power: When a system has memory issues (kernel panics, random crashes), checking the E820 map often reveals the problem - memory holes, overlapping regions, or insufficient RAM.

  3. Virtualization Understanding: Virtual machines (QEMU, VirtualBox) simulate E820 responses. Understanding this helps you debug VM memory configuration.

  4. Security Implications: Secure boot and memory isolation depend on accurate memory maps. Malicious firmware could lie about memory layout to hide malware.

2.3 Historical Context

Why E820? Before E820, there were simpler methods:

  • INT 12h: Returns conventional memory size (below 640KB) - too limited
  • INT 15h, AH=88h: Returns extended memory above 1MB - max 64MB
  • INT 15h, AX=E801h: Better, but limited to 4GB

E820 was introduced for systems with >4GB RAM and complex memory maps. The ‘SMAP’ signature (0x534D4150) stands for “System Map”.

Why the weird structure? Backward compatibility. The format evolved to handle:

  • 64-bit addresses (for large memory)
  • ACPI memory regions
  • Extended attributes (for ACPI 3.0)

2.4 Common Misconceptions

Misconception 1: “All memory from 0 to RAM_SIZE is usable”

  • Reality: Memory has holes. VGA memory at 0xA0000-0xBFFFF, BIOS at 0xF0000-0xFFFFF, device memory mapped throughout.

Misconception 2: “E820 returns entries in order”

  • Reality: Not guaranteed. Some BIOSes return entries out of order. You may need to sort them.

Misconception 3: “20 bytes is always enough”

  • Reality: Extended attributes (byte 20-23) exist on some systems. Always request 24 bytes, handle what you get.

Misconception 4: “The memory map is static”

  • Reality: UEFI systems with memory remapping, hot-plug RAM, or dynamic ACPI tables can have varying maps. Query on every boot.

Misconception 5: “I can use conventional memory freely”

  • Reality: Conventional memory (0-640KB) contains BIOS structures. Only 0x0500-0x7BFF and 0x7E00-0x9FBFF are truly free.

3. Project Specification

3.1 What You Will Build

A bootloader that:

  1. Calls INT 15h E820 to query the memory map
  2. Iterates through all memory regions
  3. Displays each region’s base address, length, and type
  4. Shows human-readable type names (Usable, Reserved, ACPI, etc.)
  5. Calculates and displays total usable RAM

3.2 Functional Requirements

  • Successfully call E820 and detect when it’s unsupported
  • Display all memory regions returned by BIOS
  • Show 64-bit addresses in hexadecimal format
  • Identify and label memory types 1-5
  • Handle the continuation loop correctly (process ALL regions)
  • Display total usable memory count

3.3 Non-Functional Requirements

  • Must work on QEMU with various memory sizes (-m 128M, -m 512M, -m 4G)
  • Should handle at least 20 memory regions (typical for modern systems)
  • Must fit within bootloader constraints (512 bytes for stage 1, or use two-stage approach)
  • Gracefully handle E820 not supported (fall back or error message)

3.4 Example Usage / Output

$ qemu-system-x86_64 -m 512M -drive format=raw,file=boot.bin

Expected Output (QEMU with 512MB):

Memory Map (E820):
Region 0: 0x0000000000000000 - 0x000000000009FBFF (639 KB) Type: Usable
Region 1: 0x000000000009FC00 - 0x000000000009FFFF (1 KB) Type: Reserved
Region 2: 0x00000000000E0000 - 0x00000000000FFFFF (128 KB) Type: Reserved
Region 3: 0x0000000000100000 - 0x000000001FFFFFFF (511 MB) Type: Usable
Region 4: 0x00000000FEC00000 - 0x00000000FFFFFFFF (20 MB) Type: Reserved

Total usable memory: 512 MB

3.5 Real World Outcome

When you complete this project:

  • You understand exactly what GRUB and Linux see during early boot
  • You can debug memory-related boot issues
  • You’ve mastered a stateful BIOS API
  • You can work with 64-bit data in 16-bit code
  • You understand physical memory layout on x86

Compare your output to Linux’s view: cat /proc/iomem or check dmesg for “e820” entries.


4. Solution Architecture

4.1 High-Level Design

┌─────────────────────────────────────────────────────────────────┐
│                     MEMORY MAP DETECTOR                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ 1. INITIALIZATION                                          │ │
│  │    - Set up segments (DS, ES = 0)                          │ │
│  │    - Set up stack (SS:SP)                                  │ │
│  │    - Clear screen (optional)                               │ │
│  │    - Print banner                                          │ │
│  └────────────────────────────────────────────────────────────┘ │
│                              │                                   │
│                              ▼                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ 2. E820 DETECTION LOOP                                     │ │
│  │    - EBX = 0 (first call)                                  │ │
│  │    - LOOP:                                                 │ │
│  │      - Call INT 15h, EAX=E820                              │ │
│  │      - Check CF (error)                                    │ │
│  │      - Check EAX = 'SMAP' (valid)                          │ │
│  │      - Parse entry at ES:DI                                │ │
│  │      - Display entry                                       │ │
│  │      - If EBX = 0: DONE                                    │ │
│  │      - Else: LOOP                                          │ │
│  └────────────────────────────────────────────────────────────┘ │
│                              │                                   │
│                              ▼                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ 3. DISPLAY ROUTINES                                        │ │
│  │    - print_qword: Display 64-bit hex number                │ │
│  │    - print_dword: Display 32-bit hex number                │ │
│  │    - print_type: Convert type code to string               │ │
│  │    - print_string: Basic string output                     │ │
│  └────────────────────────────────────────────────────────────┘ │
│                              │                                   │
│                              ▼                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ 4. SUMMARY                                                 │ │
│  │    - Calculate total usable memory                         │ │
│  │    - Display total                                         │ │
│  │    - Halt                                                  │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

4.2 Key Components

  1. E820 Caller: Sets up registers, calls INT 15h, handles errors and continuation
  2. Entry Parser: Reads 64-bit base, 64-bit length, 32-bit type from buffer
  3. Hex Printer: Converts binary to ASCII hex, handles 64-bit values
  4. Type Decoder: Maps type codes to human-readable strings
  5. String Printer: Outputs null-terminated strings via INT 10h

4.3 Data Structures

E820 Entry Buffer:

e820_buffer:
    .base_low:  dd 0        ; Bytes 0-3: Base address low DWORD
    .base_high: dd 0        ; Bytes 4-7: Base address high DWORD
    .len_low:   dd 0        ; Bytes 8-11: Length low DWORD
    .len_high:  dd 0        ; Bytes 12-15: Length high DWORD
    .type:      dd 0        ; Bytes 16-19: Region type
    .extended:  dd 0        ; Bytes 20-23: Extended attributes (optional)

Memory Type Strings:

type_usable:    db "Usable", 0
type_reserved:  db "Reserved", 0
type_acpi_recl: db "ACPI Reclaimable", 0
type_acpi_nvs:  db "ACPI NVS", 0
type_bad:       db "Bad Memory", 0
type_unknown:   db "Unknown", 0

4.4 Algorithm Overview

ALGORITHM: E820 Memory Map Detection

1. Initialize:
   entry_count = 0
   continuation = 0

2. Detection Loop:
   WHILE true:
     a. Set registers:
        EAX = 0xE820
        EBX = continuation
        ECX = 24
        EDX = 'SMAP'
        ES:DI = buffer

     b. INT 15h

     c. IF carry_flag SET:
          IF entry_count == 0:
            PRINT "E820 not supported"
          EXIT LOOP

     d. IF EAX != 'SMAP':
          PRINT "E820 error"
          EXIT LOOP

     e. CALL display_entry(buffer)

     f. entry_count++

     g. continuation = EBX

     h. IF continuation == 0:
          EXIT LOOP

3. Summary:
   PRINT "Total entries: ", entry_count
   PRINT "Total usable: ", calculate_usable()

4. HALT

5. Implementation Guide

5.1 Development Environment Setup

Same as Project 1, plus:

# Test with different memory sizes
qemu-system-x86_64 -m 128M -drive format=raw,file=boot.bin
qemu-system-x86_64 -m 512M -drive format=raw,file=boot.bin
qemu-system-x86_64 -m 2G -drive format=raw,file=boot.bin
qemu-system-x86_64 -m 4G -drive format=raw,file=boot.bin

# Compare with expected output
# Run Linux and check: dmesg | grep e820

5.2 Project Structure

memory-map/
├── boot.asm           # Main bootloader source
├── Makefile           # Build automation
├── boot.bin           # Compiled binary
└── test.sh            # Script to test with various memory sizes

5.3 The Core Question You’re Answering

“How does a bootloader discover what memory is actually available, and why can’t it just assume a flat, continuous address space?”

This project reveals that physical memory is not a simple array from 0 to RAM_SIZE. It has:

  • Holes for memory-mapped I/O devices
  • Reserved regions for firmware
  • ACPI tables for power management
  • Potentially bad memory regions

The E820 memory map is the foundation for all memory management in the operating system.

5.4 Concepts You Must Understand First

Self-Assessment Questions:

  1. E820 Call Setup: What value goes in EDX when calling E820?
    • Answer: 0x534D4150 (‘SMAP’)
  2. Entry Structure: At what offset is the memory type in an E820 entry?
    • Answer: Offset 16 (after 8-byte base and 8-byte length)
  3. Continuation: How do you know you’ve received the last entry?
    • Answer: EBX returns 0
  4. Error Detection: How do you detect if E820 is unsupported?
    • Answer: Carry flag set after INT 15h
  5. 64-bit in 16-bit: How do you print a 64-bit number in real mode?
    • Answer: Access as two 32-bit values, print high DWORD first

5.5 Questions to Guide Your Design

  1. How do you call INT 15h E820 correctly?
    • What values go in which registers?
    • Where do you store the returned data?
  2. How do you handle the continuation value?
    • Where do you store EBX between calls?
    • How do you know when to stop?
  3. How do you display 64-bit numbers?
    • Big-endian or little-endian display?
    • How do you convert nibbles to hex digits?
  4. How do you identify memory types?
    • Where is the type field?
    • What do type codes 1-5 mean?
  5. What if E820 isn’t supported?
    • Should you fall back to E801?
    • How do you detect unsupported?

5.6 Thinking Exercise

Before coding, sketch the memory map:

For a hypothetical 512MB system:

0x00000000 ┌─────────────────────────┐
           │ Conventional (640KB)    │ Type 1 (Usable)
0x0009FC00 ├─────────────────────────┤
           │ EBDA (1KB)              │ Type 2 (Reserved)
0x000A0000 ├─────────────────────────┤
           │ VGA Memory (128KB)      │ Type 2 (Reserved)
0x000C0000 ├─────────────────────────┤
           │ Option ROMs (256KB)     │ Type 2 (Reserved)
0x00100000 ├─────────────────────────┤
           │ Extended Memory (511MB) │ Type 1 (Usable)
0x1FF00000 ├─────────────────────────┤
           │ Hole/Reserved           │ Type 2 (Reserved)
0xFEC00000 ├─────────────────────────┤
           │ APIC (20MB)             │ Type 2 (Reserved)
0xFFFFFFFF └─────────────────────────┘

Trace the algorithm:

Call 1: EBX=0 → Returns region 0, EBX=0x12345678
Call 2: EBX=0x12345678 → Returns region 1, EBX=0xABCDEF00
...
Call N: EBX=0x???????? → Returns region N-1, EBX=0
DONE

5.7 Hints in Layers

Hint 1: E820 Call Structure (Basic Setup)

; Buffer for E820 results (24 bytes minimum)
memory_map_buffer: times 24 db 0

detect_memory:
    xor ebx, ebx              ; Continuation = 0 for first call
    mov di, memory_map_buffer ; ES:DI points to buffer

.loop:
    mov eax, 0xE820           ; E820 function
    mov ecx, 24               ; Buffer size
    mov edx, 0x534D4150       ; 'SMAP' signature
    int 0x15                  ; Call BIOS

    jc .error                 ; Carry flag = error
    cmp eax, 0x534D4150       ; BIOS must return 'SMAP'
    jne .error

    ; Process this entry here

    test ebx, ebx             ; Is EBX zero?
    jz .done                  ; Yes = last entry
    jmp .loop                 ; No = more entries

Hint 2: Continuation Handling

detect_memory:
    xor ebx, ebx              ; Start with continuation = 0
    mov word [entry_count], 0 ; Track how many entries

.next_entry:
    ; Save EBX before it might be modified
    push ebx

    ; Set up registers for THIS call
    mov eax, 0xE820
    mov ecx, 24
    mov edx, 0x534D4150
    mov di, memory_map_buffer

    int 0x15
    jc .error

    cmp eax, 0x534D4150
    jne .error

    ; EBX now contains continuation value for NEXT call
    ; Display this entry
    call display_memory_entry

    ; Restore and use new continuation
    pop ebx
    mov ebx, [ebx_new]        ; If you stored it

    ; Increment counter
    inc word [entry_count]

    ; Check if done
    test ebx, ebx
    jnz .next_entry           ; EBX != 0 means more entries

.done:
    ret

Hint 3: Displaying 64-bit Hex Numbers

; Print 64-bit value at ES:DI as hex
print_qword:
    push di
    add di, 7                 ; Start at highest byte
    mov cx, 8                 ; 8 bytes to display

.byte_loop:
    mov al, [es:di]          ; Get byte
    push ax

    ; High nibble
    shr al, 4
    call print_hex_digit

    ; Low nibble
    pop ax
    and al, 0x0F
    call print_hex_digit

    dec di
    loop .byte_loop

    pop di
    ret

print_hex_digit:
    ; AL contains 0-15
    cmp al, 10
    jl .is_digit
    ; A-F
    add al, 'A' - 10
    jmp .print
.is_digit:
    ; 0-9
    add al, '0'
.print:
    mov ah, 0x0E
    int 0x10
    ret

Hint 4: Parsing E820 Structure

display_memory_entry:
    mov di, memory_map_buffer

    ; Display "Base: 0x"
    mov si, base_msg
    call print_string

    ; Display 64-bit base address
    call print_qword

    ; Move to length field (offset 8)
    add di, 8
    mov si, length_msg
    call print_string
    call print_qword

    ; Get type (offset 16 from start)
    mov di, memory_map_buffer
    add di, 16
    mov eax, [es:di]

    ; Print type name
    cmp eax, 1
    je .usable
    cmp eax, 2
    je .reserved
    ; ... more types
    jmp .unknown

.usable:
    mov si, type_usable
    call print_string
    jmp .done

.reserved:
    mov si, type_reserved
    call print_string
    jmp .done

.unknown:
    mov si, type_unknown
    call print_string

.done:
    call print_newline
    ret

; Data
base_msg: db "Base: 0x", 0
length_msg: db " Length: 0x", 0
type_usable: db " (Usable)", 0
type_reserved: db " (Reserved)", 0
type_unknown: db " (Unknown)", 0

Hint 5: Edge Case Handling

; Check if E820 supported
detect_memory:
    xor ebx, ebx
    mov eax, 0xE820
    mov ecx, 24
    mov edx, 0x534D4150
    mov di, buffer
    int 0x15

    jc .e820_failed           ; Not supported
    cmp eax, 0x534D4150
    jne .e820_failed

    ; Continue...
    jmp .continue

.e820_failed:
    ; Fall back or show error
    mov si, e820_error_msg
    call print_string
    ret

; Skip zero-length regions (BIOS bugs)
.check_length:
    mov eax, [es:di + 8]      ; Length low
    or eax, [es:di + 12]      ; OR with length high
    jz .skip_entry            ; Skip if length = 0

5.8 The Interview Questions They’ll Ask

Beginner Questions

  1. “Why can’t the bootloader assume all memory is usable?”
    • Answer: Physical address space includes memory-mapped I/O, BIOS ROM, video memory, and reserved regions. Writing to these could damage hardware or corrupt system data.
  2. “What’s the difference between Type 1 and Type 2 memory?”
    • Answer: Type 1 (Usable) is normal RAM the OS can allocate. Type 2 (Reserved) is claimed by hardware/BIOS and must not be used.
  3. “How does the bootloader know it’s received all regions?”
    • Answer: BIOS returns EBX=0 after the last entry.

Intermediate Questions

  1. “Why does E820 return 64-bit addresses in 16-bit mode?”
    • Answer: Modern systems can have >4GB RAM. The BIOS stores 64-bit values in memory; we access them as pairs of 32-bit values.
  2. “What happens if you load the kernel into a reserved region?”
    • Answer: Best case: crash. Worst case: corruption of BIOS data, hardware malfunction.
  3. “How do you handle overlapping E820 regions?”
    • Answer: Some BIOSes return overlaps. Merge them, giving priority to more restrictive types (Reserved > Usable).

Advanced Questions

  1. “Why does E820 require EDX=’SMAP’?”
    • Answer: It’s a signature for backward compatibility, ensuring this variant of INT 15h is being called correctly.
  2. “What’s the Extended Attributes field for?”
    • Answer: ACPI 3.0 addition. Bit 0: ignore if not enabled. Bit 1: non-volatile memory. Present when ECX returns >= 24.
  3. “How would you implement this on UEFI?”
    • Answer: UEFI uses GetMemoryMap() boot service, returning EFI_MEMORY_DESCRIPTOR array - different structure, same concept.

Tricky Questions

  1. “Your E820 returns address 0xFFFFFFFF_FFFFFFFF. What’s wrong?”
    • Answer: BIOS bug or uninitialized data. Valid regions don’t extend to max 64-bit address. Validate returned ranges.
  2. “How do you handle zero-length regions?”
    • Answer: Skip them - invalid entries from buggy BIOSes.
  3. “Why might the memory map differ between boots?”
    • Answer: UEFI memory remapping, hot-plug RAM, dynamic ACPI tables. Query every boot, don’t cache.

5.9 Books That Will Help

Topic Book Chapters Why It Helps
Physical Memory “Computer Systems: A Programmer’s Perspective” Ch 9: Virtual Memory (Section 9.1) Conceptual foundation for address spaces
BIOS Interrupts “The Art of Assembly Language” Ch 4, 10, 13 Multi-precision arithmetic, interrupt conventions
Boot Process “Low-Level Programming” by Igor Zhirkov Ch 3, 8 Real mode layout, OS boot details
OS Memory “Operating Systems: Three Easy Pieces” Ch 13, 15 How OSes use the memory map
Intel Reference Intel SDM Volume 1 Ch 3 Authoritative CPU documentation

Online Resources:

5.10 Implementation Phases

Phase 1: Single E820 Call (4 hours)

  • Set up E820 call correctly
  • Verify it returns ‘SMAP’
  • Print success/failure message
  • Milestone: “E820 supported” displays

Phase 2: Entry Display (4 hours)

  • Parse the first entry
  • Display base address as hex
  • Milestone: First region base address shows

Phase 3: Full Loop (4 hours)

  • Implement continuation handling
  • Loop until EBX = 0
  • Count entries
  • Milestone: All regions display

Phase 4: Type Decoding (4 hours)

  • Add type strings
  • Match type codes to strings
  • Milestone: “Usable”, “Reserved” labels

Phase 5: Polish (4 hours)

  • Add total usable calculation
  • Format output nicely
  • Test with various memory sizes
  • Milestone: Complete professional output

5.11 Key Implementation Decisions

  1. Buffer Location: Place buffer after code, before signature. Or use memory above 0x7E00.

  2. Print Format: Full 16-digit hex (leading zeros) is clearer than truncated. Example: 0x0000000000100000 not 0x100000.

  3. Size Limit: This project may exceed 512 bytes. Consider:
    • Two-stage approach (load more from disk)
    • Minimal output (just hex, no labels)
    • Abbreviated type names
  4. Error Handling: If E820 fails, show clear error. Don’t silently continue.

  5. Entry Storage: Store entries for later use, or just display each immediately? Immediate display uses less memory.

6. Testing Strategy

6.1 Unit Testing

; Test hex printing
test_hex:
    mov ax, 0xABCD
    call print_word_hex   ; Should print "ABCD"

; Test E820 single call
test_e820:
    xor ebx, ebx
    mov eax, 0xE820
    ; ... should not crash

6.2 Integration Testing

# Test with different memory configurations
for mem in 128M 256M 512M 1G 2G 4G; do
    echo "Testing with $mem..."
    qemu-system-x86_64 -m $mem -drive format=raw,file=boot.bin -nographic &
    sleep 2
    pkill qemu
done

# Compare with Linux
qemu-system-x86_64 -m 512M -kernel /path/to/bzImage -append "console=ttyS0" -nographic
# Check dmesg for e820 output

6.3 Debugging Techniques

QEMU Monitor:

qemu-system-x86_64 -m 512M -drive format=raw,file=boot.bin -monitor stdio

(qemu) info registers     # Check EAX, EBX, ECX after call
(qemu) x/24xb <buffer>    # Examine E820 buffer contents
(qemu) info mtree         # Show QEMU's memory layout

Bochs:

bochs
<bochs:1> xp /24 0x????   # Examine buffer
<bochs:2> r               # Show registers
<bochs:3> trace-on        # Step through

7. Common Pitfalls & Debugging

Problem 1: No memory map appears - blank or garbage

  • Root Cause: ES register not initialized
  • Fix: xor ax, ax; mov es, ax before E820 call
  • Quick Test: Write known value to buffer, verify with QEMU monitor

Problem 2: Infinite loop - EBX doesn’t change

  • Root Cause: Carry flag set (error) but not checked
  • Fix: Add jc .error after INT 15h
  • Quick Test: Print error message on carry

Problem 3: Only first region displays

  • Root Cause: Not checking EBX for continuation
  • Fix: test ebx, ebx; jz .done; jmp .loop
  • Quick Test: Add counter, print count at end

Problem 4: 64-bit addresses show as garbage

  • Root Cause: Wrong byte order or only printing 32 bits
  • Fix: Print bytes in reverse order (high to low)
  • Quick Test: Hardcode 0x0000000100000000, verify output

Problem 5: Type field shows wrong values

  • Root Cause: Wrong offset in structure
  • Fix: Type is at offset 16, not 12 or 20
  • Quick Test: Print raw bytes of buffer

Problem 6: ECX returns unexpected values

  • Root Cause: Normal - some BIOSes return 20, some 24
  • Fix: Request 24, accept what’s returned
  • Quick Test: Print ECX after each call

Problem 7: Different output in QEMU vs real hardware

  • Root Cause: Different memory layouts
  • Fix: Expected - real hardware has more complex maps
  • Quick Test: Boot Linux, compare with dmesg e820

Problem 8: Hex display shows wrong case/characters

  • Root Cause: Wrong nibble-to-ASCII conversion
  • Fix: 0-9 → add ‘0’ (0x30), 10-15 → add ‘A’-10 (0x37)
  • Quick Test: Print 0xDEADBEEF, verify output

8. Extensions & Challenges

Extension 1: Sort Entries by Address

  • E820 doesn’t guarantee order
  • Implement bubble sort on entries
  • Display in ascending address order

Extension 2: Calculate Sizes in MB/GB

  • Divide 64-bit lengths by 1MB or 1GB
  • Display human-readable sizes

Extension 3: Detect Memory Holes

  • Compare consecutive regions
  • Report gaps between end of one and start of next

Extension 4: Store Map for Later Use

  • Save all entries in a table
  • Pass table address to kernel later

Extension 5: Fall Back to E801

  • If E820 fails, try INT 15h AX=E801h
  • Older method, less detailed but more compatible

Extension 6: Graphical Memory Map

  • Use VGA text mode colors
  • Different colors for different types
  • Visual bar showing proportions

9. Real-World Connections

Linux Kernel: arch/x86/boot/memory.c implements E820 detection. Your code is essentially a simplified version of what Linux does.

GRUB: grub-core/kern/i386/pc/mmap.c contains GRUB’s memory detection. Very similar to what you’re building.

Windows: Windows Boot Manager queries E820 and passes the map to ntoskrnl.exe. Same process, different implementation.

/proc/iomem: On a running Linux system, /proc/iomem shows the kernel’s view of physical memory - derived from E820.

dmesg e820: dmesg | grep e820 shows the raw E820 map as reported by BIOS. Compare your output!

UEFI GetMemoryMap: Modern systems use UEFI’s GetMemoryMap() instead of INT 15h, but the concept is identical.


10. Resources

Primary References

  • OSDev Wiki: Detecting Memory (x86)
  • Intel SDM Volume 3A, Chapter 15 (System Programming Guide)
  • ACPI Specification (for memory types 3 and 4)

Online Tutorials

  • OSDev Wiki: Memory Map articles
  • Bran’s Kernel Development Tutorial
  • James Molloy’s Kernel Development Tutorial

Source Code Examples

  • Linux: arch/x86/boot/memory.c
  • GRUB: grub-core/kern/i386/pc/mmap.c
  • SerenityOS: Kernel/Boot/boot.S

11. Self-Assessment Checklist

Before moving to Project 3, verify:

  • Can you explain why E820 is needed?
  • Can you set up the E820 call registers correctly from memory?
  • Can you explain the continuation mechanism?
  • Can you print 64-bit numbers in hex?
  • Can you identify all 5 memory types?
  • Have you tested with at least 3 different memory sizes?
  • Can you compare your output to Linux’s dmesg e820?
  • Can you debug a failed E820 call systematically?

12. Submission / Completion Criteria

Your implementation is complete when:

  • E820 detection loop works correctly
  • All memory regions are displayed
  • 64-bit addresses show correctly (not truncated)
  • Memory types are labeled (at least Usable vs Reserved)
  • Total usable memory is calculated
  • Works with QEMU -m 128M, -m 512M, -m 2G
  • Handles E820 not supported gracefully
  • Output matches expected format
  • Code is commented

Congratulations! You now understand physical memory detection - a fundamental skill for any systems programmer. You’ve implemented exactly what GRUB and Linux do during early boot. Proceed to Project 3 to learn about Protected Mode transition!