从C程序读取标志寄存器

Ben*_*tzi 6 c x86 assembly flags

为了好奇,我试图读取标志寄存器并以一种很好的方式将其打印出来.

我已经尝试使用gcc的asm关键字阅读它,但我无法让它工作.任何提示如何做到这一点?我正在运行Intel Core 2 Duo和Mac OS X.以下代码就是我所拥有的.我希望能告诉我是否发生溢出:

#include <stdio.h>

int main (void){
  int a=10, b=0, bold=0;
  printf("%d\n",b);
  while(1){
    a++;
  __asm__ ("pushf\n\t"
   "movl 4(%%esp), %%eax\n\t"
   "movl %%eax , %0\n\t"
   :"=r"(b)      
   :         
   :"%eax"        
   ); 
  if(b!=bold){ 
    printf("register changed \n %d\t to\t %d",bold , b);
  }
  bold = b;
  }
}
Run Code Online (Sandbox Code Playgroud)

这给出了分段错误.当我运行gdb时,我得到了这个:

Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x000000005fbfee5c
0x0000000100000eaf in main () at asm.c:9
9       asm ("pushf \n\t"
Run Code Online (Sandbox Code Playgroud)

phu*_*clv 8

这可能是XY 问题的情况。要检查溢出,您不需要像您想象的那样获取硬件溢出标志,因为可以从符号位轻松计算该标志

一个说明性的例子是,如果我们使用 8 位寄存器将 127 和 127 相加,会发生什么情况。127+127 是 254,但是使用 8 位算术,结果将是二进制 1111 1110,其二进制补码为 -2,因此为负数。正操作数的负结果(反之亦然)是溢出。然后将设置溢出标志,以便程序可以意识到问题并减轻此问题或发出错误信号。因此,当通过将两个具有相同符号的数字相加(或减去两个具有相反符号的数字)来改变最高有效位(这里被认为是符号位)时,溢出标志被设置。当两个加法操作数的符号不同(或两个减法操作数的符号相同)时,不会发生溢出。

在内部,溢出标志通常由符号位进出内部进位的异或产生。由于符号位与被视为无符号数的最高有效位相同,因此溢出标志是“无意义的”,并且在无符号数相加或相减时通常会被忽略。

https://en.wikipedia.org/wiki/Overflow_flag

所以C的实现是

int add(int a, int b, int* overflowed)
{
    // do an unsigned addition since to prevent UB due to signed overflow
    unsigned int r = (unsigned int)a + (unsigned int)b;

    // if a and b have the same sign and the result's sign is different from a and b
    // then the addition was overflowed
    *overflowed = !!((~(a ^ b) & (a ^ r)) & 0x80000000);
    return (int)r;
}
Run Code Online (Sandbox Code Playgroud)

这样,它就可以在任何体系结构上移植工作,这与仅在 x86 上工作的解决方案不同。智能编译器可能会识别该模式并在可能的情况下更改为使用溢出标志。在大多数 RISC 架构(如 MIPS 或 RISC-V)上,没有标志,所有有符号/无符号溢出必须在软件中通过分析符号位进行检查,如下所示

一些编译器具有用于检查溢出的内在函数,如ClangGCC__builtin_add_overflow中的那样。通过该内在函数,您还可以轻松了解如何在非标志架构上计算溢出。例如在 ARM 上是这样完成的

add     w3, w0, w1  # r = a + b
eon     w0, w0, w1  # a = a ^ ~b
eor     w1, w3, w1  # b = b ^ r
str     w3, [x2]    # store sum ([x2] = r)
and     w0, w1, w0  # a = a & b = (a ^ ~b) & (b ^ r)
lsr     w0, w0, 31  # overflowed = a >> 31
ret
Run Code Online (Sandbox Code Playgroud)

这只是我上面写的内容的变体

也可以看看


对于 unsigned int 来说更容易

unsigned int a, b, result = a + b;
int overflowed = (result < a);
Run Code Online (Sandbox Code Playgroud)

  • @CrouchingKitten 这是故意的。这是将值强制为 0 和 1 的标准方法,并且在 Linux 中大量使用 [!!(x) 在 C(尤其是 Linux 内核)中意味着什么?](/sf/ 2527086/995714) (2认同)

nie*_*lsj 5

您可以使用PUSHF/PUSHFD/PUSHFQ指令(请参阅http://siyobik.info/main/reference/instruction/PUSHF%2FPUSHFD了解详细信息)将标志寄存器压入堆栈.从那里你可以用C解释它.否则你可以直接测试(针对无符号算术的进位标志或带符号算术的溢出标志)和分支.

(具体来说,要测试溢出位,可以使用JO(如果设置则跳转)和JNO(如果未设置则跳转)进行分支 - 它在寄存器中的位#11(从0开始))

关于EFLAGS位布局:http://en.wikibooks.org/wiki/X86_Assembly/X86_Architecture#EFLAGS_Register

一个非常粗略的Visual C语法测试(只是wham-bam /一些跳转到调试流程),因为我不知道GCC语法:

int test2 = 2147483647; // max 32-bit signed int (0x7fffffff)
unsigned int flags_w_overflow, flags_wo_overflow;
__asm
{
    mov ebx, test2 // ebx = test value

    // test for no overflow
    xor eax, eax // eax = 0
    add eax, ebx // add ebx
    jno no_overflow // jump if no overflow

testoverflow:
    // test for overflow
    xor ecx, ecx // ecx = 0
    inc ecx // ecx = 1
    add ecx, ebx // overflow!
    pushfd // store flags (32 bits)
    jo overflow // jump if overflow
    jmp done // jump if not overflown :(

no_overflow:
    pushfd // store flags (32 bits)
    pop edx // edx = flags w/o overflow
    jmp testoverflow // back to next test

overflow:
    jmp done // yeah we're done here :)

done:
    pop eax // eax = flags w/overflow
    mov flags_w_overflow, eax // store
    mov flags_wo_overflow, edx // store
}

if (flags_w_overflow & (1 << 11)) __asm int 0x3 // overflow bit set correctly
if (flags_wo_overflow & (1 << 11)) __asm int 0x3 // overflow bit set incorrectly

return 0;
Run Code Online (Sandbox Code Playgroud)


nin*_*alj 5

编译器可以对指令进行重新排序,因此您不能依靠自己lahf位于增量旁边。实际上,可能根本没有增量。在您的代码中,您没有使用的值a,因此编译器可以完全优化它。

因此,可以将增量+检查写入汇编器中,也可以将其写入C中。

此外,lahf仅从中加载ah(8位)eflags,并且溢出标志不在此范围内。更好地使用pushf; pop %eax

一些测试:

#include <stdio.h>

int main (void){
    int a=2147483640, b=0, bold=0;
    printf("%d\n",b);
    while(1){
            a++;
            __asm__ __volatile__ ("pushf \n\t"
                            "pop %%eax\n\t"
                            "movl %%eax, %0\n\t"
                            :"=r"(b)
                            :
                            :"%eax"
                    );
            if((b & 0x800) != (bold & 0x800)){
                    printf("register changed \n %x\t to\t %x\n",bold , b);
            }
            bold = b;
    }
}


$ gcc -Wall  -o ex2 ex2.c
$ ./ex2  # Works by sheer luck
0
register changed
 200206  to      200a96
register changed
 200a96  to      200282

$ gcc -Wall -O -o ex2 ex2.c
$ ./ex2  # Doesn't work, the compiler hasn't even optimized yet!
0
Run Code Online (Sandbox Code Playgroud)