[Fixed] Proper formatting of the BIOS GDT for int 15h/ah=89h

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
Lithorien
Member
Member
Posts: 59
Joined: Tue Oct 27, 2009 1:40 pm
Location: Hanover, PA

[Fixed] Proper formatting of the BIOS GDT for int 15h/ah=89h

Post by Lithorien »

I've been working on a hand-rolled bootloader to replace GRUB in my personal project - I'm not really looking for multiboot compliance, and I'd like to have the memory map set up exactly how I want it for my kernel and not rely on another programmer's best judgement. And it's come time to switch to protected mode. I'm looking to use the BIOS function 15h/89h to automagically handle the more PITA tasks that every tutorial kernel seems to want to handle (A20, GDT, IDT, PIC IRQs, etc).

Here's the problem. When I call the BIOS function, it will correctly identify and load my GDT and IDT, however, when it attempts to jump to protected mode, it triple-faults. I pulled up the Bochs source code for rombios.c and found the assembly, and mentally traced it to the faulting line. I honestly have no idea where to go from here in terms of debugging because the error from the log doesn't make sense to me. I do know that DS/ES/SS are not set after the BIOS function call, and they should be, but I don't know what's failing here.

I appreciate any help or direction you all can give me.

stage2 code:

Code: Select all

org 0x7E00
bits 16

; --- Snip ---

pmode_switch_table:
	; Null descriptor
	dq 0
	
	; GDT pointer
	dw 0x3F
	dw gdt
	dd 0
	
	; IDT pointer
	dw 0x100
	dw idt
	db 0
	db 0xF2
	dw 0
	
	; DS
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 10010010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)
	
	; ES
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 10010010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)
	
	; SS
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 10010010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)
	
	; CS
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 10011010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)
	
	; Scratch CS
	dq 0

gdt:
	; Null descriptor
	dq 0
	
	; Kernel code descriptor
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 10011010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)
	
	; Kernel data descriptor
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 10010010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)
		
	; User code descriptor
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 11111010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)
	
	; User data descriptor
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 11110010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)
	
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 10010010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)
	
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 10010010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)
	
	dw 0xFFFF							; Limit (low)
	dw 0								; Base (low)
	db 0								; Base (middle)
	db 10011010b						; Access
	db 11001111b						; Granularity
	db 0								; Base (hi)

idt:
	times 32 dq 0

; --- Snip ---

; Enable protected mode
	mov bh,32
	mov bl,40
	mov si,WORD pmode_switch_table
	mov ah,0x89
	cli
	int 15h
	xchg bx,bx ; ----- Never reaches here -----
rombios.c snippet: (I think line 4065 is the faulting line)

Code: Select all

4007   case 0x89:
4008     // Switch to Protected Mode.
4009     // ES:DI points to user-supplied GDT
4010     // BH/BL contains starting interrupt numbers for PIC0/PIC1
4011     // This subfunction does not return!
4012 
4013 // turn off interrupts
4014 ASM_START
4015   cli
4016 ASM_END
4017 
4018       set_enable_a20(1); // enable A20 line; we're supposed to fail if that fails
4019 
4020       // Initialize CS descriptor for BIOS
4021       write_word(ES, regs.u.r16.si+0x38+0, 0xffff);// limit 15:00 = normal 64K limit
4022       write_word(ES, regs.u.r16.si+0x38+2, 0x0000);// base 15:00
4023       write_byte(ES, regs.u.r16.si+0x38+4, 0x000f);// base 23:16 (hardcoded to f000:0000)
4024       write_byte(ES, regs.u.r16.si+0x38+5, 0x9b);  // access
4025       write_word(ES, regs.u.r16.si+0x38+6, 0x0000);// base 31:24/reserved/limit 19:16
4026 
4027       BX = regs.u.r16.bx;
4028 ASM_START
4029       // Compiler generates locals offset info relative to SP.
4030       // Get BX (PIC offsets) from stack.
4031       mov  bx, sp
4032       SEG SS
4033         mov  bx, _int15_function.BX [bx]
4034 
4035       // Program PICs
4036       mov al, #0x11 ; send initialisation commands
4037       out 0x20, al
4038       out 0xa0, al
4039       mov al, bh
4040       out 0x21, al
4041       mov al, bl
4042       out 0xa1, al
4043       mov al, #0x04
4044       out 0x21, al
4045       mov al, #0x02
4046       out 0xa1, al
4047       mov al, #0x01
4048       out 0x21, al
4049       out 0xa1, al
4050       mov  al, #0xff ; mask all IRQs, user must re-enable
4051       out  0x21, al
4052       out  0xa1, al
4053 
4054       // Load GDT and IDT from supplied data
4055       SEG ES
4056         lgdt [si + 0x08]
4057       SEG ES
4058         lidt [si + 0x10]
4059 
4060       // set PE bit in CR0
4061       mov  eax, cr0
4062       or   al, #0x01
4063       mov  cr0, eax
4064       // far jump to flush CPU queue after transition to protected mode
4065       JMP_AP(0x0038, protmode_switch)
4066 
4067 protmode_switch:
4068       ;; GDT points to valid descriptor table, now load SS, DS, ES
4069       mov  ax, #0x28
4070       mov  ss, ax
4071       mov  ax, #0x18
4072       mov  ds, ax
4073       mov  ax, #0x20
4074       mov  es, ax
4075 
4076       // unwind the stack - this will break if calling sequence changes!
4077       mov   sp,bp
4078       add   sp,#4   ; skip return address
4079       popa          ; restore regs
4080       pop   ax      ; skip saved es
4081       pop   ax      ; skip saved ds
4082       pop   ax      ; skip saved flags
4083 
4084       // return to caller - note that we do not use IRET because
4085       // we cannot enable interrupts
4086       pop   cx          ; get return offset
4087       pop   ax          ; skip return segment
4088       pop   ax          ; skip flags
4089       mov   ax, #0x30   ; ah must be 0 on successful exit
4090       push  ax
4091       push  cx          ; re-create modified ret address on stack
4092       retf
Bochs log:

Code: Select all

00016497081i[BIOS ] Booting from 0000:7c00
00016514660e[CPU0 ] write_virtual_checks(): write beyond limit, r/w
00016514660e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
00016514660e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
00016514660i[CPU0 ] CPU is in protected mode (active)
00016514660i[CPU0 ] CS.d_b = 32 bit
00016514660i[CPU0 ] SS.d_b = 16 bit
00016514660i[CPU0 ] EFER   = 0x00000000
00016514660i[CPU0 ] | RAX=0000000060000011  RBX=0000000000002028
00016514660i[CPU0 ] | RCX=0000000000000003  RDX=0000000000000000
00016514660i[CPU0 ] | RSP=0000000000007bcb  RBP=0000000000007bdf
00016514660i[CPU0 ] | RSI=00000000000e7e1c  RDI=000000000000ffac
00016514660i[CPU0 ] |  R8=0000000000000000   R9=0000000000000000
00016514660i[CPU0 ] | R10=0000000000000000  R11=0000000000000000
00016514660i[CPU0 ] | R12=0000000000000000  R13=0000000000000000
00016514660i[CPU0 ] | R14=0000000000000000  R15=0000000000000000
00016514660i[CPU0 ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf af PF cf
00016514660i[CPU0 ] | SEG selector     base    limit G D
00016514660i[CPU0 ] | SEG sltr(index|ti|rpl)     base    limit G D
00016514660i[CPU0 ] |  CS:0038( 0007| 0|  0) 00000000 ffffffff 1 1
00016514660i[CPU0 ] |  DS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00016514660i[CPU0 ] |  SS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00016514660i[CPU0 ] |  ES:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00016514660i[CPU0 ] |  FS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00016514660i[CPU0 ] |  GS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00016514660i[CPU0 ] |  MSR_FS_BASE:0000000000000000
00016514660i[CPU0 ] |  MSR_GS_BASE:0000000000000000
00016514660i[CPU0 ] | RIP=0000000000004e22 (0000000000004e22)
00016514660i[CPU0 ] | CR0=0x60000011 CR2=0x0000000000000000
00016514660i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
00016514660p[CPU0 ] >>PANIC<< exception(): 3rd (13) exception with no resolution
00016514660e[CPU0 ] WARNING: Any simulation after this point is completely bogus !
00016514660p[CPU0 ] >>PANIC<< Entering to shutdown state still not implemented
info gdt:

Code: Select all

Global Descriptor Table (base=0x0000000000007e5c, limit=63):
GDT[0x00]=??? descriptor hi=0x00000000, lo=0x00000000
GDT[0x01]=Code segment, base=0x00000000, limit=0xffffffff, Execute/Read, 32-bit
GDT[0x02]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write
GDT[0x03]=Code segment, base=0x00000000, limit=0xffffffff, Execute/Read, 32-bit
GDT[0x04]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write
GDT[0x05]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write
GDT[0x06]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write
GDT[0x07]=Code segment, base=0x00000000, limit=0xffffffff, Execute/Read, Accessed, 32-bit
info idt: (Yes, these are all null - they're set up to be. Interrupts *should* be disabled at this point.)

Code: Select all

Interrupt Descriptor Table (base=0x0000000000007e9c, limit=256):
IDT[0x00]=??? descriptor hi=0x00000000, lo=0x00000000
IDT[0x01]=??? descriptor hi=0x00000000, lo=0x00000000

...

IDT[0x1e]=??? descriptor hi=0x00000000, lo=0x00000000
IDT[0x1f]=??? descriptor hi=0x00000000, lo=0x00000000
If there's any more information I can provide that I haven't, please let me know. I'll be glad to dig it up. :) Thank you!



Edit: I found the pmode_switch_table layout both in RB's Interrupt List, and a PDF found here: http://www.frontiernet.net/~fys/docs/FrstStps.pdf (pg. 329)
Last edited by Lithorien on Wed Oct 27, 2010 1:24 pm, edited 1 time in total.
User avatar
bewing
Member
Member
Posts: 1401
Joined: Wed Feb 07, 2007 1:45 pm
Location: Eugene, OR, US

Re: Proper formatting of the BIOS GDT for int 15h/ah=89h

Post by bewing »

Sorry, but it's a non-obvious bug in the BOCHS bios. Go ahead and report it to Stanislav.

You have identified the correct line. But there is a Pmode CPU switch that happens at that point.
Since there is a pmode switch, the assembler should be told to switch to outputting 32bit code at that point, with a "use32" statement. There are several points in this code where it completely fails to change between "use16" and "use32" during CPU mode switches. They are all bugs.

So, to analyze the error in detail: the cpu gets all the way to the far jump (JMP_AP), sets CS to 0x38, sets IP correctly to the next line. Gets to the line that supposedly says "mov ax, #0x28" -- except that line was compiled as 16bit code, and it is in 32bit mode now. So the line actually says something completely different, such as "halt and catch fire" or something. And since the line is some illegal wacky opcode, the CPU proceeds to triplefault at that point.
User avatar
Lithorien
Member
Member
Posts: 59
Joined: Tue Oct 27, 2009 1:40 pm
Location: Hanover, PA

Re: Proper formatting of the BIOS GDT for int 15h/ah=89h

Post by Lithorien »

bewing wrote:Sorry, but it's a non-obvious bug in the BOCHS bios. Go ahead and report it to Stanislav.

You have identified the correct line. But there is a Pmode CPU switch that happens at that point.
Since there is a pmode switch, the assembler should be told to switch to outputting 32bit code at that point, with a "use32" statement. There are several points in this code where it completely fails to change between "use16" and "use32" during CPU mode switches. They are all bugs.

So, to analyze the error in detail: the cpu gets all the way to the far jump (JMP_AP), sets CS to 0x38, sets IP correctly to the next line. Gets to the line that supposedly says "mov ax, #0x28" -- except that line was compiled as 16bit code, and it is in 32bit mode now. So the line actually says something completely different, such as "halt and catch fire" or something. And since the line is some illegal wacky opcode, the CPU proceeds to triplefault at that point.
Heh, I never expected to hit a bug in the Bochs code. Go figure! But yes - I filed the bug report.

Thank you, bewing. :)
User avatar
Owen
Member
Member
Posts: 1700
Joined: Fri Jun 13, 2008 3:21 pm
Location: Cambridge, United Kingdom
Contact:

Re: Proper formatting of the BIOS GDT for int 15h/ah=89h

Post by Owen »

I don't think this is a Bochs bug. RBIL is a little vague on this point, but nowhere does it say that this is a switch to 32-bit protected mode.

My assumption is that this is probably a PC/AT function and switches into 16-bit protected mode. A further assumption of mine is that it is probably poorly supported, and only used in practice by OS/2 if at all.
User avatar
Lithorien
Member
Member
Posts: 59
Joined: Tue Oct 27, 2009 1:40 pm
Location: Hanover, PA

Re: Proper formatting of the BIOS GDT for int 15h/ah=89h

Post by Lithorien »

Owen wrote:I don't think this is a Bochs bug. RBIL is a little vague on this point, but nowhere does it say that this is a switch to 32-bit protected mode.

My assumption is that this is probably a PC/AT function and switches into 16-bit protected mode. A further assumption of mine is that it is probably poorly supported, and only used in practice by OS/2 if at all.
Here's the thing: Bochs is making the same assumption that I am. If you look at rombios.c, they're doing the exact same steps that we'd do manually to switch to 32-bit protected mode when I call the BIOS interrupt. Enable A20, program the PICs, load the GDT and IDT, and enable the PE bit in CR0. If it was designed to enter 16-bit protected mode (AKA 286 protected mode), why are the steps to enable it the ones used for 386+?

Or have I totally missed something here?
User avatar
bewing
Member
Member
Posts: 1401
Joined: Wed Feb 07, 2007 1:45 pm
Location: Eugene, OR, US

Re: Proper formatting of the BIOS GDT for int 15h/ah=89h

Post by bewing »

No, you are understanding perfectly. The bochs code is indeed attempting to do a pmode switch -- as you can clearly see if you just look at the comments etc. in the code. The fact that the "use32" statement is missing in the code during an attempted pmode switch by the bios code is an internal bug in the code, no matter how you parse it -- and has nothing to do with the overall function of the bios function that is being called.

But Owen is right that the reason you have spotted this bug, and nobody else did, is because nobody uses this bios function. Which is likely to mean that it's not well tested, even on real BIOSes.
User avatar
Owen
Member
Member
Posts: 1700
Joined: Fri Jun 13, 2008 3:21 pm
Location: Cambridge, United Kingdom
Contact:

Re: Proper formatting of the BIOS GDT for int 15h/ah=89h

Post by Owen »

Lithorien wrote:Here's the thing: Bochs is making the same assumption that I am. If you look at rombios.c, they're doing the exact same steps that we'd do manually to switch to 32-bit protected mode when I call the BIOS interrupt. Enable A20, program the PICs, load the GDT and IDT, and enable the PE bit in CR0. If it was designed to enter 16-bit protected mode (AKA 286 protected mode), why are the steps to enable it the ones used for 386+?

Or have I totally missed something here?
The steps to enter 32 and 16 bit protected modes are the same. The only difference is the operand size of the code segment.

bewing wrote:No, you are understanding perfectly. The bochs code is indeed attempting to do a pmode switch -- as you can clearly see if you just look at the comments etc. in the code. The fact that the "use32" statement is missing in the code during an attempted pmode switch by the bios code is an internal bug in the code, no matter how you parse it -- and has nothing to do with the overall function of the bios function that is being called.
The code jumps to a 16-bit code segment anyway.
Gigasoft
Member
Member
Posts: 856
Joined: Sat Nov 21, 2009 5:11 pm

Re: Proper formatting of the BIOS GDT for int 15h/ah=89h

Post by Gigasoft »

There's a bug, but it's not in the BIOS.

You are using the function in an incorrect way. The second entry of the GDT should point to the same GDT, but yours points to a different GDT. That won't work.
User avatar
Lithorien
Member
Member
Posts: 59
Joined: Tue Oct 27, 2009 1:40 pm
Location: Hanover, PA

Re: Proper formatting of the BIOS GDT for int 15h/ah=89h

Post by Lithorien »

Gigasoft wrote:There's a bug, but it's not in the BIOS.

You are using the function in an incorrect way. The second entry of the GDT should point to the same GDT, but yours points to a different GDT. That won't work.
Oh you have got to be kidding me.

Gigasoft's right. I tried it this way (self-referencing GDT) before hand, but ended up seeing a triple fault anyway. The reason is exactly what bewing thought, but in the wrong place. Bochs is handling the pmode switch code correctly, but I dropped the ball in my own execution. Since execution just increments IP and resumes at the end of the BIOS call, any code after that point needs to use 32-bit opcodes. Turns out, I wasn't actually using 32-bit opcodes on my end, and so the triple fault was happening after it returned.

Sigh. Thank you, Gigasoft and bewing. I've been working on this for days, and it was a simple problem between the chair and keyboard.
Post Reply