GPF while jumping into 32-bit CS

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
Shizzer
Posts: 6
Joined: Sat Jan 02, 2021 2:49 pm

GPF while jumping into 32-bit CS

Post by Shizzer »

Hello,

I'm creating a bootloader and trying to jump into 32-bit code segment in order to finish protected mode activation. But I have a problem with General Protection Segmentation Fault and I was struggling with it for a while but I couldn't find a solution, unfortunately.

I use Bochs as the emulator and I'm attaching a screenshot of an exception. As you can see the CPU is already in protected mode.

This is the part of the bootloader inside the bootsector:

Code: Select all

[bits 16]

; set registers
cli
xor bx, bx
mov ax, 0x50
mov es, ax
sti

mov ah, 0x2
mov al, 0x1 ; how many sectors to read
mov ch, 0x0 ; cylinder number
mov cl, 0x2   ; read from sector 2
mov dh, 0x0   ; head 0
; drive number is already set by BIOS
int 0x13    ; stage2 will be loaded at 0x0050:0x0000 == 0x500 (physical addr)

jmp 0x50:0x0   ; jump to stage2 at (0x50*0x10+0) == 0x500
jmp $

times 510 - ($ - $$) db 0
; now the machine code is exactly 512 bytes in size
dw 0xaa55   ; 0xaa55 tells the CPU that this is the 1st stage of a bootloader
And here is the code responsible for switching CPU to protected mode:

Code: Select all

[bits 16]
[org 0x500]

cli     ; clear interrupts while switching from real to protected mode

lgdt [gdt_descriptor]   ; load address of the gdt_descriptor into gdtr
mov eax, cr0
or al, 0x1   ; set first bit of cr0
mov cr0, eax    ; make a switch to protected mode

; jump to 32-bit code segment
jmp 0x8:pmode_entry    ; I think that the problem is HERE

align 0x8  ; GDT should be aligned to an 8-byte boundary for performance reasons
gdt_start:  ; Basic flat model
    gdt_null:
        dq 0x0    ; null-descriptor
    gdt_code:
        dw 0xffff  ; limit (bits 0-15)
        dw 0x0    ; base address (bits 0-15)
        db 0x0    ; base address (bits 16-23)
        db 10011010b  ; P(15), DPL(14,13), S, Type(11-8)
        db 11001111b  ; G, D/B, L, AVL, limit (bits 16-19)
        db 0x0  ; base address (bits 24-32)
    gdt_data:
        dw 0xffff
        dw 0x0
        db 0x0
        db 10010010b
        db 11001111b
        db 0x0
gdt_end:    ; This label is used for calculating limit of the GDT

gdt_descriptor:
    dw gdt_end - gdt_start - 0x1  ; From Intel Manual Vol. 3:
                                ; "Because segment descriptors are always 8 bytes long, 
                                ; the GDT limit should always be one less than an integral multiple of eight"
    dd gdt_start    ; GDT base address

[bits 32]

; Setting data segment registers to the proper index of GDT.
; Data segment index in GDT is exactly 0x10
pmode_entry:
    mov ax, 0x10
    mov ds, ax  
    mov es, ax  
    mov ss, ax
    mov fs, ax
    mov gs, ax
In addition, here is the file that I compile in order to create a floppy image of my OS:

Code: Select all

%include "stage1.asm"
%include "protected_mode.asm"

times 1024 - ($-$$) db 0  ; alignment
I would be very thankful for the help. Happy new year :)
Attachments
bochs_GPF.png
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: GPF while jumping into 32-bit CS

Post by Octocontrabass »

Code: Select all

jmp 0x50:0x0   ; jump to stage2 at (0x50*0x10+0) == 0x500
This disagrees with your stage 2 ORG statement. Use JMP 0:0x500 instead. (You could also change the ORG statement, but I strongly recommend setting segment registers to zero in real mode. You will need more complex code to load the GDTR if you use nonzero segments.)

Code: Select all

lgdt [gdt_descriptor]   ; load address of the gdt_descriptor into gdtr
This instruction loads the data at DS:gdt_descriptor into the GDTR. You must set DS to a reasonable value (zero) before it will work correctly; you have no idea what value the BIOS used.

Code: Select all

jmp 0x8:pmode_entry    ; I think that the problem is HERE
Your screenshot says this is the faulting instruction, but the problem is most likely an invalid GDT due to a bug earlier in your code. GDT issues won't appear until you load a segment register, and this instruction loads a segment register (CS).
Shizzer
Posts: 6
Joined: Sat Jan 02, 2021 2:49 pm

Re: GPF while jumping into 32-bit CS

Post by Shizzer »

Thank you very much for your help!
I thought yesterday before going to sleep that the DS register should be set to a reasonable value and 0 is the best candidate here. I actually don't know what lies in the DS register therefore this instruction:

Code: Select all

lgdt [gdt_descriptor]
probably gives the CPU a wrong address and this is the reason why GPF appears.
sj95126
Member
Member
Posts: 151
Joined: Tue Aug 11, 2020 12:14 pm

Re: GPF while jumping into 32-bit CS

Post by sj95126 »

Am I the only one who finds it immensely aggravating that Bochs and the other virtual machines can't be bothered to clearly tell you what conditions led to the triple fault? Would it really be so hard to include an extra line of output that clearly says that the triple fault was the result of a #GP which led to a #PF or whatever?

You shouldn't have to dig through debugging logs to get a simple straightforward answer that might tell you how to fix it immediately.
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: GPF while jumping into 32-bit CS

Post by iansjack »

Bochs is open source. So fix it in your copy if you think it's important. You could then submit your fix to the developers.
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: GPF while jumping into 32-bit CS

Post by bzt »

sj95126 wrote:Am I the only one who finds it immensely aggravating that Bochs and the other virtual machines can't be bothered to clearly tell you what conditions led to the triple fault? Would it really be so hard to include an extra line of output that clearly says that the triple fault was the result of a #GP which led to a #PF or whatever?
No, you're not the only one. I have a patch that contains that output :-)
https://github.com/bztsrc/bochs

Cheers,
bzt
xeyes
Member
Member
Posts: 212
Joined: Mon Dec 07, 2020 8:09 am

Re: GPF while jumping into 32-bit CS

Post by xeyes »

sj95126 wrote:Am I the only one who finds it immensely aggravating that Bochs and the other virtual machines can't be bothered to clearly tell you what conditions led to the triple fault? Would it really be so hard to include an extra line of output that clearly says that the triple fault was the result of a #GP which led to a #PF or whatever?

You shouldn't have to dig through debugging logs to get a simple straightforward answer that might tell you how to fix it immediately.
I think it is by design and a correct behavior for other virtual machines to do what they do as their ultimate goal is to mimic a real machine.

Now we all know real machines don't magically bring out a separate display showing straightforward answers like "please make XYZ changes in filename A line number B so your OS will run fine on me" when it runs into a triple fault.

Bochs, on the other hand, is too slow and clunky for any general purpose use but kind of in this niche (so is simics) so what you mentioned might be a good fit.

Yet from their response to bugs about their latest "stable release" doesn't build (the response is like "go learn how to file bugs, CLOSED!"), it is not hard to imagine their response to a feature request :lol:
Would it really be so hard to include an extra line of output
Good news is, you can perhaps edit their source files and add 1 extra line?
Shizzer
Posts: 6
Joined: Sat Jan 02, 2021 2:49 pm

Re: GPF while jumping into 32-bit CS

Post by Shizzer »

Today I've decided to solve the mystery about jumping to 32-bit CS and I was 95% sure that the problem is with the DS register. Unfortunately, it turns out that even if the DS register is set to 0 the Bochs is complaining.

Stage1:

Code: Select all

[bits 16]

; set registers
cli
xor ax, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
sti

mov bx, 0x500   ; load to ES:BX == 0x500
mov ah, 0x2
mov al, 0x1 ; how many sectors to read
mov ch, 0x0 ; cylinder number
mov cl, 0x2   ; read from sector 2
mov dh, 0x0   ; head 0
; drive number is already set by BIOS
int 0x13    ; stage2 will be loaded at 0x0500:0x0000 == 0x500

jmp 0x0:0x500   ; jump to stage2 at 0x500
jmp $

times 510 - ($ - $$) db 0
; now the machine code is exactly 512 bytes in size
dw 0xaa55   ; 0xaa55 tells the CPU that this is the 1st stage of a bootloader
Stage2:

Code: Select all

[bits 16]
[org 0x500]

cli     ; clear interrupts while switching from real to protected mode

lgdt [gdt_descriptor]   ; load address of the gdt_descriptor into gdtr

mov eax, cr0
or al, 0x1   ; set first bit of cr0
mov cr0, eax    ; make a switch to protected mode

; jump to 32-bit code segment and set CS register
jmp 0x8:pmode_entry

align 0x8  ; GDT should be aligned to an 8-byte boundary for performance reasons
gdt_start:  ; Basic flat model
    gdt_null:
        dq 0x0    ; null-descriptor
    gdt_code:
        dw 0xffff  ; limit (bits 0-15)
        dw 0x0    ; base address (bits 0-15)
        db 0x0    ; base address (bits 16-23)
        db 10011010b  ; P(15), DPL(14,13), S, Type(11-8)
        db 11001111b  ; G, D/B, L, AVL, limit (bits 16-19)
        db 0x0  ; base address (bits 24-32)
    gdt_data:
        dw 0xffff
        dw 0x0
        db 0x0
        db 10010010b
        db 11001111b
        db 0x0
gdt_end:    ; This label is used for calculating limit of the GDT

gdt_descriptor:
    dw gdt_end - gdt_start - 0x1  ; From Intel Manual Vol. 3:
                                ; "Because segment descriptors are always 8 bytes long, 
                                ; the GDT limit should always be one less than an integral multiple of eight"
    dd gdt_start    ; GDT base address

[bits 32]

; Setting data segment registers to the proper index of GDT.
; Data segment index in GDT is exactly 0x10
pmode_entry:
    mov ax, 0x10
    mov ds, ax  
    mov es, ax  
    mov ss, ax
    mov fs, ax
    mov gs, ax
    lea eax, [0xb8000]
    mov dword [eax], 0x41414141
    jmp $
I've set the segment registers to zero in Stage1 as you advised me. I attached the Bochs output and it appears that DS is set to 0 before jumping along with other segment registers.
By the way, the address to which the CPU is trying to jump is weird (0xEA36070800) and I have no idea how this address is created. I would be thankful for the help cause I'm helpless at the moment
Attachments
bochs.png
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: GPF while jumping into 32-bit CS

Post by Octocontrabass »

You're telling NASM to assemble all of the code into a single section that starts at address 0x500, but the first 512 bytes are actually loaded at address 0x7C00. Assemble the two parts separately or use the SECTION directive to directly assemble and link your binary.
Shizzer
Posts: 6
Joined: Sat Jan 02, 2021 2:49 pm

Re: GPF while jumping into 32-bit CS

Post by Shizzer »

Ok, I will try to compile these two sections separately and mix them together into one binary file.
But could you explain to me why this is such important in my case? I always thought that the ORG directive is primarily for dealing with labels. That's why I didn't care about the ORG directive that exists in my Stage2. In Stage1 there are no labels so why the origin of this machine code matters?

To be honest, I was wondering about the compilation of these two stages separately, but I found that it can be avoided.
xeyes
Member
Member
Posts: 212
Joined: Mon Dec 07, 2020 8:09 am

Re: GPF while jumping into 32-bit CS

Post by xeyes »

Shizzer wrote:I always thought that the ORG directive is primarily for dealing with labels.
My understanding: yes it deals with labels, and since you are using labels in your code you'd want to make sure labels are loaded at where they think they are at. Otherwise it is like telling someone "you can find some place at street A number B" while that place is actually at a totally different location.
Shizzer wrote:To be honest, I was wondering about the compilation of these two stages separately, but I found that it can be avoided.
Yes it can be avoided, like Octocontrabass said, you can simply mark different parts of the code to be in different sections.
Shizzer
Posts: 6
Joined: Sat Jan 02, 2021 2:49 pm

Re: GPF while jumping into 32-bit CS

Post by Shizzer »

After making the script responsible for compiling separately stage1 and the rest of the bootloader into one binary image everything works perfectly! But I don't why. :?
xeyes wrote:My understanding: yes it deals with labels, and since you are using labels in your code you'd want to make sure labels are loaded at where they think they are at.
I use labels in my code - that's true, but labels exist only in stage2 in which the org directive was set correctly. Why do I have to use the org directive also in stage1 where there are no labels?
xeyes
Member
Member
Posts: 212
Joined: Mon Dec 07, 2020 8:09 am

Re: GPF while jumping into 32-bit CS

Post by xeyes »

Shizzer wrote:After making the script responsible for compiling separately stage1 and the rest of the bootloader into one binary image everything works perfectly! But I don't why. :?
xeyes wrote:My understanding: yes it deals with labels, and since you are using labels in your code you'd want to make sure labels are loaded at where they think they are at.
I use labels in my code - that's true, but labels exist only in stage2 in which the org directive was set correctly. Why do I have to use the org directive also in stage1 where there are no labels?
My guess: you calling it stage1 and 2 in a forum post and quoting them in 2 boxes don't make them link and load as 2 separate sections.

Without actually marking them as separate sections, they are simply concatenated together. What does readelf output show for the section headers and program headers?
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: GPF while jumping into 32-bit CS

Post by Octocontrabass »

Shizzer wrote:After making the script responsible for compiling separately stage1 and the rest of the bootloader into one binary image everything works perfectly! But I don't why. :?
The %include directive is equivalent to copying the entire contents of the included file wherever you put it. That means your ORG statement is stuck somewhere in the middle of your code. NASM doesn't care where you put the ORG statement, it could be at the end of the file and it will still apply from the very beginning. Since you have one big block of code and one ORG statement, the entire block of code (stage 1 and stage 2) is assigned addresses starting at 0x500. Since stage 1 takes 512 bytes, stage 2 starts at address 0x700.

If you had used an ORG statement in stage 1, you would have gotten an error: you're not allowed to have multiple ORG statements in a single file.
Shizzer
Posts: 6
Joined: Sat Jan 02, 2021 2:49 pm

Re: GPF while jumping into 32-bit CS

Post by Shizzer »

Thank you! Everything is clear.
Post Reply