주석 만 변경된 두 개의 프로그램 바이너리가 gcc에서 정확히 일치하지 않는 이유는 무엇입니까?


110

두 개의 C 프로그램을 만들었습니다.

  1. 프로그램 1

    int main()
    {
    }
    
  2. 프로그램 2

    int main()
    {
    //Some Harmless comments
    }
    

AFAIK, 컴파일 할 때 컴파일러 (gcc)는 주석과 중복 된 공백을 무시해야하므로 출력이 유사해야합니다.

그러나 출력 바이너리의 md5sum을 확인했을 때 일치하지 않습니다. 또한 최적화 컴파일 시도 -O3하고 -Ofast있지만 아직 일치하지 않습니다.

여기서 무슨 일이 일어나고 있습니까?

편집 : 정확한 명령과 md5sum이 있습니다 (t1.c는 프로그램 1이고 t2.c는 프로그램 2입니다)

gcc ./t1.c -o aaa
gcc ./t2.c -o bbb
98c1a86e593fd0181383662e68bac22f  aaa
c10293cbe6031b13dc6244d01b4d2793  bbb

gcc ./t2.c -Ofast -o bbb
gcc ./t1.c -Ofast -o aaa
2f65a6d5bc9bf1351bdd6919a766fa10  aaa
c0bee139c47183ce62e10c3dbc13c614  bbb


gcc ./t1.c -O3 -o aaa
gcc ./t2.c -O3 -o bbb
564a39d982710b0070bb9349bfc0e2cd  aaa
ad89b15e73b26e32026fd0f1dc152cd2  bbb

그리고 예, md5sums는 동일한 플래그를 사용하는 여러 컴파일에서 일치합니다.

BTW 내 시스템이 gcc (GCC) 5.2.0Linux 4.2.0-1-MANJARO #1 SMP PREEMPT x86_64 GNU/Linux


17
정확한 명령 줄 플래그를 포함하십시오. 예를 들어 디버그 정보가 바이너리에 전혀 포함되어 있습니까? 그렇다면, 변화하는 줄 번호는 분명히 ... 그것을 영향을 미칠 수
존 소총

4
MD5 합계가 동일한 코드의 여러 빌드에서 일관됩니까?
unenthusiasticuser 2015 년

3
나는 이것을 재현 할 수 없습니다. 나는 이것이 GCC가 (타임 스탬프를 포함하여) 컴파일 할 때 바이너리에 전체 메타 데이터를 포함한다는 사실 때문에 발생한다고 추측했을 것입니다. 사용한 정확한 명령 줄 플래그를 추가 할 수 있다면 유용 할 것입니다.
cyphar

2
MD5sum을 확인하고 멈추는 대신 hexdump와 diff를 사용하여 정확히 어떤 바이트가 다른지 확인합니다.
MM

12
"두 컴파일러 출력 간의 차이점은 무엇입니까?"라는 질문에 대한 대답이지만 흥미 롭습니다. 질문에 정당하지 않은 가정이 있습니다. 두 출력 동일 해야 하며 왜 다른지에 대한 설명 이 필요합니다 . 컴파일러는 합법적 인 C 프로그램을 제공 할 때 출력이 해당 프로그램을 구현하는 합법적 인 실행 파일임을 약속합니다. 컴파일러를 두 번 실행해도 동일한 바이너리가 생성된다는 것은 C 표준을 보장하지 않습니다.
Eric Lippert 2015 년

답변:


159

파일 이름이 다르기 때문입니다 (문자열 출력은 동일하지만). 두 개의 파일이 아닌 파일 자체를 수정하려고하면 출력 바이너리가 더 이상 다르지 않음을 알 수 있습니다. Jens와 내가 말했듯이 GCC 는 정확한 소스 파일 이름을 포함하여 빌드하는 바이너리에 전체 메타 데이터로드를 덤프하기 때문입니다 (AFAICS도 clang도 마찬가지 임).

이 시도:

$ cp code.c code2.c subdir/code.c
$ gcc code.c -o a
$ gcc code2.c -o b
$ gcc subdir/code.c -o a2
$ diff a b
Binary files a and b differ
$ diff a2 b
Binary files a2 and b differ
$ diff -s a a2
Files a and a2 are identical

이것은 md5sum이 빌드간에 변경되지 않는 이유를 설명하지만 다른 파일 간에는 다릅니다. 원하는 경우 Jens가 제안한 것을 수행하고 strings각 바이너리 의 출력을 비교할 수 있습니다 . 파일 이름이 바이너리에 포함되어 있음을 알 수 있습니다. 이 문제를 "수정" strip하려면 바이너리와 메타 데이터를 제거 할 수 있습니다.

$ strip a a2 b
$ diff -s a b
Files a and b are identical
$ diff -s a2 b
Files a2 and b are identical
$ diff -s a a2
Files a and a2 are identical

편집 : 문제를 "수정"하기 위해 바이너리를 제거 할 수 있다고 업데이트되었습니다.
cyphar 2015 년

30
이것이 MD5 체크섬이 아닌 어셈블리 출력을 비교해야하는 이유입니다.
궤도의 경쾌함 레이스


4
개체 파일 형식에 따라 컴파일 시간도 개체 파일에 저장됩니다. 따라서 예제 파일 a와 a2에 COFF 파일을 사용하는 것은 동일하지 않습니다.
Martin Rosenau 2015 년

28

가장 일반적인 이유는 컴파일러에서 추가 한 파일 이름과 타임 스탬프입니다 (일반적으로 ELF 섹션의 디버그 정보 부분에 있음).

실행 해보세요

 $ strings -a program > x
 ...recompile program...
 $ strings -a program > y
 $ diff x y

그 이유를 알 수 있습니다. 나는 한 번 이것을 사용하여 다른 디렉토리에서 컴파일 할 때 동일한 소스가 다른 코드를 일으키는 이유를 찾았습니다. 그 결과 __FILE__매크로 는 두 트리에서 다른 절대 파일 이름 으로 확장되었습니다 .


1
gcc.gnu.org/ml/gcc-help/2007-05/msg00138.html 에 따르면 (오래된, 알고 있습니다) 타임 스탬프를 저장하지 않으며 링커 문제 일 수 있습니다. 하지만 최근에 보안 회사가 바이너리의 GCC 타임 스탬프 정보를 사용하여 해킹 팀의 작업 습관을 프로파일 링 한 방법에 대한 이야기를 읽은 기억이 있습니다.
cyphar 2015 년

3
그리고 OP는 "md5sum이 동일한 플래그를 가진 여러 컴파일에 걸쳐 일치한다"고 언급하는 것은 말할 것도없이 문제를 일으키는 타임 스탬프가 아닐 가능성이 있음을 나타냅니다. 아마도 파일 이름이 다르기 때문일 것입니다.
cyphar 2015 년

1
@cyphar 다른 파일 이름은 strings / diff 접근 방식에서도 포착되어야합니다.
Jens

15

참고 : 소스 파일 이름 은 스트리핑되지 않은 바이너리로 이동하므로 이름이 다른 소스 파일에서 오는 두 프로그램은 다른 해시를 갖게됩니다.

유사한 상황에서 위의 내용이 적용되지 않는 경우 다음을 시도 할 수 있습니다.

  • strip일부 지방을 제거하기 위해 바이너리에 대해 실행 합니다. 제거 된 바이너리가 같으면 프로그램 작업에 필수적이지 않은 일부 메타 데이터입니다.
  • (차이가 실제로 어디에서보다 정확히 파악할 그러나, 또는 어셈블리 중간 출력을 생성하는 차분 실제 CPU의 지시에 없는지를 확인하는 것이다 )
  • 을 사용 strings하거나 두 프로그램을 모두 16 진으로 덤프하고 두 개의 16 진 덤프에서 diff를 실행하십시오. 차이점을 찾으면 운율이나 이유 (PID, 타임 스탬프, 소스 파일 타임 스탬프 ...)가 있는지 확인할 수 있습니다. 예를 들어 , 진단 목적으로 컴파일 타임에 타임 스탬프를 저장 하는 루틴이있을 수 있습니다 .

내 시스템입니다 gcc (GCC) 5.2.0Linux 4.2.0-1-MANJARO #1 SMP PREEMPT x86_64 GNU/Linux
등록 된 사용자

2
당신은 시도해야 실제로 두 개의 파일을. 단일 파일을 수정해도 재현 할 수 없었습니다.
cyphar 2015 년

예, 파일 이름이 범인입니다. 같은 이름으로 프로그램을 컴파일하면 같은 md5sum을 얻을 수 있습니다.
등록 된 사용자
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.