How does GDT trick actually work?

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
User avatar
Mano
Posts: 8
Joined: Mon Jan 09, 2012 4:52 am
Location: Brazil

How does GDT trick actually work?

Post by Mano »

It's a noob question. But I'm trying hard, and I still don't get what's happening.

I followed the Higher Half With GDT tutorial and "loaded" my kernel on the virtual address 0xC0100000. Everything is running fine, but I still don't get how this actually works. I first set the fake GDT, and the base address for code and data is 0x40000000. I understand that it will make the CPU see the 0xC0100000 as 0x100000 (It is where I got my kernel loaded). Then, paging is set up, and the first 4 mb of memory is mapped to 0xC0000000, so I can access the low memory through the higher virtual addresses. I also had to map the first 4mb physical addresses onto the 4mb first virtual addresses. If I don't do this, I believe I'll get a page fault when I try to set the real GDT (with tha base address 0 for code and data).

I read the discussion page of the tutorial, and someone wrote that there's a problem with it. It doesn't clear the first Page Directory Entry after setting up the new GDT. It means that if try to access the first 4 mb virtual adresses, I'll end up accessing the first 4 mb physical addresses. So I cleared the first Page Directory Entry, and I got some page faults at first, because I was directly accessing the multiboot info structure GRUB creates. But I managed it, and the kernel is now working.

But in the meanwhile, I lost the track of what's happening. How the variables addresses are being translated after all. If I try to access an address on the higher half, it will be mapped to lower memory, and it will work. If I try to access a low memory address, I'll get a page fault because it's not properly mapped. But how my kernel is working if It was loaded on the 1 mb of physical memory? I believe the far JUMP executed by the routine which sets the GDT changed the EIP to a high address. But I still don't get how the variables addresses were translated.

If I execute this code:

Code: Select all

int foo = 42;
main()
{ 
  printf("%d", &foo);
}
An address bigger than 0xC0000000 is printed. But how this happens if the kernel was loaded on the 1 mb mark? I thought the instructions would reference an address located between 1~2 mb of physical memory, and It'd cause a page fault, because this code would access a page that is not on the memory. But it works, and I'm trying to understand how.

Well, that's it. If someone could explain what's going on, I'd be thankful.
Thanks in advance.
raghuk
Member
Member
Posts: 35
Joined: Tue Jun 30, 2009 2:47 am
Location: Bangalore, India

Re: How does GDT trick actually work?

Post by raghuk »

There are two addresses involved - the load memory address (LMA) which is 0x100000 and the virtual memory address (VMA) which is 0xC0000000. Take a look at the linker script in that tutorial.

The LMA is used to load the kernel and the VMA is used to resolve the variable addresses.

Try running objdump -h <kernel> to see the VMA/LMA values.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: How does GDT trick actually work?

Post by Combuster »

The processor has two layered forms of address translation: segmentation and paging. Each of them replaces an address provided by the code with something else. The GDT trick is in design little more than a drop-in replacement for paging using segmentation.

The sequence operates in three steps:
1) paging is disabled and only the segment base applies. by adding 0x40000000 to 0xC01xxxxx you get an address in the 1-2 MB range due to 32-bit overflow. You typically do this first thing when your kernel starts.

2) paging will be enabled, but since you can't sanely modify paging and segmentation at the same time we still add 0x40000000 to 0xC01xxxxx and paging, which is applied after segmentation, must therefore map 1M linear to 1M virtual.

3) now we want to reset the segment base to zero. That means that virtual address 0xC0100000 no longer points to 0x00100000 but to 0xC01000000 before paging is applied. Since we can't change paging concurrently that means we need both 1M to point to 1M, and 3G+1M to point to 1M;

any addresses used in the previous steps are still valid after each change, the only real difference is that after "disabling" segmentation there still is the double mapping of addresses, as well as the processor control structures because they bypass segmentation. Once you clean that up you can remove the 1M to 1M mapping and use only the 3G+1M to 1M mapping.


That said, there's little to gain with the GDT trick in production code: it only allows you to do things in the kernel without getting bugged with paging issues whereas you can just as well enable paging properly first and save some code and related logic.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
Mano
Posts: 8
Joined: Mon Jan 09, 2012 4:52 am
Location: Brazil

Re: How does GDT trick actually work?

Post by Mano »

Thanks raghuk and Combuster for your replies. :lol:

I think I misunderstood that linker script. Now I know how the code works, and how the VMA and LMA were specified.
Post Reply