I am writing a small OS that will execute some code in user mode (privilege level 3). From that user level code, I want to call an interrupt back to the OS that prints a message. Right now I don't really care how my interrupt handler takes arguments or anything like that, I really just want an interrupt handler to inform me (the user) that the code has executed.
My question is: how do I run code in user mode? I have a function that sets up a Local Descriptor Table with a code segment and data segment (both with user mode privileges). What I dont understand is how I am supposed to load these segments into cs
, ss
, and ds
. I successfully load the my LDT, but I do not know how to actually use it. I have heard that I should use iret
, but I don't understand exactly how.
Another question that I have is how my interrupt handler should work. Let's say I install an interrupt handler for vector number 0x40, which I want to print "hello, user mode!". I know how to setup an interrupt handler, but I don't exactly understand how the context will be switched when entering a kernel interrupt handler from user mode. I know that the cs
register must change, since my routine will be running from the code segment specified in my IDT entry. I also understand that the stack selector probably changes as well, but I cannot be certain of this.
Could someone please explain to me what context changes are made when an interrupt gate is called?
Getting to ring 3 can be done using iret
because the way it works has been documented. When you receive an interrupt, the processor pushes:
iret
works by undoing steps 1-3 (The ISR is responsible for undoing step 4 if necessary). We can use this fact to get to ring 3 by pushing the required information to the stack and issuing an iret
instruction. Make sure you have the proper CPL in your code and stack segments (the low two bits should be set in each). However, iret
doesn't change any of the data segments, so you will need to change them manually. You use the mov
instruction to do this, but you will not be able to read data outside the stack between doing this and switching rings.
cli
mov ax, Ring3_DS
mov ds, eax
push dword Ring3_SS
push dword Ring3_ESP
pushfd
or dword [esp], 0x200 // Set IF in EFLAGS so that interrupts will be reenabled in user mode
push dword Ring3_CS
push dword Ring3_EIP
iret
If you want a complete, working example, see this tutorial.
When an interrupt is issued, the processor reads your IDT to get the proper code segment and instruction pointer for the ISR. It then looks at your TSS to find the new stack segment and pointer. It changes ss
and esp
appropriately, and then pushes the old values to the new stack. It does not change any of the data segment registers. You must do this manually if you need to access memory in your ISR.