State of EFLAGS

1.1k Views Asked by At

Over the past few days I've been struggling with a weird behaviour trying to get the states of EFLAGS. To accomplish this I've written this code:

#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;

}

When it runs, I got:

./flags
Returning EFLAGS state: 0x246

It's getting weirder when I print out the flags twice

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

It changed when I tried to print it out 6 times

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

And finally the weirdest (at least for me) when I print it out 8 times

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

So, why did I get 0x246 at the first time? Shouldn't be 0x2 according Intel's manual? Why did it change when I try to print it more times and continue change?

2

There are 2 best solutions below

0
On BEST ANSWER

So, why did I get 0x246 at the first time? Shouldn't be 0x2 according Intel's manual?

before flags_state() called first time, some code executed in system, as result most flags state is random, you can not assume any values on generic flags, like ZF (0x40) it can be and set and reset.. and how Intel's manual? can be related here ?

Why did it change when I try to print it more times and continue change?

function must not preserve ZF flag (unlike for instance DF in windows - must be 0 on return) - so which value this flag have after function return - also undefined - if only you by self not write all code on asm and gave full control over this. by fact ZF is reset after flags_state return and not changed in prolog of flags_state - as result first time - you have value which is set in previous code and then already all time the same value, which set in flags_state (you wrong that it continue change - it not change already, as show your output - 0x206 all time)

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

You must not break up the instructions between asm statements like this. The compiler will get very confused when an asm statement moves the stack pointer without putting it back. It may look okay in isolation, but imagine the function being inlined; the compiler can decide to move the asm with respect to apparently unrelated code.

Another problem is that because of the red zone, the compiler may have put important data below the stack pointer: right where your pushfq would overwrite it.

This is not so easy to resolve. My best guess would be

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

Either that or write it as a "naked" function purely in asm, so that you know the compiler isn't involved.

(As noted at https://stackoverflow.com/a/47402504/634919, a minor code-size optimization can be made by writing add $-128, %%rsp ... sub $-128, %%rsp since -128 fits in sign-extended 8 bits, but +128 does not.)

(The sub/add will itself affect the arithmetic flags as noted below, but then again they get changed so often that it's hard to attach much meaning to their values. I suppose you could use lea -128(%%rsp), %%rsp if you really care.)


As for the changing values, you're seeing changes in bits 2 and 6: the parity flag and zero flag. Since practically every arithmetic instruction sets these according to the result, and other code gets executed in between your calls (e.g. all the code of printf!) it is not surprising that we would see the values change. The carry, sign, overflow and auxiliary carry flags are similarly "volatile". Nothing is weird about this.

There's no reason to expect the value 0x2: all sorts of code has been running, and nearly all of it affects the flags, so why should all the other flags need to be clear?

If you like, you can single-step your code, instruction by instruction, in a debugger, and watch as RFLAGS changes. You would probably see it change hundreds of times between one printf and the next.