コメントのみが変更されたプログラムの2つのバイナリがgccで正確に一致しないのはなぜですか?


110

2つのCプログラムを作成しました

  1. プログラム1

    int main()
    {
    }
  2. プログラム2

    int main()
    {
    //Some Harmless comments
    }

私の知る限り、コンパイルするとき、コンパイラ(gcc)はコメントと冗長な空白を無視する必要があるため、出力は類似している必要があります。

しかし、出力バイナリのmd5sumsを確認したところ、一致していません。また、最適化-O3を使用してコンパイルしようとしました-Ofastが、それでも一致しませんでした。

ここで何が起きてるの?

編集:正確なコマンドとそこにあるmd5sumsがあります(t1.cはプログラム1であり、t2.cはプログラム2です)

gcc ./t1.c -o aaa
gcc ./t2.c -o bbb
98c1a86e593fd0181383662e68bac22f  aaa
c10293cbe6031b13dc6244d01b4d2793  bbb

gcc ./t2.c -Ofast -o bbb
gcc ./t1.c -Ofast -o aaa
2f65a6d5bc9bf1351bdd6919a766fa10  aaa
c0bee139c47183ce62e10c3dbc13c614  bbb


gcc ./t1.c -O3 -o aaa
gcc ./t2.c -O3 -o bbb
564a39d982710b0070bb9349bfc0e2cd  aaa
ad89b15e73b26e32026fd0f1dc152cd2  bbb

そして、はい、md5sumsは、同じフラグを持つ複数のコンパイル間で一致します。

ところで、私のシステムがあるgcc (GCC) 5.2.0Linux 4.2.0-1-MANJARO #1 SMP PREEMPT x86_64 GNU/Linux


17
正確なコマンドラインフラグを含めてください。たとえば、デバッグ情報はバイナリに含まれていますか?もしそうなら、行番号の変更は明らかにそれに影響します...
Jon Skeet

4
MD5の合計は、同じコードの複数のビルドで一貫していますか?
unenthusiasticuser 2015

3
これは再現できません。これは、GCCがそれらをコンパイルするときに(タイムスタンプを含む)メタデータの束全体をバイナリに埋め込むという事実が原因であると推測しました。使用した正確なコマンドラインフラグを追加できると便利です。
cyphar 2015

2
だけではなく、MD5チェックサムをチェックし、バイトが異なる正確に確認するために立ち往生、hexdumpに対して及び差分を取得する
MM

12
「2つのコンパイラ出力の違いは何ですか?」興味深いです。この質問には不当な前提があることに注意してください。2つの出力同じである必要があり、なぜ異なるのかについて説明が必要です。コンパイラが約束するのは、正当なCプログラムを与えると、出力はそのプログラムを実装する正当な実行可能ファイルになるということです。コンパイラを2回実行しても同じバイナリが生成されることは、C標準の保証ではありません。
Eric Lippert、2015

回答:


159

これは、ファイル名が異なるためです(ただし、文字列の出力は同じです)。(2つのファイルを作成するのではなく)ファイル自体を変更しようとすると、出力バイナリに違いがないことがわかります。イェンスと私が言ったように、それはGCC が正確なソースファイル名(およびCFAもそうであるようにAFAICS)を含む、メタデータの全負荷をビルドするバイナリにダンプするためです。

これを試して:

$ cp code.c code2.c subdir/code.c
$ gcc code.c -o a
$ gcc code2.c -o b
$ gcc subdir/code.c -o a2
$ diff a b
Binary files a and b differ
$ diff a2 b
Binary files a2 and b differ
$ diff -s a a2
Files a and a2 are identical

これは、ビルド間でmd5sumが変更されない理由を説明しますが、ファイルによって異なります。必要に応じて、Jensが提案したことを実行しstrings、各バイナリの出力を比較して、ファイル名がバイナリに埋め込まれていることがわかります。これを「修正」したい場合はstrip、バイナリを使用するとメタデータが削除されます。

$ strip a a2 b
$ diff -s a b
Files a and b are identical
$ diff -s a2 b
Files a2 and b are identical
$ diff -s a a2
Files a and a2 are identical

編集:バイナリを削除して問題を「修正」できることを示すために更新されました。
cyphar 2015

30
これが、MD5チェックサムではなく、アセンブリの出力を比較する理由です。
オービットのライトネスレース

1
ここでフォローアップの質問をしました
Federico Poloni、2015

4
オブジェクトファイルのフォーマットによっては、コンパイル時間もオブジェクトファイルに保存されます。したがって、サンプルファイルaとa2にCOFFファイルを使用することは同じではありません。
Martin Rosenau、2015

28

最も一般的な理由は、コンパイラによって追加されたファイル名とタイムスタンプです(通常はELFセクションのデバッグ情報部分にあります)。

実行してみてください

 $ strings -a program > x
 ...recompile program...
 $ strings -a program > y
 $ diff x y

その理由がわかるかもしれません。私はかつてこれを使用して、同じソースが異なるディレクトリでコンパイルされたときに異なるコードを引き起こす理由を見つけました。発見は、__FILE__マクロが絶対的なファイル名に展開され、両方のツリーで異なることでした。


1
gcc.gnu.org/ml/gcc-help/2007-05/msg00138.html(古い、わかっています)によると、タイムスタンプは保存されず、リンカーの問題である可能性があります。でも、最近、セキュリティ会社がバイナリのGCCタイムスタンプ情報を使用してハッキングチームの作業習慣をどのようにプロファイリングしたかについての話を読んだことを覚えています。
cyphar 2015

3
そしてOPは、「md5sumsが同じフラグを持つ複数のコンパイル間で一致する」と述べていることは言うまでもありません。これは、問題を引き起こしているのがタイムスタンプではないことを示しています。ファイル名が異なることが原因と考えられます。
cyphar 2015

1
@cyphar別のファイル名も、strings / diffアプローチでキャッチする必要があります。
Jens

15

ソースファイル名は、解凍されていないバイナリに含まれるため、異なる名前のソースファイルからの2つのプログラムは異なるハッシュを使用することに注意してください

同様の状況で、上記が当てはまらない場合は、以下を試すことができます。

  • stripバイナリに対して実行して脂肪を取り除きます。取り除かれたバイナリが同じである場合、それはプログラム操作に不可欠ではないいくつかのメタデータでした。
  • 差(差が実際より良く特定すること、ただし、または、実際のCPU命令でないことを確認するために、アセンブリの中間出力を生成です
  • を使用するstringsか、両方のプログラムを16進数にダンプして、2つの16進数ダンプでdiffを実行します。違いを見つけたら、それらに韻や理由があるかどうかを試してみてください(PID、タイムスタンプ、ソースファイルのタイムスタンプ...)。たとえば、診断目的でコンパイル時タイムスタンプを保存するルーチンがあるとします。

私のシステムがあるgcc (GCC) 5.2.0Linux 4.2.0-1-MANJARO #1 SMP PREEMPT x86_64 GNU/Linux
登録ユーザー

2
実際に2つの個別のファイルを作成してみてください。単一のファイルを修正することでそれを再現することもできませんでした。
cyphar 2015

はい、ファイル名は犯人です。同じ名前でプログラムをコンパイルすると、同じmd5sumを取得できます。
登録済みユーザー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.