我正在尝试使用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”) ”。