【问题标题】:Global Descriptor Table location全局描述符表位置
【发布时间】:2016-04-20 21:53:31
【问题描述】:

我对全局描述符表 (GDT) 的位置感到困惑。根据从 i386 到更早版本的 Intel 手册,GDTR 寄存器包含 GDT 表的基地址,该基地址被伪装成线性地址。 按照 Intel 的约定,线性地址需要分页。

不过,我想知道考虑了哪个地址空间。 Ring 3(用户级)程序完全可以修改某些段选择器(例如 ES)。此修改应触发处理器从 GDT 中的相应条目加载段描述符,该条目使用 GDTR 寄存器给出的线性地址计算基地址。

由于线性地址需要分页,我从英特尔手册中了解到,段描述符加载通过当前进程的内存分页进行。因为 Linux 肯定不想将 GDT 结构暴露给用户级程序,所以我认为它设法在用户级进程的地址空间中引入了一个漏洞。阻止这些进程读取 GDT,同时允许处理器读取它以进行段重载。

我使用以下代码进行了检查,这表明我对 GDTR 的基本线性地址完全错误。

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;
}

我机器上的程序输出(i386-compiled)是:

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

GDT 条目的线性地址和 mmap 块的线性地址完全重叠。然而 mmap 块显然与 GDT 无关。

所以我的问题最后是:哪种Intel/linux机制使GDTR的线性地址和当前进程的线性地址指向不同的内存区域?

【问题讨论】:

    标签: linux i386 segments


    【解决方案1】:

    我找到了答案,但它并不简单,所以我把它贴在这里,也许它可以帮助其他人。

    首先,我需要感谢 OSDev.org 帮助我理解这一点。

    虽然代码是为 i386 编译的,但它在 x86_64 linux 系统上运行。因此,它不是在传统的 32 位模式下运行,而是在所谓的“兼容模式”下运行。在这种模式下,旧版 32 位软件可以在 x86_64 环境中运行。

    当系统进入 intel64 (long) 模式时,它使用 64 位地址空间的高端(类似于 0xffff88021dd89000)将 GDT 放置在线性地址。每当“兼容”32 位应用程序使用 LGDT 检索 GDTR 线性地址时,它只检索线性地址的低 32 位 (0x1dd89000)。当处理器访问 GDT 时,它使用 GDTR 寄存器的完整 64 位线性地址,即使在兼容模式下也是如此。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-29
      • 1970-01-01
      • 2017-05-04
      • 2014-01-02
      相关资源
      最近更新 更多