SIDT 指令在 Linux 用户空间进程中返回错误的基地址

问题描述

我编写了以下 x86-64 程序来查看中断描述符表的基地址从哪里开始:

#include <stdio.h>
#include <inttypes.h>

typedef struct __attribute__((packed)) {
        uint16_t limit;
        uint64_t base;
}idt_data_t;

static inline void store_idt(idt_data_t *idt_data)
{
        asm volatile("sidt %0":"=m" (*idt_data));
}

int main(void)
{
        idt_data_t idt_data;

        store_idt(&idt_data);

        printf("IDT Limit : 0x%X\n",idt_data.limit);
        printf("IDT Base  : 0x%lX\n",idt_data.base);

        return 0;
}

它会打印以下内容

IDT Limit : 0xFFF
IDT Base  : 0xFFFFFE0000000000

基地址似乎不正确,因为地址应该始终是物理地址,对吗?

另外,我不确定,但限制似乎太高了。我做错了什么?

解决方法

这是一个线性地址,不一定是物理地址。换句话说,它像大多数其他地址一样受页表的约束。它必须位于从不分页到磁盘的页面中——否则它就无法处理页面错误——但它可以位于物理地址与虚拟地址不同的地址中。

在 x86-64 上,IDT 的每个条目的长度为 16 字节。有 256 个中断向量。 256 * 16 = 4096 = 0x1000。 IDTR 限制是“小于或等于”检查,因此通常使用 0xFFF。

SIDT 是新 CPU 上的特权指令,如果操作系统启用了某个功能,所以建议不要在用户模式下使用它,除非你正在编写一个漏洞 PoC 或其他东西。操作系统可能会谎报答案而不是抛出异常,但我不知道。