【问题标题】:Strange behavior when reading the contents of a register into a C variable将寄存器的内容读入 C 变量时的奇怪行为
【发布时间】:2012-04-25 01:21:26
【问题描述】:

我正在尝试使用 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更新为00a0gdtr64.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 固有的问题,所以我决定尝试使用chars 的字符串。寄存器应该是 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 的内存基址是什么(即哪个结果是正确的 [如果有的话])?

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

【问题讨论】:

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


【解决方案1】:

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

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

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

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

编辑:另请注意,第一次试验的限制结果看起来还是错误的。限制是“GDT 的大小 - 1”,并且因为 GDT 条目是 8(或 16)字节,所以限制应该总是设置最低 3 位(例如“0x???7”或“0x???F ”。

【讨论】:

  • 啊,你是对的。在我意识到价值每次都发生变化之前,我在那里进行了第一次试验。我将不得不编辑它。我也认为字节序是一个问题,但不明白为什么它在第二次和第三次运行之间不一致,因为它不像字节序会改变。我确实检查了limitaddr 的地址,他们似乎已经检查过了,但我会尝试打包结构并报告回来。
  • 所以问题确实是编译器添加的填充,将__attribute__((packed)) 添加到我的结构中就可以了。作为另一个实验,我运行程序 1000 次,发现只有 4 个值被循环通过,这似乎表明正在发生的事情是我得到的值取决于代码在哪个处理器内核上执行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-03
  • 2021-10-28
  • 2014-04-08
  • 1970-01-01
  • 1970-01-01
  • 2011-12-05
相关资源
最近更新 更多