OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 2:04 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Crash after loading GDT and flushing registers
PostPosted: Sun Sep 12, 2021 12:25 pm 
Offline
Member
Member

Joined: Tue Aug 31, 2021 7:25 am
Posts: 67
I'm still learning so I apologise if this issue is trivial.
I made my GDT in C++ which defines both the kernel and user segments, problem is with my asm (probably with my table). VirtualBox crashes after the:
Code:
mov ss, ax
instruction.

gdt.h
Code:
#pragma once
#include "stdint.h"

struct GDTDescriptor {
    uint16_t Size;
    uint32_t Offset;
} __attribute__((packed));

struct GDTEntry {
    uint16_t Limit0;
    uint16_t Base0;
    uint8_t Base1;
    uint8_t AccessByte;
    uint8_t Limit1_Flags;
    uint8_t Base2;
} __attribute__((packed));

struct GDT {
    GDTEntry Null;
    GDTEntry KernelCode;
    GDTEntry KernelData;
    GDTEntry UserNull;
    GDTEntry UserCode;
    GDTEntry UserData;
} __attribute__((packed))
__attribute((aligned(0x1000)));

extern GDT DefaultGDT;

extern "C" void LoadGDT(GDTDescriptor* gdtDescriptor);


gdt.cpp
Code:
#include "gdt.h"

GDT DefaultGDT = {
    {0, 0, 0, 0x00, 0x00, 0},
    {0, 0, 0, 0x9a, 0xa0, 0},
    {0, 0, 0, 0x92, 0xa0, 0},
    {0, 0, 0, 0x00, 0x00, 0},
    {0, 0, 0, 0x9a, 0xa0, 0},
    {0, 0, 0, 0x92, 0xa0, 0}
};


gdt.asm (separate assembly file, linked after)
Code:
global LoadGDT
LoadGDT:
    lgdt [edi]
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    retf


Loading the table in the main kernel file:
Code:
GDTDescriptor gdtDescriptor;
gdtDescriptor.Size = sizeof(GDT) - 1;
gdtDescriptor.Offset = (uint32_t)&DefaultGDT;

LoadGDT(&gdtDescriptor);


Advice and help would be greatly appreciated!

(I am running in protected mode after booting from GRUB).


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Sun Sep 12, 2021 2:18 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
YDeeps1 wrote:
(I am running in protected mode after booting from GRUB).

The descriptors in your GDT are not valid in protected mode, they're only valid in long mode. Are you trying to switch to long mode?

Also, you don't need a second null descriptor, and you probably want user mode to be ring 3 instead of ring 0.


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Sun Sep 12, 2021 2:57 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 618
Location: North Dakota, United States
For the GDT entries, you may not need to attribute it with packed since its 8 bytes long and the compiler (shouldn't) insert any extra padding. But I might be wrong about that.


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Sun Sep 12, 2021 3:06 pm 
Offline
Member
Member

Joined: Tue Aug 31, 2021 7:25 am
Posts: 67
Octocontrabass wrote:
YDeeps1 wrote:
(I am running in protected mode after booting from GRUB).

The descriptors in your GDT are not valid in protected mode, they're only valid in long mode. Are you trying to switch to long mode?

Also, you don't need a second null descriptor, and you probably want user mode to be ring 3 instead of ring 0.


I'm not actually planning to switch to long mode, I'm sticking with 32 bits. I must've gotten confused with the 32 and 64 bit GDT table.
Mind pointing out the invalid descriptors?

For now I'm keeping all my segments kernel level for testing but I'll be sure to change it to ring 3 once the time comes.


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Sun Sep 12, 2021 4:36 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
YDeeps1 wrote:
Mind pointing out the invalid descriptors?

The four that aren't null. They describe 64-bit segments, but segments may only be 16-bit or 32-bit in protected mode.


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Sun Sep 12, 2021 9:58 pm 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 1593
Also, you are using a far return at the end of the function, but no work has been done to prepare that far return. I think you need something like
Code:
  pop eax
  push 8
  push eax
  retf
at the end there, to actually load your CS with 8.

_________________
Carpe diem!


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Mon Sep 13, 2021 12:43 pm 
Offline
Member
Member

Joined: Tue Aug 31, 2021 7:25 am
Posts: 67
Octocontrabass wrote:
YDeeps1 wrote:
Mind pointing out the invalid descriptors?

The four that aren't null. They describe 64-bit segments, but segments may only be 16-bit or 32-bit in protected mode.


Huh? I'm a little confused, I've been using a range of sources as references including the osdev wiki and they all seem to point towards the entries being 32bit (I admit I've been a bit lazy).

So I rewrote some of my code and added a function which automatically deals with spread bits so I can take that frustration out and rewrote my segment descriptors based on the advice here.

Helper function:
Code:
GDTEntry gdt_create_entry(uint32_t base, uint32_t limit, uint8_t access, uint8_t flags) {
    GDTEntry gdt;

    gdt.Limit0 = (uint16_t) (limit & 0xFFFF);
    gdt.Base0 = (uint16_t) (base & 0xFFFF);
    gdt.Base1 = (uint8_t) ((base >> 16) & 0xFF);
    gdt.AccessByte = (uint8_t) access;
    gdt.Limit1_Flags = (uint8_t) ((limit >> 16) & 0x0F);
    gdt.Limit1_Flags |= (uint8_t) (flags & 0xF0);
    gdt.Base2 = (uint8_t) ((base >> 24) & 0xFF);

    return gdt;
}


Segment descriptors:
Code:
GDT DefaultGDT = {
    gdt_create_entry(0, 0, 0, 0), // null segment
    gdt_create_entry(0, 0x8000000, 0x9A, 0x40), // kernel code segment
    gdt_create_entry(0, 0x8000000, 0x92, 0x40), // kernel data segment
    gdt_create_entry(0, 0x8000000, 0xFA, 0x40), // user code segment
    gdt_create_entry(0, 0x8000000, 0xF2, 0x40) // user data segment
};


(the sudden shift from addressing 4gb memory in 4kb pages to addressing 128mb in 1bytes is since I only need 128mb memory at the moment, not the full 4gb).

(i have also set the user segment privilege levels to 3).

From my perspective this descriptor should address all 128mb of memory in 1 byte granules with appropriate execute flags being set and direction bits with the size bit being set to 32bit protected mode (written with the help of https://wiki.osdev.org/Global_Descriptor_Table).

I am also assuming that overlapping segments are fine in this context.
However it is still crashing at that position and I hate not being able to properly debug this myself.

Where did I go wrong?


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Mon Sep 13, 2021 1:03 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
YDeeps1 wrote:
Huh? I'm a little confused, I've been using a range of sources as references including the osdev wiki and they all seem to point towards the entries being 32bit (I admit I've been a bit lazy).

You were setting the flags byte to 0xA0, which has the L bit set and the D/B bit clear to indicate a 64-bit segment. For a 32-bit segment, you must clear the L bit and set the D/B bit.

YDeeps1 wrote:
Where did I go wrong?

You can't address more than 1 MiB with 1-byte granularity. The limit field has only 20 bits, and the value you're trying to write is being truncated to fit, so you're actually setting the limit to 0, giving you 1-byte segments.


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Mon Sep 13, 2021 1:34 pm 
Offline
Member
Member

Joined: Tue Aug 31, 2021 7:25 am
Posts: 67
Octocontrabass wrote:
YDeeps1 wrote:
Huh? I'm a little confused, I've been using a range of sources as references including the osdev wiki and they all seem to point towards the entries being 32bit (I admit I've been a bit lazy).

You were setting the flags byte to 0xA0, which has the L bit set and the D/B bit clear to indicate a 64-bit segment. For a 32-bit segment, you must clear the L bit and set the D/B bit.

YDeeps1 wrote:
Where did I go wrong?

You can't address more than 1 MiB with 1-byte granularity. The limit field has only 20 bits, and the value you're trying to write is being truncated to fit, so you're actually setting the limit to 0, giving you 1-byte segments.


Ah, thank you.

Code:
GDT DefaultGDT = {
    gdt_create_entry(0, 0, 0, 0),
    gdt_create_entry(0, 0x8000, 0x9A, 0xC),
    gdt_create_entry(0, 0x8000, 0x92, 0xC),
    gdt_create_entry(0, 0x8000, 0xFA, 0xC),
    gdt_create_entry(0, 0x8000, 0xF2, 0xC)
};


I have changed the limits to 128mb in 4kb blocks and have hopefully set the two bit flags to set granularity to 4kb blocks and size to 32bit mode (1100). (unless there is something wrong with my bit splitter function?)

It still however crashes; any ideas?


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Mon Sep 13, 2021 2:14 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
YDeeps1 wrote:
It still however crashes; any ideas?

How are you calling LoadGDT to make RETF do the right thing?

Is all of your code and data located within your segment limits?

That's all I can think of with the information you've posted so far.


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Mon Sep 13, 2021 3:44 pm 
Offline
Member
Member

Joined: Tue Aug 31, 2021 7:25 am
Posts: 67
Octocontrabass wrote:
YDeeps1 wrote:
It still however crashes; any ideas?

How are you calling LoadGDT to make RETF do the right thing?

Is all of your code and data located within your segment limits?

That's all I can think of with the information you've posted so far.


Right now I am actually just halting right after moving AX into SS to see if it actually loads the table (now that I think about it, doesn't sound like a viable testing approach).

I am pretty certain it is within the segments, I have even double checked the memory locations of some of the stack variables and functions and they're all within the 128mb segment.

For the RETF situation I'm not entirely sure on how to implement it. I did try null's solution but it still crashes, that might not even be the thing that crashes. Maybe my table isn't aligned properly? To reiterate:

I instantiate a GDT, calculate the size and offset of the actual entries then pass the memory location as a parameter to my assembly function (which is in EDI):

Code:
GDTDescriptor gdtDescriptor;
gdtDescriptor.Size = sizeof(GDT) - 1;
gdtDescriptor.Offset = (uint32_t)&DefaultGDT;

LoadGDT(&gdtDescriptor);


(to show my GDT implementation again):

Code:
struct GDTDescriptor {
    uint16_t Size;
    uint32_t Offset;
} __attribute__((packed));

struct GDTEntry {
    uint16_t Limit0;
    uint16_t Base0;
    uint8_t Base1;
    uint8_t AccessByte;
    uint8_t Limit1_Flags;
    uint8_t Base2;
} __attribute__((packed));

struct GDT {
    GDTEntry Null;
    GDTEntry KernelCode;
    GDTEntry KernelData;
    GDTEntry UserCode;
    GDTEntry UserData;
} __attribute__((packed))
__attribute((aligned(0x1000))); // i did have align be sketchy on me on more than one occasion with GCC C++

GDTEntry gdt_create_entry(uint32_t base, uint32_t limit, uint8_t access, uint8_t flags);

extern GDT DefaultGDT; // calls assembly function shown above in another file

extern "C" void LoadGDT(GDTDescriptor* gdtDescriptor);


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Mon Sep 13, 2021 4:16 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
YDeeps1 wrote:
then pass the memory location as a parameter to my assembly function (which is in EDI):

Whoops, my bad, I was so focused on the RETF that I didn't even notice that your memory location definitely is not in EDI. There are no 32-bit ABIs where EDI is used to pass parameters. In fact, the default 32-bit ABI passes all parameters on the stack.

You can tell GCC to pass parameters in registers, and the first parameter will be in EAX instead of EDI. It can be configured for individual functions if you don't want it to apply to the whole kernel.


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Mon Sep 13, 2021 4:47 pm 
Offline
Member
Member

Joined: Tue Aug 31, 2021 7:25 am
Posts: 67
Octocontrabass wrote:
YDeeps1 wrote:
then pass the memory location as a parameter to my assembly function (which is in EDI):

Whoops, my bad, I was so focused on the RETF that I didn't even notice that your memory location definitely is not in EDI. There are no 32-bit ABIs where EDI is used to pass parameters. In fact, the default 32-bit ABI passes all parameters on the stack.

You can tell GCC to pass parameters in registers, and the first parameter will be in EAX instead of EDI. It can be configured for individual functions if you don't want it to apply to the whole kernel.


I did actually realise that just before your reply :D I now grab the address directly from the stack (and have made sure this time several times it actually works) but it still crashes which is quite confusing. It's a shame there is no good way to debug this.


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Mon Sep 13, 2021 5:39 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
YDeeps1 wrote:
It's a shame there is no good way to debug this.

Sounds like you haven't found this page yet.

Check your VM's triple fault logs before you hook up a whole debugger - sometimes just seeing what state the CPU was in before it crashed is enough to figure out what went wrong for it to get there.


Top
 Profile  
 
 Post subject: Re: Crash after loading GDT and flushing registers
PostPosted: Tue Sep 14, 2021 1:22 pm 
Offline
Member
Member

Joined: Tue Aug 31, 2021 7:25 am
Posts: 67
Octocontrabass wrote:
YDeeps1 wrote:
It's a shame there is no good way to debug this.

Sounds like you haven't found this page yet.

Check your VM's triple fault logs before you hook up a whole debugger - sometimes just seeing what state the CPU was in before it crashed is enough to figure out what went wrong for it to get there.


I indeed haven't checked the page out.

I was looking through VirtualBox logs and couldn't find (at least to my knowledge) anything which would point me to the issue other than showing me a triple fault has occurred. The last instruction is moving ax into the dx register which matches up with my previous terrible halt debugging technique.

Here are some of the things I thought may be interesting to look at in the logs if you'd like to check them out:
https://hatebin.com/qbnkccaobp
(my intuition tells me some of those things are not good news unless I'm wrong).

Code:
lgdt [edx] ; edx contains the popped GDT table address
mov ax, 0x10
mov ds, ax ; fault occurs here


I have also logged the address of the GDT offset + size table address (decimal 18874372) which matches up with the EDX register, removing some possibilities.

Time for a proper debugger? (updated)


Last edited by YDeeps1 on Tue Sep 14, 2021 3:38 pm, edited 1 time in total.

Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 57 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