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;

}

当它运行时,我得到:

./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 吗?为什么当我尝试打印更多次并继续更改时它会改变?

解决方法

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

你不能像这样在 asm 语句之间分解指令。当 asm 语句移动堆栈指针而不将其放回时,编译器会非常困惑。孤立地看可能没问题,但想象一下函数是内联的;编译器可以决定对明显不相关的代码移动 asm。

另一个问题是,由于 red zone,编译器可能将重要数据放在下面堆栈指针:就在您的 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 和下一个之间变化数百次。

,

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

在第一次调用 flags_state() 之前,一些代码在系统中执行,因此大多数标志状态是随机的,您不能假设通用标志上的任何值,例如 ZF (0x40) 可以设置和重置..以及英特尔的手册如何?与此处相关?

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

函数不得保留 ZF 标志(与 Windows 中的 DF 不同 - 返回时必须为 0) - 因此该标志在函数返回后具有哪些值 - 也未定义 - 如果只有你自己不要在 asm 上编写所有代码并完全控制它。事实上,ZFflags_state 返回后被重置,并且在 flags_state 的序言中没有改变 - 作为第一次 - 你的值在之前的代码中设置,然后一直是相同的值,它在 flags_state 中设置(您错误地认为它继续更改 - 它尚未更改,如显示您的输出 - 始终为 0x206)