Чтение флага регистрации из программы C - PullRequest
6 голосов
/ 29 июля 2011

Ради любопытства я пытаюсь прочитать регистр флага и распечатать его хорошим способом.

Я пытался прочитать его, используя ключевое слово 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 ]

5 голосов
/ 29 июля 2011

Вы можете использовать инструкцию 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 (просто сглаз / некоторые переходы к потоку отладки), так как я не знаю о синтаксисе 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;
4 голосов
/ 29 июля 2011

Компилятор может переупорядочивать инструкции, поэтому вы не можете полагаться на то, что ваш 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
2 голосов
/ 21 мая 2019

Вы не можете ничего предположить о том, как GCC реализовал операцию a++, или даже выполнил вычисления до вашего встроенного asm или перед вызовом функции.

Вы могли бы сделать a (неиспользуемым) входом для встроенного ассема, но gcc все равно мог бы использовать lea для копирования и добавления вместо inc или add или постоянного распространения после встраивания мог превратить его в mov -посредственный.

И, конечно, gcc мог бы выполнить некоторые другие вычисления, которые пишут FLAGS прямо перед вашим встроенным ассемблером.

Нет способа сделать a++; asm(...) безопасным для этого

Стоп, вы на неправильном пути. Если вы настаиваете на использовании asm, вам нужно сделать add или inc внутри asm, чтобы вы могли прочитать выходные данные флагов. Если вы заботитесь только о флаге переполнения используйте SETCC, в частности seto %0, чтобы создать 8-битное выходное значение. Или, лучше, используйте синтаксис вывода флага GCC6, чтобы сообщить компилятору, что логический выходной результат находится в условии OF во FLAGS в конце встроенного asm.

Кроме того, переполнение со знаком в C является неопределенным поведением, поэтому фактическое переполнение в a++ уже является ошибкой. Обычно не проявляется , если вы каким-либо образом обнаружите его после факта, но если вы используете a в качестве индекса массива или что-то еще, gcc мог расширить его до 64-битного, чтобы избежать повторного расширения знака-расширения .

GCC имеет встроенные функции для добавления с обнаружением переполнения, так как gcc5

Существуют встроенные функции для подписанных / неподписанных add, sub и mul, см. Руководство GCC , которые позволяют избежать UB со знаком переполнения и сообщают о наличии переполнения.

  • bool __builtin_add_overflow (type1 a, type2 b, type3 *res) - универсальная версия
  • bool __builtin_sadd_overflow (int a, int b, int *res) является подписанной int версией
  • bool __builtin_saddll_overflow (long long int a, long long int b, long long int *res) - подписанная 64-битная long long версия.

Компилятор будет пытаться использовать аппаратные инструкции для реализации этих встроенных функций, где это возможно, например, условный переход при переполнении после сложения, условный переход при переносе и т. Д.

Существует версия saddl на тот случай, если вы хотите выполнить операцию любого размера long на целевой платформе. (Для x86-64 gcc int всегда 32-битный, long long всегда 64-битный, но long зависит от Windows или не Windows. Для платформ, таких как AVR, int будет 16- бит, и только long будет 32-битным.)

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

компилируется с gcc -O3 для x86-64 System V с этим asm, на Godbolt

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

ICC19 использует setcc в целочисленном регистре, а затем сохраняет его, то же различие, что и в мопах, но с худшим размером кода.

После встраивания в вызывающую программу, которая сделала if(of) {}, он должен просто jo или jno вместо фактического использования setcc для создания целого числа 0/1; в общем, это должно быть эффективно встроено.


Кроме того, начиная с gcc7, есть встроенная функция для запроса переполнения дополнения (после перехода к данному типу) без возврата значения.

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

компилируется с gcc -O3 для x86-64 System V с этим asm, также на Godbolt

overflows:
        xor     eax, eax
        add     edi, esi
        seto    al
        ret

См. Также Обнаружение переполнения со знаком в C / C ++

2 голосов
/ 02 марта 2014

Возможно, это проблема XY

Чтобы проверить переполнение, вам не нужно использовать флаг переполнения, достаточно просто проверить биты знака

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 флаг отсутствует, и все переполнения со знаком / без знака должны проверяться программным обеспечением путем анализа таких битов знака

Для неподписанных int гораздо проще

unsigned int a, b, result = a + b;
int overflowed = (result < a);
2 голосов
/ 30 июля 2011

Другие предложили хороший альтернативный код и причины, по которым то, что вы пытаетесь сделать, вероятно, не дает желаемого результата, но реальная ошибка в вашем коде заключается в том, что вы повредили состояние стека, нажимая без всплывающих окон. Я бы переписал asm как:

pushf
pop %0

Или вы можете просто add $4,%%esp в конце ассама исправить указатель стека, если вы предпочитаете неэффективный способ.

0 голосов
/ 21 мая 2019

Следующая программа на C будет считывать регистр FLAGS при компиляции с GCC и любым компьютером x86 или x86_64 в соответствии с соглашением о вызовах, в котором целые числа возвращаются в %eax. Вам может потребоваться передать аргумент -zexecstack компилятору.

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

Попробуйте онлайн!

Забавно выглядящий строковый литерал разбирается до

0x0000000000000000:  9C    pushfq 
0x0000000000000001:  58    pop    rax
0x0000000000000002:  C3    ret    
...