Returning to Real Mode with Far Jump Failing
Posted: Wed Apr 16, 2025 8:47 am
Hey friends, I'm working on a challenge in a toy x86 OS that's run with qemu. The OS successfully transitions from real mode to protected mode, initializing the GDT, IDT, and remapping the PIC along the way. I'm trying to use an amalgam of inline assembly and compiled assembly programs to return to real mode and execute a program that relies on BIOS interrupts (0x10, 0x16, etc). I started with the the guide provided on the wiki for the transition in between modes and cross-referenced examples I found elsewhere, but I'm running into issues with the far jump instruction and am stuck in unreal mode as far as I can tell. I've gone through multiple iterations attempting this transition so I won't start by posting code, but this is the general process I've followed:
- copy my target program to 0x7c00 (tested in a boot sector initially to ensure valid code)
- disable interrupts
- reset cr0 without the paging or PE bits
- reload the IVT limits to 0x0 - 0x3ff
- clear the GDT registers
- remap the PIC
- set ESP to 0x7b00
- clear all registers (CS/DS/GS/SS/etc)
- far jump to 0x0000:0x7c00
When I trace this transition with gdb, I can verify that cr0, IVT, and GDT registers look like they do when I examine them prior to the real mode -> protected mode transition. However, the CS/DS/GS... registers still have limits at 0xffffffff instead of their initial 0xffff. When the code at 0x7c00 is executed, even though it was compiled with the bits 16 specifier, I can see that gdb is handling 32-bit addresses instead of the 16-bit addresses I expected. Lastly, whenever a syscall occurs, I can see that the segment isn't being processed correctly (the IVT address for 0x10 is 0xc0005663, but the memory location jumped to is 0x5663 instead of the expected 0xc5663 with proper segment:offset addressing. I also verified that there is valid handler code at 0xc5663). Consequently, I don't think the far jump is properly resetting the CS:IP limitations. Am I missing a critical part of the transition process or is there a standard way apart from a far jump to reset those segment limitations?
- copy my target program to 0x7c00 (tested in a boot sector initially to ensure valid code)
- disable interrupts
- reset cr0 without the paging or PE bits
- reload the IVT limits to 0x0 - 0x3ff
- clear the GDT registers
- remap the PIC
- set ESP to 0x7b00
- clear all registers (CS/DS/GS/SS/etc)
- far jump to 0x0000:0x7c00
When I trace this transition with gdb, I can verify that cr0, IVT, and GDT registers look like they do when I examine them prior to the real mode -> protected mode transition. However, the CS/DS/GS... registers still have limits at 0xffffffff instead of their initial 0xffff. When the code at 0x7c00 is executed, even though it was compiled with the bits 16 specifier, I can see that gdb is handling 32-bit addresses instead of the 16-bit addresses I expected. Lastly, whenever a syscall occurs, I can see that the segment isn't being processed correctly (the IVT address for 0x10 is 0xc0005663, but the memory location jumped to is 0x5663 instead of the expected 0xc5663 with proper segment:offset addressing. I also verified that there is valid handler code at 0xc5663). Consequently, I don't think the far jump is properly resetting the CS:IP limitations. Am I missing a critical part of the transition process or is there a standard way apart from a far jump to reset those segment limitations?