C#의 .net5이상부터 Determiniistic이 true로 셋팅되어 있어서 발생.
.csproj에 기본 셋팅이 되어 있으니 false로 셋팅하면 문제해결.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Deterministic>true</Deterministic>
...
</PropertyGroup>
...
</Project>
Deterministic 빌드의 유래와 역사
기원과 배경
1. 초기 문제점 (2000년대 초반)
전통적인 빌드 시스템의 문제:
같은 소스 코드 → 다른 바이너리
원인:
- 빌드 시간 타임스탬프
- 랜덤 GUID (Module Version ID)
- 빌드 환경 차이 (경로, 환경변수 등)
- 컴파일러 내부의 비결정적 요소
문제:
개발자 A의 빌드: MyApp.exe (10MB, Hash: ABC123...)
개발자 B의 빌드: MyApp.exe (10MB, Hash: DEF456...)
→ 같은 소스인데 파일이 다름!
→ 코드 리뷰 어려움
→ 보안 검증 불가능
→ 캐싱 효과 없음
2. 재현 가능한 빌드의 필요성 (2010년대 초)
주요 동기:
보안 및 신뢰성
문제: "이 바이너리가 정말 이 소스에서 나왔나?"
→ 백도어, 악성코드 삽입 검증 불가능
→ 공급망 공격(Supply Chain Attack) 위험
실제 사례:
- Ken Thompson의 "Trusting Trust" 공격 (1984)
- 컴파일러 자체에 백도어를 심을 수 있음
- 소스 코드만 봐서는 알 수 없음
빌드 캐싱 및 효율성
일반 빌드:
src/File.cs (수정 안 됨)
→ File.dll (타임스탬프 변경됨)
→ 캐시 무효화
→ 전체 재빌드 필요
Deterministic 빌드:
src/File.cs (수정 안 됨)
→ File.dll (완전히 동일)
→ 캐시 히트
→ 빌드 건너뜀
3. 오픈소스 운동의 영향 (2013년~)
Reproducible Builds 프로젝트 시작:
https://reproducible-builds.org/
목표:
- 누구나 소스에서 동일한 바이너리 생성 가능
- 빌드 과정의 투명성 확보
- 악성코드 삽입 방지
주요 참여 프로젝트:
- Debian Linux (선구자, 2013년 시작)
- Tor Browser
- Bitcoin Core
- F-Droid (Android 앱)
4. Microsoft의 도입 (2015년~)
Roslyn 컴파일러 개발 (C# 6.0 시대)
// 2015년, Roslyn에 Deterministic 옵션 추가
csc /deterministic MyApp.cs
초기 목적:
- NuGet 패키지 캐싱 최적화
- 빌드 서버 효율성 향상
- Git 히스토리 정리 (바이너리 변경 추적)
Visual Studio 2015 업데이트
<!-- MSBuild 14.0부터 지원 -->
<PropertyGroup>
<Deterministic>true</Deterministic>
</PropertyGroup>
5. 기본값 변경 (2017년~)
.NET Core 출시 (.NET Core 1.0~)
<!-- .NET Core SDK 스타일 프로젝트 -->
<Project Sdk="Microsoft.NET.Sdk">
<!-- Deterministic이 기본값 true -->
</Project>
변경 이유:
- 클라우드 빌드 환경 최적화
- Docker 컨테이너 레이어 캐싱
- CI/CD 파이프라인 효율성
타임라인:
2015: Roslyn에 /deterministic 옵션 추가 (선택적)
2016: NuGet 패키지 빌드에 권장
2017: .NET Core 2.0, 기본값 true
2019: .NET Core 3.0, 강력히 권장
2020: .NET 5+, 완전히 기본값
기술적 구현
1. PE 헤더 TimeDateStamp
전통적 방식:
// 컴파일러 내부
time_t buildTime = time(NULL); // 현재 시간
PE_Header.TimeDateStamp = buildTime;
Deterministic 방식:
// 컴파일러 내부
uint8_t hash[32];
SHA256(sourceCode, hash);
PE_Header.TimeDateStamp = *(uint32_t*)hash; // 해시의 첫 4바이트
2. Module Version ID (MVID)
// 전통적 방식
module.ModuleVersionId = Guid.NewGuid();
// Deterministic 방식
var contentHash = ComputeContentHash(assemblyBytes);
module.ModuleVersionId = DeriveGuidFromHash(contentHash);
3. PDB (디버그 정보)
일반 PDB:
- Age: 증가하는 카운터
- GUID: 랜덤
- Timestamp: 실제 시간
Deterministic PDB:
- Age: 1 (고정)
- GUID: 내용 기반
- Timestamp: 내용 기반
산업 표준화
1. NIST (미국 국립표준기술연구소)
NIST SP 800-218: Secure Software Development Framework
→ Reproducible Builds 권장
2. SLSA (Supply-chain Levels for Software Artifacts)
Level 2 이상: Reproducible Builds 필요
Google, Linux Foundation 주도
3. 주요 언어별 지원
| 언어 | Deterministic 지원 | 시작 연도 |
| C# | ✅ Roslyn | 2015 |
| Rust | ✅ 기본값 | 2015 |
| Go | ✅ go build -trimpath | 2018 |
| Java | ✅ javac --release | 2014 |
| C/C++ | ⚠️ 부분적 (GCC -frandom-seed) | 2016 |
실제 사례
1. NuGet 패키지 서명
문제: 같은 소스로 빌드했는데 서명이 다름
해결: Deterministic 빌드 → 동일한 바이너리 → 동일한 서명
2. Docker 이미지 최적화
# 일반 빌드
COPY src/ /app/src/
RUN dotnet build
→ 매번 새 레이어 (타임스탬프 변경)
# Deterministic 빌드
COPY src/ /app/src/
RUN dotnet build /p:Deterministic=true
→ 소스 안 바뀌면 레이어 재사용
3. Git LFS 절약
일반 빌드:
매 커밋마다 새 바이너리 → Git LFS 용량 증가
Deterministic 빌드:
소스 안 바뀌면 바이너리도 동일 → Git LFS 용량 절약
철학적 배경
"Trust, but Verify" (신뢰하되 검증하라)
개발자: "이 바이너리는 깨끗합니다"
사용자: "정말? 직접 빌드해서 확인하겠습니다"
Deterministic 빌드가 없으면:
→ 소스에서 빌드해도 다른 바이너리
→ 검증 불가능
Deterministic 빌드가 있으면:
→ 소스에서 빌드하면 동일한 바이너리
→ 검증 가능 ✅
현재 상황 (2024년)
<!-- .NET 8+ 기본 설정 -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Deterministic>true</Deterministic> <!-- 자동 -->
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> <!-- CI 최적화 -->
</PropertyGroup>
</Project>
장점:
- ✅ 빌드 캐싱 최적화
- ✅ 보안 검증 가능
- ✅ CI/CD 효율성 향상
- ✅ Docker 레이어 최적화
단점:
- ❌ 실제 빌드 시간 알 수 없음
- ❌ PE 헤더의 날짜가 의미 없음
- ❌ 디버깅 시 혼란 가능
요약
Deterministic 빌드의 유래:
- 2000년대: 재현 불가능한 빌드 문제 인식
- 2013: Debian의 Reproducible Builds 프로젝트 시작
- 2015: Microsoft Roslyn에 /deterministic 추가
- 2017: .NET Core에서 기본값으로 채택
- 현재: 산업 표준, 보안 필수 요소
핵심 목적:
- 🔒 보안: 공급망 공격 방지
- ⚡ 효율: 빌드 캐싱 최적화
- 🔍 투명성: 빌드 과정 검증 가능
오늘날 Deterministic 빌드는 보안과 효율성의 표준이 되었습니다!
반응형
'IT관련 > C#' 카테고리의 다른 글
| Visual Studio 2017에서 C# 원격 디버깅 설정 방법 (0) | 2025.10.23 |
|---|---|
| C# 원격 디버깅 완벽 가이드: Visual Studio로 원격 서버 디버깅하기 (0) | 2025.10.23 |
| dll 에도 관리자 권한이 필요한가? (0) | 2025.10.02 |
| requireAdministrator를 셋팅했지만 관리자권한으로 실행되지 않을때... (0) | 2025.10.02 |
| 프로그램에는 진입점에 적합한 정적 'Main' 메서드가 포함되어 있지 않습니다. 오류 해결방법 (0) | 2025.10.01 |