2026-09-06
The asker is building a hobby kernel (MapleKernel) and trying to transition the CPU from 32-bit protected mode back to 16-bit real mode. After executing a far jump into what should be 16-bit code, the CPU keeps decoding the instructions as 32-bit. They suspect it may be a build/link problem — some special technique for producing mixed-mode binaries that they're missing — but it could also be QEMU or the mode-switch sequence itself.
Why this is genuinely hard: "Going back to real mode" is not the inverse of "going to protected mode." It's a documented multi-step dance in the Intel SDM (Vol. 3, "Switching Back to Real-Address Mode"), and any missed step leaves the CPU in a hybrid state where the CS descriptor cache still holds 32-bit attributes even though the mode bits say otherwise. The classic symptom is exactly what the asker describes: bytes assembled as 16-bit get decoded as 32-bit because the D flag of the cached CS descriptor is still 1.
The correct sequence looks roughly like:
cli).CR0.PE.CS:IP (segment*16 + offset must equal the physical load address).The build side: The linker/assembler part is real but secondary. You need .code16 directives (or [bits 16] in NASM) for the destination block, and the code must be linked at an address reachable by a real-mode segmented pointer (below 1MB, ideally page-aligned to something like 0x1000 so CS=0x0100, IP=0 works cleanly). If you're using GCC, you cannot really produce 16-bit code with it — that section must be hand-written assembly. Objcopy the ELF to a flat binary; ELF loaders aren't going to help you here.
Gotchas:
CR0.PG=0) before clearing PE, or you fault.lidt with base=0, limit=0x3FF) before enabling interrupts again.