我对全局描述符表(GDT)的位置感到困惑。根据从i386到早期版本的英特尔手册,GDTR寄存器包含GDT表的基地址,该地址假装是线性地址。 遵循英特尔惯例,线性地址可以进行分页。
然而,我想知道考虑哪个地址空间。环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编译)是:
gdt size: 127
gdt base: 1dd89000
mmgdt try: 1dd89000
mmgdt chk: 1dd89000
GDT条目的线性地址和mmap块的线性地址完全重叠。然而,mmap块显然与GDT无关。
所以我的问题最终是:哪个Intel / linux机制使GDTR的线性地址和当前进程的线性地址指向不同的内存区域?
答案 0 :(得分:2)
我找到了答案,并不简单,所以我在这里发帖,所以也许可以帮助其他人。
首先,我需要承认OSDev.org帮助我理解这一点。
虽然代码是为i386编译的,但它运行在x86_64 linux系统上。因此,它不是在传统的32位模式下运行,而是在所谓的“compat模式”下运行。在此模式下,允许传统的32位软件在x86_64环境中运行。
当系统进入intel64(长)模式时,它使用64位地址空间的高端(类似于0xffff88021dd89000)将GDT置于线性地址。每当“compat”32位应用程序使用LGDT检索GDTR线性地址时,它只检索线性地址的低32位(0x1dd89000)。当处理器访问GDT时,它使用GDTR寄存器的完整64位线性地址,即使在compat模式下也是如此。