0

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?

phuclv
  • 37,963
  • 15
  • 156
  • 475
Bruno Criado
  • 120
  • 7
  • in x86-64 it's RFLAGS, not EFLAGS, and is 64-bit long – phuclv Jun 10 '21 at 03:35
  • 2
    Yeah, you're right @phuclv. I've said EFLAGS because the bits are reserved from bit 22 to 63. – Bruno Criado Jun 10 '21 at 03:39
  • 2
    You're not likely to ever see 0x2. 0x200 is the interrupt flag which should never be 0 in a user program. – sj95126 Jun 10 '21 at 04:49
  • before *flags_state* called first time, the `ZF(0x40)` flag is set in your case. function reset this flag during execution. so on next call - `ZF` already not set. not undertand why this confused you – RbMm Jun 10 '21 at 08:11

2 Answers2

5
  __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.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • hmmm I see it now and makes total sense. Thanks for the clarification @Nate Eldredge – Bruno Criado Jun 10 '21 at 04:07
  • *compiler may have put important data below the stack pointer* - sure that can not. if code run in kernel mode, on kernel stack - interrupt can be at any time and it overwrite all bellow stack pointer. not view any *red zone* in windows how minimum – RbMm Jun 10 '21 at 08:13
  • @RbMm: That's correct, if you're using GCC/clang to compile kernel code, you need to use `-mno-red-zone` to disable that part of the x86-64 System V ABI. [Why can't kernel code use a Red Zone](https://stackoverflow.com/q/25787408). On non-Windows, the ABI includes a red-zone, which GCC/clang will use. ([Why is there no "sub rsp" instruction in this function prologue and why are function parameters stored at negative rbp offsets?](https://stackoverflow.com/q/28693863)) From the question, they ran their test program with `./flags`, which would be normal for a Linux or Mac system. – Peter Cordes Jun 10 '21 at 09:05
  • @RbMm: And Nate is correct, this is a know problem for using the stack from GNU C inline asm. e.g. [Using base pointer register in C++ inline asm](https://stackoverflow.com/q/34520013) is an attempt that breaks because it uses push/pop inside an asm statement. [Inline assembly that clobbers the red zone](https://stackoverflow.com/q/6380992). Remember, not everything is Windows. – Peter Cordes Jun 10 '21 at 09:08
  • @PeterCordes - are true, that if interrupt on x86-x64 will be in kernel mode (so no stack switch) - cpu (not op system) save eflags, eip(rip) just below current stack pointer (not preserve any "red zone") and this can not be configured ? if i not mistakes - yes. in this case red-zone can be used only in user mode. of course some compilers can have special switches for use/not use this. despite i not view big sense it this (why, if need, simply not down/up stack pointer ?) however i think main sense of question not in this, but in - most flags ave volatile and must not be preserved – RbMm Jun 10 '21 at 09:28
  • 1
    @RbMm: Yes, that's exactly what I said in my answer on [Why can't kernel code use a Red Zone](https://stackoverflow.com/a/38043511) which I already linked. And also yes, this bug in the code in the question would only be relevant if compiled with optimization, and it inlined into a function with local vars in the red zone. The possible bug is `pushf` stepping other things the compiler is keeping below RSP, *not* the asm failing to get RFLAGS into the C variable `flags` accurately. – Peter Cordes Jun 10 '21 at 09:35
  • @PeterCordes thanks for info. in cl (msvc) exist [__readeflags](https://learn.microsoft.com/en-us/cpp/intrinsics/readeflags?view=msvc-160) intrinsics. possible other compilers also have some like this – RbMm Jun 10 '21 at 09:41
  • 2
    @RbMm: Unlikely; MSVC has intrinsics for everything instead of supporting inline asm. GCC/clang have usable inline asm that was designed cleanly, so OSes are expected to use that for stuff like setting/clearing control flags (like IF or AC) in EFLAGS. (Getting the status flags like CF / OF / ZF from EFLAGS isn't particularly useful after compiler-generated code; you don't know whether it used ADD or LEA, or even whether the last computation was something from the C source at all. So it's not something you want often. And in a kernel you'll have -mno-redzone so you can just pushf/pop in asm) – Peter Cordes Jun 10 '21 at 09:45
1

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)

RbMm
  • 31,280
  • 3
  • 35
  • 56