I thought my interrupt handling system was working, but it seems not. I believe my problem can be summed up as follows:
- My operating system uses long mode in x86-64.
- When IRET is called from an ISR, a #GP exception is raised with error code 0.
- When IRET is called, the top of the stack (in this particular instance) looks like this:
Code:
Address Value
STACK 0xffff80007ff7dfa8 = 0xffffffff80000c62
STACK 0xffff80007ff7dfb0 = 0x0000000000000008
STACK 0xffff80007ff7dfb8 = 0x0000000000000286
STACK 0xffff80007ff7dfc0 = 0xffff80007ff7dfd0
STACK 0xffff80007ff7dfc8 = 0x0000000000000010
STACK 0xffff80007ff7dfd0 = 0x0000000000000000
- The GDT looks like this:
Code:
GDT[0x0000]=??? descriptor hi=0x00000000, lo=0x00000000
GDT[0x0008]=Code segment, base=0x00000000, limit=0x00000fff, Execute/Read, Non-Conforming, Accessed, 64-bit
GDT[0x0010]=Data segment, base=0x00000000, limit=0x00000fff, Read/Write, Accessed
- The CPU registers look like this (before IRET):
Code:
rax: 00000000_00000000
rbx: 00000000_00000000
rcx: 00000000_00ffffff
rdx: 00000000_0051fa64
rsp: ffff8000_7ff7dfa8
rbp: ffff8000_7ff7dff0
rsi: 00000000_000000f9
rdi: ffffffff_800030f0
r8 : 00000000_00000010
r9 : 00000000_00000000
r10: 00000000_00000000
r11: 00000000_00000000
r12: 00000000_00000000
r13: 00000000_00000000
r14: 00000000_00000000
r15: 00000000_00000000
rip: ffffffff_80002470
eflags 0x00000282: id vip vif ac vm rf nt IOPL=0 of df IF tf SF zf af pf cf
So, everything looks good. I've verified that 0xffffffff80000c62 (the top of the stack, which should be popped by IRET into RIP) is the correct return address. The CS and SS images on the stack are correct. The RSP image is correct too. I'm unsure about the RFLAGS image on the stack (0x286), but it seems fine.
But when IRET is executed in this state, I get a #GP exception (with error code 0). Furthermore, Bochs gives the following error on the command line:
Code:
10618899027e[CPU0 ] fetch_raw_descriptor: LDTR.valid=0
So, looking through Bochs's source code I can see that this particular message is only shown when the CPU is trying to use the LDT (instead of the GDT as my CS and SS images would suggest). I cannot think of why this would possibly be happening.
But when reasoning about this problem, I thought of something. During IRET, RSP is popped
before SS. Perhaps this means that SS would actually be popped from the stack that the RSP image points to? This seems like strange behaviour/CPU design. This can't be the case, right? Presumably the RSP image that's popped is not put into the actual RSP register until the whole IRET frame is popped. But I did just want to verify that.
So according to Intel, possible causes of a #GP(0) exception during IRET (in long mode) are:
If EFLAGS.NT[bit 14] = 1.
If the return code segment selector is NULL.
If the stack segment selector is NULL going back to compatibility mode.
If the stack segment selector is NULL going back to CPL3 64-bit mode.
If a NULL stack segment selector RPL is not equal to CPL going back to non-CPL3 64-bit mode.
If the return instruction pointer is not within the return code segment limit.
If the return instruction pointer is non-canonical.
So. EFLAGS.NT is not set. CS is theoretically not NULL. SS's RPL is theoretically equal to CPL (0). The return instruction pointer is definitely canonical. I'm unsure about the return instruction pointer being within the CS segment limit, but my understanding was under x86-64 long mode, GDT segment's limits and bases were automatically just "the whole of memory", basically?
Can anyone work out why this exception is being raised, then?