การคอมไพล์โปรแกรมใหม่สร้างไบนารีเหมือนกันแบบบิตต่อบิตหรือไม่?


25

ถ้าฉันจะคอมไพล์โปรแกรมเป็นไบนารีเดียวให้ทำการตรวจสอบจากนั้นทำการคอมไพล์ใหม่บนเครื่องเดียวกันด้วยการตั้งค่าคอมไพเลอร์และคอมไพเลอร์ที่เหมือนกันและการตรวจสอบโปรแกรมที่คอมไพล์แล้ว

ถ้าเป็นเช่นนั้นทำไมเป็นเช่นนี้? หากไม่มีการมี CPU ที่แตกต่างกันจะส่งผลให้ไบนารีที่ไม่เหมือนกันหรือไม่


8
มันขึ้นอยู่กับคอมไพเลอร์ บางคนฝังการประทับเวลาดังนั้นคำตอบคือ "ไม่" สำหรับสิ่งเหล่านั้น
ta.speot.is

จริง ๆ แล้วมันขึ้นอยู่กับรูปแบบที่ปฏิบัติการไม่ใช่คอมไพเลอร์ รูปแบบที่สามารถใช้งานได้บางรูปแบบเช่นรูปแบบ PE ของ Windows รวมถึงการประทับเวลาซึ่งสัมผัสกับเวลาและวันที่รวบรวมในขณะที่รูปแบบอื่น ๆ เช่นรูปแบบ ELF ของ Linux ไม่ได้ทำ ทั้งสองวิธีคำถามนี้ขึ้นอยู่กับคำจำกัดความของ "เลขฐานสองที่เหมือนกัน" ภาพตัวเองจะ / ควรเป็นบิตเดียวกันหากไฟล์ต้นฉบับเดียวกันถูกคอมไพล์ด้วยคอมไพเลอร์และไลบรารีเดียวกันและสวิตช์และทุกอย่าง แต่ส่วนหัวและข้อมูลเมตาอื่น ๆ อาจแตกต่างกันไป
Synetech

คำตอบ:


19
  1. คอมไพล์โปรแกรมเดียวกันด้วยการตั้งค่าเดียวกันในเครื่องเดียวกัน:

    แม้ว่าคำตอบที่ชัดเจนคือ "มันขึ้นอยู่กับ" แต่ก็มีเหตุผลที่คาดหวังว่าคอมไพเลอร์ส่วนใหญ่จะกำหนดเวลาส่วนใหญ่และไบนารีที่ผลิตควรจะเหมือนกัน แท้จริงแล้วระบบควบคุมเวอร์ชันบางรุ่นขึ้นอยู่กับสิ่งนี้ ยังมีข้อยกเว้นอยู่เสมอ มันค่อนข้างเป็นไปได้ว่าบางที่ไหนสักแห่งคอมไพเลอร์จะตัดสินใจที่จะแทรกการประทับเวลาหรือบางอย่างเช่น (iirc, Delphi ไม่ตัวอย่างเช่น) หรือกระบวนการสร้างเองอาจทำเช่นนั้น ฉันเห็น makefiles สำหรับโปรแกรม C ซึ่งตั้งค่าแมโครตัวประมวลผลล่วงหน้าเป็นเวลาประทับปัจจุบัน (ฉันเดาว่าจะนับว่าเป็นการตั้งค่าคอมไพเลอร์ที่แตกต่างกัน)

    นอกจากนี้โปรดทราบว่าหากคุณเชื่อมโยงไบนารีแบบคงที่คุณจะสามารถรวมสถานะของไลบรารีที่เกี่ยวข้องทั้งหมดในเครื่องของคุณได้อย่างมีประสิทธิภาพและการเปลี่ยนแปลงใด ๆ ในหนึ่งในนั้นจะส่งผลต่อไบนารีของคุณ ดังนั้นจึงไม่ใช่แค่การตั้งค่าคอมไพเลอร์ที่เกี่ยวข้อง

  2. คอมไพล์โปรแกรมเดียวกันบนเครื่องที่แตกต่างกับ CPU ที่แตกต่างกัน

    ที่นี่การเดิมพันทั้งหมดจะปิด คอมไพเลอร์สมัยใหม่ส่วนใหญ่มีความสามารถในการเพิ่มประสิทธิภาพเฉพาะเป้าหมาย หากเปิดใช้งานตัวเลือกนี้ไบนารีก็มีแนวโน้มที่จะแตกต่างกันเว้นแต่ว่าซีพียูจะคล้ายกัน นอกจากนี้โปรดดูหมายเหตุด้านบนเกี่ยวกับการลิงก์แบบสแตติก: สภาพแวดล้อมการกำหนดค่ามีค่าเกินกว่าการตั้งค่าคอมไพเลอร์ เว้นแต่คุณจะมีการควบคุมการกำหนดค่าที่เข้มงวดมากเป็นไปได้มากว่ามีบางอย่างแตกต่างกันระหว่างสองเครื่อง


1
สมมติว่าฉันใช้ GCC และฉันไม่ได้ใช้ตัวเลือก march (ตัวเลือกที่ปรับไบนารีให้เหมาะสมสำหรับตระกูลเฉพาะของ CPU) และฉันต้องคอมไพล์ไบนารีด้วย CPU หนึ่งตัวและจากนั้น CPU อื่นจะมี ความแตกต่าง?
David

1
@ David: มันยังคงขึ้นอยู่ ก่อนอื่นไลบรารี่ที่คุณเชื่อมโยงอาจมีบิลด์เฉพาะสถาปัตยกรรม ดังนั้นผลลัพธ์ของgcc -cอาจจะเหมือนกัน แต่รุ่นที่เชื่อมโยงแตกต่างกัน นอกจากนี้มันไม่ใช่แค่-march; ยังมี-mtune/-mcpu และ-mfpmatch(และอาจเป็นไปได้อื่น ๆ ) สิ่งเหล่านี้บางอย่างอาจมีค่าเริ่มต้นที่แตกต่างกันในการติดตั้งที่แตกต่างกันดังนั้นคุณอาจต้องบังคับกรณีที่เลวร้ายที่สุดที่เป็นไปได้สำหรับเครื่องของคุณอย่างชัดเจน การทำเช่นนั้นอาจลดประสิทธิภาพลงอย่างมากโดยเฉพาะถ้าคุณเปลี่ยนกลับเป็น i386 โดยไม่ใช้ sse และแน่นอนถ้าซีพียูตัวใดตัวหนึ่งของคุณเป็น ARM และอีกอันคือ i686 ...
rici

1
นอกจากนี้ GCC เป็นหนึ่งในคอมไพเลอร์ที่มีปัญหาซึ่งเพิ่มการประทับเวลาลงในไบนารีหรือไม่?
David

@david: afaik ไม่
rici

8

สิ่งที่คุณถามคือ "เป็นตัวกำหนดผลลัพธ์" หากคุณคอมไพล์โปรแกรมหนึ่งครั้งให้ทำการคอมไพล์อีกครั้งโดยทันทีคุณอาจท้ายด้วยไฟล์เอาต์พุตเดียวกัน อย่างไรก็ตามหากมีการเปลี่ยนแปลงอะไร - แม้แต่การเปลี่ยนแปลงเล็กน้อย - โดยเฉพาะอย่างยิ่งในองค์ประกอบที่โปรแกรมคอมไพล์ใช้แล้วผลลัพธ์ของคอมไพเลอร์อาจเปลี่ยนแปลงได้


2
จุดที่ดีมากอย่างแน่นอน บทความนี้มีข้อสังเกตที่น่าสนใจมาก โดยเฉพาะอย่างยิ่งการคอมไพล์ด้วย GCC อาจไม่สามารถกำหนดค่าได้เกี่ยวกับอินพุตในบางกรณีตัวอย่างเช่นวิธีที่ mangles ทำงานในเนมสเปซนิรนามซึ่งใช้ตัวสร้างตัวเลขสุ่มภายใน -frandom-seed=stringที่จะได้รับชะตาในกรณีนี้โดยเฉพาะอย่างยิ่งการจัดหาเมล็ดสุ่มเริ่มต้นโดยการระบุตัวเลือก
แอ๊

7

การคอมไพล์โปรแกรมใหม่สร้างไบนารีเหมือนกันแบบบิตต่อบิตหรือไม่?

สำหรับคอมไพเลอร์ทั้งหมด? ไม่คอมไพเลอร์ C # อย่างน้อยไม่ได้รับอนุญาต

Eric Lippert มีการวิเคราะห์อย่างละเอียดมากว่าทำไมผลลัพธ์ของคอมไพเลอร์ไม่ได้กำหนดขึ้น

[T] เขาคอมไพเลอร์ C # โดยการออกแบบไม่เคยสร้างไบนารีเดียวกันสองครั้ง คอมไพเลอร์ C # ฝัง GUID ที่สร้างขึ้นใหม่ในทุก ๆ แอสเซมบลีทุกครั้งที่คุณเรียกใช้ดังนั้นจึงมั่นใจได้ว่าไม่มีแอสเซมบลีสองตัวที่เหมือนกันแบบบิตต่อบิต เพื่ออ้างจากข้อมูลจำเพาะของ CLI:

คอลัมน์ Mvid จะจัดทำดัชนี GUID ที่ไม่ซ้ำกัน [... ] ที่ระบุอินสแตนซ์ของโมดูลนี้ [... ] Mvid ควรถูกสร้างขึ้นใหม่สำหรับทุกโมดูล [... ] ในขณะที่ [runtime] นั้นไม่ได้ใช้ Mvid เครื่องมืออื่น ๆ (เช่น debuggers [... ]) พึ่งพาความจริงที่ว่า Mvid มักจะแตกต่างจากโมดูลหนึ่งไปยังอีก

ถึงแม้ว่ามันจะเฉพาะเจาะจงกับคอมไพเลอร์รุ่น C # แต่จุดต่าง ๆ ในบทความสามารถนำไปใช้กับคอมไพเลอร์ใด ๆได้

ก่อนอื่นเราคาดว่าเราจะได้รับรายชื่อไฟล์ที่เหมือนกันทุกครั้งในลำดับเดียวกัน แต่ในบางกรณีถึงระบบปฏิบัติการ เมื่อคุณพูดว่า "csc * .cs" ลำดับที่ระบบปฏิบัติการนำเสนอรายการของไฟล์ที่ตรงกันเป็นรายละเอียดการนำไปปฏิบัติของระบบปฏิบัติการ คอมไพเลอร์ไม่ได้เรียงลำดับรายการนั้นลงในคำสั่งมาตรฐาน


ไม่ควรยากที่จะสร้างแบบจำลองที่สร้างขึ้นใหม่ (นอกเหนือจากเขตข้อมูลที่ถูกทิ้งอย่างง่ายดายเช่นเวลารวบรวมและ GUID แอสเซมบลี) ตัวอย่างเช่นการเรียงลำดับไฟล์อินพุตในคำสั่งแบบบัญญัติเป็นแบบหนึ่งซับ แม้ GUID นั้นอาจเป็นแฮชของส่วนที่เหลือของชุดประกอบแทนที่จะสร้างขึ้นใหม่
CodesInChaos

ฉันถือว่าคุณหมายถึงคอมไพเลอร์ Microsoft C # หรือว่าเป็นข้อกำหนดของข้อกำหนดหรือไม่
David

@David ข้อมูลจำเพาะของ CLI ต้องการมัน คอมไพเลอร์ C # ของ Mono จะต้องทำเช่นเดียวกัน เหมือนกันสำหรับคอมไพเลอร์ 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: รุ่นเก่าคู่มือบอกว่ามันเป็นแหล่งที่มาของการไม่ determinism แต่ไม่ได้อีกต่อไป ไม่แน่ใจว่าสิ่งนี้ได้รับความคุ้มครอง-frandom-seedหรือไม่

Debian ซ้ำสร้างโครงการความพยายามที่จะสร้างมาตรฐาน Debian แพคเกจไบต์โดยไบต์และเมื่อเร็ว ๆ นี้ได้รับทุนมูลนิธิลินุกซ์ ซึ่งรวมถึงมากกว่าการรวบรวม แต่ควรเป็นที่สนใจ

BuildrootมีBR2_REPRODUCIBLEตัวเลือกที่อาจให้แนวคิดบางอย่างเกี่ยวกับระดับแพ็คเกจ แต่มันยังไม่เสร็จสมบูรณ์ ณ จุดนี้

หัวข้อที่เกี่ยวข้อง:


3

โครงการhttps://reproducible-builds.org/นั้นเกี่ยวกับเรื่องนี้และพยายามอย่างหนักที่จะตอบคำถามของคุณว่า "ไม่พวกเขาจะไม่แตกต่าง" ในสถานที่ต่างๆ NixOS และ Debian นั้นสามารถทำซ้ำได้มากกว่า 90% สำหรับแพ็คเกจของพวกเขา

หากคุณรวบรวมไบนารีและฉันรวบรวมไบนารีและพวกเขาเป็นบิตสำหรับบิตเหมือนกันจากนั้นฉันสามารถมั่นใจได้ว่าซอร์สโค้ดและเครื่องมือเป็นสิ่งที่กำหนดเอาท์พุทและคุณไม่ได้แอบในบาง รหัสโทรจันระหว่างทาง

หากเรารวมความสามารถในการทำซ้ำกับความสามารถในการบูตจากแหล่งที่มนุษย์สามารถอ่านได้เนื่องจากhttp://bootstrappable.org/กำลังทำงานอยู่เราจะได้รับระบบที่ถูกกำหนดโดยแหล่งที่มาที่มนุษย์อ่านได้และจากนั้นเราก็มาถึงจุดที่ เราสามารถวางใจได้ว่าเรารู้ว่าระบบกำลังทำอะไร


1
ลิงก์ที่น่าสนใจ ฉันเป็นแฟนบอย Buildroot แต่ถ้ามีคนให้ Nix ARM cross arch เซ็ตอัพที่บู๊ทกับ QEMU ฉันจะมีความสุข :-)
Ciro Santilli 新疆改造中心中心法轮功事件

ฉันไม่ได้พูดถึง Guix เพราะฉันไม่รู้ว่าจะหาเบอร์ของพวกเขาได้ที่ไหน แต่พวกเขาอยู่ก่อนหน้า NixOS บนรถไฟจำลองการทำซ้ำด้วยเครื่องมือตรวจสอบและเช่นนั้นฉันมั่นใจว่าพวกเขาอยู่บนพื้นฐานที่เท่าเทียมกันหรือดีกว่า
clacke

2

ฉันจะบอกว่าไม่มันไม่ได้กำหนด 100% ก่อนหน้านี้ฉันเคยทำงานกับ GCC รุ่นหนึ่งซึ่งสร้างไบนารีเป้าหมายสำหรับโปรเซสเซอร์ Hitachi H8

ไม่มีปัญหากับการประทับเวลา แม้ว่าปัญหาการประทับเวลาจะถูกเพิกเฉยสถาปัตยกรรมโปรเซสเซอร์เฉพาะอาจอนุญาตให้มีการเข้ารหัสคำสั่งเดียวกันใน 2 วิธีที่แตกต่างกันเล็กน้อยซึ่งบางบิตสามารถเป็น 1 หรือ 0 ประสบการณ์ก่อนหน้านี้ของฉันแสดงให้เห็นว่าไบนารีที่สร้างขึ้นนั้นเป็นเวลาเดียวกันที่สุด แต่บางครั้ง gcc จะสร้างไบนารีที่มีขนาดเท่ากัน แต่บางไบต์แตกต่างกันเพียง 1 บิตเช่น 0XE0 กลายเป็น 0XE1


และนั่นนำไปสู่พฤติกรรมที่แตกต่างหรือ "ปัญหาร้ายแรง" หรือไม่?
Florian Straub

1

โดยทั่วไปไม่มี คอมไพเลอร์ที่มีความซับซ้อนพอสมควรส่วนใหญ่จะรวมเวลาในการรวบรวมในโมดูลวัตถุ แม้ว่าคุณจะต้องรีเซ็ตนาฬิกาคุณจะต้องแม่นยำมากเมื่อคุณเริ่มต้นการคอมไพล์ (และจากนั้นหวังว่าการเข้าถึงดิสก์ ฯลฯ จะมีความเร็วเท่าเดิม)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.