Page 1 of 20
How to make a GDT?
Posted: Mon Aug 15, 2022 4:32 pm
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.
Re: How to make a GDT?
Posted: Mon Aug 15, 2022 4:58 pm
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.
Re: How to make a GDT?
Posted: Mon Aug 15, 2022 5:15 pm
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.
Re: How to make a GDT?
Posted: Mon Aug 15, 2022 6:08 pm
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?
Re: How to make a GDT?
Posted: Mon Aug 15, 2022 6:14 pm
by kzinti
Did you read the wiki and the manuals as pointed out above?
Re: How to make a GDT?
Posted: Mon Aug 15, 2022 11:57 pm
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.
Re: How to make a GDT?
Posted: Tue Aug 16, 2022 12:19 am
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.
Re: How to make a GDT?
Posted: Tue Aug 16, 2022 4:00 am
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?
Re: How to make a GDT?
Posted: Tue Aug 16, 2022 9:14 am
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.
Re: How to make a GDT?
Posted: Tue Aug 16, 2022 11:50 am
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.
Re: How to make a GDT?
Posted: Tue Aug 16, 2022 12:14 pm
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.
Re: How to make a GDT?
Posted: Tue Aug 16, 2022 12:24 pm
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.
Re: How to make a GDT?
Posted: Tue Aug 16, 2022 1:06 pm
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.
Re: How to make a GDT?
Posted: Tue Aug 16, 2022 1:06 pm
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.
Re: How to make a GDT?
Posted: Tue Aug 16, 2022 2:00 pm
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.