Summary
A protected‑mode jump to a kernel loaded at 0x00010000 failed silently, leaving VirtualBox on a black screen. The root cause was a combination of incorrect segment usage, invalid far jumps, and loading the kernel to the wrong physical address relative to the GDT and protected‑mode setup.
Root Cause
The failure came from several interacting issues:
- The kernel was loaded using BIOS INT 13h in real mode, but the segment/offset calculation did not match the intended physical address.
- The far jump
jmp 0x08:0x00010000is invalid, because the offset in a protected‑mode far jump is a linear address, not a physical address, and must be reachable through paging/segmentation rules. - The GDT descriptors did not map the linear address 0x00010000, so the CPU jumped into unmapped memory.
- The kernel was assembled with
[org 0x10000], but the bootloader loaded it to0x1000:0000(physical 0x10000), which is correct — but only in real mode. After switching to protected mode, segmentation rules changed. - The protected‑mode entry point was executed before the kernel was guaranteed to be in the correct linear address space.
Why This Happens in Real Systems
These mistakes are extremely common in early OS development:
- Segment arithmetic changes completely when switching from real mode to protected mode.
- Physical vs. linear vs. logical addresses are easy to confuse.
- GDT descriptors must be aligned with the intended memory layout, or jumps land in undefined memory.
- BIOS disk reads always load to physical memory, but protected mode uses linear addressing.
Real-World Impact
These issues typically cause:
- Black screens (execution jumps into unmapped memory)
- Triple faults (CPU resets instantly, often invisible in VMs)
- Silent hangs (CPU executes garbage instructions)
- No VGA output (kernel never runs)
Example or Code (if necessary and relevant)
Below is a minimal example of a correct protected‑mode jump to a kernel at physical 0x00100000 (1 MB), which is the conventional location:
; Load kernel to 0x00100000 in real mode
mov ax, 0x1000
mov es, ax
mov bx, 0x0000
int 0x13
; After enabling protected mode:
jmp 0x08:0x00100000
This works only if the GDT code segment base is 0x00000000 and the limit covers at least 0x00100000.
How Senior Engineers Fix It
Experienced OS engineers solve this by enforcing strict address discipline:
- Load the kernel to a well‑defined physical address (commonly 1 MB).
- Use a flat GDT where base = 0x00000000 and limit = 0xFFFFFFFF.
- Ensure the kernel’s ORG matches the physical load address.
- Perform the far jump only after protected mode is fully enabled.
- Verify BIOS disk reads actually succeed (carry flag check).
- Print debug characters before and after each major step.
A correct protected‑mode jump looks like:
- GDT base = 0
- Code segment selector = 0x08
- Kernel linear address = physical address
- Kernel ORG = same physical address
Why Juniors Miss It
Beginners typically overlook:
- The difference between physical, linear, and logical addresses
- That far jumps in protected mode use linear addresses
- That GDT descriptors define the CPU’s view of memory
- That BIOS loads sectors into physical memory only
- That ORG does not change where the code is actually loaded
They often assume “jump to 0x00010000” means the same thing in both real mode and protected mode — but it doesn’t.
If you want, I can walk through how to restructure AtomOS’s boot flow so the Nucleus always loads and executes reliably.