Question Details

No question body available.

Tags

c assembly operating-system gdt usermode

Answers (1)

January 15, 2026 Score: 4 Rep: 4,076 Quality: Medium Completeness: 60%

TLDR for those who came here looking for clues when facing Bochs' log like fetchrawdescriptor: GDT: index (ff57) 1fea > limit (2f):

Start Bochs with debugger enabled, execute show int & show dbgall in bochs debugger cli, and then run your image. This would make Bochs to log all interrupts/exceptions; if this error occurred during interrupt handling, you'll get some more clues into what is happening.


Now, to the question. First let's see the logs.

19849108391e[CPU0  ] fetchrawdescriptor: GDT: index (ff57) 1fea > limit (2f)
19849108391e[CPU0  ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)

Vector 0x08 is a double fault vector. So presumably there was some exception, and attempt to process it resulted it double fault. Moreover, attempt to process a double fault also resulted in a fault presumably because trap segments value in IDT was not a valid system segment. Something is wrong in your IDT; and also there was that first exception, so something is wrong there too.

19849108391i[CPU0  ] | EAX=00100023...
19849108391i[CPU0  ] | ESP=00800000...
19849108391i[CPU0  ] | SEG sltr(index|ti|rpl)     base    limit G D
19849108391i[CPU0  ] |  CS:001b( 0003| 0|  3) 00000000 ffffffff 1 1
19849108391i[CPU0  ] |  DS:0023( 0004| 0|  3) 00000000 ffffffff 1 1
19849108391i[CPU0  ] |  SS:0023( 0004| 0|  3) 00000000 ffffffff 1 1
19849108391i[CPU0  ] | EIP=00100023 (00100023)
19849108391i[CPU0  ] | CR2=0x00100023
19849108391i[CPU0  ] 0x0000000000100023>> or byte ptr ds:[esi-117], ah : 08668B

Ok, so exception happens upon jump to User mode. CS is 0x1b as expected, rest of segments are 0x23 as expected. ESP is 0x800000 as expected. It seems cross-privilege level iret did its job. Segment shadow contents looks fine as well. EIP looks wrong however. It points into kernel, somewhere in the middle of instruction. CR2 is the same as EIP which suggests initial exception was a paging exception - CPU couldn't read the code instruction (which is expected I guess if the kernel pages are set as system). EAX is the same as EIP (and it holds initial EIP value in enterusermode, so probably the very first instruction faulted).

19849108391e[CPU0  ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting

This is the triple-fault reset because double-fault could not be processed

Ok, so 1) exception handling doesn't work, and it should(?) and 2) bad EIP.

That 0x23 in the end of EIP is highly suspect because it matches the data segment value. Surely enough, enterusermode is buggy and fix is trivial:

  00100b10 :
  100b10:       fa                      cli
  100b11:       8b 44 24 04             mov    0x4(%esp),%eax
  100b15:       66 b8 23 00             mov    $0x23,%ax

With that fixed, we get

19960859176i[CPU0  ] | EIP=00108000 (00108000)
19960859176i[CPU0  ] | CR2=0x00108000
19960859176i[CPU0  ] 0x0000000000108000>> mov eax, 0xdeadbeef : B8EFBEADDE

Ok, so now it faults at actual usermode start. Why does it do that? Well, because its still a system-mode page. There is nothing in your code to prepare actual user-mode region of virtual addresses. From the book (para 11.2):

There are a few things every user mode process needs: Page frames for code, data and stack. At the moment it suffices to allocate one page frame for the stack and enough page frames to fit the program’s code. Don’t worry about setting up a stack that can be grow and shrink at this point in time, focus on getting a basic implementation work first. The binary from the GRUB module has to be copied to the page frames used for the programs code. A page directory and page tables are needed to map the page frames described above into memory. At least two page tables are needed, because the code and data should be mapped in at 0x00000000 and increasing, and the stack should start just below the kernel, at 0xBFFFFFFB, growing towards lower addresses. The U/S flag has to be set to allow PL3 access.

None of that has been done. There is no stack space allocated, there are no user-mode pages to hold the user-space code. This fault is expected - the kernel just isn't ready to run userspace.

Ok, with that figured out let's return to IDT handling. Looking at interruptsinstallidt it is immediately obvious that it could use some love. At least internal exceptions handlers should be all filled up (also note exception 7 is not error-code bearing; list of such exceptions is provided in the book). It would be a good idea to add some handling of the exceptions also; one common approach is to log an exception, cpu state etc. Well, see bochs log for an example. Your code could do something similar.

I have to say the book has dropped the ball here. This is a tricky subject, and the book gives two rough examples of C side (para 6.3) and assembly side (para 6.4) of interrupt handler - but they are not compatible with each other! interrupthandler has to be called according to its declaration (or vice versa); kudos to you for fixing that (not sure if struct ordering is correct though).

All that said, the code does look fine at the first glance. It should've been a page fault, which should've been handled at least somehow. Moreover, Bochs debugger can show IDT contents - and it's fine, segment values are correct. Let's switch bochs debugging logs on and see what's happening in more details ("show int", "show dbgall" in bochs debugger cli):

CPU 0: Exception 0x0e - (#PF) page fault occurred (errorcode=0x0005)
CPU 0: Interrupt 0x0e occurred (errorcode=0x0005)
07358149912e[CPU0  ] fetchrawdescriptor: GDT: index (ff57) 1fea > limit (2f)
CPU 0: Exception 0x0a - (#TS) invalid TSS occurred (errorcode=0xff51)
CPU 0: Exception 0x08 - (#DF) double fault occurred (errorcode=0x0000)
CPU 0: Interrupt 0x08 occurred (errorcode=0x0000)
07358149912e[CPU0  ] fetchrawdescriptor: GDT: index (ff57) 1fea > limit (2f)
CPU 0: Exception 0x0a - (#TS) invalid TSS occurred (error_code=0xff51)

Ah yes, IDT itself is not an issue. What we have here is a cross-privilege trap (switching from user-mode at ring 3 to kernel at ring 0). That involves switching stack, and switching stack needs TSS (so CPU has a place to store ring 3 SS/ESP and load ring 0 SS/ESP). There is no TSS set in the code therefore any exceptions in ring 3 would result in crash. Well, the book hints at such problems in para 11.3:

For now, we should have interrupts disabled, as it requires a little more work to get inter-privilege level interrupts to work properly (see the section “System calls”).

And indeed, later on in section "system calls" (para 13.2) it talks about setting up TSS and why it is needed. Without that done, you have to rely on bochs debugger and logs to deal with any bugs.