从C程序读取标志寄存器

时间:2011-07-29 16:25:02

标签: c assembly x86 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;
  }
}

这会产生分段错误。当我运行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"

6 个答案:

答案 0 :(得分: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;

答案 1 :(得分:4)

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

因此,要么在汇编程序中写入increment + check,要么在C中写入。

此外,lahf仅从ah加载eflags(8位),而溢出标志位于此之外。更好地使用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

答案 2 :(得分:2)

其他人提供了良好的替代代码,并说明为什么你要做的事情可能不会给你想要的结果,但代码中的实际错误是你通过推送而没有弹出来破坏堆栈状态。我会把asm重写为:

pushf
pop %0

或者你可以在你的asm结尾处add $4,%%esp来修复堆栈指针,如果你喜欢低效的方式。

答案 3 :(得分:2)

这可能是XY problem

的情况

要检查溢出,您不需要使用溢出标志,只需检查符号位就足够了

int a, b;
unsigned int r = (unsigned int)a + (unsigned int)b;    // do unsigned addition since signed addition do not overflow in C

int overflowed = ((~(a ^ b)) & (a ^ r)) & 0x80000000;  // if a and b have same sign and the result's sign is different from a and b then the addition was overflowed
int result     = (int)r;

这种方式可以移植,不需要在x86上。在MIPS上没有标志,所有签名/无符号溢出必须由软件通过分析这样的符号位来检查

对于unsigned int,它更容易

unsigned int a, b, result = a + b;
int overflowed = (result < a);

答案 4 :(得分:2)

您不能假设GCC如何实现a++操作,甚至在内联asm之前或函数调用之前,甚至都无法计算

您可以将a用作内联汇编的(未使用的)输入,但是gcc仍然可以选择使用lea进行复制和添加,而不是inc或{{ 1}}或内联之后的常量传播可能会将其变成add立即。

当然,gcc还可以做其他一些计算,这些代码可以在嵌入式asm之前写FLAGS。

没有办法确保mov的安全性

立即停止,您走在错误的轨道上。 如果您坚持使用asm,则需要在asm内部进行a++; asm(...)add ,以便读取标志输出。只关心溢出标志,请使用SETCC(特别是inc)来创建8位输出值。或者更好的方法是,使用GCC6标志输出语法来告诉编译器,在内联汇编的末尾,FLAGS中的布尔输出结果处于OF条件。

此外,C中的已签名溢出是未定义的行为,因此实际上导致seto %0中的溢出已经是一个错误。如果事后以某种方式检测到它,它通常不会显示 本身,但是如果您使用a++作为数组索引或某些gcc可能将其扩展为64位以避免重做符号扩展名。

自gcc5起,GCC内置了带有溢出检测功能的add

有用于签名/未签名的add,sub和mul see the GCC manual的内置程序,可以避免签名溢出的UB,并告诉您是否有溢出。

  • a是通用版本
  • bool __builtin_add_overflow (type1 a, type2 b, type3 *res)是签名的bool __builtin_sadd_overflow (int a, int b, int *res)版本
  • int是经过签名的64位bool __builtin_saddll_overflow (long long int a, long long int b, long long int *res)版本。
  

在可能的情况下,编译器将尝试使用硬件指令来实现这些内置功能,例如加法后的条件跳转,进位条件跳转等。

如果您希望在目标平台上进行任何大小的long long的操作,都有一个saddl版本。 (对于x86-64 gcc,long始终为32位,int始终为64位,但是long long取决于Windows与非Windows。对于AVR之类的平台,{ {1}}是16位,只有long是32位。)

int

针对x86-64系统V用long编译到此asm on Godbolt

int checked_add_int(int a, int b, bool *of) {
    int result;
    *of = __builtin_sadd_overflow(a, b, &result);
    return result;
}

ICC19使用gcc -O3到一个整数寄存器中,然后将其存储(与oups相同,但代码大小更差)。

内联到执行checked_add_int: mov eax, edi add eax, esi # can't use the normal lea eax, [rdi+rsi] seto BYTE PTR [rdx] and BYTE PTR [rdx], 1 # silly compiler, it's already 0/1 ret 的调用方之后,应仅使用setccif(of) {}而不是实际使用jo创建整数0/1;通常,这应该有效地内联。


此外,自gcc7起,有一个内置函数来询问添加项(提升为给定类型后)是否会溢出而不返回值。

jno

使用setcc for x86-64 System V编译到该asm,也可以在Godbolt上

#include <stdbool.h>
int overflows(int a, int b) {
    bool of = __builtin_add_overflow_p(a, b, (int)0);
    return of;
}

另请参阅Detecting signed overflow in C/C++

答案 5 :(得分:0)

在按照GCC和任何x86或x86_64机器(遵循将整数返回到ipcMain.on('button-click',()=>{ //Load html into Window. mainWindow.loadURL(url.format({ pathname: path.join(__dirname, 'second.html'), protocol: 'file:', slashes: true })); }) 的调用约定)进行编译时,以下C程序将读取FLAGS寄存器。您可能需要将%eax参数传递给编译器。

-zexecstack

Try it online!

看起来很有趣的字符串文字反汇编为

#include<stdio.h>
#include<stdlib.h>

int(*f)()=(void*)L"\xc3589c";

int main( int argc, char **argv ) {
  if( argc < 3 ) {
    printf( "Usage: %s <augend> <addend>\n", *argv );
    return 0;
  }
  int a=atoi(argv[1])+atoi(argv[2]);
  int b=f();
  printf("%d CF %d PF %d AF %d ZF %d SF %d TF %d IF %d DF %d OF %d IOPL %d NT %d RF %d VM %d AC %d VIF %d VIP %d ID %d\n", a, b&1, b/4&1, b>>4&1, b>>6&1, b>>7&1, b>>8&1, b>>9&1, b>>10&1, b>>11&1, b>>12&3, b>>14&1, b>>16&1, b>>17&1, b>>18&1, b>>19&1, b>>20&1, b>>21&1 );
}