Apakah kompilasi ulang program menghasilkan biner identik bit-for-bit?

25

Jika saya mengkompilasi suatu program menjadi satu biner, membuat sebuah checksum, dan kemudian mengkompilasinya pada mesin yang sama dengan pengaturan kompiler dan kompiler yang sama dan checksum pada program yang dikompilasi ulang, akankah checksum gagal?

Jika demikian, mengapa ini? Jika tidak, akankah CPU yang berbeda menghasilkan biner yang tidak identik?

David
sumber
8
Itu tergantung pada kompiler. Beberapa dari mereka menyematkan perangko waktu, jadi jawabannya adalah "tidak" bagi mereka.
ta.speot.is
Sebenarnya itu tergantung pada format yang dapat dieksekusi , bukan kompiler. Beberapa format yang dapat dieksekusi seperti format PE Windows termasuk cap waktu yang disentuh pada waktu dan tanggal kompilasi, sedangkan format lain seperti format ELF Linux tidak. Either way, pertanyaan ini bergantung pada definisi "biner identik". Gambar itu sendiri akan / harus bitwise identik jika file sumber yang sama dikompilasi dengan kompiler yang sama dan pustaka dan switch dan semuanya, tetapi header dan metadata lainnya dapat bervariasi.
Synetech

Jawaban:

19
  1. Kompilasi program yang sama dengan pengaturan yang sama pada mesin yang sama:

    Meskipun jawaban pasti adalah "itu tergantung", masuk akal untuk berharap bahwa sebagian besar kompiler akan menjadi deterministik sebagian besar waktu, dan bahwa biner yang dihasilkan harus identik. Memang, beberapa sistem kontrol versi tergantung pada ini. Namun, selalu ada pengecualian; sangat mungkin bahwa beberapa kompiler di suatu tempat akan memutuskan untuk memasukkan timestamp atau semacamnya (iirc, Delphi, misalnya). Atau proses build itu sendiri mungkin melakukan itu; Saya telah melihat makefiles untuk program C yang mengatur makro preprocessor ke timestamp saat ini. (Saya kira itu akan dihitung sebagai pengaturan kompiler yang berbeda.)

    Perlu diketahui juga bahwa jika Anda menautkan biner secara statis, maka Anda secara efektif memasukkan keadaan semua pustaka yang relevan pada mesin Anda, dan setiap perubahan pada salah satu dari itu juga akan memengaruhi biner Anda. Jadi bukan hanya pengaturan kompiler yang relevan.

  2. Kompilasi program yang sama pada mesin yang berbeda dengan CPU yang berbeda.

    Di sini, semua taruhan dibatalkan. Sebagian besar kompiler modern mampu melakukan optimasi target-spesifik; jika opsi ini diaktifkan, maka binari cenderung berbeda kecuali CPU serupa (dan bahkan kemudian, itu mungkin). Juga, lihat catatan di atas tentang penautan statis: lingkungan konfigurasi jauh melampaui pengaturan kompiler. Kecuali Anda memiliki kontrol konfigurasi yang sangat ketat, sangat mungkin ada sesuatu yang berbeda antara kedua mesin.

rici
sumber
1
Katakanlah saya menggunakan GCC, dan saya tidak menggunakan opsi pawai (opsi yang mengoptimalkan biner untuk keluarga CPU tertentu), dan saya akan mengkompilasi biner dengan satu CPU, dan kemudian dengan CPU lain akan ada perbedaan?
David
1
@ David: Masih tergantung. Pertama, perpustakaan yang Anda tautkan mungkin memiliki bangunan khusus arsitektur. Jadi output dari gcc -cmungkin identik, tetapi versi tertaut berbeda. Juga, bukan hanya -march; ada juga -mtune/-mcpu dan -mfpmatch(dan mungkin yang lain). Beberapa di antaranya mungkin memiliki standar yang berbeda pada instalasi yang berbeda, jadi Anda mungkin perlu memaksakan kasus terburuk yang mungkin untuk mesin Anda secara eksplisit; melakukan hal itu dapat secara signifikan mengurangi kinerja, terutama jika Anda kembali ke i386 tanpa sse. Dan, tentu saja, jika salah satu CPU Anda adalah ARM dan yang lainnya i686 ...
rici
1
Juga, apakah GCC salah satu dari kompiler yang dimaksud yang menambahkan stempel waktu ke binari?
David
@david: afaik, tidak.
rici
8

Yang Anda tanyakan adalah "adalah output deterministik ." Jika Anda mengkompilasi program sekali, segera mengkompilasinya lagi Anda mungkin akan berakhir dengan file output yang sama. Namun, jika ada yang berubah - bahkan perubahan kecil - terutama dalam komponen yang digunakan oleh program yang dikompilasi, maka output dari kompiler juga dapat berubah.

headkase
sumber
2
Poin yang sangat bagus. Artikel ini memiliki beberapa pengamatan yang sangat menarik. Khususnya, kompilasi dengan GCC mungkin tidak bersifat deterministik berkenaan dengan input dalam kasus-kasus tertentu, misalnya dalam hal bagaimana fungsi-fungsi mangles dalam ruang nama anonim, yang mana ia menggunakan generator nomor acak secara internal. Untuk mendapatkan determinisme dalam kasus khusus ini, berikan benih acak awal dengan menentukan opsi -frandom-seed=string.
ack
7

Apakah kompilasi ulang program menghasilkan biner identik bit-for-bit?

Untuk semua kompiler? Tidak. Kompilator C #, setidaknya, tidak diizinkan.

Eric Lippert memiliki rincian yang sangat menyeluruh tentang mengapa output dari kompiler tidak deterministik .

[T] he C # compiler dengan desain tidak pernah menghasilkan biner yang sama dua kali. Kompiler C # menanamkan GUID yang baru dibuat di setiap rakitan, setiap kali Anda menjalankannya, dengan demikian memastikan bahwa tidak ada dua rakitan yang identik bit-untuk-bit. Mengutip dari spesifikasi CLI:

Kolom Mvid akan mengindeks GUID unik [...] yang mengidentifikasi instance modul ini. [...] Mvid harus dibuat baru untuk setiap modul [...] Sementara [runtime] itu sendiri tidak menggunakan Mvid, alat lain (seperti debugger [...]) bergantung pada fakta bahwa Mvid hampir selalu berbeda dari satu modul ke modul lainnya.

Meskipun spesifik untuk versi kompiler C #, banyak poin dalam artikel dapat diterapkan ke kompiler mana pun .

Pertama, kami mengasumsikan bahwa kami selalu mendapatkan daftar file yang sama setiap kali, dalam urutan yang sama. Tapi itu dalam beberapa kasus hingga sistem operasi. Ketika Anda mengatakan "csc * .cs", urutan di mana sistem operasi menawarkan daftar file yang cocok adalah detail implementasi dari sistem operasi; kompiler tidak mengurutkan daftar itu menjadi urutan kanonik.

ta.speot.is
sumber
Seharusnya tidak sulit untuk membuat reproducible yang dibangun (terlepas dari beberapa bidang yang mudah dibuang seperti waktu kompilasi dan GUID perakitan). Misalnya, menyortir file input ke dalam urutan kanonik adalah one-liner. Bahkan itu GUID bisa menjadi hash dari sisa majelis bukannya yang baru dihasilkan.
CodesInChaos
Saya berasumsi Anda maksud kompiler Microsoft C #, atau itu persyaratan spesifikasi?
David
@ David Spek CLI membutuhkannya. Compiler C # Mono harus melakukan hal yang sama. Ditto untuk setiap VB .NET compiler.
ta.speot.is
4
Standar ECMA tidak harus memiliki cap waktu atau perbedaan MVID. Tanpa itu, setidaknya mungkin untuk binari identik di C #. Dengan demikian alasan utama adalah keputusan desain yang dipertanyakan dan bukan kendala teknis yang nyata.
Shiv
7
  • -frandom-seed=123mengontrol beberapa keacakan internal GCC. man gccmengatakan:

    Opsi ini menyediakan seed yang digunakan GCC sebagai pengganti angka acak dalam menghasilkan nama simbol tertentu yang harus berbeda di setiap file yang dikompilasi. Ini juga digunakan untuk menempatkan perangko unik dalam file data cakupan dan file objek yang menghasilkannya. Anda dapat menggunakan opsi -frandom-seed untuk menghasilkan file objek identik yang dapat direproduksi.

  • __FILE__: letakkan sumber dalam folder tetap (mis. /tmp/build)

  • untuk __DATE__, __TIME__, __TIMESTAMP__:
    • libfaketime: https://github.com/wolfcw/libfaketime
    • menimpa makro itu dengan -D
    • -Wdate-timeatau -Werror=date-time: memperingatkan atau gagal jika salah satu __TIME__, __DATE__atau __TIMESTAMP__digunakan. Kernel Linux 4.4 menggunakannya secara default.
  • gunakan Dbendera dengan ar, atau gunakan https://github.com/nh2/ar-timestamp-wiper/tree/master untuk menghapus prangko
  • -fno-guess-branch-probability: versi manual yang lebih lama mengatakan itu adalah sumber non-determinisme, tetapi sekarang tidak lagi . Tidak yakin apakah ini dilindungi -frandom-seedatau tidak.

Debian Reproducible membangun upaya proyek untuk membakukan paket Debian byte-by-byte, dan baru-baru ini mendapatkan hibah Linux Foundation . Itu termasuk lebih dari sekedar kompilasi, tetapi harus menarik.

Buildroot memiliki BR2_REPRODUCIBLEopsi yang dapat memberikan beberapa gagasan pada tingkat paket, tetapi masih jauh dari selesai pada saat ini.

Utas terkait:

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
sumber
3

Proyek https://reproducible-builds.org/ adalah semua tentang ini, dan berusaha keras untuk membuat jawaban atas pertanyaan Anda "tidak, mereka tidak akan berbeda" di banyak tempat sebanyak mungkin. NixOS dan Debian kini memiliki reproduksibilitas lebih dari 90% untuk paket mereka.

Jika Anda mengkompilasi biner, dan saya mengkompilasi biner, dan mereka sedikit-untuk-bit identik, maka saya dapat diyakinkan bahwa kode sumber dan alat-alat adalah apa yang menentukan output, dan bahwa Anda tidak menyelinap di beberapa kode trojan di sepanjang jalan.

Jika kita menggabungkan reproduktifitas dengan bootstrappability dari sumber yang dapat dibaca manusia, seperti yang dilakukan oleh http://bootstrappable.org/ , kita mendapatkan sistem yang ditentukan dari bawah ke atas oleh sumber yang dapat dibaca oleh manusia, dan hanya pada saat itulah kita berada pada titik di mana kita bisa percaya bahwa kita tahu apa yang dilakukan sistem.

klak
sumber
1
Tautan keren. Saya seorang fanroot Buildroot, tetapi jika seseorang memberi saya pengaturan lintas lengkung Nix ARM yang menjalankan QEMU, saya akan senang :-)
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Saya tidak menyebutkan Guix karena saya tidak tahu di mana menemukan nomor mereka, tetapi mereka sebelum NixOS di kereta reproduktifitas dengan alat verifikasi dan semacamnya, jadi saya yakin mereka memiliki kedudukan yang sama atau lebih baik.
clacke
2

Saya akan mengatakan TIDAK, itu tidak 100% deterministik. Saya sebelumnya bekerja dengan versi GCC yang menghasilkan binari target untuk prosesor Hitachi H8.

Itu tidak masalah dengan cap waktu. Bahkan jika masalah cap waktu diabaikan, arsitektur prosesor tertentu dapat memungkinkan instruksi yang sama untuk dikodekan dalam 2 cara yang sedikit berbeda di mana beberapa bit bisa 1 atau 0. Pengalaman saya sebelumnya menunjukkan bahwa biner yang dihasilkan adalah PALING sama dengan waktu. tetapi kadang-kadang gcc akan menghasilkan binari dengan ukuran yang identik tetapi beberapa byte berbeda hanya dengan 1 bit misalnya 0XE0 menjadi 0XE1.

JavaMan
sumber
Dan apakah itu mengarah pada perilaku yang berbeda atau "masalah serius"?
Florian Straub
1

Secara umum, tidak. Kebanyakan kompiler yang cukup canggih akan menyertakan waktu kompilasi dalam modul objek. Bahkan jika Anda mengatur ulang jam Anda harus sangat akurat sehubungan dengan ketika Anda memulai kompilasi (dan kemudian berharap bahwa akses disk, dll, adalah kecepatan yang sama seperti sebelumnya).

Daniel R Hicks
sumber