How to make a GDT?

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
zap8600
Member
Member
Posts: 195
Joined: Tue Nov 02, 2021 11:26 am
Libera.chat IRC: zap8600

How to make a GDT?

Post by zap8600 »

Hello. I'm currently making an OS by following https://wiki.osdev.org/Bare_Bones. I'm having trouble figuring out if I need to use GRUB's GDT or if I need to make my own. If I need to use GRUB's GDT, how do I load it? If I do make my own, how do I do it and how do I load it? Any help would be appreciated.
Gigasoft
Member
Member
Posts: 856
Joined: Sat Nov 21, 2009 5:11 pm

Re: How to make a GDT?

Post by Gigasoft »

You must make your own. The layout is described in Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, chapter 3. The GDT's address and limit is loaded using the LGDT instruction.
Octocontrabass
Member
Member
Posts: 5562
Joined: Mon Mar 25, 2013 7:01 pm

Re: How to make a GDT?

Post by Octocontrabass »

zap8600 wrote:I'm having trouble figuring out if I need to use GRUB's GDT or if I need to make my own.
The tutorial you're following uses the Multiboot specification. According to the Multiboot specification, you must create your own GDT.
zap8600 wrote:If I do make my own, how do I do it and how do I load it?
Create a GDT by placing values in memory according to the Intel and AMD manuals. Since you're using C, you could use an array of uint8_t, or an array of uint64_t, or even an array of structs - but if you choose structs you have to pay attention to struct padding rules.

Load your GDT by creating a GDTR descriptor in memory and using the LGDT instruction to load the GDTR descriptor into GDTR. As with the GDT, you can use an array of uint8_t or a struct, but again, you must pay attention to struct padding rules. (You don't need the GDTR descriptor after you have loaded GDTR, so it's fine to create it on the stack or in some other temporary location.)

After you've loaded your GDT, load new selectors into all of the segment registers. If you forget this step, you may run into problems later, and it can be tough to debug since you can't read the hidden descriptor part of the segment registers (unless you're using a VM that lets you do it).

The wiki has some pages on this topic. I think the diagrams in the Intel/AMD manuals are easier to read, though.
zap8600
Member
Member
Posts: 195
Joined: Tue Nov 02, 2021 11:26 am
Libera.chat IRC: zap8600

Re: How to make a GDT?

Post by zap8600 »

Ok. I still don't think I know what I'm doing.
So I'll load the GDT with inline assembly, then I'll need values for the limit and the base.
How will I find the base? And what will the limit need to be?
kzinti
Member
Member
Posts: 898
Joined: Mon Feb 02, 2015 7:11 pm

Re: How to make a GDT?

Post by kzinti »

Did you read the wiki and the manuals as pointed out above?
zap8600
Member
Member
Posts: 195
Joined: Tue Nov 02, 2021 11:26 am
Libera.chat IRC: zap8600

Re: How to make a GDT?

Post by zap8600 »

I have. I'm not sure what it is, but something about it just makes it really confusing. I'm usually better at learning from examples or existing code.
Octocontrabass
Member
Member
Posts: 5562
Joined: Mon Mar 25, 2013 7:01 pm

Re: How to make a GDT?

Post by Octocontrabass »

The wiki has example code. But example code is not a substitute for reading the manual.

What about it is confusing? Maybe you just haven't found the part of the manual that explains it.
zap8600
Member
Member
Posts: 195
Joined: Tue Nov 02, 2021 11:26 am
Libera.chat IRC: zap8600

Re: How to make a GDT?

Post by zap8600 »

Ok. I've used the wiki's code to create GDT segment descriptors. Now I need to create a struct for the GDT. Then I need to encode it. Correct?
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: How to make a GDT?

Post by Schol-R-LEA »

I'm not sure what you mean by 'create GDT segment descriptors' if you haven't set up the representation of the GDT entries yet. Are you referring to the code that loads the GDT Register, and if so, how are you doing that if the table itself isn't set up?

If you can, please post the relevant code you are referring to, and perhaps post a link to your offsite repo so we can see the larger picture of your project (you do have your code under version control, right? If not, drop everything right now and get that taken care of, stat).

While a struct is common way of representing the GDT entries in C (or even in assembly, if your assembler has a data structure facility), it isn't strictly necessary; you could use an array of uint8_ts or a single uint64_t to represent each entry, if you find those more convenient. I personally find the struct approach easier to work with, but it isn't the only way.

No matter how you do it, you would then want to have an array of those entries, and then populate the fields of the individual entries themselves with the appropriate settings for the Null Entry, a System Code entry, System Data, at least one TSS, and at least a few User Code and User Data entries - as well as space for additional entries to be updated later.

Only once you have the table populated should you load the GDTR.

Again, the example from my own code can be seen here. though it probably isn't much help from a C perspective. In C, you would almost certainly want to have one or more functions which manipulate the GDT entries, rather than statically initializing them as I did here; this example really only suits a temporary GDT for going from Legacy BIOS to protected mode, which doesn't apply in your case given that you are using GRUB.

I won't pretend to be an expert on the GDT, so if I anyone more experienced in this topic spots any mistakes in what I just said, please correct me.
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: How to make a GDT?

Post by nullplan »

Schol-R-LEA wrote:In C, you would almost certainly want to have one or more functions which manipulate the GDT entries, rather than statically initializing them as I did here;
In a flat address space, there is basically no reason to do so. Because then all you need is a code and data segment each for user and kernel, and a TSS. Only once you start supporting SMP, you will need to make a decision: Since each CPU needs its own TSS, do you put all TSSes in the same GDT, or do you give each CPU its own GDT? I chose the latter since it is easier.

My GDT code in its entirety (bear in mind that my OS is for 64-bit mode):

Code: Select all

enum {
    NULL_DESC,
    KCODE_DESC,
    KDATA_DESC,
    UDATA_DESC,
    UCODE_DESC,
    TSS_DESC,   /* TSS is long */
    TSSU_DESC,
    MAX_GDT
};
static void init_gdt(uint64_t *gdt, const struct tss* tss)
{
    uint64_t tssp = (uint64_t)tss;
    gdt[KCODE_DESC] = 0x00af9a000000ffff;
    gdt[KDATA_DESC] = 0x00af92000000ffff;
    gdt[UDATA_DESC] = 0x00aff2000000ffff;
    gdt[UCODE_DESC] = 0x00affa000000ffff;
    gdt[TSS_DESC] = (tssp << 16) & 0xffffff0000 | (tssp << 32) & 0xff00000000000000 | sizeof (struct tss) - 1 | (uint64_t)0x89 << 40;
    gdt[TSSU_DESC] = tssp >> 32;
}

Code: Select all

/* void load_gdt(const void *base, size_t len, uint16_t cs, uint16_t ds); */
.global load_gdt
.type load_gdt,@function
load_gdt:
.cfi_startproc
    addq $-16,%rsp
.cfi_def_cfa_offset 24
    movq %rdi, 8(%rsp)
    decq %rsi
    movw %si, 6(%rsp)
    lgdtq 6(%rsp)
    movw %cx, %ds
    movw %cx, %es
    movw %cx, %fs
    movw %cx, %gs
    movw %cx, %ss
    movq 16(%rsp), %rax
    movq %rax, 0(%rsp)
    movw %dx, 8(%rsp)
    lretq $8
.cfi_endproc
.size load_gdt,.-load_gdt
I am unwilling to give the affair any more thought. Why create macros or structures when it is going to be used once per boot? The meaning of these things does not become more obvious from using those techniques; you basically need to read the code with the documentation open anyway. And with the documentation, the magic numbers are self-explanatory.
Carpe diem!
zap8600
Member
Member
Posts: 195
Joined: Tue Nov 02, 2021 11:26 am
Libera.chat IRC: zap8600

Re: How to make a GDT?

Post by zap8600 »

Ok. Now I'm really confused.

I'm attempting to make a GDT like this. What does the struct need to look like? After the struct is set up, what needs to be done next?

Sorry if I'm being annoying. I'm just very confused.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: How to make a GDT?

Post by Schol-R-LEA »

Thank you; this clears up something I think I was confused about, as I had assumed that one would need a separate user and data segment each for each user process. From what you are saying, I gather that if one is using a flat address space, the GDT only needs a single shared code and data user segment, and can rely entirely on paging for managing process separation. Is this correct?

I am beginning to see that all the years of studying OS development has still left me unclear on many aspects of practical OS implementation. I would have been better served by doing more actual programming, and refining my understanding of the actual system, rather than spending all my time on theory, even if it meant iterating on several successive projects along the way.

I am seriously questioning at this point if I even can understand the details well enough to accomplish even my short-term goals. This is the same sort of self-doubt and self-loathing which kept me focused on theory, though, so I need to get through that - just sit down and use what I already know, realizing that I still have a lot to learn, and that much of it can only be learned through practical experience.
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: How to make a GDT?

Post by nullplan »

Schol-R-LEA wrote:Thank you; this clears up something I think I was confused about, as I had assumed that one would need a separate user and data segment each for each user process. From what you are saying, I gather that if one is using a flat address space, the GDT only needs a single shared code and data user segment, and can rely entirely on paging for managing process separation. Is this correct?
Yes. No need to dynamically modify the GDT at run-time (I only do it during initialization, since the TSS is dynamically allocated).

There might be a need, however, in the case of a 32-bit OS that allows users to set the FS or GS base (In 64-bit mode, those addresses are set with MSRs). However, I would not go the Linux route, I would instead have one fixed segment for FS and one for GS, and just swap out the base addresses on task switch (and then reload FS and GS, yes. Otherwise the descriptor cache is not updated).

FS and GS are used by threading libraries quite often.
Schol-R-LEA wrote:I am seriously questioning at this point if I even can understand the details well enough to accomplish even my short-term goals. This is the same sort of self-doubt and self-loathing which kept me focused on theory, though, so I need to get through that - just sit down and use what I already know, realizing that I still have a lot to learn, and that much of it can only be learned through practical experience.
Well, I'm stuck at the bloody memory manager. And just when I thought I had a handle on it, I find out about DMA allocators, and need to rethink a few things. But then I remember Yahtzee Croshaw's advice about writer's block: Just force yourself to write, to get to the stuff you want to write. If the result is crap, that's why it is a first draft.

And Schol, if anyone on this forum can understand all the details, it is you. Your writings on OS design theory and CPU history are eye-opening and your writing is very clear.
Carpe diem!
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: How to make a GDT?

Post by Schol-R-LEA »

zap8600 wrote:Ok. Now I'm really confused.

I'm attempting to make a GDT like this. What does the struct need to look like? After the struct is set up, what needs to be done next?

Sorry if I'm being annoying. I'm just very confused.
Not a problem; we all get confused by this at times.

As nullplan shows, there's no need to use a struct; however, I do personally find it a bit more clear to have an explicit structure which shows to fields individually.

The thing to remember is that what actually matters is the value as it exists in memory, and memory has no types or structures - those are things which get imposed on memory by the programmer and by the programming language. While the fields of the entries are determined by hardware requirements, the memory itself is just a bag o' bytes.

Now, if you want to use a C struct to organize that memory, you will want something like this:

Code: Select all

struct __attribute__((packed, aligned(4))) GDT 
{
    uint16_t base_low;
    uint16_t limit_low;
    uint8_t base_mid;
    uint8_t access;
    uint8_t limit_high_and_flags;
    uint8_t base_high;
};
Note the need to apply the __attribute__((packed, aligned(4))) type attribute modifier to the structure; this is because, by default, most compilers will pad out smaller word sizes to the system word size (4 bytes in 32-bit protected mode, 8 bytes in long mode) to align the data fields. Since the data structure in this case has to have the exact field locations, you need to inform the compiler to 'pack' the data, rather than pad it. The 'aligned' part of the attribute forces the zeroth byte of the data structure to be set at a four-byte address boundary.

Again, if I have misrepresented any of this, please correct me.
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.
Octocontrabass
Member
Member
Posts: 5562
Joined: Mon Mar 25, 2013 7:01 pm

Re: How to make a GDT?

Post by Octocontrabass »

Schol-R-LEA wrote:Note the need to apply the __attribute__((packed, aligned(4))) type attribute modifier to the structure; this is because, by default, most compilers will pad out smaller word sizes to the system word size (4 bytes in 32-bit protected mode, 8 bytes in long mode) to align the data fields.
Struct elements are aligned to their natural alignment, not the system word size. No padding is necessary to correctly align this struct's elements, so there is no need to pack it.

The GDT doesn't need to be aligned, so the struct doesn't need to be aligned either. But, if you do want to align your GDT, you should align it to 8-byte boundaries.
Post Reply