Project 3: Real Mode to Protected Mode Transition
Master the critical CPU mode transition that every modern x86 operating system must perform - switching from 16-bit Real Mode to 32-bit Protected Mode, unlocking 4GB address space and memory protection.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | ★★★★☆ Expert |
| Time Estimate | 2-3 weeks |
| Language | x86 Assembly (NASM) |
| Prerequisites | Projects 1-2, understanding of memory segmentation concept |
| Key Topics | Global Descriptor Table (GDT), A20 gate, CR0 control register, far jumps, pipeline flushing, VGA memory-mapped I/O |
1. Learning Objectives
After completing this project, you will be able to:
- Explain the architectural differences between Real Mode and Protected Mode
- Construct and load a Global Descriptor Table (GDT) with proper segment descriptors
- Enable the A20 gate using multiple methods (BIOS, Fast A20, keyboard controller)
- Manipulate CPU control registers to enable Protected Mode
- Execute the critical far jump to flush the instruction pipeline
- Write directly to VGA memory (0xB8000) for output in Protected Mode
- Debug triple faults using QEMU and Bochs debuggers
- Understand x86 privilege rings and segment selectors
2. Theoretical Foundation
2.1 Core Concepts
The Transition Challenge
When an x86 CPU powers on, it awakens in Real Mode - a 16-bit compatibility mode that behaves like the original 8086 from 1978. This mode has severe limitations:
- Only 1MB addressable memory (20-bit addresses)
- No memory protection (any code can access any memory)
- No privilege levels (everything runs at maximum privilege)
- 16-bit registers limit computational capability
Protected Mode unlocks the full power of modern x86:
- 4GB addressable memory (32-bit addresses)
- Memory protection via segment limits and privilege levels
- Hardware-enforced privilege rings (Ring 0-3)
- 32-bit registers and operations
- Foundation for virtual memory (paging)
┌─────────────────────────────────────────────────────────────────────────────┐
│ MODE TRANSITION OVERVIEW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ REAL MODE PROTECTED MODE │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 16-bit │ │ 32-bit │ │
│ │ 1MB memory │ │ 4GB memory │ │
│ │ Segment:Offset │ ────────► │ Selector:Offset │ │
│ │ No protection │ │ Ring protection │ │
│ │ BIOS available │ │ No BIOS! │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ TRANSITION STEPS: │
│ 1. Disable interrupts (CLI) │
│ 2. Load GDT (LGDT instruction) │
│ 3. Enable A20 gate │
│ 4. Set PE bit in CR0 │
│ 5. Far jump to flush pipeline │
│ 6. Reload segment registers │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Global Descriptor Table (GDT)
The GDT is a table in memory that defines memory segments in Protected Mode. Each entry (8 bytes) describes a segment’s base address, limit (size), and access rights.
Why it’s required: In Protected Mode, segment registers (CS, DS, SS, ES) no longer hold physical addresses - they hold selectors (indices into the GDT). The CPU uses the GDT to translate these selectors into actual memory access rules.
GDT Entry Structure (8 bytes):
┌─────────────────────────────────────────────────────────────────────────────┐
│ GDT DESCRIPTOR STRUCTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Byte 7 Byte 6 Byte 5 Byte 4 Bytes 3-2 Bytes 1-0 │
│ ┌────────┬────────┬────────┬────────┬────────────┬────────────┐ │
│ │ Base │ Flags │ Access │ Base │ Base │ Limit │ │
│ │ 31:24 │ Limit │ Byte │ 23:16 │ 15:0 │ 15:0 │ │
│ │ │ 19:16 │ │ │ │ │ │
│ └────────┴────────┴────────┴────────┴────────────┴────────────┘ │
│ │
│ ACCESS BYTE (Byte 5): │
│ ┌───┬───┬───┬───┬───┬───┬───┬───┐ │
│ │ P │ DPL │ S │ E │ DC│ RW│ A │ │
│ └───┴───────┴───┴───┴───┴───┴───┘ │
│ P = Present (1 = valid segment) │
│ DPL = Descriptor Privilege Level (0-3, Ring level) │
│ S = Descriptor Type (1 = code/data, 0 = system) │
│ E = Executable (1 = code, 0 = data) │
│ DC = Direction/Conforming │
│ RW = Readable (code) / Writable (data) │
│ A = Accessed (set by CPU) │
│ │
│ FLAGS (High nibble of Byte 6): │
│ ┌───┬───┬───┬───┐ │
│ │ G │ D │ L │AVL│ │
│ └───┴───┴───┴───┘ │
│ G = Granularity (1 = 4KB pages, 0 = 1 byte) │
│ D = Default operation size (1 = 32-bit, 0 = 16-bit) │
│ L = Long mode (1 = 64-bit, must be 0 for 32-bit) │
│ AVL = Available for system use │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Minimum GDT for Protected Mode:
| Index | Offset | Descriptor | Purpose |
|---|---|---|---|
| 0 | 0x00 | NULL Descriptor | Required by CPU (all zeros) |
| 1 | 0x08 | Code Segment | Base=0, Limit=4GB, Execute/Read, Ring 0 |
| 2 | 0x10 | Data Segment | Base=0, Limit=4GB, Read/Write, Ring 0 |
Segment Selectors: After mode switch, you load selectors (not addresses) into segment registers:
- CS = 0x08 (index 1, code segment)
- DS = ES = FS = GS = SS = 0x10 (index 2, data segment)
The A20 Gate - Historical Absurdity
One of the most bizarre aspects of x86 boot programming is the A20 line. Understanding why it exists helps appreciate x86’s backwards-compatibility obsession.
The Problem (1981): The original 8086 had 20 address lines (A0-A19) = 1MB addressable. Some programs exploited a “feature”: addresses above 0xFFFFF would WRAP AROUND to the beginning due to 20-bit limitation.
The Disaster (1982): The 80286 had 24 address lines (A0-A23) = 16MB addressable. Now 0x100000 was a REAL address, not a wrap! Old programs that depended on wrap-around BROKE.
IBM’s “Solution” (1984): Add a gate to FORCE address line A20 to zero, simulating wrap-around! They connected this gate to… the KEYBOARD CONTROLLER (8042). Why? It had a spare pin. Seriously.
┌─────────────────────────────────────────────────────────────────────────────┐
│ A20 GATE DIAGRAM │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Address Lines: A23 A22 A21 A20 A19 A18 ... A1 A0 │
│ │ │ │ │ │
│ │ │ │ └─── A20 Gate ─── Keyboard Controller │
│ │ │ │ │ │
│ │ │ │ ┌───┴───┐ │
│ │ │ │ │ AND │ │
│ │ │ │ │ Gate │ │
│ │ │ │ └───┬───┘ │
│ │ │ │ │ │
│ │ │ │ ▼ │
│ │ │ └────────► Memory │
│ │
│ A20 Gate = 0: A20 line forced to 0 (wrap-around mode) │
│ A20 Gate = 1: A20 line passes through (normal operation) │
│ │
│ WITH A20 DISABLED: │
│ • 0x100000 → 0x000000 (wrap!) │
│ • 0x100001 → 0x000001 (wrap!) │
│ • Every odd megabyte maps to even megabyte │
│ │
│ Memory appears like this: │
│ 0x000000 ─┬─ First megabyte │
│ 0x0FFFFF ─┘ │
│ 0x100000 ─┬─ Mirrors first megabyte! (if A20 disabled) │
│ 0x1FFFFF ─┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Why you MUST enable A20: Without it, your kernel at 0x100000 would actually be at 0x000000, corrupting BIOS data structures.
Methods to enable A20:
| Method | How | Compatibility |
|---|---|---|
| BIOS INT 15h | AX=2401h, INT 15h | Most modern BIOSes |
| Fast A20 Gate | Port 0x92, bit 1 | Most modern systems |
| Keyboard Controller | Write to port 0x64/0x60 | Original, always works |
| UEFI | Already enabled! | UEFI systems |
CR0 Register and the PE Bit
CR0 (Control Register 0) is a 32-bit CPU register controlling operating modes:
CR0 Register:
┌────────────────────────────────────────────────────────────────┐
│ Bit 31 │ Bit 30 │ ... │ Bit 16 │ ... │ Bit 0 │
├────────┼────────┼─────┼────────┼─────┼────────────────────────┤
│ PG │ CD │ │ WP │ │ PE │
└────────┴────────┴─────┴────────┴─────┴────────────────────────┘
PE (Bit 0): Protection Enable
• PE = 0: Real Mode
• PE = 1: Protected Mode
PG (Bit 31): Paging Enable
• PG = 0: Paging disabled
• PG = 1: Paging enabled
The mode switch is literally:
mov eax, cr0
or eax, 1 ; Set bit 0 (PE)
mov cr0, eax
But this only changes internal CPU state - segment registers still hold real-mode values. That’s why the far jump is required immediately after.
Far Jumps and Pipeline Flushing
A far jump (jmp segment:offset) loads a new value into CS and jumps to a new address. After setting PE, you must do:
jmp 0x08:protected_mode_label
Where 0x08 is the GDT code segment selector.
Why it’s required: The CPU’s prefetch queue still contains real-mode instructions after setting PE. A far jump flushes this queue and reloads CS with a protected-mode selector, forcing the CPU to re-decode instructions using the new segment descriptor.
VGA Memory-Mapped I/O at 0xB8000
In Protected Mode, BIOS interrupts (like int 0x10 for printing) no longer work - they’re real-mode code. VGA memory-mapped I/O is your proof that protected mode is working.
┌─────────────────────────────────────────────────────────────────────────────┐
│ VGA TEXT MODE MEMORY MAP │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ VGA TEXT BUFFER at 0xB8000: │
│ │
│ 80 columns × 25 rows = 2000 characters │
│ Each character = 2 bytes (character + attribute) │
│ Total buffer size = 4000 bytes │
│ │
│ Memory layout: │
│ 0xB8000 ┌────┬────┬────┬────┬────┬────┬────┬────┬─...─┐ │
│ │Chr │Attr│Chr │Attr│Chr │Attr│Chr │Attr│ │ │
│ │ 0 │ 0 │ 1 │ 1 │ 2 │ 2 │ 3 │ 3 │ │ │
│ └────┴────┴────┴────┴────┴────┴────┴────┴─...─┘ │
│ Row 0, Col 0 ─────────────────────────► Row 0, Col 79 │
│ │
│ Attribute byte: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Bit 7 │ Bits 6-4 │ Bit 3 │ Bits 2-0 │ │
│ ├─────────┼───────────────┼─────────┼────────────────────┤ │
│ │ Blink │ Background │ Bright │ Foreground │ │
│ │ │ color (0-7) │ │ color (0-7) │ │
│ └─────────┴───────────────┴─────────┴────────────────────┘ │
│ │
│ COLOR CODES: │
│ ┌────┬─────────────┬────┬──────────────────┐ │
│ │ 0 │ Black │ 8 │ Dark Gray │ │
│ │ 1 │ Blue │ 9 │ Light Blue │ │
│ │ 2 │ Green │ 10 │ Light Green │ │
│ │ 3 │ Cyan │ 11 │ Light Cyan │ │
│ │ 4 │ Red │ 12 │ Light Red │ │
│ │ 5 │ Magenta │ 13 │ Light Magenta │ │
│ │ 6 │ Brown │ 14 │ Yellow │ │
│ │ 7 │ Light Gray │ 15 │ White │ │
│ └────┴─────────────┴────┴──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Example: Print ‘P’ in white on blue at top-left:
mov edi, 0xB8000 ; VGA text memory base
mov al, 'P' ; Character
mov ah, 0x1F ; Attribute: white (F) on blue (1)
mov [edi], ax ; Write character + attribute
2.2 Why This Matters
This project matters because:
- Every modern x86 OS performs this transition: Windows, Linux, macOS (on Intel) - all must switch from Real Mode to Protected Mode during boot
- Security foundation: Protected Mode’s privilege rings are the basis for kernel/user mode separation
- Architecture understanding: This is the boundary between legacy 16-bit and modern 32-bit computing
- Debugging skills: Triple faults are notoriously hard to debug - mastering this builds essential low-level debugging skills
- Interview differentiator: Few developers understand CPU mode transitions at this level
2.3 Historical Context
The transition from Real Mode to Protected Mode reflects 40+ years of x86 evolution:
- 1978: Intel 8086 introduces 16-bit Real Mode (1MB address space)
- 1982: Intel 80286 adds Protected Mode, but it’s not easily switchable back to Real Mode
- 1985: Intel 80386 introduces true 32-bit Protected Mode with paging, and ability to switch modes freely
- 1984: IBM introduces A20 gate hack to maintain 8086 compatibility
- 2003: AMD64/x86-64 introduces Long Mode (64-bit), requiring Protected Mode as stepping stone
Every x86 system today still powers on in Real Mode for backward compatibility, then transitions through Protected Mode (and optionally Long Mode) during boot.
2.4 Common Misconceptions
-
“Protected Mode is just about 32-bit registers”: Wrong - it fundamentally changes how memory addressing works, introduces privilege levels, and enables hardware-enforced security boundaries.
-
“The far jump is just for changing code location”: Wrong - it’s specifically required to flush the CPU’s instruction prefetch queue and load a Protected Mode code segment selector into CS.
-
“BIOS interrupts work in Protected Mode if you set things up right”: Wrong - BIOS code is 16-bit Real Mode code that expects segment:offset addressing. It cannot run in Protected Mode.
-
“A20 is always enabled on modern systems”: Not guaranteed - many BIOSes disable it for compatibility. Always enable it explicitly.
-
“The GDT null descriptor is just padding”: Wrong - it’s required by the CPU architecture. Loading a null selector (0x0000) generates a fault, protecting against uninitialized segments.
3. Project Specification
3.1 What You Will Build
A bootloader that:
- Starts in 16-bit Real Mode (loaded at 0x7C00 by BIOS)
- Prints status messages using BIOS interrupts
- Sets up a minimal Global Descriptor Table (GDT)
- Enables the A20 gate
- Switches to 32-bit Protected Mode
- Prints a welcome message using direct VGA memory writes
- Demonstrates the mode transition visually (different colors before/after)
3.2 Functional Requirements
| Requirement | Description |
|---|---|
| FR-1 | Boot from MBR (512 bytes with 0xAA55 signature) |
| FR-2 | Display “[Real Mode]” messages using INT 10h before transition |
| FR-3 | Load GDT with null, code, and data segment descriptors |
| FR-4 | Enable A20 gate (at least two methods with fallback) |
| FR-5 | Set PE bit in CR0 to enter Protected Mode |
| FR-6 | Execute far jump to flush instruction pipeline |
| FR-7 | Reload all segment registers with Protected Mode selectors |
| FR-8 | Display “[Protected Mode]” message using VGA memory at 0xB8000 |
| FR-9 | Use different text colors before/after transition to show mode change |
| FR-10 | Halt cleanly (CLI + HLT loop) after displaying message |
3.3 Non-Functional Requirements
| Requirement | Description |
|---|---|
| NFR-1 | Total binary size must be exactly 512 bytes |
| NFR-2 | Must work in QEMU, Bochs, and VirtualBox |
| NFR-3 | Should work on real hardware in Legacy BIOS mode |
| NFR-4 | Code must be well-commented for educational purposes |
| NFR-5 | A20 enable must verify success before proceeding |
3.4 Example Usage / Output
# Assemble the bootloader
$ nasm -f bin pm_boot.asm -o pm_boot.bin
# Verify size
$ ls -l pm_boot.bin
-rw-r--r-- 1 user user 512 Jan 15 10:30 pm_boot.bin
# Verify boot signature
$ hexdump -C pm_boot.bin | tail -n 1
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa
# Run in QEMU
$ qemu-system-x86_64 -drive format=raw,file=pm_boot.bin
3.5 Real World Outcome
When you run the bootloader in QEMU, you’ll see:
[Real Mode] Starting bootloader...
[Real Mode] GDT loaded
[Real Mode] A20 enabled
[Switching to Protected Mode...]
[Protected Mode] Welcome to 32-bit land!
The visual transition from BIOS-interrupt printing (perhaps gray text) to direct VGA memory writes (perhaps bright green or white text) is your proof that you’ve crossed the boundary. The text color change makes the transition obvious and verifiable.
4. Solution Architecture
4.1 High-Level Design
┌─────────────────────────────────────────────────────────────────────────────┐
│ PROTECTED MODE BOOTLOADER ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ MEMORY LAYOUT: │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ 0x0000 - 0x04FF │ BIOS IVT + Data Area │ │
│ ├──────────────────┼─────────────────────────────────────────────────────┤ │
│ │ 0x0500 - 0x7BFF │ Stack area (grows down) │ │
│ ├──────────────────┼─────────────────────────────────────────────────────┤ │
│ │ 0x7C00 - 0x7DFF │ Our bootloader (512 bytes) │ │
│ ├──────────────────┼─────────────────────────────────────────────────────┤ │
│ │ 0x7E00 - onwards │ Free (for future stage 2) │ │
│ ├──────────────────┼─────────────────────────────────────────────────────┤ │
│ │ 0xB8000 │ VGA text buffer (Protected Mode output) │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │
│ EXECUTION FLOW: │
│ ┌────────────────┐ │
│ │ 1. Real Mode │ │
│ │ Entry │ │
│ └───────┬────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ 2. Init Segs │────►│ 3. Print Real │ │
│ │ & Stack │ │ Mode Msg │ │
│ └────────────────┘ └───────┬────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ 4. Load GDT │────►│ 5. Enable A20 │────►│ 6. Set CR0.PE │ │
│ │ (LGDT) │ │ Gate │ │ = 1 │ │
│ └────────────────┘ └────────────────┘ └───────┬────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ 7. Far Jump │ │
│ │ jmp 0x08:PM │ │
│ └───────┬────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ 10. Halt │◄────│ 9. Print PM │◄────│ 8. Load Data │ │
│ │ (HLT) │ │ Message │ │ Segments │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
4.2 Key Components
| Component | Description | Size |
|---|---|---|
| Real Mode Entry | Segment/stack init, INT 10h printing | ~50 bytes |
| GDT | Null + Code + Data descriptors | 24 bytes |
| GDT Pointer | Size and base address for LGDT | 6 bytes |
| A20 Enable | Multiple methods with verification | ~40 bytes |
| Mode Switch | CR0 manipulation + far jump | ~15 bytes |
| Protected Mode Code | Segment reload + VGA output | ~60 bytes |
| Data | Message strings | ~80 bytes |
| Padding + Signature | Fill to 512 bytes + 0xAA55 | Variable + 2 bytes |
4.3 Data Structures
GDT Pointer (GDTR):
struct gdt_ptr {
uint16_t limit; // Size of GDT - 1
uint32_t base; // Linear address of GDT
} __attribute__((packed));
GDT Entries:
Offset 0x00: NULL Descriptor (8 bytes of 0x00)
Offset 0x08: Code Segment (Base=0, Limit=0xFFFFF, Access=0x9A, Flags=0xCF)
Offset 0x10: Data Segment (Base=0, Limit=0xFFFFF, Access=0x92, Flags=0xCF)
4.4 Algorithm Overview
- Initialize Real Mode environment: Set segment registers to 0, set up stack
- Print status: Use INT 10h teletype to show Real Mode is active
- Build GDT: Create flat memory model (base=0, limit=4GB)
- Load GDT: Use LGDT instruction with pointer to GDT
- Enable A20: Try BIOS method first, fall back to Fast A20
- Disable interrupts: CLI before mode switch
- Set PE bit: Read CR0, set bit 0, write CR0
- Far jump:
jmp 0x08:protected_mode_startto flush pipeline - Reload segments: Set DS, SS, ES, FS, GS to data selector (0x10)
- VGA output: Write directly to 0xB8000 with character+attribute pairs
- Halt: Infinite HLT loop
5. Implementation Guide
5.1 Development Environment Setup
Required Tools:
# macOS
brew install nasm qemu
# Ubuntu/Debian
sudo apt install nasm qemu-system-x86
# Verify installation
nasm -version # Should be 2.14+
qemu-system-x86_64 --version # Should be 6.0+
Recommended Tools:
- Bochs: For detailed debugging (
brew install bochsorapt install bochs) - Hex editor: For binary inspection (Hex Fiend on macOS, HxD on Windows)
- VS Code: With x86 Assembly extension for syntax highlighting
5.2 Project Structure
pm_boot/
├── pm_boot.asm # Main bootloader source
├── Makefile # Build automation
├── run.sh # QEMU launch script
├── debug.sh # QEMU with GDB server
└── bochsrc.txt # Bochs configuration (optional)
Makefile:
all: pm_boot.bin
pm_boot.bin: pm_boot.asm
nasm -f bin $< -o $@
run: pm_boot.bin
qemu-system-x86_64 -drive format=raw,file=$<
debug: pm_boot.bin
qemu-system-x86_64 -s -S -drive format=raw,file=$<
clean:
rm -f pm_boot.bin
5.3 The Core Question You’re Answering
“How does the CPU transition from 16-bit Real Mode to 32-bit Protected Mode, and why is this one of the most critical moments in the boot process?”
This is about understanding the architectural boundary between legacy 16-bit computing and modern 32-bit computing. In real mode, the CPU behaves like an 8086 from 1978 - 20-bit addresses (1MB limit), no memory protection, direct hardware access. Protected mode unlocks the full 32-bit register width, 4GB address space, privilege levels, and virtual memory support.
But the transition isn’t just flipping a switch - it requires carefully setting up memory segmentation descriptors, dealing with a bizarre hardware hack (the A20 line), manipulating control registers, and flushing the CPU pipeline. Get any step wrong and you get a triple fault (CPU reset). This project forces you to understand x86 architecture at the register level.
5.4 Concepts You Must Understand First
Before writing code, verify you understand these by answering the self-check questions:
| Concept | Self-Check Question | Book Reference |
|---|---|---|
| GDT Structure | Can you explain what each byte in an 8-byte GDT entry represents? | “Low-Level Programming” by Igor Zhirkov, Ch. 4, pp. 147-165 |
| Segment Selectors | What’s the difference between selector 0x08 and 0x10? | Intel SDM Vol. 3A, Section 3.4.2 |
| A20 Gate | Why would memory at 0x100000 wrap to 0x000000? | OSDev Wiki: A20 Line |
| CR0 Register | What does setting bit 0 in CR0 do? | Intel SDM Vol. 3A, Section 2.5 |
| Far Jumps | Why can’t you use a near jump after setting PE? | “Modern X86 Assembly” by Kusswurm, pp. 67-69 |
| VGA Memory | At what address is the VGA text buffer? | “Write Great Code Vol. 2” by Hyde, Ch. 8 |
5.5 Questions to Guide Your Design
As you implement the mode transition, ask yourself:
- How do you construct a GDT entry?
- What are the bit positions for base address, limit, access byte, flags?
- Why does the limit need to be page-granular (4KB chunks) for a flat memory model?
- What does the access byte 0x9A mean for code segments vs 0x92 for data segments?
- Why is a far jump required after setting the PE bit in CR0?
- What happens to the CPU’s instruction prefetch queue?
- Why doesn’t a near jump work?
- What does the selector 0x08 represent in the far jump instruction?
- How do you verify that A20 is actually enabled?
- Can you write a test that detects memory wraparound?
- What are the different methods to enable A20 (Fast A20, keyboard controller, BIOS)?
- What happens if you skip A20 enabling?
- Why can’t you use BIOS interrupts in protected mode?
- Where does the BIOS code live in memory?
- What mode is BIOS code compiled for?
- What would happen if you tried
int 0x10after mode switch?
- What causes a triple fault during mode transition?
- What happens if the GDT base address is wrong?
- What if you don’t set up stack segment (SS) correctly?
- Why does misaligned GDT cause issues?
5.6 Thinking Exercise
Before writing any code, perform this mental exercise:
Trace the CPU State Through Mode Transition
Draw two columns: “Real Mode State” and “Protected Mode State”. Track these values through the transition:
- CS register: Real mode value -> Selector value after far jump
- CR0 register: PE bit 0 -> PE bit 1
- GDTR register: Uninitialized -> Points to your GDT base address
- Memory access at 0x100000: What happens if A20 disabled vs enabled?
- Stack pointer (SS:SP): How does the stack segment descriptor translate this?
Then, draw the GDT structure:
Offset 0x00: Null Descriptor (all zeros, 8 bytes)
Offset 0x08: Code Segment Descriptor (8 bytes)
Offset 0x10: Data Segment Descriptor (8 bytes)
For each descriptor, label the bit fields:
- Base address (bits where?)
- Limit (bits where?)
- Access byte (bit 40-47): P (present), DPL (privilege), S (descriptor type), Type
- Flags (bit 52-55): G (granularity), D/B (default operation size), L (64-bit), AVL
Can you construct a valid code segment descriptor by hand?
- Base = 0x00000000 (flat memory)
- Limit = 0xFFFFF (4GB with page granularity)
- Access = 0x9A (present, ring 0, code, executable, readable)
- Flags = 0xC (page granularity, 32-bit)
Convert this to the weird split-field format Intel uses for descriptors.
5.7 Hints in Layers
If you get stuck, reveal hints progressively:
Hint 1: GDT Structure (Conceptual)
Your GDT needs exactly three 8-byte entries:
- Null descriptor (offset 0x00): All 8 bytes are 0x00
- Code segment descriptor (offset 0x08): Base=0, Limit=0xFFFFF, Access=0x9A, Flags=0xCF
- Data segment descriptor (offset 0x10): Base=0, Limit=0xFFFFF, Access=0x92, Flags=0xCF
These create a “flat memory model” - code and data segments cover the entire 4GB address space with page granularity (4KB chunks).
Hint 2: A20 Enabling Methods (Practical)
Three methods to enable A20, in order of preference:
- Fast A20 (modern): Write 0x02 to port 0x92
- Keyboard controller (traditional): Send command 0xD1 to port 0x64, then 0xDF to port 0x60
- BIOS (easiest but slow):
int 0x15, ax=0x2401
Use method 3 first (BIOS) for quick success, then implement method 1 (Fast A20) to understand hardware I/O.
Hint 3: The Mode Switch Sequence (Step-by-Step)
; 1. Disable interrupts (they'll break in protected mode without IDT)
cli
; 2. Load GDT
lgdt [gdt_descriptor] ; gdt_descriptor has size-1 and address
; 3. Enable A20
; (use one of the methods from Hint 2)
; 4. Set PE bit in CR0
mov eax, cr0
or eax, 1 ; Set bit 0 (PE)
mov cr0, eax
; 5. Far jump to flush pipeline and load CS
jmp 0x08:protected_mode_start ; 0x08 = code segment selector
[bits 32]
protected_mode_start:
; 6. Load data segments
mov ax, 0x10 ; 0x10 = data segment selector
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
Hint 4: VGA Direct Write (Code Example)
[bits 32]
print_protected:
mov edi, 0xB8000 ; VGA text memory
mov al, 'P' ; Character
mov ah, 0x0F ; Attribute: white on black
mov [edi], ax ; Write character+attribute
add edi, 2 ; Next character cell
mov al, 'M'
mov [edi], ax
; Continue for each character...
The attribute byte format:
- Bits 0-3: Foreground color (0xF = white)
- Bits 4-6: Background color (0x0 = black)
- Bit 7: Blink (usually ignored)
Hint 5: Common Pitfalls and Debugging (Troubleshooting)
- Triple fault immediately after mode switch:
- Check: Is GDT base address correct in
gdt_descriptor? - Check: Is GDT aligned properly? (Should be at least 2-byte aligned)
- Check: Did you forget the null descriptor at offset 0?
- Check: Is GDT base address correct in
- VGA memory write doesn’t show up:
- Are you in 32-bit mode? Check: Does
[bits 32]come after the far jump? - Is 0xB8000 correctly computed? In protected mode, use direct address, not segment:offset.
- Did you load DS with the data selector (0x10) before dereferencing?
- Are you in 32-bit mode? Check: Does
- System reboots randomly:
- A20 line likely disabled - memory corruption is causing crashes.
- Run A20 test: Write 0xAA to 0x000000, 0x55 to 0x100000, read 0x000000 - if it’s 0x55, A20 is off.
- QEMU works but real hardware doesn’t:
- QEMU is lenient. Real hardware requires:
- Proper GDT alignment
- A20 actually enabled (QEMU often ignores this)
- Correct segment limit calculations
- QEMU is lenient. Real hardware requires:
- Bochs debugger commands for diagnosis:
info gdt- Shows GDT contentsinfo registers- Shows CR0, segment registersx /10xb 0xB8000- Examine VGA memorytrace-on- Step-through execution- Break on exceptions:
catch #GP,catch #DF
5.8 The Interview Questions They’ll Ask
If you list this project on your resume, expect these questions:
- “What is the GDT and why is it required for protected mode?”
- What they’re testing: Understanding of x86 segmentation
- Good answer: “The GDT defines memory segments in protected mode. Each entry describes a segment’s base, limit, and access rights. The CPU uses segment selectors (loaded into CS, DS, etc.) as indices into the GDT to validate memory accesses. It’s required because protected mode segment registers don’t hold addresses - they hold selectors.”
- Red flag answer: “It’s just a table you need to set up.” (Too vague, no understanding of mechanism)
- “What happens if you don’t enable the A20 line?”
- What they’re testing: Knowledge of historical x86 quirks and debugging skills
- Good answer: “Without A20 enabled, address bit 20 is forced to zero, causing wraparound. Memory accesses to odd megabytes (1MB-2MB, 3MB-4MB, etc.) get aliased to even megabytes (0-1MB, 2MB-3MB). This silently corrupts memory access in protected mode. I’d detect it by writing different values to 0x000000 and 0x100000, then reading both - if they’re the same, A20 is off.”
- Red flag answer: “The bootloader crashes.” (No understanding of the specific failure mode)
- “Why does the far jump after setting the PE bit use a specific selector like 0x08?”
- What they’re testing: Understanding of segment selectors and pipeline behavior
- Good answer: “0x08 is the selector for the GDT code segment descriptor at offset 8 (index 1). The far jump serves two purposes: it loads CS with the protected-mode selector, and it flushes the CPU’s instruction prefetch queue, which still contains real-mode decoded instructions. Without this, the CPU would misinterpret subsequent instructions.”
- Red flag answer: “Because that’s the code segment.” (Doesn’t explain the selector encoding or why far jump is mandatory)
- “How do you print text in protected mode without BIOS interrupts?”
- What they’re testing: Understanding of memory-mapped I/O
- Good answer: “VGA text mode memory is at 0xB8000. Each character is 2 bytes: ASCII code and attribute byte. I set up a data segment descriptor covering this range, load it into DS, then write directly to [0xB8000]. For example, to print ‘A’ in white-on-black at the top-left, I write 0x41 (ASCII ‘A’) to 0xB8000 and 0x0F (white on black) to 0xB8001.”
- Red flag answer: “You can’t print in protected mode.” (Wrong - shows lack of resourcefulness)
- “What causes a triple fault during mode transition?”
- What they’re testing: Debugging experience and understanding of CPU exception handling
- Good answer: “A triple fault occurs when the CPU tries to handle an exception but generates another exception while doing so. Common causes: invalid GDT base address (causes GP fault when loading segment), no valid interrupt descriptor table yet (causes fault during exception delivery), stack issues. The CPU resets because it has no way to recover. I’d use Bochs debugger to catch the first fault.”
- Red flag answer: “The code is wrong.” (No diagnostic insight)
- “Why is the null descriptor at GDT offset 0 required?”
- What they’re testing: Attention to architectural details
- Good answer: “Intel requires it by specification. Loading a null selector (0x0000) into a segment register generates a fault, which protects against uninitialized segments. Also, it ensures that any selector calculation error that results in zero will fault rather than silently accessing memory.”
- Red flag answer: “I don’t know, but it’s always there in examples.” (Admits lack of curiosity)
5.9 Books That Will Help
| Concept | Book | Specific Chapters/Pages | Why It Helps |
|---|---|---|---|
| GDT Structure | “Low-Level Programming” by Igor Zhirkov | Chapter 4 (pp. 147-165) | Shows GDT setup in context of building an OS, with NASM examples |
| Segment Descriptors | Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A | Section 3.4.2-3.4.5 | The authoritative source - explains every bit field in descriptors |
| A20 Line History | “The Indispensable PC Hardware Book” by Hans-Peter Messmer | Chapter 6 | Historical context of why A20 exists and how 8042 keyboard controller controls it |
| Protected Mode Entry | “Modern X86 Assembly Language Programming” by Daniel Kusswurm | Chapter 4 (pp. 67-75) | Step-by-step mode switch with explanations of pipeline flushing |
| VGA Text Mode | “Write Great Code, Volume 2” by Randall Hyde | Chapter 8 (pp. 342-358) | Memory-mapped I/O concepts with VGA as example |
| Memory Segmentation | “Operating Systems: Three Easy Pieces” by Remzi H. Arpaci-Dusseau | Chapter 16 | High-level conceptual model before diving into x86 details |
| Bootloader Techniques | “Operating System Concepts” (10th ed.) by Silberschatz, Galvin, Gagne | Chapter 2.11 (Boot Process) | Context of where mode switch fits in overall boot sequence |
| x86 Assembly Basics | “Programming from the Ground Up” by Jonathan Bartlett | Chapters 4-6 | Assembly fundamentals if you’re new to x86 syntax |
| Debugging Bootloaders | OSDev Wiki - Bochs Debugger Guide | Online resource | Practical debugger commands for diagnosing triple faults |
Recommended Reading Order:
- Start with “Operating Systems: Three Easy Pieces” Chapter 16 for conceptual understanding of segmentation
- Read “Low-Level Programming” Chapter 4 for practical GDT implementation
- Keep Intel Manual Volume 3A, Section 3.4-3.5 open as reference for bit-level details
- Use OSDev Wiki for A20 and VGA specifics
- When stuck, consult Kusswurm’s book for the exact mode switch sequence
Pro tip: Print out Intel Manual Section 3.4.5 (Segment Descriptor Tables) and annotate it. The descriptor bit layout is crucial and you’ll reference it constantly.
5.10 Implementation Phases
Phase 1: Real Mode Foundation (Day 1-2)
- Set up project structure and build system
- Write basic Real Mode bootloader with segment initialization
- Implement print_string function using INT 10h
- Test: “Hello from Real Mode!” displays
Phase 2: GDT Construction (Day 3-4)
- Define GDT structure in assembly
- Create null, code, and data segment descriptors
- Build GDT pointer structure
- Load GDT with LGDT instruction
- Test: No crash after LGDT (doesn’t mean it’s correct yet!)
Phase 3: A20 Gate (Day 5-6)
- Implement BIOS A20 enable (INT 15h, AX=2401h)
- Implement Fast A20 method (port 0x92)
- Add A20 verification test
- Test: Write/read different addresses to confirm A20
Phase 4: Mode Switch (Day 7-10)
- Disable interrupts (CLI)
- Set PE bit in CR0
- Execute far jump with code selector
- Reload data segment registers
- Test: If system doesn’t triple fault, mode switch worked
Phase 5: Protected Mode Output (Day 11-14)
- Implement VGA direct write function
- Display “Protected Mode!” message
- Add visual differentiation (color change)
- Test: Both Real Mode and Protected Mode messages visible
Phase 6: Polish and Debug (Day 15+)
- Test in Bochs for strict validation
- Test on real hardware if available
- Add error handling and status codes
- Document and comment code
5.11 Key Implementation Decisions
-
GDT Location: Place GDT within the 512-byte bootloader or in separate memory? For simplicity, embed it in the bootloader binary.
-
A20 Method: Start with BIOS method for compatibility, add Fast A20 as primary. Always verify A20 is actually enabled.
-
Stack Location: Place stack at 0x7C00 (grows down from bootloader) or at 0x9000? 0x7C00 is simpler and provides ~30KB of stack space.
-
Error Handling: In 512 bytes, complex error messages are impossible. Use single characters (‘E’, ‘G’, ‘A’, ‘P’) as status indicators, or rely on visible progress (text appears = that stage worked).
-
Segment Model: Use flat model (base=0, limit=4GB) for simplicity. This is what modern OSes use anyway.
6. Testing Strategy
6.1 Unit Testing Approach
Since this is bare-metal code, traditional unit testing isn’t possible. Use staged verification:
| Stage | Test | Expected Result |
|---|---|---|
| 1 | Assemble and check size | Exactly 512 bytes |
| 2 | Check boot signature | Last 2 bytes are 0x55 0xAA |
| 3 | Boot in QEMU | Window opens, no “No bootable device” |
| 4 | Real Mode print | Text appears using BIOS |
| 5 | A20 test | Write 0x00 to 0x000000, 0xFF to 0x100000, read 0x000000 - should be 0x00 |
| 6 | Mode switch | No triple fault, system stable |
| 7 | VGA output | Text at different color appears |
6.2 Testing Commands
# Verify binary size and signature
ls -l pm_boot.bin # Should be 512 bytes
hexdump -C pm_boot.bin | tail -n 1 # Should end with 55 aa
# Test in QEMU (standard)
qemu-system-x86_64 -drive format=raw,file=pm_boot.bin
# Test in QEMU with monitor for debugging
qemu-system-x86_64 -drive format=raw,file=pm_boot.bin -monitor stdio
# (qemu) info registers # Check register values
# (qemu) x/20i 0x7C00 # Disassemble bootloader
# Test in QEMU with GDB
qemu-system-x86_64 -s -S -drive format=raw,file=pm_boot.bin &
gdb -ex "target remote :1234" -ex "break *0x7C00" -ex "continue"
# Test in Bochs (stricter)
bochs -f bochsrc.txt
6.3 Verification Checklist
- Binary is exactly 512 bytes
- Last two bytes are 0x55 0xAA (little-endian)
- Boots in QEMU without errors
- Real Mode message displays correctly
- GDT loads without crash
- A20 gate enables successfully
- Mode switch completes without triple fault
- Protected Mode message displays in different color
- System halts cleanly (doesn’t reboot)
- Works in Bochs (stricter emulation)
- (Optional) Works on real hardware in Legacy BIOS mode
7. Common Pitfalls & Debugging
Protected mode transition is notorious for causing triple faults - where the CPU resets because exception handling itself failed. Here are the most common issues and how to fix them:
Problem 1: “QEMU immediately reboots / resets when executing LGDT or the far jump”
Why: Triple fault caused by incorrect GDT base address or malformed GDT pointer structure
Common causes:
- GDT pointer structure not packed (compiler added padding, making it >6 bytes)
- GDT base address calculated incorrectly (using non-physical address)
- Loading GDTR while DS register points to wrong segment
Fix:
- Add
__attribute__((packed))in C or verify alignment in assembly - Set DS = 0x0000 before LGDT if using ORG 0x7C00
- Verify GDT pointer: 2 bytes for limit, 4 bytes for base = 6 bytes total
Quick test:
# In QEMU monitor:
(qemu) info registers # Check GDT base after LGDT
Problem 2: “Code runs after mode switch but crashes when accessing data”
Why: Segment registers still contain real mode values instead of protected mode selectors
Common causes:
- Forgot to reload DS, ES, FS, GS, SS after mode switch
- Using real mode segment values (0x0000) instead of GDT selector (0x10)
- Stack segment (SS) not updated, causing invalid stack operations
Fix: Immediately after far jump, load all segment registers with data selector (0x10):
mov ax, 0x10 ; Data selector
mov ds, ax
mov es, ax
mov ss, ax
mov fs, ax
mov gs, ax
mov esp, 0x90000 ; Set up new stack
Problem 3: “Screen goes blank after JMP to protected mode”
Why: VGA memory (0xB8000) accessed with wrong segment or A20 line not enabled
Common causes:
- A20 gate still disabled, causing address wraparound (0xB8000 -> 0x38000)
- Writing to text mode buffer with incorrect segment
Fix:
- Enable A20 before mode switch
- After mode switch, use flat addressing:
mov dword [0xB8000], 0x4F574F48 - Verify with:
in al, 0x92; test al, 2- if A20 bit not set, A20 is off
Problem 4: “Works in QEMU but triple faults on real hardware”
Why: QEMU is forgiving about timing and initialization order; real hardware is not
Common causes:
- CLI instruction missing (interrupts fire before IDT is set up)
- Cache/TLB not flushed after changing control registers
- BIOS left hardware in unexpected state
Fix:
- Add
clibefore mode switch - After setting CR0.PE, do far jump to flush pipeline
- After GDT load, add small delay:
jmp $+2; nop; nop
Problem 5: “Stack corruption / random crashes after mode switch”
Why: Stack pointer invalid or stack overlaps with code/data
Common causes:
- Forgot to set up ESP after mode switch
- Stack grows down into GDT, code, or video memory
- SS:SP from real mode still active (could be anywhere!)
Fix:
- Choose safe stack address (0x90000 is common, well below 1MB)
- Set ESP explicitly:
mov esp, 0x90000 - Ensure stack area doesn’t conflict with loaded code
Debugging Techniques
- Bochs with magic breakpoints:
xchg bx, bxtriggers Bochs debugger - QEMU with GDB:
qemu-system-i386 -s -S -drive file=pm_boot.bin,format=raw # In another terminal: gdb -ex "target remote :1234" -ex "break *0x7C00" - Print debugging: Write characters to VGA memory at each step:
mov byte [0xB8000], 'A' ; Step 1 reached ; ... more code ... mov byte [0xB8002], 'B' ; Step 2 reached - Triple fault detection: If QEMU/Bochs reboots instantly, triple fault occurred
- Register dumps: In GDB:
info registersshows all register values and current mode
Key Diagnostic Questions
- Does it work in QEMU but not Bochs? -> Likely relying on QEMU’s forgiving behavior
- Does it triple fault at LGDT? -> Check GDT pointer structure and DS value
- Does it triple fault at far jump? -> Verify CS selector and code segment descriptor
- Does it crash after mode switch? -> Check if all segment registers updated
- Do you see garbage on screen? -> A20 line or segment register issue
8. Extensions & Challenges
Once your basic Protected Mode bootloader works, try these extensions:
Extension 1: Multiple A20 Methods (Beginner)
Implement all three A20 enable methods with automatic fallback:
- Try BIOS INT 15h first
- Fall back to Fast A20 (port 0x92)
- Fall back to keyboard controller method
- Verify A20 is actually enabled after each attempt
Extension 2: Colored Status Messages (Beginner)
Use VGA attributes to show status:
- Real Mode messages in gray (0x07)
- “Switching…” message in yellow (0x0E)
- Protected Mode messages in bright green (0x0A)
- Error messages in red (0x04)
Extension 3: Memory Detection in Protected Mode (Intermediate)
Call E820 to detect memory BEFORE mode switch, store results, then display the memory map AFTER switching to Protected Mode using VGA writes.
Extension 4: Return to Real Mode (Advanced)
Implement the reverse transition: Protected Mode -> Real Mode. This requires:
- Disabling paging (if enabled)
- Loading Real Mode compatible GDT
- Clearing PE bit
- Far jump to Real Mode code
- This is how V86 mode and SMM work
Extension 5: Long Mode Entry (Expert)
Extend the bootloader to enter 64-bit Long Mode:
- Set up 4-level page tables (PML4)
- Enable PAE in CR4
- Set LME in EFER MSR
- Enable paging
- Far jump to 64-bit code segment
Extension 6: Basic Interrupt Handler (Expert)
Set up an Interrupt Descriptor Table (IDT) with:
- Division by zero handler
- General protection fault handler
- Timer interrupt handler Test by triggering each interrupt
9. Real-World Connections
How Production Bootloaders Handle This
GRUB 2:
- Stage 1 (boot.img, 512 bytes) loads Stage 1.5 (core.img)
- Stage 1.5 switches to Protected Mode and loads Stage 2
- Uses similar GDT structure but more sophisticated A20 handling
- Source:
grub-core/boot/i386/pc/boot.S
Linux Kernel:
- Real Mode kernel setup code at
arch/x86/boot/ - Protected Mode entry at
arch/x86/boot/pm.c - Uses
go_to_protected_mode()function - GDT defined in
arch/x86/boot/pmjump.S
Windows Boot Manager:
- Bootmgr transitions through Real -> Protected -> Long Mode
- Uses similar techniques but with Secure Boot verification
- Documented in Windows Internals books
Industry Applications
- Firmware Development: BIOS/UEFI engineers deal with mode transitions daily
- Hypervisor Development: VMware, Hyper-V, KVM all manage guest CPU mode switches
- Security Research: Boot-level malware (bootkits) exploit mode transition vulnerabilities
- Embedded Systems: Many x86 embedded devices run in Protected Mode without full OS
- Operating System Development: Every OS kernel starts from this foundation
10. Resources
Official Documentation
- Intel 64 and IA-32 SDM - Volume 3A, Chapters 2-3
- AMD64 Architecture Programmer’s Manual - Volume 2
Online Tutorials
- OSDev Wiki: Protected Mode - Essential reference
- OSDev Wiki: GDT - Detailed GDT explanation
- OSDev Wiki: A20 Line - A20 enable methods
- Brokenthorn OS Development - Step-by-step tutorials
Tools
- NASM - Assembler
- QEMU - Emulator for testing
- Bochs - Emulator with detailed debugging
- GDB - Debugger for QEMU
Books (Priority Order)
- “Low-Level Programming” by Igor Zhirkov - Best modern bootloader guide
- “Operating Systems: Three Easy Pieces” - Conceptual foundation (free online)
- “Write Great Code, Volume 2” - VGA and hardware access
- “Modern X86 Assembly Language Programming” - Detailed mode switch
11. Self-Assessment Checklist
Understanding Check
- I can explain why Real Mode only addresses 1MB (20-bit addressing)
- I can describe the purpose of each field in a GDT entry
- I understand why the null descriptor is required
- I can explain the A20 gate problem and its historical origin
- I know why a far jump is required after setting PE bit
- I understand why BIOS interrupts don’t work in Protected Mode
Implementation Check
- My GDT has null, code, and data segment descriptors
- My GDT pointer has correct size (limit) and base address
- I enable A20 and verify it’s actually enabled
- I disable interrupts before mode switch
- My far jump uses the correct code segment selector (0x08)
- I reload all data segment registers after mode switch
- I set up a valid stack pointer in Protected Mode
- I can write to VGA memory at 0xB8000
Testing Check
- Bootloader is exactly 512 bytes with 0xAA55 signature
- Works in QEMU without triple fault
- Real Mode message displays correctly
- Protected Mode message displays in different color
- Works in Bochs (stricter than QEMU)
- (Bonus) Works on real hardware
Interview Readiness
- I can whiteboard the mode switch sequence
- I can explain GDT structure and selectors
- I can debug a triple fault scenario
- I understand privilege rings and their purpose
- I can compare Real Mode vs Protected Mode vs Long Mode
12. Submission / Completion Criteria
Your project is complete when:
- Code Requirements:
- Single
pm_boot.asmfile (or equivalent) - Assembles to exactly 512 bytes
- Contains boot signature 0xAA55 at bytes 510-511
- Well-commented (explain each section’s purpose)
- Single
- Functionality Requirements:
- Displays Real Mode status message using INT 10h
- Loads GDT with minimum 3 descriptors
- Enables A20 gate with verification
- Switches to Protected Mode without triple fault
- Displays Protected Mode message using VGA memory
- Visual distinction between modes (different colors)
- Halts cleanly (doesn’t reboot)
- Testing Requirements:
- Works in QEMU x86_64
- Works in Bochs (if available)
- Passes all verification checks in Testing Strategy section
- Documentation Requirements:
- README explaining how to build and run
- Code comments explaining non-obvious decisions
- (Optional) Memory map diagram showing layout
- Learning Verification:
- Can answer 4/6 interview questions without reference
- Can explain GDT structure from memory
- Can describe A20 problem and solution
- Understand why far jump is mandatory
Bonus Achievements:
- Implemented all three A20 methods with fallback
- Returns to Real Mode successfully
- Displays E820 memory map in Protected Mode
- Works on real hardware (with evidence)
Summary
This project teaches one of the most fundamental transitions in x86 computing: the switch from Real Mode to Protected Mode. You’ve learned:
- Architecture: How the x86 CPU evolved from 16-bit to 32-bit operation
- Segmentation: The GDT structure and how segment selectors work
- Historical quirks: The A20 gate and why it exists
- CPU control: Manipulating CR0 and the importance of pipeline flushing
- Low-level I/O: Direct VGA memory access without OS support
- Debugging: Diagnosing and fixing triple faults
This foundation is essential for understanding:
- Operating system development
- Hypervisor technology
- Firmware and BIOS programming
- Security research at the boot level
- 64-bit Long Mode transitions
Next Steps: Project 4 (Two-Stage Bootloader) builds on this by loading additional code from disk, allowing you to break free from the 512-byte constraint and write more sophisticated boot code.