linuxi386segments

Global Descriptor Table location


I'm confused with the location of the Global Descriptor Table (GDT). According to Intel Manuals from i386 to earlier ones, the GDTR register contains a base address of the GDT table which is pretended to be a linear address. Following Intel conventions, linear addresses are subject to paging.

Nevertheless, I'm wondering which address space is considered. Ring 3 (user-land) programs are perfectly allowed to modify some segment selectors (ES for example). This modification should trigger the processor to load segment descriptor from corresponding entry in the GDT which base address is computed using the linear address given by the GDTR register.

Because linear address are subject to paging, I understand from Intel manuals, that segment descriptor loads go through the memory paging of current process. Because Linux certainly doesn't want to expose the GDT structure to user-land programs, I thought that it somehow managed to introduce a hole in the address space of user-land processes; preventing these processes to read the GDT, while allowing the processor to read it for segment reloads.

I checked by using the following code which showed I'm completely wrong about the GDTR's base linear address.

int
main()
{
  struct
  {
    uint16_t  pad;
    uint16_t  size;
    uintptr_t base;
  } gdt_info;

  __asm__ volatile ("sgdt %0" : "=m" (gdt_info.size) );

  void* try_mmgdt = (void*)( gdt_info.base & ~0xfff );
  void* chk_mmgdt = mmap(try_mmgdt, 0x4000, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

  std::cout << "gdt size: \t" << std::dec << gdt_info.size << std::endl;
  std::cout << "gdt base: \t" << std::hex << gdt_info.base << std::endl;
  std::cout << "mmgdt try:\t" << std::hex << uintptr_t(try_mmgdt) << std::endl;
  std::cout << "mmgdt chk:\t" << std::hex << uintptr_t(chk_mmgdt) << std::endl;

  return 0;
}

The program output (i386-compiled) on my machine is:

gdt size:       127
gdt base:       1dd89000
mmgdt try:      1dd89000
mmgdt chk:      1dd89000

The linear addresses of GDT entries and linear addresses of the mmap chunk perfectly overlap. Nevertheless the mmap chunk has obviously no relation with the GDT.

So my question finally is: which Intel/linux mechanism makes the linear address of the GDTR and the linear address of the current process point to different memory region ?


Solution

  • I found the answer, and its not straightforward so I'm posting it here so maybe it can help other.

    First, I need to acknowledge OSDev.org for helping me understand that.

    Though the code is compiled for i386, its running on a x86_64 linux system. Thus, it's not running in legacy 32-bits mode, but rather in the so called "compat mode". In this mode, legacy 32-bits software are allowed to run on an x86_64 environment.

    When the system entered intel64 (long) mode, it placed the GDT at a linear address using the high end of the 64-bits address space (something like 0xffff88021dd89000). Whenever a "compat" 32-bits application retrieve the GDTR linear address using LGDT, it only retrieves the lower 32 bits of the linear address (0x1dd89000). When the processor access the GDT, it uses the full 64-bits linear address of the GDTR register, even in compat-mode.