web-dev-qa-db-ja.com

プログラムを再コンパイルすると、ビットごとに同一のバイナリが生成されますか?

プログラムを単一のバイナリにコンパイルしてチェックサムを作成し、同じマシンで同じコンパイラとコンパイラ設定を使用して再コンパイルし、再コンパイルしたプログラムをチェックサムすると、チェックサムが失敗しますか?

もしそうなら、これはなぜですか?そうでない場合、異なるCPUを使用すると、同一でないバイナリが生成されますか?

26
David
  1. 同じマシンで同じ設定で同じプログラムをコンパイルします。

    決定的な答えは「依存する」ですが、ほとんどのコンパイラはほとんどの場合に決定論的であり、生成されるバイナリは同一であるべきであると期待するのは妥当です。実際、一部のバージョン管理システムはこれに依存しています。それでも、例外は常にあります。 someコンパイラがどこかにタイムスタンプなどを挿入することを決定する可能性はかなりあります(iirc、Delphiなど)。または、ビルドプロセス自体がそれを行う場合もあります。プリプロセッサマクロを現在のタイムスタンプに設定するCプログラムのmakefileを見てきました。 (ただし、これは別のコンパイラ設定としてカウントされると思います。)

    また、バイナリを静的にリンクすると、マシン上のすべての関連ライブラリの状態が効果的に組み込まれることに注意してください。これらのいずれかを変更すると、バイナリにも影響します。したがって、関連するのはコンパイラ設定だけではありません。

  2. CPUが異なる別のマシンで同じプログラムをコンパイルします。

    ここでは、すべての賭けはオフです。最新のコンパイラのほとんどは、ターゲット固有の最適化を実行できます。このオプションを有効にすると、CPUが類似していない限り、バイナリが異なる可能性があります(それでも可能です)。また、静的リンクに関する上記の注記を参照してください。構成環境はコンパイラー設定をはるかに超えています。非常に厳密な構成制御がない限り、2つのマシン間で何かが異なる可能性が非常に高くなります。

19
rici

あなたが求めているのは、「出力deterministicです」です。プログラムを一度コンパイルした後、すぐに再度コンパイルした場合、おそらく同じ出力ファイルが作成されます。ただし、何かが変更された場合-小さな変更でも-特にコンパイルされたプログラムが使用するコンポーネントで、コンパイラーの出力も変更される可能性があります。

8
headkase
  • -frandom-seed=123は、GCC内部のランダム性を制御します。 man gccさんのコメント:

    このオプションは、GCCがすべてのコンパイル済みファイルで異なる必要がある特定のシンボル名を生成するときに乱数の代わりに使用するシードを提供します。また、カバレッジデータファイルとそれらを生成するオブジェクトファイルに一意のスタンプを配置するためにも使用されます。 -frandom-seedオプションを使用して、再現可能な同一のオブジェクトファイルを作成できます。

  • __FILE__:ソースを固定フォルダーに入れます(例:/tmp/build

  • __DATE____TIME____TIMESTAMP__
    • libfaketime: https://github.com/wolfcw/libfaketime
    • これらのマクロを-Dでオーバーライドする
    • -Wdate-timeまたは-Werror=date-time__TIME____DATE__、または__TIMESTAMP__のいずれかが使用されている場合、警告または失敗します。 Linuxカーネル4.4はデフォルトでそれを使用します。
  • Dフラグをarとともに使用するか、 https://github.com/nh2/ar-timestamp-wiper/tree/master を使用してスタンプをワイプします
  • -fno-guess-branch-probability古い手動バージョン 非決定論のソースであると言いますが、 もうない です。これが-frandom-seedの対象かどうかは不明です。

Debian 再現可能なビルドプロジェクト は、Debianパッケージをバイト単位で標準化しようとし、最近 Linux Foundationの助成金 を取得しました。これにはコンパイルだけではありませんが、興味深いことです。

Buildroot には、パッケージレベルでいくつかのアイデアを提供するBR2_REPRODUCIBLEオプションがありますが、現時点ではまだ完全ではありません。

関連スレッド:

プログラムを再コンパイルすると、ビットごとに同一のバイナリが生成されますか?

すべてのコンパイラについて?いいえ。C#コンパイラは少なくとも許可されていません。

Eric Lippertには コンパイラーの出力が確定的でない理由の完全な内訳 があります。

[T] C#コンパイラーは、設計により、同じバイナリを2回生成することはありません。 C#コンパイラは、実行するたびに、新しく生成されたGUIDをすべてのアセンブリに埋め込みます。これにより、2つのアセンブリがビットごとに同一にならないようにします。CLI仕様から引用するには:

Mvid列は、モジュールのこのインスタンスを識別する一意のGUID [...]にインデックスを付けます。[...] Mvidは、すべてのモジュールに対して新しく生成する必要があります[...] [runtime]自体はMvidを使用しません。他のツール(デバッガー[...]など)は、Mvidがほとんどの場合モジュールごとに異なるという事実に依存しています。

これはC#コンパイラのバージョンに固有のものですが、この記事の多くの点をanyコンパイラに適用できます。

まず、毎回同じ順序で常に同じファイルのリストを取得すると想定しています。しかし、それは場合によってはオペレーティングシステム次第です。 「csc * .cs」と言う場合、オペレーティングシステムが一致するファイルのリストを提供する順序は、オペレーティングシステムの実装の詳細です。コンパイラーはそのリストを正規の順序にソートしません。

7
ta.speot.is

いいえ、それは100%確定的ではありません。以前は、Hitachi H8プロセッサ用のターゲットバイナリを生成するバージョンのGCCを使用していました。

タイムスタンプは問題ありません。タイムスタンプの問題が無視されても、特定のプロセッサアーキテクチャでは、同じ命令を2つのわずかに異なる方法でエンコードできる場合があります。ビットは1または0になる場合があります。以前の経験では、生成されたバイナリはほとんど同じ時間でした。しかし、時々、gccは同じサイズのバイナリを生成しますが、一部のバイトは1ビットだけ異なる0XE0は0XE1になります。

3
JavaMan

一般的には違います。最も合理的に洗練されたコンパイラは、オブジェクトモジュールにコンパイル時間を含めます。クロックをリセットする場合でも、コンパイルを開始したときを非常に正確にする必要があります(そして、ディスクアクセスなどが以前と同じ速度であることを願っています)。

1
Daniel R Hicks