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


25

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

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


8
コンパイラに依存します。それらのいくつかはタイムスタンプを埋め込むので、それらに対する答えは「いいえ」です。
ta.speot.is

実際には、コンパイラではなく実行形式に依存します。WindowsのPE形式などの一部の実行可能形式には、コンパイル日時に関係するタイムスタンプが含まれていますが、LinuxのELF形式などの他の形式には含まれていません。どちらにしても、この質問は「同一のバイナリ」の定義にかかっています。同じソースファイルが同じコンパイラ、ライブラリ、スイッチなどでコンパイルされている場合、イメージ自体はビット単位で同一である必要がありますが、ヘッダーやその他のメタデータは異なる場合があります。
Synetech

回答:


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

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

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

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

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


1
GCCを使用していて、マーチオプション(特定のCPUファミリ用にバイナリを最適化するオプション)を使用していなかったとし、1つのCPUでバイナリをコンパイルしてから、別のCPUで差?
デビッド

1
@David:まだ依存しています。まず、リンク先のライブラリには、アーキテクチャ固有のビルドが含まれている場合があります。したがって、の出力はgcc -c同じである可能性がありますが、リンクされたバージョンは異なります。また、それだけでは-marchありません。-mtune/-mcpu and -mfpmatch(およびおそらくその他)もあります。これらのいくつかは、インストールごとにデフォルトが異なる可能性があるため、マシンの最悪のケースを明示的に強制する必要があります。これを行うと、特にsseなしでi386に戻る場合、パフォーマンスが大幅に低下する可能性があります。そして、もちろん、CPUの1つがARMで、もう1つのCPUがi686である場合
...-rici

1
また、GCCはバイナリにタイムスタンプを追加する問題のコンパイラの1つですか?
デビッド

@david:afaik、いいえ。
リチ

8

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


2
とても良い点です。この記事には非常に興味深い所見があります。特に、GCCを使用したコンパイルは、特定の場合、たとえば内部で乱数ジェネレーターを使用する匿名名前空間の関数をマングルする方法など、入力に関して確定的ではない場合があります。この特定のケースで決定論を取得するには、オプションを指定して初期ランダムシードを指定します-frandom-seed=string
ack 14

7

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

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

Eric Lippertは、コンパイラの出力が決定論的ではない理由について非常に徹底的な内訳を持ってます。

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

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

C#コンパイラーのバージョンに固有ですが、記事の多くのポイントはどのコンパイラーにも適用できます。

最初に、毎回同じ順序で常に同じファイルのリストを取得することを前提としています。しかし、場合によってはオペレーティングシステム次第です。「csc * .cs」と言うとき、オペレーティングシステムが一致するファイルのリストを提供する順序は、オペレーティングシステムの実装の詳細です。コンパイラはそのリストを標準的な順序に並べ替えません。


ビルドを再現可能にするのは難しくありません(コンパイル時間やアセンブリGUIDなどの簡単に破棄されるフィールドを除きます)。たとえば、入力ファイルを標準的な順序に並べ替えるのは1行です。そのGUIDでさえ、新しく生成されるのではなく、アセンブリの残りのハッシュである可能性があります。
CodesInChaos

Microsoft C#コンパイラを意味すると思いますか、それとも仕様の要件ですか?
デビッド

@David CLI仕様では必須です。MonoのC#コンパイラは同じことをしなければなりません。VB .NETコンパイラの場合も同様です。
ta.speot.is

4
ECMA標準には、タイムスタンプまたはMVIDの違いは必要ありません。それらがなければ、少なくともC#の同一のバイナリで可能です。したがって、主な理由は疑わしい設計決定であり、実際の技術的な制約ではありません。
シブ

7
  • -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 Reproducible buildsプロジェクトは、Debianパッケージをバイト単位で標準化しようと試みており、最近、Linux Foundationの助成金を得ました。これには単なるコンパイル以上のものが含まれますが、興味深いものです。

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

関連するスレッド:


3

プロジェクトhttps://reproducible-builds.org/はこれに関するすべてであり、可能な限り多くの場所で「いいえ、違いはありません」というあなたの質問に対する答えを作るために懸命に努力しています。NixOSとDebianは現在、パッケージの再現性が90%を超えています。

バイナリをコンパイルし、バイナリをコンパイルし、それらがビットごとに同一である場合、ソースコードとツールが出力を決定するものであり、あなたがいくつかにこっそり入っていないことを確信できます途中でトロイの木馬コード。

http://bootstrappable.org/が取り組んでいるように、再現性と人間が読み取れるソースからのブートストラップ可能性を組み合わせると、人間が読み取れるソースによってシステムが一から決定され、それが初めてです。システムが何をしているかを知っていると信頼できます。


1
クールなリンク。私はBuildrootのファンですが、誰かがQEMUで起動するNix ARMクロスアーチセットアップをくれた場合、私は満足します:-)
Ciro Santilli新疆改造中心法轮功

数字の場所がわからないためGuixについては言及しませんでしたが、検証ツールなどを備えた再現性トレインでNixOSの前にいたので、彼らは同等以上の立場にあると確信しています。
8:46の

2

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

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


そして、それは異なる行動や「深刻な問題」につながりましたか?
フローリアンストラウブ

1

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.