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
-
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.
-
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.
-
Virtualization Understanding: Virtual machines (QEMU, VirtualBox) simulate E820 responses. Understanding this helps you debug VM memory configuration.
-
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:
- Calls INT 15h E820 to query the memory map
- Iterates through all memory regions
- Displays each region’s base address, length, and type
- Shows human-readable type names (Usable, Reserved, ACPI, etc.)
- 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
- E820 Caller: Sets up registers, calls INT 15h, handles errors and continuation
- Entry Parser: Reads 64-bit base, 64-bit length, 32-bit type from buffer
- Hex Printer: Converts binary to ASCII hex, handles 64-bit values
- Type Decoder: Maps type codes to human-readable strings
- 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:
- E820 Call Setup: What value goes in EDX when calling E820?
- Answer: 0x534D4150 (‘SMAP’)
- Entry Structure: At what offset is the memory type in an E820 entry?
- Answer: Offset 16 (after 8-byte base and 8-byte length)
- Continuation: How do you know you’ve received the last entry?
- Answer: EBX returns 0
- Error Detection: How do you detect if E820 is unsupported?
- Answer: Carry flag set after INT 15h
- 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
- How do you call INT 15h E820 correctly?
- What values go in which registers?
- Where do you store the returned data?
- How do you handle the continuation value?
- Where do you store EBX between calls?
- How do you know when to stop?
- How do you display 64-bit numbers?
- Big-endian or little-endian display?
- How do you convert nibbles to hex digits?
- How do you identify memory types?
- Where is the type field?
- What do type codes 1-5 mean?
- 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
- “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.
- “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.
- “How does the bootloader know it’s received all regions?”
- Answer: BIOS returns EBX=0 after the last entry.
Intermediate Questions
- “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.
- “What happens if you load the kernel into a reserved region?”
- Answer: Best case: crash. Worst case: corruption of BIOS data, hardware malfunction.
- “How do you handle overlapping E820 regions?”
- Answer: Some BIOSes return overlaps. Merge them, giving priority to more restrictive types (Reserved > Usable).
Advanced Questions
- “Why does E820 require EDX=’SMAP’?”
- Answer: It’s a signature for backward compatibility, ensuring this variant of INT 15h is being called correctly.
- “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.
- “How would you implement this on UEFI?”
- Answer: UEFI uses
GetMemoryMap()boot service, returningEFI_MEMORY_DESCRIPTORarray - different structure, same concept.
- Answer: UEFI uses
Tricky Questions
- “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.
- “How do you handle zero-length regions?”
- Answer: Skip them - invalid entries from buggy BIOSes.
- “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:
- OSDev Wiki: Detecting Memory
- OSDev Wiki: Memory Map
- Ralf Brown’s Interrupt List - INT 15h details
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
-
Buffer Location: Place buffer after code, before signature. Or use memory above 0x7E00.
-
Print Format: Full 16-digit hex (leading zeros) is clearer than truncated. Example: 0x0000000000100000 not 0x100000.
- 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
-
Error Handling: If E820 fails, show clear error. Don’t silently continue.
- 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, axbefore 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 .errorafter 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!