OSDev.org

The Place to Start for Operating System Developers
It is currently Mon Apr 29, 2024 12:07 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 14 posts ] 
Author Message
 Post subject: Triple Fault when entering Long Mode [FIXED]
PostPosted: Sun Oct 15, 2023 12:20 am 
Offline

Joined: Sun Oct 15, 2023 12:07 am
Posts: 10
Good morning, I'm working on a small hobby OS project and so far I'm stuck on getting long mode working,
it seems to work but if I do a segmented/far jump it triple faults and gets into a reboot loop, I've been losing my mind trying to figure out this error
I have attached my code down below.
I am using QEMU to debug, and I can't pinpoint the issue even with
Quote:
-d cpu
or
Quote:
-d int
args, any help?


Attachments:
long-mode.s [1.16 KiB]
Downloaded 34 times


Last edited by proto639 on Sun Oct 15, 2023 7:24 pm, edited 1 time in total.
Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 3:42 am 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 1608
There are quite a few problems with your code. First, you are mapping the first 1GB of address space with 2MB huge pages. However, you are not supposed to use huge pages that span multiple memory types, and it is likely that the memory types are going to be mixed at the end of the first 1MB.

Your GDT has all zero segment limits. That might not matter in 64-bit mode, but it will kill you while you are in 32-bit mode after loading the GDT and the segments. Suddenly no data access will work anymore. And finally, that transfer to long mode should probably be done with a far jmp instead of a push.

Personally, I was never a fan of these mixed-environment kernels some tutorials peddle. I have a separate binary that sets up paging, mapping the kernel according to its ELF headers and setting up a few other things, before jumping to the ELF entry point. This binary reads the physical memory info from its boot environment, dynamically allocates whatever space is needed, and adds all of the allocated memory to the list of "reserved" addresses it gives the main kernel. Doing this means I have no issue using 4kB pages.

_________________
Carpe diem!


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 8:42 am 
Offline

Joined: Sun Oct 15, 2023 12:07 am
Posts: 10
nullplan wrote:
There are quite a few problems with your code. First, you are mapping the first 1GB of address space with 2MB huge pages. However, you are not supposed to use huge pages that span multiple memory types, and it is likely that the memory types are going to be mixed at the end of the first 1MB.

Your GDT has all zero segment limits. That might not matter in 64-bit mode, but it will kill you while you are in 32-bit mode after loading the GDT and the segments. Suddenly no data access will work anymore. And finally, that transfer to long mode should probably be done with a far jmp instead of a push.

Personally, I was never a fan of these mixed-environment kernels some tutorials peddle. I have a separate binary that sets up paging, mapping the kernel according to its ELF headers and setting up a few other things, before jumping to the ELF entry point. This binary reads the physical memory info from its boot environment, dynamically allocates whatever space is needed, and adds all of the allocated memory to the list of "reserved" addresses it gives the main kernel. Doing this means I have no issue using 4kB pages.

My bad about the push, I was testing out some other methods I saw.
How big are pages usually? The tutorial I saw used 2MB pages, also where do you see the zero segment limits? I can't really read the GDT all that well, apologies.


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 11:37 am 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 1608
proto639 wrote:
My bad about the push, I was testing out some other methods I saw.
How big are pages usually? The tutorial I saw used 2MB pages, also where do you see the zero segment limits? I can't really read the GDT all that well, apologies.

Pages are normally 4kB. You can use pages larger than that (namely 2MB and 1GB pages), but those are huge pages, and there are limitations to their use. They cannot span areas with multiple memory types, for one thing. Most OSes implement 4kB paging, with optional use of huge pages if the situation permits, and having a consistent memory type is one of those requirements. In order to test for that, you'd need to worry about MTRRs, so maybe you should skip that for now and only do the 4kB paging.

The segment limit is part of the GDT entry, namely the low 16 bits, bits 48-51, and the G bit (bit 55). They set how big the segment is (more exactly: They set the highest offset in the segment.). I also at first didn't understand your segments. I am used to looking at them in hex now, because the fields do line up well with 4-bit boundaries. You know, it is basically
Code:
code: dq 0xaaflptaaaaaallll
Where a is the base address, l is the limit, f is the flags nibble (the G, D, L, and AVL bits), p is the permissions nibble (the P, DPL, and S fields), and t is the type nibble. The GDT you have in your code amounts to:
Code:
section .rodata
gdt64:
    dq 0
.code: equ $ - gdt64
    dq 0x00209a0000000000
.data: equ $ - gdt64
    dq 0x0000920000000000
.pointer:
    dw .pointer - gdt64 - 1
    dq gdt64
And if you load the second one in 32-bit mode (which you do), then you get a segmentation fault for accessing any data item beyond offset 0. But it is easily fixed if you just change it to
Code:
.code: dq 0x00af9a000000ffff
.data: dq 0x00cf92000000ffff
It takes the exact same amount of space, but now the limit is 4GB. Don't worry, once you get to 64-bit mode, the limit will be ignored.

_________________
Carpe diem!


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 1:00 pm 
Offline

Joined: Sun Oct 15, 2023 12:07 am
Posts: 10
nullplan wrote:
proto639 wrote:
My bad about the push, I was testing out some other methods I saw.
How big are pages usually? The tutorial I saw used 2MB pages, also where do you see the zero segment limits? I can't really read the GDT all that well, apologies.

Pages are normally 4kB. You can use pages larger than that (namely 2MB and 1GB pages), but those are huge pages, and there are limitations to their use. They cannot span areas with multiple memory types, for one thing. Most OSes implement 4kB paging, with optional use of huge pages if the situation permits, and having a consistent memory type is one of those requirements. In order to test for that, you'd need to worry about MTRRs, so maybe you should skip that for now and only do the 4kB paging.

The segment limit is part of the GDT entry, namely the low 16 bits, bits 48-51, and the G bit (bit 55). They set how big the segment is (more exactly: They set the highest offset in the segment.). I also at first didn't understand your segments. I am used to looking at them in hex now, because the fields do line up well with 4-bit boundaries. You know, it is basically
Code:
code: dq 0xaaflptaaaaaallll
Where a is the base address, l is the limit, f is the flags nibble (the G, D, L, and AVL bits), p is the permissions nibble (the P, DPL, and S fields), and t is the type nibble. The GDT you have in your code amounts to:
Code:
section .rodata
gdt64:
    dq 0
.code: equ $ - gdt64
    dq 0x00209a0000000000
.data: equ $ - gdt64
    dq 0x0000920000000000
.pointer:
    dw .pointer - gdt64 - 1
    dq gdt64
And if you load the second one in 32-bit mode (which you do), then you get a segmentation fault for accessing any data item beyond offset 0. But it is easily fixed if you just change it to
Code:
.code: dq 0x00af9a000000ffff
.data: dq 0x00cf92000000ffff
It takes the exact same amount of space, but now the limit is 4GB. Don't worry, once you get to 64-bit mode, the limit will be ignored.

Thank you so much! It does enter into 64-bit mode, however I'm now facing an entirely new issue.
Whenever I do my long jump into my long mode label with this:
Code:
jmp gdt64.code:long_mode_start
, it seems the area it jumps to is bogus and is filled with zeros, QEMU with -d in_asm shows this whenever it jumps:
Code:
0x010006f8:  00 00                    addb     %al, (%rax)

and it goes on for several seconds, until it seemingly triple-faults again (probably unrelated to the GDT) and shuts down
this is what the jump instruction shows according to QEMU:
Code:
0x0010130b:  ea 12 03 00 01 08 00     ljmpl    $0x8:$0x1000312

----------------
IN:
0x01000312:  00 00                    addb     %al, (%rax)


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 5:53 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
proto639 wrote:
Code:
0x0010130b:
0x01000312:

Those two addresses are awfully far apart. How are you linking your binary?


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 6:33 pm 
Offline

Joined: Sun Oct 15, 2023 12:07 am
Posts: 10
Octocontrabass wrote:
proto639 wrote:
Code:
0x0010130b:
0x01000312:

Those two addresses are awfully far apart. How are you linking your binary?

You're right, I just noticed how far they really are, I think I have my .text around 0x010000? Here's my linker script if you need it:

Code:
ENTRY(start)

SECTIONS {
    . = 1M;
    _kernel_physical_start = .;

    .boot :
    {
        *(.multiboot_header)
    }

    . = ALIGN(0x1000);
    _boot_end = .;

    . += 0xFFFFFFFF80000000;
    _kernel_virtual_start = .;
    .text : AT(_boot_end)
    {
        *(.multiboot)
        *(.text)
    }
}

If you want to see everything, here's the repository:
https://github.com/ocso639/divisix/


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 6:53 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
Why are you using -Ttext to override your linker script?


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 6:58 pm 
Offline

Joined: Sun Oct 15, 2023 12:07 am
Posts: 10
Octocontrabass wrote:

oh god how did i miss that
I got rid of that, but now my linker complains about this:

Code:
long-mode.o: in function `start':
long-mode.s:(.text+0x1): relocation truncated to fit: R_X86_64_32 against `.bss'
long-mode.s:(.text+0x9): relocation truncated to fit: R_X86_64_32 against `.bss'
long-mode.s:(.text+0xe): relocation truncated to fit: R_X86_64_32 against `.bss'
long-mode.s:(.text+0x16): relocation truncated to fit: R_X86_64_32 against `.bss'
long-mode.o: in function `start.map_p2_table':
long-mode.s:(.text+0x2e): relocation truncated to fit: R_X86_64_32 against `.bss'
long-mode.s:(.text+0x3c): relocation truncated to fit: R_X86_64_32 against `.bss'
long-mode.s:(.text+0x7c): relocation truncated to fit: R_X86_64_32 against `.text'
make: *** [makefile:15: all] Error 1

I'm having a hard time trying to understand what it means by this


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 7:22 pm 
Offline

Joined: Sun Oct 15, 2023 12:07 am
Posts: 10
Nevermind, I changed my linker script and I managed to fix it up, it's working perfectly now!
Thank you all for your help


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 7:28 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
proto639 wrote:
Code:
relocation truncated to fit: R_X86_64_32

Your code is referencing symbols in the .text and .bss sections using instructions that only support unsigned 32-bit addresses. Your linker script assigns virtual addresses for .text and .bss somewhere above 0xFFFFFFFF80000000, so the addresses can't be represented as unsigned 32-bit values.

Since it looks like all of those references occur in the 32-bit startup code that runs before you've jumped to the higher half, you could solve the problem by moving those symbols to a section that isn't in the higher half. Or, if you want to be clever about it, you could add an offset to each symbol so the linker can insert the higher-half addresses but the added offset results in the correct lower-half address. (The linker might still complain that the symbol+offset doesn't fit in 32 bits.)

Or, you can avoid the whole problem by not trying to include the 32-bit setup code in your 64-bit kernel binary.

proto639 wrote:
Nevermind, I changed my linker script and I managed to fix it up, it's working perfectly now!

...You sure about that? Your linker script doesn't specify where .bss, .data, or .rodata will go, so things might not be working as well as you think. (And don't forget wildcards! If you use ".rodata" instead of ".rodata*" you'll miss sections with names like ".rodata.str.1".)


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 7:35 pm 
Offline

Joined: Sun Oct 15, 2023 12:07 am
Posts: 10
Octocontrabass wrote:
proto639 wrote:
Code:
relocation truncated to fit: R_X86_64_32

Your code is referencing symbols in the .text and .bss sections using instructions that only support unsigned 32-bit addresses. Your linker script assigns virtual addresses for .text and .bss somewhere above 0xFFFFFFFF80000000, so the addresses can't be represented as unsigned 32-bit values.

Since it looks like all of those references occur in the 32-bit startup code that runs before you've jumped to the higher half, you could solve the problem by moving those symbols to a section that isn't in the higher half. Or, if you want to be clever about it, you could add an offset to each symbol so the linker can insert the higher-half addresses but the added offset results in the correct lower-half address. (The linker might still complain that the symbol+offset doesn't fit in 32 bits.)

Or, you can avoid the whole problem by not trying to include the 32-bit setup code in your 64-bit kernel binary.

proto639 wrote:
Nevermind, I changed my linker script and I managed to fix it up, it's working perfectly now!

...You sure about that? Your linker script doesn't specify where .bss, .data, or .rodata will go, so things might not be working as well as you think. (And don't forget wildcards! If you use ".rodata" instead of ".rodata*" you'll miss sections with names like ".rodata.str.1".)

It does boot, but now that you mention it there might be a hidden problem somewhere, waiting for the right moment to strike
I need to include 32 bit setup code in my binary as GRUB switches to protected mode (I don't know what it does under EFI, so I may be wrong if it's long mode in EFI)


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 7:53 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
proto639 wrote:
I need to include 32 bit setup code in my binary as GRUB switches to protected mode

You can have two separate binaries. GRUB can load as many files as you like in the form of Multiboot modules; just tell it your kernel is one of the modules it should load and then your kernel can be separate from the 32-bit setup code.

proto639 wrote:
(I don't know what it does under EFI, so I may be wrong if it's long mode in EFI)

It does the same thing. (And EFI can be 32-bit too.)

Now that I think of it, you might be interested in a more modern bootloader such as Limine.


Top
 Profile  
 
 Post subject: Re: Triple Fault when entering Long Mode
PostPosted: Sun Oct 15, 2023 7:59 pm 
Offline

Joined: Sun Oct 15, 2023 12:07 am
Posts: 10
Octocontrabass wrote:
proto639 wrote:
I need to include 32 bit setup code in my binary as GRUB switches to protected mode

You can have two separate binaries. GRUB can load as many files as you like in the form of Multiboot modules; just tell it your kernel is one of the modules it should load and then your kernel can be separate from the 32-bit setup code.

proto639 wrote:
(I don't know what it does under EFI, so I may be wrong if it's long mode in EFI)

It does the same thing. (And EFI can be 32-bit too.)

Maybe, that does seem more efficient as I don't need to recompile the kernel, I just need to recompile the long mode loader and keep the kernel as-is

Octocontrabass wrote:
Now that I think of it, you might be interested in a more modern bootloader such as Limine.

I could try that, but I'm most familiar with GRUB

I just looked further into Limine, and I see there's SMP requests (or whatever they're called), I think I might try that..


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot], SemrushBot [Bot] and 20 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group