How to make a GDT?
How to make a GDT?
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?
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.
-
- Member
- Posts: 5562
- Joined: Mon Mar 25, 2013 7:01 pm
Re: How to make a GDT?
The tutorial you're following uses the Multiboot specification. According to the Multiboot specification, you must create your own GDT.zap8600 wrote:I'm having trouble figuring out if I need to use GRUB's GDT or if I need to make my own.
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.zap8600 wrote:If I do make my own, how do I do it and how do I load it?
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?
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?
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?
Did you read the wiki and the manuals as pointed out above?
Re: How to make a GDT?
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.
-
- Member
- Posts: 5562
- Joined: Mon Mar 25, 2013 7:01 pm
Re: How to make a GDT?
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.
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?
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?
- Schol-R-LEA
- Member
- Posts: 1925
- Joined: Fri Oct 27, 2006 9:42 am
- Location: Athens, GA, USA
Re: How to make a GDT?
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.
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.
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.
Re: How to make a GDT?
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.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;
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
Carpe diem!
Re: How to make a GDT?
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.
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.
- Schol-R-LEA
- Member
- Posts: 1925
- Joined: Fri Oct 27, 2006 9:42 am
- Location: Athens, GA, USA
Re: How to make a GDT?
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.
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.
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.
Re: How to make a GDT?
Yes. No need to dynamically modify the GDT at run-time (I only do it during initialization, since the TSS is dynamically allocated).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?
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.
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.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.
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!
- Schol-R-LEA
- Member
- Posts: 1925
- Joined: Fri Oct 27, 2006 9:42 am
- Location: Athens, GA, USA
Re: How to make a GDT?
Not a problem; we all get confused by this at times.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.
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;
};
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.
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.
-
- Member
- Posts: 5562
- Joined: Mon Mar 25, 2013 7:01 pm
Re: How to make a GDT?
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.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.
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.