Microsoft MVP성태의 닷넷 이야기
글쓴 사람
홈페이지
첨부 파일
 

C# - 재현 가능한 빌드(reproducible builds) == Deterministic builds

아래의 글에,

[플밍노트] 재현 가능한 빌드, 재현 가능한 시드
; http://www.jiniya.net/ng/2018/07/reproducible-builds

"재현 가능한 빌드"에 대한 이야기가 나옵니다.

윈도우 10부터 재현 가능한 빌드(reproducible builds)로 방향을 정했고, 그 정책의 일환으로 타임스탬프를 타임스탬프가 아닌 해시 값으로 변경했다는 것이다. 재현 가능한 빌드라는 건 한 마디로 정리하면 동일 소스 코드로 컴파일을 하면 동일 바이너리가 생성된다는 것을 보장한다는 개념이다. 즉, 니가 소스 코드를 한 줄도 안 고쳤다면 넌 어디에서건 다시 컴파일해도 동일한 바이너리를 가지게 된다는 의미다


오호... 그렇다면 C# 컴파일러에도 동일한 기능이 구현되어 있지 않았을까요? 역시나 검색해 보니 다음의 글이 나옵니다.

Deterministic builds in Roslyn - April 5th, 2016 
; http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.html

즉, C# 컴파일러도 "/deterministic" 옵션을 제공하며 이것을 켠 경우에 다음의 3가지 값들이 "동일한 소스 코드"라면 바뀌지 않고 유지된다는 것입니다.

MVID: a GUID identifying the PE which is newly generated for every PE produced by the compiler 1.
PDB ID: a GUID identifying the PDB matching PDB which is newly generated on every build.
Date / Time stamp: Seconds since the epoch which is calculated on every build.

실제로 한번 테스트를 해볼까요? ^^

Visual Studio로 기본 콘솔 프로젝트를 만들고 빌드 후, dumpbin.exe로 확인해 보면 PDB ID와 Date / Time stamp를 확인할 수 있습니다.

C:\> dumpbin /HEADERS ConsoleApp1.exe
Microsoft (R) COFF/PE Dumper Version 14.14.26433.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file ConsoleApp1.exe

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES
             14C machine (x86)
               3 number of sections
        5B568E67 time date stamp Tue Jul 24 11:26:47 2018
... [생략]...
  Debug Directories

        Time Type        Size      RVA  Pointer
    -------- ------- -------- -------- --------
    5B568E67 cv           11C 00002648      848    Format: RSDS, {EE2C5EFD-C1B4-46D9-8730-6CA1889D1B18}, 1, E:\ConsoleApp1\ConsoleApp1\obj\Debug\ConsoleApp1.pdb
... [생략]...

MVID의 경우에는 dumpbin으로는 안 나오고 ildasm.exe를 실행해 보면 역어셈블된 il 파일에 다음과 같이 주석으로 담겨 있습니다.

...[생략]...
.module ConsoleApp1.exe
// MVID: {4AC6DAEA-D641-4696-AB54-DCB39211C9B2}
.imagebase 0x00400000
.file alignment 0x00000200
...[생략]...

일단, 소스 코드 변경 없이 그대로 다시 "Rebuild"를 한 후 PDB ID, MVID, time stamp를 확인해 보면 다음과 같이 바뀐 것을 볼 수 있습니다.

5B568F18 time date stamp Tue Jul 24 11:29:44 2018

Debug Directories

    Time Type        Size      RVA  Pointer
-------- ------- -------- -------- --------
5B568F18 cv           11C 00002648      848    Format: RSDS, {F97D62CB-E0D6-4944-BCC5-CC4B49F11D01}, 1, E:\ConsoleApp1\ConsoleApp1\obj\Debug\ConsoleApp1.pdb

// MVID: {691860F5-CE27-4483-846C-5F35EAC80AEC}

자, 그럼 이제 deterministic 빌드를 해볼까요? ^^ csc.exe에서는 /deterministic 옵션을 주면 되지만 Visual Studio에서는 msbuild를 이용하기 때문에 "Deterministic builds in Roslyn" 글의 설명에 따라 csproj 프로젝트 파일에 다음의 옵션을 추가하면 됩니다.

<Deterministic>true</Deterministic>

위의 옵션을 적용 후 최초 빌드 시에는 PDB ID, MVID, timestamp 모두 바뀝니다.

F99E2A84 time date stamp

Debug Directories

    Time Type        Size      RVA  Pointer
-------- ------- -------- -------- --------
94630E89 cv            76 00002664      864    Format: RSDS, {A3EC929A-FDFB-4EB2-AA83-70D680502520}, 1, E:\ConsoleApp1\ConsoleApp1\obj\Debug\ConsoleApp1.pdb

// MVID: {78A79916-27E0-4CE0-AD63-70ABB20217AD}

하지만 그 후부터는 Rebuild를 하면, 즉 이미 출력되어 있던 바이너리를 모두 삭제하고 동일한 소스 코드로부터 입력받은 경우 동일한 바이너리를 생성합니다. 실제로 출력된 바이너리에 대해 hex 비교를 해도 완전히 동일하다고 나옵니다.




혹시 적용 후 생각지 못한 점이 있을까요? 한 가지 있다면, deterministic 빌드인 경우 timestamp가 더 이상 시간 값이 아닌 hash라는 점입니다.

[deterministic 적용 전 - 시간 데이터 출력]
5B568E67 time date stamp Tue Jul 24 11:26:47 2018

[deterministic 적용 후 - hash 값이기 때문에 시간 데이터를 출력 못함]
F99E2A84 time date stamp

이 때문에 PDB Viewer 같은 도구를 이용해 해당 바이너리의 생성 시간을 알 수 없습니다. 아마 대부분의 개발 업체에서 바이너리로부터 생성 시간을 얻어와 어떤 자료로 이용하는 경우는 없을 것이기 때문에 크게 문제 될 것은 없어 보입니다.

또한, "Deterministic builds in Roslyn" 글에서도 언급하고 있지만 AssemblyVersionAttribute이나 AssemblyFileVersionAttribute 특성에 "*"를 지정해 자동 빌드 번호 증가를 명시한 경우에는 버전 정보가 달라지므로 당연히 deterministic 빌드 옵션을 켜도 매번 바뀔 수밖에 없다고 합니다. 아마도 이것 역시 대부분의 개발 업체에서는 명시적인 버전 지정을 할 것이기 때문에 제약 사항이 되진 않을 것입니다.

따라서, 이제부터는 특별한 사유가 없다면 닷넷 프로젝트의 경우 "Deterministic" 옵션을 켜두시는 것이 권장됩니다.




소스 코드의 변경은 컴파일러가 받아들이는 토큰 값을 기준으로 한 것이 아닙니다. 가령, 코드 끝에 공백을 삽입하고 저장한 경우 그것은 소스 코드가 변경된 것이나 다름없습니다. 그래서 그런 경우에도 동일 바이너리가 생성되지는 않습니다.

그 외에, /pathmap 옵션도 있습니다.

/pathmap - <PathMap>source=dest</PathMap>

이 옵션은 PDB를 위한 것인데요, PDB의 경우 디버깅뿐만 아니라 콜 스택을 뜨는 상황(ex: 예외 발생에 따른 이벤트 로그)에서 소스 코드가 위치한 파일 및 라인 번호를 보여주는데 사용됩니다. 따라서 소스 파일의 위치가 바뀌면 pdb도 바뀌게 되어 바이너리가 달라질 수밖에 없습니다. 그럴 때 pathmap을 지정해 공통 경로를 가질 수 있게 할 수 있어 역시나 pdb 파일이 바뀔 필요가 없어지는 것입니다.

그나저나, 이 옵션이 Visual Studio 2015의 두 번째 업데이트부터 추가되었었군요. ^^

New C# and VB features in Visual Studio 2015 Update 2
; https://blogs.msdn.microsoft.com/dotnet/2016/04/02/whats-new-for-c-and-vb-in-visual-studio/





[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]





[최초 등록일: ]
[최종 수정일: 7/24/2018 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 



2018-08-01 04시49분
[이성환] 성태님 덕분에 알게된 이 기능이 정말 제대로 완소 기능이네요!
PDB 생성 당시 사용된 소스코드가 있다면 비주얼스튜디오에서 BP 찍고 바로 디버깅도 가능하군요. 스택 로그만으로 코드를 따라가야했던 불편함에서 어느 정도 해방되었네요.

감사합니다 >ㅁ<
[손님]
2018-08-01 09시05분
아래의 기능과 혼동하신 거 아닌가요? ^^

(GitHub 등과 직접 연동해) 소스 코드 디버깅을 쉽게 해 주는 SourceLink
; http://www.sysnet.pe.kr/2/0/11630

원래 PDB 생성 당시 사용된 소스 코드가 있다면 디버깅이 가능했습니다. 단지, 이 글의 기능은 PDB 생성이 소스 코드가 변경되지 않는다면 동일한 PDB가 나오는 정도입니다. 이로 인해 심볼 서버 등을 구성하는 상황에서 PDB 파일의 공간을 절약할 수 있다는 장점이 있습니다.
정성태
2018-08-02 03시48분
[이성환] 아.. 제가 잘못 이해한 것일 수도 있겠네요. ;ㅁ;
근데 제가 말씀드린 부분은 PDB 를 별도로 보관하지 않을 경우라고 봐야하겠네요.

완성된 바이너리 패키지를 내보낼 때에는 PDB를 포함하지 않도록 하는데(패키징 시 별도로 PDB를 보관하지 않은 상태)
나중에 해당 바이너리로 디버깅을 하려고 하면,바이너리 패키징 당시 사용한 소스코드로 커밋을 돌려서 리빌드하게 되고,
패키징된 바이너리와 현재 리빌드해 생성된 PDB 와 연결이 안 됐던 경험이 있어서요.
(심볼 로딩은 됐지만 BP hit 안 되는 상황 등등..)
특히 리모트 디버깅 상황에서 자주 겪었던 문제라.. 이게 진짜 그 문제 때문인지는 기억이 잘 안 나네요.

근데 Deterministic 사용하니까 PDB 를 보관하지 않아도 로컬 개발 위치에서 리빌드해서 그냥 잘 연결돼서리... -ㅂ-

그런 것이었슴다...(__)
[손님]
2018-08-02 03시59분
그런 시나리오라고는 제가 전혀 예상치 못했군요. 말씀하신 상황에서도 유용한게 맞습니다. ^^
정성태

... 16  [17]  18  19  20  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
11669정성태8/24/20181711개발 환경 구성: 394. 윈도우 환경에서 elasticsearch의 한글 블로그 검색 인덱스 구성
11668정성태8/28/20181729오류 유형: 478. 윈도우 업데이트(KB4458842) 이후 SQL Server 서비스 시작 오류
11667정성태8/28/20181505오류 유형: 477. "Use Unicode UTF-8 for worldwide language support" 옵션 설정 시 SQL Server 2016 설치 오류 [1]
11666정성태8/22/20181440사물인터넷: 37. 아두이노 - 코딩으로 대신하는 오실레이터 회로의 소리 출력파일 다운로드1
11665정성태8/22/20182008사물인터넷: 36. 오실레이터 회로 동작을 아두이노의 코딩으로 구현하는 방법파일 다운로드1
11664정성태8/22/20182285개발 환경 구성: 393. 윈도우 환경에서 elasticsearch의 한글 형태소 분석기 설치
11663정성태8/25/20182963개발 환경 구성: 392. 윈도우 환경에서 curl.exe를 이용한 elasticsearch 6.x 기본 사용법
11662정성태8/21/20181595사물인터넷: 35. 병렬 회로에서의 커패시터파일 다운로드1
11661정성태8/21/20181538사물인터넷: 34. 트랜지스터 동작 - 컬렉터-이미터 간의 저항 측정파일 다운로드1
11660정성태9/4/20181413사물인터넷: 33. 세라믹 커패시터의 동작 방식파일 다운로드1
11659정성태8/19/20181639사물인터넷: 32. 9V 전압에서 테스트하는 PN2222A 트랜지스터파일 다운로드1
11658정성태8/18/20182517사물인터넷: 31. 커패시터와 RC 회로파일 다운로드3
11657정성태8/21/20182036사물인터넷: 30. 릴레이(Relay) 제어파일 다운로드3
11656정성태8/18/20181204사물인터넷: 29. 트랜지스터와 병렬로 연결한 LED파일 다운로드1
11655정성태8/18/20182074사물인터넷: 28. 저항과 병렬로 연결한 LED파일 다운로드1
11654정성태8/18/20181561사물인터넷: 27. 병렬 회로의 저항, 전압 및 전류파일 다운로드1
11653정성태8/18/20181403사물인터넷: 26. 입력 전압에 따른 LED의 전압/저항 변화파일 다운로드1
11652정성태9/11/20181321사물인터넷: 25. 컬렉터 9V, 베이스에 5V와 3.3V 전압으로 테스트하는 C1815 트랜지스터파일 다운로드1
11651정성태9/4/20182612사물인터넷: 24. 9V 전압에서 테스트하는 C1815 트랜지스터파일 다운로드3
11650정성태8/18/20181424사물인터넷: 23. 가변저항으로 분압파일 다운로드1
11649정성태8/14/20181535사물인터넷: 22. 저항에 따른 전류 테스트파일 다운로드1
11648정성태10/17/20182145사물인터넷: 21. 퓨즈를 이용한 회로 보호파일 다운로드3
11647정성태8/8/20181630오류 유형: 476. 음수의 음수는 여전히 음수가 되는 수(절대값이 음수인 수)
11646정성태8/8/20181408오류 유형: 475. gacutil.exe 실행 시 "Failure initializing gacutil" 오류 발생
11645정성태8/8/20181630오류 유형: 474. 닷넷 COM+ - Failed to load the runtime. [1]
11644정성태8/8/20182171디버깅 기술: 118. windbg - 닷넷 개발자를 위한 MEX Debugging Extension 소개
... 16  [17]  18  19  20  21  22  23  24  25  26  27  28  29  30  ...