1

I am now trying to figure out why I am getting a General Protection Fault instantly after an iret. The return address of the iret points to a valid location inside my kernel code. When using gdb, if I step instructionwise, the GPF happens exactly as I execute the iret, all the time, so I am pretty sure that it has to do something with what the CPU does after that.

I have the logs down below and as far as I understand, the Error lies within the GDT at index 2. Is my GDT wrong or missing something?

I am new to operating systems and just fiddeling around. If you need any more information, I'll gladly provide.

My gdt.s:

.globl gdt

.section .data
gdt:
    .short 0,0,0,0 # NULL Deskriptor

    .short 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
    .short 0x0000 # base address=0
    .short 0x9A00 # code read/exec
    .short 0x00CF # granularity=4096,

    .short 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
    .short 0x0000 # base address=0
    .short 0x9200 # data read/write
    .short 0x00CF # granularity=4096,

The qemu Output with "-d int" (iret from v=20 causes v=0d):

SMM: after RSM
EAX=000000b5 EBX=00007de3 ECX=00005678 EDX=00000003
ESI=07ecf5b0 EDI=07fbec5e EBP=00006924 ESP=00006924
EIP=00007de3 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =db00 000db000 ffffffff 00809300
CS =f000 000f0000 ffffffff 00809b00
SS =0000 00000000 ffffffff 00809300
DS =0000 00000000 ffffffff 00809300
FS =0000 00000000 ffffffff 00809300
GS =ca00 000ca000 ffffffff 00809300
LDT=0000 00000000 0000ffff 00008200
TR =0000 00000000 0000ffff 00008b00
GDT=     00000000 00000000
IDT=     00000000 000003ff
CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000004 CCD=00000001 CCO=EFLAGS  
EFER=0000000000000000
     0: v=20 e=0000 i=0 cpl=0 IP=0010:00102aae pc=00102aae SP=0018:001097b8 env->regs[R_EAX]=000b83dc
EAX=000b83dc EBX=00010000 ECX=000001e0 EDX=00000001
ESI=00000000 EDI=00000000 EBP=001097d8 ESP=001097b8
EIP=00102aae EFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00105000 00000012
IDT=     00105020 000007ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=000b83dc CCO=EFLAGS  
EFER=0000000000000000
check_exception old: 0xffffffff new 0xd
     1: v=0d e=0010 i=0 cpl=0 IP=0008:00100336 pc=00100336 SP=0018:001097ac env->regs[R_EAX]=000b83dc
EAX=000b83dc EBX=00010000 ECX=000001e0 EDX=00000001
ESI=00000000 EDI=00000000 EBP=001097d8 ESP=001097ac
EIP=00100336 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00df9b00 DPL=0 CS32 [-RA]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00105000 00000012
IDT=     00105020 000007ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000004 CCD=001097ac CCO=EFLAGS  
EFER=0000000000000000

x/32b $esp right before iret:

0x1097ac:       0xae    0x2a    0x10    0x00    0x10    0x00    0x00    0x00
0x1097b4:       0x06    0x02    0x00    0x00    0x53    0x00    0x00    0x00
0x1097bc:       0x00    0x00    0x00    0x00    0x06    0x00    0x00    0x00
0x1097c4:       0x01    0x00    0x00    0x00    0x36    0x1d    0x10    0x00

my handler code:

.macro IRQ vector has_push
.align 8
interrupt_entry_\vector:

    .if \has_push == 0
        pushl $0
    .endif

    pushl %edx
    pushl %ecx
    pushl %eax

    pushl %esp
    pushl $\vector
    call interrupt_handler

    addl $8, %esp

    popl %eax
    popl %ecx
    popl %edx

    addl $4, %esp

    iret
.endm

--------------------------- Update 1 ---------------------------

Minimal working Code-Example: pastebin

Additional findings: The GPF only happens, if I run qemu with -cdrom os.iso. When running with -kernel bin/os.bin, no GPF will happen.

--------------------------- Update 2 ---------------------------

I fixed the GDT Limit now to being 0x17. Output of qemu with -cdrom os.iso is now:

check_exception old: 0xffffffff new 0xd
 40571: v=0d e=0010 i=0 cpl=0 IP=0008:00100335 pc=00100335 SP=0018:001087ec env->regs[R_EAX]=00000000
EAX=00000000 EBX=00010000 ECX=001047f8 EDX=000000a1
ESI=00000000 EDI=00000000 EBP=001087f8 ESP=001087ec
EIP=00100335 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00103000 00000017
IDT=     00104000 000007ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=001087e0 CCO=EFLAGS  
EFER=0000000000000000

--------------------------- Update 3 ---------------------------

(gdb) x/64b $esp [right before call interrupt_handler]
0x1087d8:       0x20    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x1087e0:       0xf8    0x47    0x10    0x00    0xa1    0x00    0x00    0x00
0x1087e8:       0x00    0x00    0x00    0x00    0x1e    0x1f    0x10    0x00
0x1087f0:       0x08    0x00    0x00    0x00    0x02    0x02    0x00    0x00
0x1087f8:       0x00    0x00    0x00    0x00    0x16    0x00    0x10    0x00
0x108800 <i>:   0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x108808:       0x01    0x52    0x01    0x00    0x00    0x00    0x00    0x00
0x108810:       0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
(gdb) x/64b $esp [right after call interrupt_handler, before add $4]
0x1087d8:       0x20    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x1087e0:       0xf8    0x47    0x10    0x00    0xa1    0x00    0x00    0x00
0x1087e8:       0x00    0x00    0x00    0x00    0x1e    0x1f    0x10    0x00
0x1087f0:       0x08    0x00    0x00    0x00    0x02    0x02    0x00    0x00
0x1087f8:       0x00    0x00    0x00    0x00    0x16    0x00    0x10    0x00
0x108800 <i>:   0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x108808:       0x01    0x52    0x01    0x00    0x00    0x00    0x00    0x00
0x108810:       0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
cediwelli
  • 370
  • 1
  • 8
  • 1
    Something strange going on, your GDT entry #2, selector `0x10` seems to be a data segment so how can you have code running from there? I suspect you forgot to reload `CS` after some earlier manipulation so it's still using cached stuff, but `iret` then performs the reload and finds it is invalid. – Jester Aug 22 '22 at 22:49
  • I'm a little confused by pushing `%esp` before calling `interrupt_handler` - does `interrupt handler` use it as an argument? I suspect you probably have some stack corruption that's causing `iret` to load bad values. I'd set a breakpoint just before calling `interrupt_handler` and just after, and check the state of everything, in particular the value of `%esp`. – sj95126 Aug 23 '22 at 00:04
  • @sj95126 the stack contents seem to be correctly pointing back to the source address though. Also, `iret` popping the 3 dwords will restore `esp` to `001097b8` which it was initially. – Jester Aug 23 '22 at 00:23
  • 1
    The GDT limit being reported as `00000012` is also extremely suspicious. You should really post a [mcve] and make sure you are running the code you post. – Jester Aug 23 '22 at 00:31
  • So `0x00102aae` is the valid address to return to? In that case, the next part of the stack frame is `CS` which is `0x10` but based on your GDT, it should be `0x8`. – sj95126 Aug 23 '22 at 00:31
  • 1
    Note that the source is reported as `IP=0010:00102aae` which already conflicts with the GDT as I mentioned in my first comment. – Jester Aug 23 '22 at 00:34
  • @Jester I coulnd't confirm your first theory until now, but I managed to paste a working code example on pastebin (see Edit Update 1), also I found out, that this only happens when running qemu over `-cdrom` instead of `-kernel`. I will go over your suggestions once more and will inform you, if I was able to find more. Thanks – cediwelli Aug 23 '22 at 07:31
  • @sj95126 Yes, the `interrupt_handler` used to take the `%esp` as an argument because of debugging. I've now removed it since I do not need it any longer, thanks for pointing out. Please check my edit on the question. I have posted minimal working code example and a finding regarding `qemu` which "solves" the problem. – cediwelli Aug 23 '22 at 07:34
  • 1
    One thing I find curious is that according to the QEMU debug output the length of your GDT is 0x12 bytes. That is 18 bytes. I'm assuming you probably have 3 8-byte entries for a total of 24 bytes. That would mean the GDT length in a GDT record should be 24 bytes (actually 23 as it is preferable to be the length-1). Is it possible you set the length to decimal 18 instead of hex 0x18 (preferably 0x17) – Michael Petch Aug 23 '22 at 07:53
  • @MichaelPetch Oh god, yeah. Thanks for pointing that out again. Jester already pointed that out but when I checked my code I just saw "18" and shrugged it off. I have now corrected it to be 0x18-1. But this sadly doesn't fix the issue :( – cediwelli Aug 23 '22 at 08:06
  • 1
    Okay another thing I see is `SP=0018:001097b8` That is SS=0x0018. That suggests to me that SS is still set to the value that GRUB Bootloader was using. When you create your own GDT after reloading with the `lgdt` instruction you need to set all the segment registers (SS, ES, FS, GS, DS, and CS) to the appropriate entries in your GDT. I can only assume you haven't done this last part. – Michael Petch Aug 23 '22 at 08:14
  • @sj95126 I tried your suggestion (checking stack before and after call interrupt_handler). Stack seems fine to me, but I'll be happy if you'd check for me again. Posted above in Update 3 – cediwelli Aug 23 '22 at 08:18
  • 1
    I have an example of inline assembly that does the `lgdt` and reloads the segment registers in this answer: https://stackoverflow.com/a/57630069/3857942 . Unfortunately I just noticed that inline assmebly uses Intel syntax rather than AT&T as you are using. If you scroll down to my answer in the OSDev forum you can find a version using AT&T syntax: https://forum.osdev.org/viewtopic.php?f=1&t=37478 – Michael Petch Aug 23 '22 at 08:44
  • @MichaelPetch Wow, thanks. I might just be too stupid for the GDT and segmentation. Please post your AT&T example as an answer and I'll accept it ASAP. – cediwelli Aug 23 '22 at 08:57
  • 1
    I'm presently not in a place where I can easily post an answer and I was about to head to bed. I can do so in the morning when I get up. – Michael Petch Aug 23 '22 at 08:58

0 Answers0