Page 1 of 1
GPF while jumping into 32-bit CS
Posted: Sat Jan 02, 2021 3:10 pm
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
Re: GPF while jumping into 32-bit CS
Posted: Sat Jan 02, 2021 7:12 pm
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).
Re: GPF while jumping into 32-bit CS
Posted: Sun Jan 03, 2021 10:07 am
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:
probably gives the CPU a wrong address and this is the reason why GPF appears.
Re: GPF while jumping into 32-bit CS
Posted: Sun Jan 03, 2021 11:10 am
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.
Re: GPF while jumping into 32-bit CS
Posted: Sun Jan 03, 2021 11:31 am
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.
Re: GPF while jumping into 32-bit CS
Posted: Sun Jan 03, 2021 12:06 pm
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
Re: GPF while jumping into 32-bit CS
Posted: Sun Jan 03, 2021 11:17 pm
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
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?
Re: GPF while jumping into 32-bit CS
Posted: Tue Jan 05, 2021 4:12 pm
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
Re: GPF while jumping into 32-bit CS
Posted: Tue Jan 05, 2021 4:31 pm
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.
Re: GPF while jumping into 32-bit CS
Posted: Tue Jan 05, 2021 4:45 pm
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.
Re: GPF while jumping into 32-bit CS
Posted: Tue Jan 05, 2021 6:06 pm
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.
Re: GPF while jumping into 32-bit CS
Posted: Tue Jan 05, 2021 6:22 pm
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?
Re: GPF while jumping into 32-bit CS
Posted: Tue Jan 05, 2021 6:53 pm
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?
Re: GPF while jumping into 32-bit CS
Posted: Tue Jan 05, 2021 7:32 pm
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.
Re: GPF while jumping into 32-bit CS
Posted: Wed Jan 06, 2021 5:47 am
by Shizzer
Thank you! Everything is clear.