프로그램을 다시 컴파일하면 비트 단위의 동일한 바이너리가 생성됩니까?


25

프로그램을 단일 바이너리로 컴파일하려면 체크섬을 만든 다음 동일한 컴파일러 및 컴파일러 설정으로 동일한 컴퓨터에서 다시 컴파일하고 다시 컴파일 된 프로그램을 체크섬하면 체크섬이 실패합니까?

그렇다면 왜 그렇습니까? 그렇지 않은 경우 다른 CPU를 사용하면 동일하지 않은 이진이 생성됩니까?


8
컴파일러에 따라 다릅니다. 그들 중 일부는 타임 스탬프를 포함하므로 대답은 "아니오"입니다.
ta.speot.is는

실제로 컴파일러가 아닌 실행 파일 형식 에 따라 다릅니다 . Windows PE 형식과 같은 일부 실행 가능 형식에는 컴파일 시간과 날짜에 영향을주는 타임 스탬프가 포함되지만 Linux의 ELF 형식과 같은 다른 형식은 포함되지 않습니다. 어느 쪽이든,이 질문은“동일 바이너리”의 정의에 달려 있습니다. 동일한 소스 파일이 동일한 컴파일러 및 라이브러리 및 스위치 및 모든 것으로 컴파일되면 이미지 자체는 비트 단위로 동일해야하지만 헤더 및 기타 메타 데이터는 다를 수 있습니다.
Synetech

답변:


19
  1. 동일한 머신에서 동일한 설정으로 동일한 프로그램을 컴파일하십시오.

    결정적인 대답은 "의존적"이지만, 대부분의 컴파일러는 대부분 결정 론적이며 생성 된 바이너리는 동일해야한다고 기대하는 것이 합리적입니다. 실제로 일부 버전 제어 시스템은 이에 의존합니다. 여전히 예외는 있습니다. 어딘가에 어떤 컴파일러가 타임 스탬프를 삽입하기로 결정할 가능성이 있습니다 (iirc, 델파이는 예를 들어 델파이). 또는 빌드 프로세스 자체가 그렇게 할 수도 있습니다. 전 처리기 매크로를 현재 타임 스탬프로 설정하는 C 프로그램의 makefile을 보았습니다. (하지만 다른 컴파일러 설정으로 간주됩니다.)

    또한 바이너리를 정적으로 연결하면 시스템에있는 모든 관련 라이브러리의 상태를 효과적으로 통합 할 수 있으며이 중 하나의 변경 사항도 바이너리에 영향을 미칩니다. 따라서 관련된 컴파일러 설정 만이 아닙니다.

  2. CPU가 다른 다른 머신에서 동일한 프로그램을 컴파일하십시오.

    여기, 모든 베팅이 종료되었습니다. 대부분의 최신 컴파일러는 대상별 최적화를 수행 할 수 있습니다. 이 옵션을 사용하면 CPU가 유사하지 않으면 바이너리가 다를 수 있습니다 (그렇더라도 가능할 수도 있음). 또한 정적 링크에 대한 위의 참고 사항을 참조하십시오. 구성 환경은 컴파일러 설정을 훨씬 뛰어 넘습니다. 매우 엄격한 구성 제어가 없으면 두 시스템간에 차이가있을 가능성이 큽니다.


1
GCC를 사용 중이고 행진 옵션 (특정 CPU 제품군에 대해 이진을 최적화하는 옵션)을 사용하지 않고 하나의 CPU로 이진을 컴파일하고 다른 CPU를 사용하면 차?
David

1
@David : 그것은 여전히 ​​다릅니다. 먼저, 연결하려는 라이브러리에 아키텍처 별 빌드가있을 수 있습니다. 따라서 출력은 gcc -c동일 할 수 있지만 링크 된 버전은 다릅니다. 또한, 그것은 단지 아닙니다 -march; 이 또한 -mtune/-mcpu-mfpmatch(및 기타). 이들 중 일부는 설치마다 기본값이 다를 수 있으므로 컴퓨터에 최악의 경우를 명시 적으로 지정해야합니다. 이렇게하면 특히 sse없이 i386으로 되돌릴 경우 성능이 크게 저하 될 수 있습니다. 그리고 물론, 만약 당신의 CPU 중 하나가 ARM이고 다른 하나는 i686
이라면

1
또한 GCC는 바이너리에 타임 스탬프를 추가하는 문제의 컴파일러 중 하나입니까?
David

@ 데이비드 : afaik, no.
rici

8

당신이 요구하는 것은 "출력 결정 론적 "입니다. 프로그램을 한 번 컴파일 한 경우 즉시 다시 컴파일하면 아마도 동일한 출력 파일로 끝날 것입니다. 그러나 컴파일 된 프로그램이 사용하는 구성 요소에서 변경 사항이 있더라도 (작은 변경이라도) 컴파일러의 출력도 변경 될 수 있습니다.


2
정말 좋은 지적입니다. 이 기사 에는 매우 흥미로운 관찰 결과가 있습니다. 특히 GCC를 사용한 컴파일 은 특정 경우, 예를 들어 내부에서 난수 생성기를 사용하는 익명 네임 스페이스의 함수를 조작하는 방식과 관련하여 입력과 관련하여 결정적 이지 않을 수 있습니다. 이 특정한 경우 결정 성을 얻으려면 옵션을 지정하여 초기 임의 시드를 제공하십시오 -frandom-seed=string.
ack

7

프로그램을 다시 컴파일하면 비트 단위의 동일한 바이너리가 생성됩니까?

모든 컴파일러에 대해? 아니요. 적어도 C # 컴파일러는 허용되지 않습니다.

Eric Lippert는 왜 컴파일러의 출력이 결정적이지 않은지에 대해 매우 철저한 분석을했습니다 .

C # 컴파일러는 의도적으로 같은 바이너리를 두 번 생성하지 않습니다. C # 컴파일러는 실행할 때마다 모든 어셈블리에 새로 생성 된 GUID를 포함하므로 두 어셈블리가 비트 단위로 동일하지 않습니다. CLI 사양에서 인용하려면 :

Mvid 열은 모듈의이 인스턴스를 식별하는 고유 한 GUID [...]를 색인화해야합니다. [...] 모든 모듈에 대해 Mvid를 새로 생성해야합니다. [...] [런타임] 자체는 Mvid를 사용하지 않지만 다른 도구 (예 : 디버거 [...])는 Mvid는 거의 항상 모듈마다 다릅니다.

C # 컴파일러 버전에 따라 다르지만 기사의 많은 부분을 모든 컴파일러에 .

우선, 매번 같은 순서로 항상 같은 파일 목록을 얻는다고 가정합니다. 그러나 일부 경우 운영 체제에 따라 다릅니다. "csc * .cs"라고 말하면 운영 체제가 일치하는 파일 목록을 생성하는 순서는 운영 체제의 구현 세부 사항입니다. 컴파일러는 해당 목록을 정식 순서로 정렬하지 않습니다.


컴파일 타임이나 어셈블리 GUID와 같이 쉽게 버려지는 필드를 제외하고 빌드 된 재현성을 만드는 것이 어렵지 않아야합니다. 예를 들어 입력 파일을 정식 순서로 정렬하는 것은 하나의 라이너입니다. 그 GUID조차도 새로 생성되는 대신 나머지 어셈블리의 해시 일 수 있습니다.
코드 InChaos

나는 당신이 Microsoft C # 컴파일러를 의미한다고 생각합니까, 아니면 사양의 요구 사항입니까?
David

@David CLI 사양에 필요합니다. Mono의 C # 컴파일러도 같은 작업을 수행해야합니다. 모든 VB .NET 컴파일러에 적합합니다.
ta.speot.is는

4
ECMA 표준에는 타임 스탬프 또는 MVID 차이가 없어도됩니다. 그것들이 없으면 C #에서 동일한 바이너리에 대해 가능합니다. 따라서 주된 이유는 의심스러운 디자인 결정이며 실제 기술적 제약이 아닙니다.
Shiv

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하지 않습니다.

데비안 Reproducible은 데비안 패키지를 바이트 단위로 표준화하려는 프로젝트 시도를 빌드 하고 최근에 Linux Foundation 보조금을 받았습니다. . 여기에는 컴파일 이상의 것이 포함되지만 관심이 있어야합니다.

Buildroot 에는 BR2_REPRODUCIBLE패키지 수준에 대한 몇 가지 아이디어를 제공 할 수 있는 옵션이 있지만이 시점에서는 아직 완료되지 않았습니다.

관련 스레드 :


3

https://reproducible-builds.org/ 프로젝트 는 이것에 관한 것이며 가능한 한 많은 장소에서 귀하의 질문에 "아니오, 다르지 않을 것입니다"라는 답을 얻으려고 열심히 노력하고 있습니다. NixOS와 데비안은 현재 패키지의 재현성이 90 % 이상입니다.

바이너리를 컴파일하고 바이너리를 컴파일하고 비트가 동일하면 소스 코드와 도구가 출력을 결정하는 것으로 안심할 수 있습니다. 길을 따라 트로이 코드.

http://bootstrappable.org/ 에서 인간이 읽을 수있는 소스의 부트 스트랩 기능과 재현성을 결합하면 인간이 읽을 수있는 소스를 기반으로 시스템을 처음부터 결정할 수 있습니다. 우리는 시스템이 무엇을하고 있는지 알고 있다고 믿을 수 있습니다.


1
멋진 링크. 나는 Buildroot fanboy이지만 누군가 누군가가 QEMU에서 부팅하는 Nix ARM 크로스 아치 설정을 제공하면 기쁠 것입니다 :-)
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Guix는 번호를 어디에서 찾을 수 있는지 모르기 때문에 언급하지 않았지만, 검증 도구 등을 사용하여 재현성 열차에서 NixOS 이전에 있었기 때문에 동일한 수준에 있거나 더 나은지 확신합니다.
clacke

2

아니오라고 말하면 100 % 결정적이 아닙니다. 이전에는 Hitachi H8 프로세서의 대상 바이너리를 생성하는 GCC 버전으로 작업했습니다.

타임 스탬프에는 문제가 없습니다. 타임 스탬프 문제가 무시 되더라도 특정 프로세서 아키텍처에서는 동일한 비트가 1 또는 0 일 수있는 2 가지 방식으로 인코딩 될 수 있습니다. 그러나 때때로 gcc는 동일한 크기의 바이너리를 생성하지만 1 비트 만 다른 바이트의 일부는 0XE0이 0XE1이됩니다.


그리고 그로 인해 다른 행동이나 "심각한 문제"가 발생 했습니까?
Florian Straub

1

일반적으로 아닙니다. 가장 합리적으로 복잡한 컴파일러에는 컴파일 시간이 객체 모듈에 포함됩니다. 시계를 재설정하더라도 컴파일을 시작했을 때와 관련하여 매우 정확해야합니다 (그리고 디스크 액세스 등이 이전과 같은 속도가 되길 바랍니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.