EFLAGS 状态

Bru*_*ado 0 assembly x86-64 inline-assembly eflags

在过去的几天里,我一直在为一种试图获得 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;

}
Run Code Online (Sandbox Code Playgroud)

当它运行时,我得到:

./flags
Returning EFLAGS state: 0x246
Run Code Online (Sandbox Code Playgroud)

当我打印两次标志时,它变得更奇怪了

Returning EFLAGS state: 0x246
Returning EFLAGS state: 0x206
Run Code Online (Sandbox Code Playgroud)

当我尝试打印 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
Run Code Online (Sandbox Code Playgroud)

最后是最奇怪的(至少对我来说),当我打印 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
Run Code Online (Sandbox Code Playgroud)

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

Nat*_*dge 5

  __asm__ __volatile__("pushfq");
  __asm__ __volatile__("pop %%rax": "=a"(flags));
Run Code Online (Sandbox Code Playgroud)

你不能像这样分解 asm 语句之间的指令。当 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;
}
Run Code Online (Sandbox Code Playgroud)

要么将其写为纯粹在 asm 中的“裸”函数,以便您知道不涉及编译器。

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

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


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

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

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

  • @RbMm:不太可能;MSVC 具有所有内容的内在函数,而不是支持内联汇编。GCC/clang 具有设计简洁的可用内联汇编,因此操作系统应该使用它来设置/清除 EFLAGS 中的控制标志(如 IF 或 AC)。(在编译器生成代码后,从 EFLAGS 获取 CF / OF / ZF 等状态标志并不是特别有用;您不知道它是否使用了 ADD 还是 LEA,甚至不知道最后的计算是否来自 C 源代码.所以这不是你经常想要的东西。在内核中你将有 -mno-redzone 这样你就可以在asm中pushf/pop) (2认同)