将寄存器的内容读入C变量时的奇怪行为

时间:2012-04-25 01:21:27

标签: c gcc assembly linux-kernel cpu-registers

我试图使用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已迁移到addr的末尾。否则,我们看起来像是正确地址的材料。

所以,我想知道我使用的struct是不是固有的问题,所以我决定尝试一串char s。寄存器应该是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的内容会改变每次执行?
  • 为什么使用struct和char数组之间有区别?
  • GDT的基本内存地址是什么(即哪个结果是正确的[如果有的话])?

非常感谢任何帮助。感谢您阅读此内容。

1 个答案:

答案 0 :(得分:2)

你面临的问题可能至少有两个。

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

第二个问题是endian-ness。 80x86是little-endian,这意味着字节0x12,0x34,0x45,0x67代表32位整数0x67452312。

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

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