Hello community, I am working on my '2nd stage' bootloader, after having setup my MBR.  The MBR boots just fine, and then starts into the Bootloader.  Nothing fancy happens until the GDT setting up phase.  I then setup Protected Mode, and attempt to load the kernel.

Unfortunately, my code seems to be dysfunctional, but with the most odd thing... I have tried debugging by splitting the code into segments separated with a unrecoverable halt, and if a message was printed, that piece was deemed good to go.  None of that worked.  So far, I have determined, that the CPU will successfully reach the jump instruction before the perameter, but will then  triple-fault.  I have wracked my brain in an attempt to figure out what this issue is, been at it about 3.5 hours, and need the Gods of Assembly to intervene.

Attached is my code for the bootloader.

Thanks to all who appear and help, in advanced.

;XIX System 19 Bootloader - Version pre-Alpha
;Copyright (C) 2009 Brian D. Knopp
;
;This program is free software: you can redistribute it and/or modify
;it under the terms of the GNU General Public License as published by
;the Free Software Foundation, using version 3 of the License.
;
;This program is distributed in the hope that it will be useful,
;but WITHOUT ANY WARRANTY; without even the implied warranty of
;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;GNU General Public License for more details.
;
;You should have received a copy of the GNU General Public License
;along with this program.  If not, see <http://www.gnu.org/licenses/>.
;
;
;This version of the XIX System 19 Bootloader is used
;specifically to setup a suitable environment to load
;the XIX Kernel and execute it.  Future fleatures
;include the ability to detect the best route to perform
;its appointed duty by BIOS ID, error handling/reporting
;and multi-boot features.
;
;For full documentation, read the source-enclosed
;bootloader.ldr.doc file

;Assembler Directive denoting this as 16-bet Real Mode
;Assembler Directive denoting this as existing at offset 0x0000

begin: ;function to jump over the data section and into the main boot sequence
jmp boot


;Section for Functions
prntstr: ;function to print a string loaded into AL
mov ah, 0x0E ;load 0x0E (teletype) into AH

mov bh, 0x00 ;set page number to 0 (page 1)
mov bl, 0x000E ;set attribute to Green Text, Black Background, non-flashing

.nxtchar ;label to repeat int 0x10
lodsb ;load string in SI, into AL
or al, al ;check to see if AL = null char (set zero if true)

jz .ret ;jump over int 0x10 and exit function

int 0x10 ;call int 0x10 to print the string
jmp .nxtchar ;repeat the routine if null character is not yet present

.ret ;label to jump to, to get out of the loop
ret

a20en: ;function to enable the A20 Gate
cli ;clear interrupts to disallow interuption of procedure

in al, 0x92 ;open AL to 0x92
or al, 0x02 ;send value 0x02 to AL register
out 0x92, al ;close AL 0x92

sti ;re-enable interupts

lea si, ;load the A20 Message into SI register
call prntstr ;print the string
ret

krnlod: ;function to load the kernel into memory
jmp .rddsk ;jump over error function to read the disk

.rderr ;label to jump to if error occurs
lea si, ;load the error sting into SI
call prntstr ;print the string
cli ;clear interupts
hlt ;enter unrecoverable halt

.rddsk
mov ax, 0x8112 ;load 0x8112 into AX register (can't directly manipulate ES)
mov es, ax ;set ES to contents of AX (location of memory to read to)
xor bx, bx ;set offset to 0x0000

mov ah, 0x02 ;place 0x02 (read function) into AH
mov al, 0x10 ;read 16 sectors
mov ch, 0x00 ;read from cylinder 1
mov cl, 0x03 ;read from sector 3
mov dh, 0x00 ;read from head 0
mov dl, ;read from drive

int 0x13 ;call int 0x13 to read from disk
jc .rderr ;if carry flag is set (read unsuccessfull) go to error routine

lea si, ;load the address of the Kernel Load Message into SI
call prntstr ;print the string
ret

gdtset: ;function to setup and load the Global Descriptor Table
mov ax, 0x7000
mov ds, ax ;setup segment registers
mov es, ax
mov si, gdt ;start of GDT table into SI register
mov di, ;locate GDT at 0x500 im memory
mov cx, ;size of the GDT (defined my fancy footwork)

cld ;clear the direction flag
rep movsb ;move byte from DS:SI to ES:DI

lgdt ;load the Global Descriptor Table

lea si, ;load the address of the GDT Setup Message into SI
call prntstr ;print the string
ret

pmoden: ;function to enable Protected Mode
cli ;permanently clear interrupts
mov eax, cr0 ;move contents of ControlRegister0 into ExtendedAX register
or al, 0x01 ;compare AL to value 1
mov cr0, eax ;move CR0 into EAX register
ret


;Section for Strings
sysmess db 'XIX System 19 Bootloader - Version pre-Alpha',13,10,10,0 ;System Information String
;13 = char return
;10 = new line
;0 = null character (terminate)

a20mess db 'A20 Gate Successfully Enabled',13,10,10,0

krnmess db 'Kernel Loading Successful',13,10,10,0

krnloderr db 'ERROR - Kernel Loading Unsuccessfull - ABORT',13,10,0

gdtmess db 'Global Descriptor Table Successfully Setup',13,10,10,0

pmodmess db 'Will now enable Protected Mode, and boot the Kernel',13,10,10,0


;Section for Variables
bootdrv db 0 ;variable to hold boot drive number

;Section for Descriptor Tables
gdtr:
gdtsze dw gdtend-gdt-1 ;size of GDT is from gdtend subtract gdt minus 1 (for null descriptor?)
gdtbse dd 0x500 ;where GDT is located at (0x500)

gdt:
null equ $-gdt ;this is the null descriptor
dd 0x0 ;fill the null selector with 0x00 properties
dd 0x0

code equ $-gdt  ;code segment with 64KB flat memory model
dw 0x1000 ;first word of limit field (use with second to denote 64kb size)
dw 0x0 ;first word of the base field (Descriptor begins at 0x00)
db 0x0 ;third byte of the base field
db 0x9A ;first byte of attribute flags: (present,ring0,predef,nonconforming,read/execute,accessed)
db 0x40 ;second word of the limit/attribute field: (0x0000 limit2, byte granularity,32bit,predef*2)
db 0x00 ;third byte of the base field

data equ $-gdt ;data segment with 64KB flat memory model
dw 0x1000 ;first word of limit field (use with second to denote 64KB size)
dw 0x0 ;first word of the base field (Descriptor begins at 0x00)
db 0x0 ;third byte of the base field
db 0x93 ;first byte of attribute flags: (present,ring0,predef,nonexpdown,read/write,accessed)
db 0x40 ;second word of the limit/attribute field: (0x0000 limit2, byte granularity,32but,predef*2)
db 0x0 ;third byte of the base field
gdtend: ;empty function to denote end of GDT in memory


;Main Program
boot:
mov , dl ;save starting drive into variable

mov ax, 0x7000 ;setup the stack
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x2000 ;stack size is 0x200 bytes
    cld ;clear the direction flag

lea si, ;load effective address of sting sysmess into SI
call prntstr ;call function to print the string

call a20en ;call the function to enable the A20 Gate

call krnlod ;call the function to load the kernel

call gdtset ;call the function to setup the Global Descripter Table

lea si, ;load the PMode enabling message
call prntstr ;print the string
call pmoden ;call the function to enable Protected Mode

jmp code:.bootkrnl ;jump to 32 bit code to straighten out EIP


.bootkrnl
;refrest segment registers based upon GDT
mov eax, data
mov ds, eax
mov es, eax
mov gs, eax
mov fs, eax
mov ss, eax ;stack at location of data register (flat)

mov esp, 0x1000 ;stack size is 64KB

jmp code:0x8112 ;jump to residence of Kernel
cli ;REMOVE AFTER DEBUG
hlt ;halt processor from executing this binary

;set bootloader to 512 bytes (1 segment) long
times 512-($-$$) db 0 ;fill unused space upto 512 bytes with 0
Posted on 2009-11-05 14:42:07 by XeonX369

;Section for Descriptor Tables
gdtr:
gdtsze dw gdtend-gdt-1 ;size of GDT is from gdtend subtract gdt minus 1 (for null descriptor?)
gdtbse dd 0x500 ;where GDT is located at (0x500)

Of all the addresses we work with, this is the only(?) one that isn't segment:offset - it needs to be a linear address. If this code was loaded at 0:someoffset, "gdt" would be right. Otherwise add loadseg<<4 to "gdt". An even 0x500 seems "unlikely"(?).

I haven't tested this (a "problem" with Linux is that I hate to reboot), or looked it over thoroughly, but this jumps out at me as a possible problem.

(that's why they call it the "triple fault club" :)

Best,
Frank

Posted on 2009-11-05 15:57:29 by fbkotler

Of all the addresses we work with, this is the only(?) one that isn't segment:offset - it needs to be a linear address. If this code was loaded at 0:someoffset, "gdt" would be right. Otherwise add loadseg<<4 to "gdt". An even 0x500 seems "unlikely"(?).


Take another look at his gdtset subroutine.
Posted on 2009-11-06 01:06:51 by SpooK

Unfortunately, my code seems to be dysfunctional, but with the most odd thing... I have tried debugging by splitting the code into segments separated with a unrecoverable halt, and if a message was printed, that piece was deemed good to go.  None of that worked.  So far, I have determined, that the CPU will successfully reach the jump instruction before the perameter, but will then  triple-fault.  I have wracked my brain in an attempt to figure out what this issue is, been at it about 3.5 hours, and need the Gods of Assembly to intervene.


Where exactly in RAM are you loading your LBR to?

While thinking about the answer for the above question, take a good hard look at the base and limit of your GDT code segment entry.

Also, when changing SS or (E)SP, it is recommended to disable interrupts while doing so.
Posted on 2009-11-06 01:53:07 by SpooK


Of all the addresses we work with, this is the only(?) one that isn't segment:offset - it needs to be a linear address. If this code was loaded at 0:someoffset, "gdt" would be right. Otherwise add loadseg<<4 to "gdt". An even 0x500 seems "unlikely"(?).


Take another look at his gdtset subroutine.


Okay... so the gdt is being moved to 7000h:500h, or linear 0x70500, right? Or am I still missing something? That's not what we're telling lgdt.

Let me clarify(?)... "gdtr", as in "lgdt " is a segment:offset address, as usual. The address within that needs to be linear.

Best,
Frank

Posted on 2009-11-06 02:13:22 by fbkotler

Let me clarify(?)... "gdtr", as in "lgdt " is a segment:offset address, as usual. The address within that needs to be linear.


LGDT is a memory operation, it will load 48-bits (16-bit limit, 32-bit base address) from the specified memory location, gdtr in this case, and store it in the GDT Register... hence GDTR.

Assuming he is loading his LBR to 0x7000:0000 (0x00070000), then there are still two obvious problems.

1.) The GDT is specified to be at 0x00000500, but in gdtset he is setting ES to 0x7000, in which will cause rep movsb to copy the GDT to 0x00070500.
2.) The base address and limit of the GDT code segment is essentially covering the first 64KB, in which will cause a triple-fault as soon as the 32-bit far jump to .bootkrnl (still located at 0x00070000+) is attempted.

So either the GDTR base address needs to change to 0x00070500 or ES in gdtset needs to change to 0x0000, and the base address and limit of the GDT code segment needs to be corrected.

Also note that in 16-bit Real Mode, GDTR will mask the upper 8-bits of the 32-bit base address, thus making it a 24-bit base address. So, make sure your GDT is located below the 16MB mark in such a case.
Posted on 2009-11-06 11:20:09 by SpooK
I realize that this problem has arisen out of a slight misunderstanding of the way a GDT works.  Due to the guidance of the Gurus here on this forum, I feel like my knowledge on this topic has been amended.  However, I still feel in error with the regard to the Base and Limit values in the code segment.  I therefore sincerely beg a minor assistance yet more.

If I wanted to have a 64 KB segment size (Don't I already have that?), what values would I set these bytes and words to?  I still believe that I misunderstand about correcting the base and limit values.  I wish to know why, and what would the best values be?

I give many thanks to all you.
Posted on 2009-11-06 12:35:42 by XeonX369

If I wanted to have a 64 KB segment size (Don't I already have that?), what values would I set these bytes and words to?  I still believe that I misunderstand about correcting the base and limit values.  I wish to know why, and what would the best values be?


The best response would be to first ask why you would want a 64KB segment size.

So, why would you want a 64KB segment size... especially in 32-bit Protected Mode?
Posted on 2009-11-06 13:06:01 by SpooK
The reasoning why I wanted a 64KB segment is that I felt like restricting myself somewhat while my kernel was relatively small, and then expand latter.

Yes, I do realize, that I could just setup full ~4GB segment size right now, but I would rather do that sort of improvement in the future.  I do realize that this seems quite stupid right now, but this is the OS that I plan to write.  I think what the problem I am having is that I am a bit confused with Intel's fragmented GDT design.

Therefore, that is the answer to your question.  I would truly appreciate help with this issue, and will accept any suggestions.
Posted on 2009-11-06 14:27:20 by XeonX369

Yes, I do realize, that I could just setup full ~4GB segment size right now, but I would rather do that sort of improvement in the future.  I do realize that this seems quite stupid right now, but this is the OS that I plan to write.  I think what the problem I am having is that I am a bit confused with Intel's fragmented GDT design.


Well, no "improvement" is required. It is very typical to set up GDT entries to their max limits so that the entire memory space can operate in a flat manner.

Your GDT can be as small as 24 bytes (NULL, CODE and DATA entries) and doesn't have to change at all after initial GDTR load. The GDT just needs to be consistently accessible.

I've seen this many times where people think they'll implement segmentation in a unique or useful way, as to make less work or a more secure system, but end up making things slower, more complex and generally worse than even the simplest of page-based scenarios.

By trying to enforce segmentation, you will be spending more time changing the GDT and troubleshooting resultant problems. It is a mechanism that was designed to solve an old problem and doesn't really belong outside of 16-bit Real Mode.

If you plan on moving beyond 16-bit Real Mode, setup the GDT to max limits and don't look back.

However, if you really want to go down this road, then you need to adjust your GDT code entry to have a base of 0x00070000.
Posted on 2009-11-06 16:34:54 by SpooK
True. Paging is simply an improved version of segmentation and segmentation is kept just for compatibility.

Load GDTR and forget about it. Instead focus on efficient paging implementation.
Posted on 2009-11-06 17:02:54 by ti_mo_n
Or if you want to limit segments, set limits to 0FFFFFFFFh to get it working, first. Then fiddle with base and limits until you've got what you want.

Best,
Frank

Posted on 2009-11-07 05:10:45 by fbkotler
Sorry for the delayed response, my network was down and I didn't have access to my data.  :shock:

I have decided to go with Paging, so I have set the granularity to be appropriate.  The error that I have is with the base values.  I want a full 4GB space with paging, so I set the limit value to FFFF in the first limit word, and in the second byte-size section (with the attributes), I set to F.  The trouble here is that no matter what value I set 'base' as, whether it is 0x00070000, or a flat out 0, nothing seems to work.  I suppose this comes out of my own stupidity, but I do indeed need advice.

Thanks.

EDIT:  Forgot to say that I want to setup Paging in my Kernel Routine, so pretty much my problem lies with the Base value not accepting anything that I throw at it.
Posted on 2009-11-17 14:06:17 by XeonX369