17

私はこの小さなコード スニペットを持っています (これは私が抱えている問題の最小限の実例です):

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

void xorBuffer(unsigned char* dst, unsigned char* src, int len)
{
    while (len != 0)
    {
        *dst ^= *src;
        dst++;
        src++;
        len--;
    }
}

int main()
{
    unsigned char* a = malloc(32);
    unsigned char* b = malloc(32);
    int t;

    memset(a, 0xAA, 32);
    memset(b, 0xBB, 32);

    xorBuffer(a, b, 32);

    printf("result = ");
    for (t = 0; t < 32; t++) printf("%.2x", a[t]);
    printf("\n");

    return 0;
}

このコードは、2 つの 32 バイト メモリ バッファの排他的論理和を実行することになっています (概念的には、これで実行する必要がありますa = a ^ b)。0xAA ^ 0xBB = 0x11 なので、「11」を 32 回出力する必要があります。

私の問題は、これを MinGW-GCC (Windows) でコンパイルすると、デバッグ モード (最適化なし) で完全に動作しますが、-O3 から始まる最適化が有効になっている場合、xorBuffer ループの途中で SIGILL でクラッシュすることです。また、問題のあるループにprintfを入れると、再び完全に機能します。スタックの破損が疑われますが、ここで何が間違っているのかわかりません。

最適化を有効にしてGDBでデバッグしようとすると、すべてのGDBがすべての変数に対して「変数最適化」されていることが示されるため、原因が失われます(もちろん、変数を出力しようとすると、突然機能します)。

ここで一体何が起こっているのか誰か知っていますか?私はこの問題についてあまりにも長い時間を費やしてきました。先に進むには、適切に修正する必要があります。私の推測では、基本的な C ポインターの知識が欠けていると思いますが、コードは正しいように見えます。バッファのインクリメントによるものかもしれませんが、私の知る限りでは、sizeof(unsigned char) == 1各バイトを 1 つずつ通過する必要があります。

価値があるのは、私の Linux ボックスで GCC を最適化してもコードが機能することです。

それで... ここでの取引は何ですか?ありがとう!

要求どおり、プログラム全体のアセンブリ出力:

-O2 あり:カチカチ

-O3 あり:クリック感

GCC 4.6.2(MinGWで実行)でこの動作を確認しました

4

2 に答える 2

8

私はこれをUnwindの答えの延長として追加しています(これは私を正しい軌道に乗せたので受け入れます)。

最適化されたコードをふるいにかけた後、AVXの指示に気づきました。私のプロセッサがAVX命令セットをサポートしていることを考えると、最初は問題は発生しないと思いました。ただし、AVX1とAVX2の2つの異なるAVXバージョンがあることがわかりました。そして、私のプロセッサはAVX1のみをサポートしていますが、プロセッサが2つのバージョンのいずれかをサポートしている限り、gccはAVX2オペコードを無差別に使用します(llvmは同じ間違いを犯しましたが、バグレポートがあります)。これは、私が考える限り、誤った操作であり、コンパイラのバグです。

その結果、AVX1システムでAVX2コードが生成され、明らかに不正な命令が発生します。32バイト未満の入力で失敗しないコード(256ビットのレジスタ幅のため)から、CPUサポートがSSE3に制限された仮想マシンであるLinuxボックスで動作するコードまで、多くのことを説明しています。

修正は、-O3を無効にして-O2に戻ることです。ここで、gccは最もハードコアなSIMD命令に頼って単純なコードを最適化しないか、volatileキーワードを使用して、バイトごとにバッファーを通過させます。丹念に、そのように:

*(unsigned char volatile *)dst ^= *(unsigned char volatile *)src;

もちろん、これは非常に遅く、おそらく-O2(プログラム全体の影響を無視する)を使用するよりも悪いですが、代わりにintごとにバッファintを通過し、最後にパディングすることで回避できます。速度。

もう1つの良い修正は、このバグがないバージョンのgccにアップグレードすることです(このバージョンはまだ存在しない可能性があります。私はチェックしていません)。

編集:最終的な修正は、GCCで-mno-avxフラグをスローすることです。これにより、すべてのAVXオペコードが無効になり、コードを変更せずにバグが完全に無効になります(パッチが適用されたコンパイラバージョンが利用可能になると、簡単に削除できます)。

なんてひどいコンパイラのバグ。

于 2012-08-22T11:30:06.450 に答える
8

私のコメントから:

コンパイラーがターゲット・アーキテクチャーに関する正しい情報を持っていることを確認してください。出力を読む-O3と、コンパイラが SIMD 最適化を設定しているように見えます。実際には、ベクトル命令 ( などmovdqa) を使用してコードをより並列化しています。ターゲット プロセッサが、コンパイラが出力するコードと 100% 一致しない場合、不正な命令が発生する可能性があります。

于 2012-08-22T11:03:33.353 に答える