Hello, in this lecture we will start building our kernel.
The first thing we will do in the kernel is manage the system resources in one place such as
gdt and idt.
So we copy and paste the gdt here.
Alright, we write load gdt and gdt pointer.
Notice that the address of gdt in gdt pointer is 8 bytes.
or quadword. In 64-bit mode,
we load the pointer with 2-byte limit and 8-byte base address.
So we need to change the double word to quadword.
What we are going to do next is we are going to load the code segment descriptor into cs register.
Instead of using jump instruction,
we load the descriptor using another instruction return instruction.
So we write retf
indicating that this is a far return. Normal return will not load the descriptor in cs register.
So we use far return to do that. In most cases, when we execute return instruction,
we know that we will return from current procedure to the caller.
The return address is pushed on the stack by the caller and go to the procedure.
In the procedure, return instruction will pop the address in rip register which will branch to that location.
Apparently, what we are doing here is not the case.
What we are actually doing here is fabricate the scenario where we are called by other caller
and the far return instruction executes and we are back to the caller.
So the return address need to be pushed on the stack on our own and we will jump to that address
when far return executes.
Generally we have two situations regarding to far return. One is return to the same privilege level
which is what we are doing right now.
Another is return to the different privilege level.
When we jump to ring 3, we will use this technique.
For the moment, let’s see how to do the same privilege level far return.
As you see, the top of stack should be like this.
The first 8 bytes are the code segment selector, here we choose 8. Because the code segment descriptor is
the second entry of gdt.
So we push 8.
Then the offset is in the next 8 bytes. So we save the address of location we want to branch in here
and we define the label kernel entry
to represent this location.
Let's define it.
Ok push these two data on the stack and then we can execute return instruction. Also, note that the default operand size
of far return is 32 bits.
Each data we pushed on the stack is 64 bits.
So we need to add operand-size override prefix
48 ourselves to change the operand size to 64-bits.
In the kernel entry, we simply print K and add the infinite loop in the end.
So we move the code
to kernel entry.
Alright, let’s build the project and see the result.
As you can see, the character K is printed.