我正在尝试使用gcc内联汇编将寄存器的内容(特别是gdtr)读入C变量。为此,我正在修改我找到的here的一些代码,但是这些代码是为32位处理器编写的。因此,在将指令修改为64位时,我遇到了一些我希望有人可以向我解释的奇怪行为。

首先,gdtr结构应该用于模拟gdtr寄存器的结构。

struct gdtr64 {
    uint16_t limit;
    uint64_t addr;
};


很简单。当我尝试通过执行以下操作将寄存器的内容输出到这样的结构中时:

struct gdtr64 gdtr64 = {0xcccc,0xa2a2a2a2a2a2a2a2};
printf("gdtr64 limit: %x\ngdtr64 addr: %llx\n", gdtr64.limit, gdtr64.addr);
printf("<--asm call-->\n");
__asm__ volatile("sgdt %0\n" : :"m"(gdtr64));
printf("gdtr64 limit: %x\ngdtr64 addr: %llx\n", gdtr64.limit, gdtr64.addr);


我得到:

gdtr64 limit: cccc
gdtr64 addr: a2a2a2a2a2a2a2a2
<--asm call-->
gdtr64 limit: a0
gdtr64 addr: a2a2a2a2a2a2ffff


调用之前的值只是垃圾值,因此我可以知道更改了什么。我们可以看到限制已从cccc更新为00a0,并且gdtr64.addr的最后两个字节已更改。这对我来说没有多大意义。

作为实验,我运行了相同的代码,除了我将gdtr64.addr传递给了汇编部分:

struct gdtr64 gdtr64 = {0xcccc,0xa2a2a2a2a2a2a2a2};
printf("gdtr64 limit: %x\ngdtr64 addr: %llx\n", gdtr64.limit, gdtr64.addr);
printf("<--asm call-->\n");
__asm__ volatile("sgdt %0\n" : :"m"(gdtr64.addr));
printf("gdtr64 limit: %x\ngdtr64 addr: %llx\n", gdtr64.limit, gdtr64.addr);


输出使我感到惊讶:

gdtr64 limit: cccc
gdtr64 addr: a2a2a2a2a2a2a2a2
<--asm call-->
gdtr64 limit: cccc
gdtr64 addr: ff8076db40000097


在这种情况下,我们在gdtr64.limit占用的内存地址之后开始写入,但是我们看到写入的内容有本质上的不同。在上一个示例中作为限制的00a0已迁移到地址的末尾。否则,我们将拥有适当地址的外观。

因此,我想知道不是我使用的struct所固有的问题吗,所以我决定尝试一串char。该寄存器应为10个字节长,因此:

char gdtr_char[10] = "0000000000";
printf("GDTR_CHAR: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x \n",
    (unsigned char) gdtr_char[0],
    (unsigned char) gdtr_char[1],
    (unsigned char) gdtr_char[2],
    (unsigned char) gdtr_char[3],
    (unsigned char) gdtr_char[4],
    (unsigned char) gdtr_char[5],
    (unsigned char) gdtr_char[6],
    (unsigned char) gdtr_char[7],
    (unsigned char) gdtr_char[8],
    (unsigned char) gdtr_char[9]
);
printf("<--asm call-->\n");
__asm__ volatile("sgdt %0\n" : :"m"(gdtr_char[0]));
printf("GDTR_CHAR: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x \n",
    (unsigned char) gdtr_char[0],
    (unsigned char) gdtr_char[1],
    (unsigned char) gdtr_char[2],
    (unsigned char) gdtr_char[3],
    (unsigned char) gdtr_char[4],
    (unsigned char) gdtr_char[5],
    (unsigned char) gdtr_char[6],
    (unsigned char) gdtr_char[7],
    (unsigned char) gdtr_char[8],
    (unsigned char) gdtr_char[9]
);


请原谅我的冗长,我的C语言技能正在……发展中。结果是:

GDTR_CHAR: 30 30 30 30 30 30 30 30 30 30
<--asm call-->
GDTR_CHAR: 97 00 00 50 dd 76 80 ff ff ff


同样,初始值是垃圾,但是我们可以看到,在读取寄存器后,所有10个字节都被占用了,但顺序与尝试第二次尝试时得到的顺序相反。总结一下:

trial 1 limit: 00a0
trial 1 addr:  ************ffff
-------------------------------
trial 2 limit: ****
trial 2 addr:  ff8076db40000097
-------------------------------
trial 3 array: 97 00 00 40 db 76 80 ff ff ff
reversed:      ff ff ff 80 76 db 40 00 00 97 //byte-wise


顺便说一句,尽管将其分解为单独的“试验”,但这些试验只能一次运行。寄存器的内容似乎在两次执行之间改变(我也觉得很奇怪)。说了这么多,我无法解决以下问题:


为什么GDTR的内容每次执行都会更改?
为什么在使用结构与字符数组之间有区别?
GDT的基本内存地址是什么(也就是说,正确的结果[如果有的话])?


任何帮助将不胜感激。感谢您阅读本文。

最佳答案

您可能至少面临2个问题。

第一个问题是编译器添加了用于对齐的填充,因此您认为包含“ 16位限制和64位地址”的结构可能是“ 16位限制,即48位CPU期望的填充”和64位地址”。大多数编译器都有(非标准)扩展来打包结构(例如“ #pragma pack”或“ __attribute__((packed)))”)。

第二个问题是字节序。 80x86是低位字节序,这意味着字节0x12、0x34、0x45、0x67表示32位整数0x67452312。

我假设第二和第三次试用的限制为0x0097,地址部分为0xFFFFFF8076DB4000。不过,我不确定第一次测试(看起来GDTR在第一次和第二次测试之间发生了变化)。

编辑:还请注意,无论如何,第一次试验的极限结果看起来都是错误的。限制为“ GDT的大小-1”,并且由于GDT条目每个为8(或16)个字节,因此该限制应始终设置最低的3位(例如“ 0x ??? 7”或“ 0x ??? F”) ”。

09-06 21:08