0

在过去的几天里,我一直在努力解决试图获得 EFLAGS 状态的奇怪行为。为此,我编写了以下代码:

#include <stdio.h>

int flags_state()
{

  int flags = 0;

  __asm__ __volatile__("pushfq");
  __asm__ __volatile__("pop %%rax": "=a"(flags));

  return flags;
}

int main()
{

  printf("Returning EFLAGS state: 0x%x\n", flags_state());
  return 0;

}

当它运行时,我得到:

./flags
Returning EFLAGS state: 0x246

当我两次打印标志时,它变得越来越奇怪

Returning EFLAGS state: 0x246
Returning EFLAGS state: 0x206

当我尝试打印 6 次时它发生了变化

Returning EFLAGS state: 0x246
Returning EFLAGS state: 0x202
Returning EFLAGS state: 0x202
Returning EFLAGS state: 0x202
Returning EFLAGS state: 0x202
Returning EFLAGS state: 0x202

最后是最奇怪的(至少对我而言),我打印了 8 次

Returning EFLAGS state: 0x246
Returning EFLAGS state: 0x206
Returning EFLAGS state: 0x206
Returning EFLAGS state: 0x206
Returning EFLAGS state: 0x206
Returning EFLAGS state: 0x206
Returning EFLAGS state: 0x206
Returning EFLAGS state: 0x206

那么,为什么我第一次得到 0x246 呢?根据英特尔的手册,不应该是 0x2 吗?为什么当我尝试打印更多次并继续更改时它会发生变化?

4

2 回答 2

5
  __asm__ __volatile__("pushfq");
  __asm__ __volatile__("pop %%rax": "=a"(flags));

您不能像这样分解 asm 语句之间的指令。当 asm 语句移动堆栈指针而不放回它时,编译器会感到非常困惑。它可能看起来不错,但想象一下内联函数;编译器可以决定移动汇编相对于明显不相关的代码。

另一个问题是,由于红色区域,编译器可能已将重要数据放在堆栈指针下方:就在您pushfq将覆盖它的位置。

这不是那么容易解决的。我最好的猜测是

unsigned long get_rflags(void) {
    unsigned long result;
    asm("sub $128, %%rsp ; pushfq ; pop %0 ; add $128, %%rsp" 
        : "=r"  (result) : : "cc");
    return result;
}

要么将其编写为纯粹在 asm 中的“裸”函数,因此您知道编译器不参与其中。

(如https://stackoverflow.com/a/47402504/634919所述,可以通过编写来进行较小的代码大小优化add $-128, %%rsp......sub $-128, %%rsp因为 -128 适合符号扩展的 8 位,但 +128 不适合。)

sub/add意志本身会影响下面提到的算术标志,但是它们又经常被更改,以至于很难给它们的值赋予太多意义。我想lea -128(%%rsp), %%rsp如果你真的在乎的话,你可以使用。)


至于变化的值,您会看到第 2 位和第 6 位的变化:奇偶校验标志和零标志。由于几乎每条算术指令都根据结果设置这些值,并且在您的调用之间执行其他代码(例如printf!的所有代码),因此我们会看到值发生变化也就不足为奇了。进位、符号、溢出和辅助进位标志同样是“易失的”。这没什么好奇怪的。

没有理由期望值 0x2:各种代码都在运行,几乎所有代码都会影响标志,那么为什么所有其他标志都需要清除呢?

如果您愿意,您可以在调试器中逐条指令地单步执行您的代码,并观察 RFLAGS 的变化。您可能会看到它在一个 printf 和下一个之间更改了数百次。

于 2021-06-10T03:39:36.917 回答
1

那么,为什么我第一次得到 0x246 呢?根据英特尔的手册,不应该是 0x2 吗?

在第一次调用之前flags_state(),系统中执行了一些代码,结果大多数标志状态是随机的,你不能假设通用标志上的任何值,比如ZF (0x40)它可以设置和重置..英特尔的手册如何?可以在这里关联吗?

为什么当我尝试打印更多次并继续更改时它会发生变化?

函数不能保留ZF标志(例如DF在 Windows 中 - 返回时必须为 0) - 所以这个标志在函数返回后具有哪个值 - 也是未定义的 - 只要你自己不要在 asm 上编写所有代码并完全控制它。事实上,在返回ZF后重置flags_state并且在序言中没有改变flags_state- 作为结果第一次 - 你有在以前的代码中设置的值,然后已经设置了相同的值flags_state(你错了它继续改变- 它没有改变已经,如您的输出所示 - 0x206 一直)

于 2021-06-10T08:36:58.847 回答