インラインアセンブラ[gcc、intel、c]を使用して、操作後にキャリーフラグが設定されているかどうかを確認するにはどうすればよいですか?
5 に答える
sbb %eax,%eax
キャリー フラグが設定されている場合は eax に -1 が格納され、クリアされている場合は 0 が格納されます。eax を事前に 0 にクリアする必要はありません。それ自体から eax を減算すると、それが行われます。条件付きジャンプを使用する代わりに、結果をビットマスクとして使用して計算結果を変更できるため、この手法は非常に強力です。
キャリー フラグのテストは、インライン asm ブロック内で実行された演算によって設定された場合にのみ有効であることに注意してください。Cコードで実行された計算のキャリーをテストすることはできません。これは、コンパイラーがキャリーフラグを壊すものを最適化/再順序付けできるあらゆる種類の方法があるためです。
条件付きジャンプjc
(運ぶ場合はジャンプ) またはjnc
(運ぶ場合はジャンプ) を使用します。
または、キャリーフラグを格納できます。
;; Intel syntax
mov eax, 0
adc eax, 0 ; add with carry
ただし、x86アセンブラはSETccという名前の専用の高速ALUフラグテスト命令であり、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命令では、2つのフラグを同時にテストできます。
編集: 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;
命令setcを使用したループ(1M回の反復)は、 adc命令を使用したループよりも5倍以上高速です。
編集:より現実的なケースになるように、テスト結果がレジスターCLの計算可能なレジスターALに格納される2番目のテストを追加しました。
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命令を使用したRutineパーツは、約20%高速です。
最初の関数は符号なし加算を実行し、キャリー フラグ (CF) を使用してオーバーフローをテストします。volatile はそのままにしておく必要があります。そうしないと、オプティマイザーが命令を再配置するため、結果が正しくないことがほぼ保証されます。オプティマイザーが を a に変更するのを見てきまし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;
}
次の関数は、signed 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 されるまで、多くの人々はおそらく余分な作業と美的でない美しさを拒否するでしょう。
int r, a, b;
...
if(!add_i32(a, b, &r))
abort(); // Integer overflow!!!
...
インライン GCC アセンブリは、GCC 3.1 以降で使用できます。C Expression Operands を使用した Assembler Instructions を参照するか、「GCC Extended Assembly」を検索してください。
最後に、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 を使用してコンパイルする必要があります。