In this lecture, we will see how to jump from ring0 to ring3.
As we have learned in the previous lectures, the CPL is stored in the lower 2 bits of cs and ss registers.
We are running in ring 0 so far.
If we check the lower 2 bits of cs register
,
we will see the value is 0 meaning that we are running in ring0.
What we need to do is prepare the code segment descriptor for ring3 and load the descriptor to cs register.
Once the descriptor is successfully loaded into cs register, we will run in ring3.
In this process, the data segment descriptor for ss register is needed.
This is the only case where we load the data segment descriptor ourselves.
OK, let's add two more descriptors.
the first one is code segment descriptor for ring3. The value is pretty much the same,
so we just copy and paste.
The difference is that the value here is F
instead of 9. When we look at it in binary format, we can see the DPL is changed from 0 to 3
.
So the privilege level of the descriptor we added here is 3.
The data segment descriptor is simple as well.
The DPL field of the descriptor is also 3. The value here is F
And this is the value we need to assign to the data segment descriptor.
Let's look at the attributes.
and present bit is set to 1. 10 means this descriptor is data segment descriptor
.
The w bit is 1 indicating that the data segment is writable. Ok, with the descriptors prepared,
we use interrupt return to jump from ring0 to ring3. So let's do it.
To return to the ring3, we have to prepare 5 8-byte data on the stack.
As you see, the top of the stack is RIP value which specifies where we will return.
The next data is the code segment selector we will load into cs register after we return.
Then the value of rflags which contains the status of the CPU.
When we return, the value will be loaded in rflags register. The stack pointer is stored in the next location
which will be loaded in register RSP. The last one is stack segment selector.
Because stack is last-in first-out structure, we push the data in reverse order.
In this example, the stack segment selector is 18 in hexadecimal so the descriptor we reference is the fourth one.
and the RPL is 3. Since we want to get to ring3,
the DPL of the descriptor and RPL of ss selector are set to 3.
OK, so
we push
18 ors 3
You can simply add them together and push the result
.
Then we push
7c00. The stack pointer is set to 7c00 in this example.
Then the value of rflags. We only set the bit 1 to 1 which is required.
So we push 2.
Other flags such as carry flag overflag are set to 0.
The interrupt flag is not set because when we are running in ring3 and an interrupt is fired,
task state segment TSS is needed to call the handler, we haven’t implemented the TSS
up to this point,
so we don’t enable interrupt when we return.
The code segment selector is 10 which references the third descriptor and the RPL is 3.
So we push
10 ors 3
The return address is user entry.
So we push user entry.
Ok now we can write interrupt return.
When the return executes, the rsp is set to 7c00 and we will jump to user entry.
Cs and ss registers will load the descriptor just as we set here.
We haven’t defined the label user entry.
Let's do it.
For the simplicity, we only check the lower 2 bits of cs register
which indicates the privilege level we are currently at.
So we move ax cs
and al 11 in binary.
so we use this instruction to preserves the lower 2 bits of al register and clear other bits.
If the value in al now is 3,
it means we are running in ring3.
We compare al and 3 if not euqal we jump to UEnd.
Note that we cannot execute halt instruction in ring3.
So in the uend,
we just jump to the current location
to end our program. If we are in ring3, we print the character U
indicating that we are running in user mode.
OK, we save the file and test it out.
And here, the number should be hexadecimal number and also we comment out sti instruction because we don't
want to enable interrupt.
We build the project in the terminal
and test it in the bochs.
OK, you can see u is printed.
So we successfully jump to ring3.