Проверьте, установлен ли флаг флага

Используя встроенный ассемблер [gcc, intel, c], как проверить, установлен ли флаг переноса после операции?

Ответы

Ответ 1

С условными прыжками jc (прыгать, если переносить) или jnc (прыгать, если не переносить).

Или вы можете сохранить флаг переноса,

;; Intel syntax
mov eax, 0
adc eax, 0 ; add with carry

Ответ 2

sbb %eax,%eax будет хранить -1 в eax, если флаг переноса установлен, 0, если он ясен. Нет необходимости предварительно очищать eax до 0; вычитание eax из себя делает это для вас. Этот метод может быть очень сильным, поскольку вы можете использовать результат в виде битовой маски для изменения результатов вычислений вместо использования условных переходов.

Вы должны знать, что действителен только для проверки флага переноса, если он был установлен арифметикой, выполняемой INSIDE для встроенного блока asm. Вы не можете протестировать перенос вычисления, которое было выполнено в C-коде, потому что существует множество способов, которыми компилятор мог бы оптимизировать/изменить порядок вещей, которые бы сбивали флаг переноса.

Ответ 3

Тем не менее, ассемблер x86 hes посвятил быстрым инструкциям теста флага ALU с именем SETcc, где cc - желаемый флаг ALU. Поэтому вы можете написать:

setc    AL                           //will set AL register to 1 or clear to 0 depend on carry flag

or

setc    byte ptr [edx]               //will set memory byte on location edx depend on carry flag

or even

setc    byte ptr [CarryFlagTestByte]  //will set memory variable on location CarryFlagTestByte depend on carry flag

С помощью команды SETcc вы можете тестировать флаги, такие как перенос, ноль, знак, переполнение или четность, некоторые команды SETcc позволяют сразу тестировать сразу два флага.

EDIT: Добавлен простой тест, сделанный в Delphi, чтобы уйти от сомнений относительно термина быстро

procedure TfrmTest.ButtonTestClick(Sender: TObject);
  function GetCPUTimeStamp: int64;
  asm
    rdtsc
  end;
var
 ii, i: int64;
begin
  i := GetCPUTimeStamp;
  asm
    mov   ecx, 1000000
@repeat:
    mov   al, 0
    adc   al, 0
    mov   al, 0
    adc   al, 0
    mov   al, 0
    adc   al, 0
    mov   al, 0
    adc   al, 0
    loop  @repeat
  end;
  i := GetCPUTimeStamp - i;

  ii := GetCPUTimeStamp;
  asm
    mov   ecx, 1000000
@repeat:
    setc  al
    setc  al
    setc  al
    setc  al
    loop  @repeat
  end;
  ii := GetCPUTimeStamp - ii;
  caption := IntToStr(i) + '  ' +  IntToStr(ii));
end;

Петля (1M итераций), использующая команду setc, более чем в 5 раз быстрее, чем цикл с adc.

EDIT: добавлено второе испытание, результат теста, хранящийся в регистре AL, который компилируется в регистре CL, будет более реалистичным.

procedure TfrmTestOtlContainers.Button1Click(Sender: TObject);
  function GetCPUTimeStamp: int64;
  asm
    rdtsc
  end;

var
 ii, i: int64;
begin
  i := GetCPUTimeStamp;
  asm
    xor   ecx, ecx
    mov   edx, $AAAAAAAA

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

  end;
  i := GetCPUTimeStamp - i;

  ii := GetCPUTimeStamp;
  asm
    xor   ecx, ecx
    mov   edx, $AAAAAAAA

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

  end;
  ii := GetCPUTimeStamp - ii;
  caption := IntToStr(i) + '  ' +  IntToStr(ii);
end;

Рутиновая часть с инструкцией SETcc все еще быстрее примерно на 20%.

Ответ 4

Первая функция выполняет беззнаковое добавление, а затем проверяет переполнение с использованием флага переноса (CF). Нестабильность должна оставаться. В противном случае оптимизатор изменит инструкции, что в значительной степени обеспечивает неверный результат. Я видел, как оптимизатор менял jnc на jae (который также основан на CF).

/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_u32(uint32_t a, uint32_t b, uint32_t* r)
{
    volatile int no_carry = 1;
    volatile uint32_t result = a + b;

    asm volatile
    (
     "jnc 1f          ;"
     "movl $0, %[xc]  ;"
     "1:              ;"
     : [xc] "=m" (no_carry)
     );

    if(r)
        *r = result;

    return no_carry;
}

Следующая функция для подписанных int. Используется одинаковое использование volatile. Обратите внимание, что целочисленная математическая математика переходит на флаг OF через jno. Я видел, как оптимизатор изменил это на jnb (который также основан на OF).

/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_i32(int32_t a, int32_t b, int32_t* r)
{   
    volatile int no_overflow = 1;
    volatile int32_t result = a + b;

    asm volatile
    (
     "jno 1f          ;"
     "movl $0, %[xo]  ;"
     "1:              ;"
     : [xo] "=m" (no_overflow)
     );

    if(r)
        *r = result;

    return no_overflow;
}

В общей картине вы можете использовать следующие функции. В той же большой картине многие люди, вероятно, откажутся от дополнительной работы и эстетической некрасивости до тех пор, пока pwn'd не переполнит/обернется/не будет

int r, a, b;
...

if(!add_i32(a, b, &r))
    abort(); // Integer overflow!!!

...

Встроенная сборка GCC доступна в GCC 3.1 и выше. См. "Инструкции ассемблера с операндами C-выражений" или выполните поиск "Расширенная сборка GCC".

Наконец, то же самое в Visual Studio будет выглядеть следующим образом (не так много разницы в генерации кода), но синтаксис намного проще, так как MASM позволяет вам перейти на метку C:

/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_i32(__int32 a, __int32 b, __int32* r)
{   
    volatile int no_overflow = 1;
    volatile __int32 result = a + b;

    __asm
    {
        jno NO_OVERFLOW;
        mov no_overflow, 0;
    NO_OVERFLOW:
    }

    if(r)
        *r = result;

    return no_overflow;
}

На плохой стороне вышеуказанный код MASM применим только для сборки x86. Для сборки x64 нет вставки, поэтому вам нужно будет ее скопировать в сборку (в отдельный файл) и использовать MASM64 для компиляции.