Ответ 1
С условными прыжками jc
(прыгать, если переносить) или jnc
(прыгать, если не переносить).
Или вы можете сохранить флаг переноса,
;; Intel syntax
mov eax, 0
adc eax, 0 ; add with carry
Используя встроенный ассемблер [gcc, intel, c], как проверить, установлен ли флаг переноса после операции?
С условными прыжками jc
(прыгать, если переносить) или jnc
(прыгать, если не переносить).
Или вы можете сохранить флаг переноса,
;; Intel syntax
mov eax, 0
adc eax, 0 ; add with carry
sbb %eax,%eax
будет хранить -1 в eax, если флаг переноса установлен, 0, если он ясен. Нет необходимости предварительно очищать eax до 0; вычитание eax из себя делает это для вас. Этот метод может быть очень сильным, поскольку вы можете использовать результат в виде битовой маски для изменения результатов вычислений вместо использования условных переходов.
Вы должны знать, что действителен только для проверки флага переноса, если он был установлен арифметикой, выполняемой INSIDE для встроенного блока asm. Вы не можете протестировать перенос вычисления, которое было выполнено в C-коде, потому что существует множество способов, которыми компилятор мог бы оптимизировать/изменить порядок вещей, которые бы сбивали флаг переноса.
Тем не менее, ассемблер 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%.
Первая функция выполняет беззнаковое добавление, а затем проверяет переполнение с использованием флага переноса (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 для компиляции.