Debugging our issue
Let's add our flags back and debug with GDB, when debugging with GDB we
will sometimes want to see the next instructions using the program counter.
In real mode we have to account for segment base when calculating the
physical address. (I'm pretty sure this is only a quirk with real mode
by the way). x/20i $pc (which shows the next 20
instructions) should become: x/20i (($cs * 16) + $pc) I have made a
.gdbinit script to access these commands easier:
set architecture i8086
display/i (($cs * 16) + $pc)
define xi
x/20i (($cs * 16) + $pc)
end
define sii
si
x/10i (($cs * 16) + $pc)
end
Now when we run the kernel we should see "Guest has not initialized the
display." Before we connect we must run the gdb command to go into
GDB. Then do file kernel.elf to load our labels,
source .gdbinit to load our GDB init file,
and finally target remote localhost:1234 to connect to QEMU.
Everything is set up now. Set a breakpoint at start and type c to
continue execution until we hit our breakpoint. We can then try the xi
command I have made to see our next instructions. They should match
our code.
You can set a breakpoint at our enter_protected label and then step
towards our jump, you'll see it will jump to an
unintended point, so something is going wrong. Take a look around, I'll
give you some commands that will be useful for GDB, and then
I'll give you the solution.
Some useful GDB commands
(Addresses and registers I put here are placeholders to represent commands and not specific to check for our problem)
Printing registers
info registers
print/x $eax
print/x $eip
The first shows information about all registers. The last two show information about specific registers.
Disassemble instructions/functions
x/20i $pc
x/20i 0x7C00
x/20i p_mode_main
The first prints instructions at current location indicated by the program counter, second does at an address, third does it at a label.
Viewing raw memory
x/16bx 0x7C00
x/16hx 0x7C00
x/16wx 0x7C00
Views raw memory, useful when we aren't sure if GDB is decoding our instructions correctly. We can check reference manuals to make sure memory is represented how we want it to. First does bytes, second does words, third does double words.
Breakpoint stuff
break 0x7C00
break p_mode_main
info breakpoints
delete 1
This is how we make, get information about, and delete breakpoints.
Watching execution
display/i $pc
display/x $eax
These output registers after each step (si) command.
Find the error!
You are now equipped to find the error. The next piece of text will showcase how to find the solution. I suggest you try to find it yourself a bit before you look at my solution, being proficient with debugging is an important skill as I've said before
Solution to our triple fault
In GDB, if we make a breakpoint at enter_protected, and then we go
right before our lgdt command and use:
(gdb) x/8bx (($cs * 16) + $pc)to see raw memory, we will see this
output:
0x9019 <enter_protected+1*>: 0x0f 0x01 0x16 0x8e 0x90 0x0f 0x20 0xc0
0xf 0x1 0x16 is the opcode for our lgdt instruction. 0x8e 0x90 is our
operand, which decodes to 0x908e (with the other bytes being the next instruction).
0x908e is the address of gdtr, but this is not how we are supposed to use lgdt.
In real mode this address is interpreted as an offset from the DS
segment base. Our code is loaded at physical address 0x9000, so we need to use
the offset of gdtr relative to start instead. We much change our
instruction to:
lgdt [gdtr - start] ; load GDTR with the GDT's address and size
We may also notice that we are not printing correctly too, this is the same issue, so let's change that too:
mov si, hello_string - start
We should now be in protected mode! Another good debugging technique is checking other people's implementations. That's how I originally solved this issue, but it's also solvable via GDB. If you're thinking "How could I even possibly realize that" Then welcome to bare metal programming :)