PDB(프로그램 데이터베이스)란 무엇인가 - 디버그 정보·심볼·Source Link 이해하기

· 업데이트: · · .NET, C#, Visual Studio, PDB, 디버깅, 심볼, Source Link, 진단, 운영, 기존 자산 활용

수정 이력(1건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

한국어 전면 재작성에 맞춰 본문 표현을 바로잡았습니다. 기술적인 주장은 일본어판과 같습니다.
최초 공개
이 글을 인용하기(DOI: 10.5281/zenodo.21635312)

이 글은 Zenodo에 보관되어 있습니다. 항상 최신 버전으로 연결되는 DOI와 지금 보고 있는 버전에 고정된 DOI를 아래에 함께 제시합니다.

小村 豪 (2026). 「PDB(프로그램 데이터베이스)란 무엇인가 - 디버그 정보·심볼·Source Link 이해하기」. 합동회사 코무라소프트. https://doi.org/10.5281/zenodo.21635312 https://comcomponent.com/ko/blog/pdb-program-database/

DOI(최신 버전)
10.5281/zenodo.21635312
DOI(이 버전)
10.5281/zenodo.21635313

1. 가장 먼저 알아둘 것

.NET이나 C++ 애플리케이션을 빌드하면 .dll이나 .exe와 함께 .pdb 파일이 생성되는 경우가 있습니다.

예를 들면 이런 출력입니다.

MyApp.exe
MyApp.dll
MyApp.pdb

.pdb가 무엇인지 모른 채 개발하다 보면 이런 의문이 생깁니다.

  • .pdb는 프로덕션에 두어도 되는가
  • .pdb가 없으면 애플리케이션이 동작하지 않는가
  • Release 빌드인데 .pdb가 나오는 것은 이상한가
  • .pdb에는 소스 코드가 통째로 들어 있는가
  • .pdb가 있으면 반드시 breakpoint를 걸 수 있는가
  • 덤프 분석이나 장애 조사에서 왜 .pdb가 필요해지는가
  • NuGet 패키지에서는 .pdb를 어떻게 다루어야 하는가
  • Source Link, 심볼 서버, .snupkg는 무엇이 다른가

PDB는 평소 구현에서는 중심이 되지 않습니다. 하지만 장애 조사, 덤프 분석, 라이브러리 배포, 운영 로그의 가독성, 디버깅 경험에서는 상당히 중요합니다.

먼저 결론을 적습니다.

PDB는 실행 파일이나 어셈블리와 소스 코드를 연결하기 위한 디버그 정보 파일입니다. 애플리케이션을 동작시키기 위한 본체가 아니라, “어느 명령이 어느 소스 줄에 대응하는가”, “어느 로컬 변수가 무엇인가”, “어느 소스를 보면 되는가”를 디버거나 진단 도구에게 알려주기 위한 것입니다.

이 글에서는 PDB를 단순한 “디버깅용 곁다리 파일”로 보지 않고, 실무에서 어떻게 다루어야 할 산출물인지라는 관점에서 정리합니다.

목적별 읽기 포인트

전부 43장이지만, 통독이 필수는 아닙니다. 알고 싶은 것이 정해져 있다면 다음 장만 읽으면 됩니다.

알고 싶은 것 읽을 장
PDB란 무엇인지, 무엇이 들어 있는지 2장 → 5장 → 6장
프로덕션에 둘지만 판단하고 싶다 24장(판단 축) → 25장(기밀성) → 27장(배치 패턴)
NuGet 배포 방침만 정하고 싶다 20장(Source Link) → 22장(embedded) → 23장(.snupkg) → 41장(설정 예)
빌드 설정을 어떻게 할지 정하고 싶다 13장(PDB 종류) → 14장(DebugType) → 41장(권장 설정)
디버깅에서 심볼을 읽지 못한다 18장(탐색 순서) → 29장(원인 분리 절차) → 30장(Just My Code)
프로덕션 장애의 덤프를 분석하고 싶다 11장(스택 트레이스) → 12장(덤프 분석) → 26장(CI/CD에서의 보관)
흔한 오해를 먼저 없애고 싶다 7장〜10장
설정만 알고 싶다 다음 절과 41장

결론부터 ── 실무의 권장 설정

41장까지 기다리지 않고, 자주 쓰는 3가지 패턴의 결론만 먼저 제시합니다. 이유와 예외는 각 장에서 설명합니다.

대상 권장 설정 한 줄로
사내 애플리케이션 <DebugType>portable</DebugType>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
Release에서도 PDB를 만들고, CI 산출물로 반드시 보관한다
공개 NuGet 라이브러리 위에 더해
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
Source Link를 켜고, PDB는 .snupkg로 배포한다
작은 내부 도구 <DebugType>embedded</DebugType> PDB를 빼먹지 않는다. 크기 증가는 허용한다

외부 배포 제품은 사정이 갈리므로 41장을 읽어 주세요. 그리고 어느 패턴이든 공통인 원칙이 하나 있습니다.

프로덕션에 배치할지와 관계없이, 그 빌드에서 나온 PDB는 반드시 보관한다.

나중에 같은 PDB를 다시 만드는 일은, 같은 커밋에서 다시 빌드해도 의외로 어렵기 때문입니다(40장).

이 글의 지식 맵

PDB는 빌드 후의 .dll/.exe와 소스 코드를 연결하는 디버그 정보 파일이며, 애플리케이션 실행 자체에는 필요하지 않지만 예외 스택 트레이스의 행 번호 표시나 덤프 분석에는 PDB가 필요합니다. .NET Core 이후에는 크로스 플랫폼 Portable PDB가 중심이 되며, csproj의 DebugType으로 portable·embedded·none 등의 생성 방식을 구성합니다. Source Link는 PDB에 기록된 리포지토리 정보에서 빌드 시점 커밋의 소스를 가져오는 구조이며, 심볼 서버나 .snupkg는 PDB 자체를 배포·보관하는 경로로 역할이 다릅니다. 외부 배포에서는 로컬 변수 이름까지 포함하는 private symbols가 아니라 함수 이름에 한정한 public symbols가 권장됩니다. PDB를 삭제하면 결정적 빌드에서도 완전한 재현은 어렵기 때문에, CI/CD에서 커밋 ID나 빌드 번호와 연결해 반드시 보관하는 것이 실무상의 권장입니다.

PDB와 디버그 정보 생태계의 지식 맵PDB가 바이너리와 소스를 연결하는 디버그 정보라는 점, Portable PDB와 Windows PDB의 차이, DebugType으로 생성 방식을 전환하는 것, Source Link·심볼 서버·snupkg를 통한 PDB 배포 경로, 덤프 분석이나 스택 트레이스 표시에 PDB가 필요하다는 점, CI/CD에서의 보관이 PDB 상실을 막는다는 점을 보여주는 그림전제로 한다전제로 한다전제로 한다이용한다양립하지 않는다전제로 한다에 저장된다의 후속에서 구성할 수 있다에서 구성할 수 있다전제로 한다양립하지 않는다에 저장된다이용한다권장되는 대응방지한다완화한다사용은 비권장권장되는 대응권장되는 대응PDB(프로그램 데이터베이스)스택 추적의 파일 이름·줄 번호 표시덤프 분석Windows PDBWinDbg.snupkg(NuGet 심볼 패키지)Portable PDB심볼 서버DebugType(MSBuild 속성)embedded PDBSource LinkJust My CodeCI/CD에서의 PDB 보관PDB 상실결정적 빌드(Deterministic Build)private symbolsPDB 외부 배포public symbols

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 20건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. PDB란 무엇인가

PDB는 Program Database의 약어로, 한국어로는 프로그램 데이터베이스라고 합니다. .pdb 파일은 심볼 파일이라고도 자주 불립니다.

심볼이란, 대략적으로 말하면 프로그램 안의 이름이나 위치에 관한 정보입니다. 예를 들면 이런 것들입니다.

  • 함수명
  • 메서드명
  • 로컬 변수명
  • 매개변수명
  • 타입 정보
  • 소스 파일명
  • 소스 줄 번호
  • 소스에서의 위치와 컴파일 후 명령의 대응
  • 디버거가 breakpoint를 놓기 위한 정보
  • Source Link용 소스 가져오기 정보

소스 코드를 작성할 때는 이렇게 사람이 읽을 수 있는 이름이 있습니다.

public decimal CalculateTotalPrice(Order order)
{
    var subtotal = order.Lines.Sum(x => x.Price * x.Quantity);
    var tax = subtotal * 0.10m;
    return subtotal + tax;
}

그러나 빌드 후의 .dll이나 .exe는 소스 코드 그 자체가 아닙니다. .NET이라면 IL과 메타데이터, 네이티브 C++이라면 기계어에 가까운 바이너리가 됩니다.

그 결과, 이런 정보는 실행 파일만으로는 충분히 알 수 없거나 알기 어려워집니다.

이 기계어 / IL은 어느 .cs의 몇 번째 줄인가
이 주소는 어느 함수의 어느 위치인가
이 로컬 변수의 이름은 무엇이었는가
이 breakpoint는 실제로 어느 명령 위치에 놓아야 하는가
이 스택 프레임은 어느 소스에 대응하는가

PDB는 이 간극을 메우기 위한 파일입니다.

3. PDB는 실행에 필요한가

일반적으로 PDB는 애플리케이션 실행에 필요하지 않습니다. .dll이나 .exe가 있으면 애플리케이션은 시작할 수 있습니다. .pdb가 없다고 해서 일반적인 처리가 실행되지 못하게 되는 것은 아닙니다.

다만 PDB가 없으면 이런 일이 어려워집니다.

하고 싶은 일 PDB가 없을 때 생기는 문제
Visual Studio에서 정확하게 스텝 실행하고 싶다 소스 줄과 실행 위치의 대응을 잡을 수 없다
breakpoint를 걸고 싶다 대응하는 명령 위치를 몰라 breakpoint가 미해결이 되는 경우가 있다
예외 스택 트레이스에 파일명·줄 번호를 내고 싶다 줄 번호 정보가 나오지 않거나 불충분해진다
덤프 파일을 분석하고 싶다 스택이나 변수, 형의 읽기가 어려워진다
외부 라이브러리 안으로 step into하고 싶다 라이브러리 소스와 연결할 수 없다
네이티브 크래시를 읽고 싶다 주소만 남고 함수명이나 위치를 알 수 없다

즉 PDB는 「실행하기 위한 파일」이 아니라 「조사하기 위한 파일」입니다.

이 차이는 중요합니다. 프로덕션 장애가 났을 때 애플리케이션은 PDB 없이 동작하고 있어도, 조사하는 사람은 PDB가 없으면 곤란합니다. 그래서 PDB는 실행 환경에 둘지와는 별개로, 빌드 산출물로서 반드시 보관해 두어야 합니다.

4. PDB가 있으면 무엇이 좋은가

PDB가 있으면 디버거나 진단 도구가 바이너리를 사람이 읽을 수 있는 정보로 되돌리기 쉬워집니다.

예를 들어 PDB가 없는 크래시 정보는 이런 표시가 되는 경우가 있습니다.

MyApp.dll!0x00007ff9a1234567
MyApp.dll!0x00007ff9a1234abc
MyApp.dll!0x00007ff9a1234def

PDB가 올바르게 로드되면 여기까지 보이게 됩니다.

MyApp.Services.OrderService.CalculateTotalPrice(Order order) Line 42
MyApp.Controllers.OrderController.Post(CreateOrderRequest request) Line 87
MyApp.Program.Main(string[] args) Line 16

이 차이는 큽니다.

전자는 주소부터 조사를 시작해야 합니다. 후자는 처음부터 「어느 메서드의 어느 줄인가」에 도달할 수 있습니다.

장애 조사에서는 처음 30분 안에 원인에 다가갈 수 있는지가 중요합니다. PDB가 남아 있는 것만으로 조사의 출발점이 완전히 달라집니다.

5. PDB에 들어 있는 것

PDB에 들어가는 정보는 언어, 컴파일러, PDB 형식, 빌드 설정에 따라 달라집니다. 그래서 「PDB에는 반드시 이것이 들어 있다」고 단순하게 단정할 수는 없습니다.

다만 .NET 개발자의 감각으로는 주로 이런 정보를 기대합니다.

소스 파일과 컴파일 후 코드의 대응
소스 줄 번호
메서드나 함수의 심볼
로컬 변수명
스코프 정보
소스 파일의 경로나 체크섬
Source Link 정보
경우에 따라 임베드된 소스

특히 중요한 것은 소스에서의 위치와 실행 시 위치의 대응입니다.

C#으로 쓴 한 줄이 IL이나 JIT 후의 네이티브 코드에서는 여러 명령이 되는 경우가 있습니다. 반대로 최적화 때문에 여러 소스 줄이 합쳐지거나, 사라지거나, 순서가 바뀐 것처럼 보이기도 합니다.

디버거는 PDB 정보를 써서 「지금 표시해야 할 소스 줄」을 판단합니다.

6. PDB에 들어 있지 않은 것

PDB는 들어 있는 것보다 들어 있지 않은 것을 이해하는 편이 오해가 줄어듭니다.

PDB는 일반적으로 이런 것이 아닙니다.

  • 애플리케이션 본체가 아니다
  • 실행에 필수인 런타임 파일이 아니다
  • 소스 코드 전체의 완전한 백업이 아니다
  • Git 리포지토리의 대체가 아니다
  • 모든 빌드 설정이나 환경 정보를 완전히 복원하는 것이 아니다
  • 그것만으로 장애 원인을 자동으로 설명해 주는 것이 아니다

다만 주의할 점이 있습니다.

PDB에는 소스 파일 경로, 타입명, 함수명, 로컬 변수명, 경우에 따라서는 Source Link 정보나 임베드 소스가 포함될 수 있습니다.

그래서 PDB는 「소스 코드 그 자체가 아니니 아무 생각 없이 공개해도 된다」고 말할 수 없습니다.

내부 프로젝트명, 사용자명이 포함된 경로, 사내 디렉터리 구성, 미공개 타입명, 업무 로직을 추정할 수 있는 이름 등이 포함될 가능성이 있습니다.

7. 흔한 오해 1: PDB가 있으면 프로덕션이 느려진다

PDB가 옆에 놓여 있다는 것만으로 일반적인 애플리케이션 처리가 느려지는 것은 아닙니다.

PDB는 디버거나 진단 도구가 심볼 정보를 필요로 할 때 쓰는 것입니다. 애플리케이션의 일반 처리가 매번 PDB를 읽으면서 동작하는 것은 아닙니다.

물론 예외 스택 트레이스에서 파일명이나 줄 번호를 해석하거나, 디버거를 연결하거나, 프로파일러나 진단 도구가 심볼을 읽는 장면에서는 사용됩니다.

그러나 「PDB를 두면 항상 느려지니 절대 프로덕션에 두면 안 된다」는 이해는 거칠습니다.

실무에서는 이렇게 생각하는 편이 안전합니다.

실행 성능 이유만으로 PDB를 지울 필요는 적다
공개 범위·정보 유출·산출물 크기·운영 정책 이유로 배치 방침을 정한다
배치하지 않는 경우에도 같은 빌드의 PDB는 반드시 보관한다

8. 흔한 오해 2: Release 빌드라면 PDB는 필요 없다

Release 빌드에서도 PDB는 유용합니다. 오히려 프로덕션 장애 조사에서 필요해지는 것은 Release 빌드의 PDB입니다.

프로덕션에서 동작하는 것이 Release 빌드라면 Debug 빌드의 PDB를 가지고 있어도 의미가 없습니다. 디버거가 필요로 하는 것은 그 프로덕션 바이너리를 만들었을 때 생성된 PDB입니다.

여기서 중요한 것은 이 구분입니다.

항목 의미
Debug / Release 최적화, 조건부 컴파일, 출력 설정 등의 빌드 구성
PDB의 유무 디버그 정보를 생성·저장할지
디버깅 용이성 최적화 유무, PDB 내용, 소스 일치, JIT 동작 등으로 정해진다

Release 빌드는 최적화가 되어 있는 경우가 많아 Debug 빌드보다 스텝 실행이 이해하기 어려워집니다. 로컬 변수가 최적화로 사라지거나, 소스 줄 순서대로 멈추지 않는 경우가 있습니다.

그래도 PDB가 있으면 이런 정보는 얻기 쉬워집니다.

  • 예외가 난 소스 줄
  • 스택 위의 메서드명
  • 덤프 분석 시의 대응 위치
  • 프로파일링 결과의 함수명
  • 로그와 소스의 대조

Release라서 PDB가 불필요한 것이 아닙니다. Release이기 때문에 그 빌드에 대응하는 PDB를 남겨 둘 필요가 있습니다.

9. 흔한 오해 3: PDB가 있으면 어떤 바이너리든 디버깅할 수 있다

PDB는 어떤 .dll이나 .exe에든 재사용할 수 있는 것이 아닙니다.

디버거는 대상 바이너리와 PDB가 일치하는지를 확인합니다. 일치하지 않는 PDB를 억지로 쓰면 소스 줄, 함수, 변수의 대응이 어긋나기 때문입니다.

예를 들어 이런 상황에서는 PDB가 있어도 쓸 수 없거나 도움이 되지 않는 경우가 있습니다.

로컬에서 다시 빌드한 PDB를 프로덕션 DLL에 맞추려 하고 있다
같은 버전 번호이지만 실제로는 다른 커밋에서 빌드되어 있다
hotfix 후의 DLL에 hotfix 전의 PDB를 로드시키려 하고 있다
최적화 설정이나 조건부 컴파일이 다르다

PDB는 「같은 소스라면 대체로 맞는다」가 아니라 「같은 빌드 산출물에 대응해야 한다」고 생각해야 합니다.

그래서 CI/CD에서는 이 단위로 저장하는 것이 기본입니다.

커밋 ID
빌드 번호
산출물의 버전
.dll / .exe
.pdb
소스 참조 정보

이 조합을 깨지 않는 것이 중요합니다.

10. 흔한 오해 4: PDB가 있으면 소스 코드가 없어도 완전히 읽을 수 있다

PDB가 있어도 소스 코드가 반드시 들어 있는 것은 아닙니다.

PDB는 주로 소스와 바이너리를 대응시키는 정보를 가집니다. 소스 파일의 경로나 체크섬, Source Link 정보는 가질 수 있지만, 일반적인 PDB가 항상 소스 본문을 통째로 가지는 것은 아닙니다.

그래서 디버거에서 외부 라이브러리로 step into하고 싶다면 다음 중 하나가 필요해집니다.

로컬에 같은 소스 파일이 있다
Source Link로 올바른 커밋의 소스를 가져올 수 있다
PDB에 소스가 임베드되어 있다
디컴파일한 소스로 대신한다

Visual Studio에는 .NET 어셈블리를 디컴파일해 표시하는 기능도 있습니다. 다만 디컴파일 결과는 원래 소스 그 자체가 아닙니다. 주석, 공백, 로컬 변수명, 원래의 작성 방식, 전처리기 조건 등은 사라지거나 바뀝니다.

조사에 쓰기에는 편리하지만, 원본 소스의 대체로 과신하지 않는 편이 좋습니다.

11. PDB와 스택 트레이스

.NET의 예외 스택 트레이스에서는 PDB가 있는지에 따라 보이는 모습이 달라집니다.

PDB가 없어도 메서드명이나 타입명이 표시되는 경우는 있습니다. .NET 어셈블리에는 메타데이터가 있기 때문입니다.

그러나 파일명이나 줄 번호까지 내고 싶다면 PDB가 중요해집니다.

예를 들어 PDB가 없는 경우의 스택 트레이스는 이렇게 되기 쉽습니다.

System.InvalidOperationException: Order is invalid
   at MyApp.Services.OrderService.Validate(Order order)
   at MyApp.Controllers.OrderController.Post(CreateOrderRequest request)

PDB가 있고 줄 번호를 해석할 수 있는 경우에는 이렇게 바뀝니다.

System.InvalidOperationException: Order is invalid
   at MyApp.Services.OrderService.Validate(Order order) in /src/MyApp/Services/OrderService.cs:line 42
   at MyApp.Controllers.OrderController.Post(CreateOrderRequest request) in /src/MyApp/Controllers/OrderController.cs:line 87

운영 중에 이 차이가 나면 조사 속도가 크게 달라집니다.

다만 파일 경로가 나온다는 것 자체가 정보 공개가 되는 경우도 있습니다. 외부로 돌려주는 오류 응답에 스택 트레이스를 내지 않고, 로그 보관처를 제한하는 식의 다른 대책도 필요합니다.

12. PDB와 덤프 분석

PDB가 가장 고맙게 느껴지는 것은 덤프 분석 때입니다.

프로덕션에서 이런 문제가 났다고 합시다.

  • 프로세스가 크래시했다
  • CPU 사용률이 높게 유지되었다
  • 데드락처럼 보인다
  • 메모리가 계속 늘어난다
  • 응답이 돌아오지 않는다
  • 네이티브 라이브러리와의 경계에서 떨어진다

이때 덤프 파일을 얻어 분석합니다.

그러나 덤프만으로는 충분하지 않습니다. 덤프에 포함되는 것은 그 시점의 프로세스 상태입니다. 거기에 찍힌 스택이나 모듈을 사람이 읽을 수 있는 이름이나 소스 줄로 바꾸려면 대응하는 PDB가 필요해집니다.

.NET에서는 dotnet-dump, Visual Studio, WinDbg, SOS 등을 써서 분석합니다. 네이티브가 포함되는 경우에는 WinDbg의 심볼 설정이 중요해집니다.

덤프 분석에서 흔한 실패를 듭니다.

프로덕션 DLL은 남아 있지만 PDB가 없다
PDB는 있지만 로컬에서 다시 빌드한 다른 것이었다
Windows / .NET 런타임 심볼을 로드하지 못했다
자사 앱의 PDB는 있지만 서드파티 라이브러리 심볼이 없다
심볼 경로가 설정되어 있지 않아 디버거가 PDB를 찾지 못한다

덤프 분석은 문제가 난 뒤에 준비해도 늦는 경우가 있습니다. 빌드 시점에 PDB를 저장해 두고, 조사 때 꺼낼 수 있게 해 두는 것이 중요합니다.

13. Windows PDB와 Portable PDB

PDB에는 여러 형식이 있습니다.

실무에서 알아둘 대표는 이 두 가지입니다.

종류 주요 맥락 특징
Windows PDB Visual C++, 기존의 Windows 디버깅 Windows 네이티브 개발에서 자주 쓰이는 형식
Portable PDB .NET / .NET Core 이후 크로스 플랫폼으로 다룰 수 있는 .NET용 형식

.NET Core 이후에는 Portable PDB가 중요합니다. Portable PDB는 Windows뿐 아니라 Linux나 macOS에서도 다룰 수 있는 형식입니다.

.NET Framework 시대의 프로젝트나 오래된 Visual Studio 설정에서는 Windows PDB를 볼 기회도 있습니다. 현행 .NET의 SDK 스타일 프로젝트에서는 보통 Portable PDB를 전제로 생각하는 경우가 늘어나고 있습니다.

여기서 주의할 점은 확장자가 둘 다 .pdb라는 것입니다.

확장자만으로는 형식의 차이를 알 수 없습니다. 「PDB」라고 하면 어느 맥락의 이야기인지 확인합니다.

.NET의 Portable PDB 이야기인가
Visual C++의 Windows PDB 이야기인가
.NET Framework의 오래된 프로젝트 이야기인가
NuGet 배포용 PDB 이야기인가
WinDbg에서 쓰는 심볼 이야기인가

14. .NET의 DebugType

C# 프로젝트에서는 DebugType으로 디버그 정보 출력 방법을 지정할 수 있습니다.

대표적인 값은 다음과 같습니다.

DebugType 의미
portable Portable PDB를 별도 파일로 생성한다
embedded Portable PDB에 해당하는 디버그 정보를 .dll / .exe에 임베드한다
full 현재 플랫폼의 기본 형식으로 PDB를 생성한다
pdbonly C# 6.0 이후에는 full과 실질적으로 차이가 없는 취급
none PDB를 생성하지 않는다

현행 .NET SDK 스타일 프로젝트에서는 C#의 DebugType이 Debug / Release 모두 기본값이 portable입니다. 그래서 보통 Portable PDB를 내기 위해서만 DebugType을 명시할 필요는 없습니다.

명시적으로 쓸 의미가 있는 것은, 프로젝트나 조직으로서 「이 형식으로 고정한다」고 보이고 싶을 때, 오래된 프로젝트와의 차이를 알기 쉽게 하고 싶을 때, 또는 embedded / none처럼 기본값과 다른 방침을 고를 때입니다.

NuGet 라이브러리나 외부 배포에서는 portable, embedded, .snupkg 중 무엇을 고를지를 검토합니다.

예를 들어 명시적으로 Portable PDB를 낸다면 이렇게 씁니다.

<PropertyGroup>
  <DebugType>portable</DebugType>
</PropertyGroup>

PDB를 별도 파일로 두지 않고 어셈블리에 임베드한다면 이렇습니다.

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

Release 빌드에서 굳이 PDB를 내고 싶지 않다면 이렇게 쓸 수 있습니다.

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <DebugType>none</DebugType>
</PropertyGroup>

다만 이것은 신중히 결정해야 합니다. Release PDB를 내지 않는 설정으로 하면 프로덕션 장애 조사에서 우리 자신이 곤란해질 가능성이 있습니다.

15. DebugSymbols=false만으로 충분한가

PDB 생성을 멈추고 싶을 때 DebugSymbolsfalse로 하는 예를 봅니다.

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <DebugSymbols>false</DebugSymbols>
</PropertyGroup>

다만 PDB를 확실히 생성하지 않으려는 의도라면 DebugTypenone으로 하는 편이 명확합니다.

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <DebugType>none</DebugType>
</PropertyGroup>

이 설정은 라이브러리나 애플리케이션의 배포 정책으로 쓰는 경우는 있습니다. 그러나 거듭 말하지만, PDB를 만들지 않으면 장애 조사 능력이 떨어집니다.

실무에서는 이런 구분이 많습니다.

PDB는 빌드 산출물로서 반드시 생성한다
프로덕션 서버에 배치할지는 따로 판단한다
배치하지 않는 경우에도 CI 산출물이나 심볼 서버에 보관한다

16. C++의 PDB는 .NET과 조금 다르다

C++의 PDB는 .NET의 PDB와 맥락이 조금 다릅니다.

Visual C++에서는 /Zi/ZI 같은 옵션으로 PDB가 생성됩니다. 또한 컴파일러가 쓰는 PDB와, 링커가 최종 .exe / .dll용으로 만드는 PDB가 관계합니다.

C++에서는 네이티브 코드의 주소, 함수, 형, 로컬 변수, 인라인 전개, 최적화 후의 위치 등을 읽기 위해 PDB가 매우 중요합니다.

또한 네이티브 장애 조사에서는 PDB가 없으면 이런 상태가 되기 쉽습니다.

예외 주소는 알 수 있다
모듈명은 알 수 있다
하지만 함수명이나 소스 줄은 알 수 없다

C++와 C#이 혼재하는 애플리케이션, P/Invoke, C++/CLI, 네이티브 DLL을 호출하는 .NET 앱에서는 .NET 쪽 PDB뿐 아니라 네이티브 쪽 PDB도 필요해집니다.

17. public symbol과 private symbol

Windows 심볼 세계에는 public symbolsprivate symbols라는 구분이 있습니다.

대략적으로 말하면 이 차이입니다.

종류 포함되는 정보의 이미지
private symbols 로컬 변수, 형, 인수, 상세한 내부 정보 등을 포함하는 완전한 쪽에 가까운 정보
public symbols 공개용으로 좁힌 함수명이나 주소 등의 정보

외부로 배포하는 심볼에서는 private symbols를 빼고 public symbols만 남기는 경우가 있습니다.

이것은 디버깅 가능성과 정보 공개 범위의 균형을 잡기 위해서입니다.

예를 들어 자사 제품의 크래시 분석을 위해 최소한의 함수명은 내고 싶다. 그러나 내부의 로컬 변수명이나 타입 정보까지는 내고 싶지 않다. 그런 경우에 stripped PDB를 쓰는 경우가 있습니다.

Windows 대상 네이티브 개발에서는 PDBCopy 등으로 private symbol을 제거한 PDB를 만드는 경우가 있습니다.

한편 사내 장애 조사용으로는 완전한 PDB를 보관해 둘 필요가 있습니다. 외부용으로 좁힌 PDB만 남아 있으면 깊은 조사에서 곤란합니다.

18. 디버거는 PDB를 어디서 찾는가

Visual Studio나 WinDbg는 여러 장소에서 PDB를 찾습니다.

대표적인 장소는 이 근처입니다.

프로젝트의 출력 폴더
.dll / .exe와 같은 폴더
.dll / .exe에 기록되어 있는 PDB의 원래 경로
Visual Studio의 심볼 설정에서 지정한 폴더
로컬 심볼 캐시
사내 심볼 서버
Microsoft Symbol Server
NuGet.org Symbol Server
Azure Artifacts 등의 심볼 서버

PDB가 있는데도 로드되지 않는 경우에는 이 점을 순서대로 확인합니다.

PDB가 대상 바이너리와 일치하는가
PDB가 디버거의 검색 경로에 들어 있는가
심볼 서버에 접근할 수 있는가
로컬 캐시에 오래된 것이 남아 있지 않은가
대상 모듈의 심볼 로드가 비활성화되어 있지 않은가
Just My Code 설정으로 외부 코드 취급이 되어 있지 않은가

Visual Studio에서는 디버깅 중에 Modules 창을 보면 각 모듈의 심볼 로드 상태를 확인할 수 있습니다.

Debug
  Windows
    Modules

여기서 대상 DLL의 Symbol Status를 봅니다.

흔한 것은 이 네 가지 표시입니다.

Symbols loaded.
Cannot find or open the PDB file.
PDB does not match image.
Skipped loading symbols.

PDB 관련으로 막히면 먼저 Modules 창을 보는 것이 지름길입니다.

19. 심볼 서버란 무엇인가

여기부터 23장까지는 PDB를 「어디에 두고, 어떻게 디버거에 전달하는가」의 이야기가 이어집니다. 등장하는 요소가 많으니 먼저 전체 관계를 그림으로 둡니다.

같은 폴더에 둔다CI 산출물에 보관심볼 서버로 발행NuGet으로 배포dll에 임베드빌드dll / exe와 pdb가 동시에 만들어진다dll / exe배포·실행되는 것pdb디버그 정보소스 리포지토리그 빌드의 커밋PDB를 어떻게 전달하는가dll 옆에 pdb27장 패턴 1CI artifact27장 패턴 2심볼 서버19장snupkg23장embedded PDB22장디버거 / 진단 도구Source Link20장·21장줄 번호가 있는 스택 트레이스스텝 실행덤프 분석

그림의 왼쪽 절반은 「PDB를 어떻게 보관·배포하는가」, 오른쪽 절반은 「디버거가 그것을 어떻게 쓰는가」입니다. 이 안에서 Source Link만 역할이 다릅니다. PDB를 전달하는 수단이 아니라, 전달된 PDB에서 소스 코드를 가져오기 위한 정보이기 때문입니다. 바이너리, PDB, 소스는 세 가지가 따로 관리되므로, 이 셋을 다시 연결하는 것이 19장부터 23장의 주제가 됩니다.

그러면 심볼 서버부터 보겠습니다.

심볼 서버는 PDB 같은 심볼 파일을 디버거가 필요할 때 가져올 수 있게 하는 메커니즘입니다.

단순히 공유 폴더에 PDB를 두는 것만으로도 어느 정도는 운영할 수 있습니다. 그러나 버전이 늘어나면 바로 무너집니다.

MyApp v1.0.0의 PDB
MyApp v1.0.1의 PDB
MyApp v1.0.1 hotfix의 PDB
MyApp v1.1.0-beta의 PDB
고객 A용만 설정이 다른 PDB

같은 파일명의 MyApp.pdb가 수십 개나 나옵니다.

심볼 서버는 PDB를 단순한 파일명이 아니라 바이너리와의 일치 정보를 바탕으로 정리합니다. 그래서 디버거는 「이 DLL에 맞는 PDB」를 찾기 쉬워집니다.

실무에서 생각할 수 있는 구성의 예입니다.

Microsoft Symbol Server
  Windows나 .NET 런타임 등의 심볼 가져오기에 쓴다

NuGet.org Symbol Server
  공개 NuGet 패키지의 심볼 가져오기에 쓴다

사내 심볼 서버
  자사 앱·자사 라이브러리의 PDB를 저장한다

로컬 심볼 캐시
  한 번 가져온 PDB를 재사용해 디버깅을 빠르게 한다

사내 서비스에서 프로덕션 장애를 조사한다면, CI가 PDB를 사내 심볼 스토어로 발행하는 메커니즘을 마련하면 편리합니다.

20. Source Link란 무엇인가

Source Link는 PDB와 소스 관리 시스템을 연결하는 메커니즘입니다.

PDB가 「이 위치는 이 소스 파일에 대응한다」고 알아도, 그 소스 파일이 로컬에 없으면 디버거가 표시할 수 없습니다.

Source Link가 있으면 디버거는 PDB에 들어간 정보를 써서 GitHub, Azure Repos, GitLab, Bitbucket 등에서 대응하는 커밋의 소스 파일을 가져올 수 있습니다.

즉 Source Link가 해결하는 것은 이런 문제입니다.

NuGet으로 가져온 라이브러리 안으로 step into하고 싶다
로컬에 그 라이브러리 소스를 clone하지 않았다
그러나 PDB와 리포지토리 정보는 있다
디버거가 올바른 커밋의 소스를 가지러 간다

이것은 라이브러리 이용자의 경험을 크게 개선합니다.

Source Link의 핵심은 「최신 main 브랜치」가 아니라 「그 바이너리를 만든 커밋」을 참조하는 것입니다.

최신 소스가 아니라 빌드 시점의 소스에 연결되는 데 의미가 있습니다.

.NET 8 이후 SDK에서는 Source Link 취급이 개선되어 있습니다.

GitHub, Azure Repos, GitLab, Bitbucket 등 자주 쓰는 제공자에서는 .NET SDK 쪽에 Source Link 메커니즘이 포함되게 되었습니다.

그래서 예전처럼 반드시 Microsoft.SourceLink.GitHub 등을 명시적으로 추가한다는 이해는 점점 낡아지고 있습니다.

다만 확인이 필요한 경우도 있습니다.

.NET 8 SDK 미만으로 빌드하고 있다
SDK 스타일이 아닌 오래된 프로젝트이다
온프레미스의 독자 Git 호스팅을 쓰고 있다
표준 지원 외의 Source Link 제공자를 쓰고 있다
NuGet 패키지의 메타데이터까지 갖추고 싶다

NuGet 패키지에서 리포지토리 정보를 내고 싶은 경우에는 이 설정을 씁니다.

<PropertyGroup>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>

추적되지 않은 파일을 PDB에 임베드해야 하는 경우에는 이 설정을 검토합니다.

<PropertyGroup>
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>

다만 임베드는 정보 공개 범위에 영향을 줍니다. 무엇을 PDB에 넣을지는 공개 대상과 운영 방침에 맞춰 결정할 필요가 있습니다.

22. embedded PDB란 무엇인가

DebugTypeembedded를 지정하면 Portable PDB의 디버그 정보가 .dll이나 .exe에 임베드됩니다. 이 경우 별도 파일의 .pdb는 생성되지 않습니다.

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

이것이 편리한 것은 이런 장면입니다.

  • 단일 파일에 가까운 형태로 배포하고 싶다
  • PDB를 빼먹지 않고 싶다
  • 소규모 내부 도구에서 디버그 정보도 함께 가지고 싶다
  • NuGet 패키지에서 PDB 배포 수고를 줄이고 싶다

한편 단점도 있습니다.

  • 어셈블리 크기가 늘어난다
  • 배포물에 디버그 정보가 항상 포함된다
  • 정보 공개 범위 제어가 거칠어진다
  • 큰 라이브러리에서는 restore나 배포 크기에 영향을 준다

embedded PDB는 편리하지만 「일단 전부 embedded로 두면 된다」는 것은 아닙니다.

특히 외부 공개 라이브러리에서는 .snupkg에 의한 심볼 패키지, Source Link, 일반 PDB 동봉, embedded 중 무엇이 좋은지를 생각합니다.

23. .snupkg란 무엇인가

.snupkg는 NuGet의 심볼 패키지 형식입니다.

일반적인 NuGet 패키지는 .nupkg입니다. 심볼 패키지는 .snupkg입니다.

MyLibrary.1.2.3.nupkg
MyLibrary.1.2.3.snupkg

.nupkg는 이용자가 참조하는 라이브러리 본체를 포함합니다. .snupkg는 디버그용 PDB를 배포하기 위해 씁니다.

여기서 중요한 것은 .snupkg가 기본적으로 관리 코드의 Portable PDB용이라는 점입니다. 적어도 NuGet.org의 심볼 서버에서는 Portable PDB만 지원되며, C++ 등의 네이티브 프로젝트가 생성하는 Windows PDB는 받지 않습니다. Windows PDB를 배포·보관하고 싶다면 레거시 .symbols.nupkg, 사내 심볼 서버, CI artifact 등 다른 경로를 검토합니다.

만들려면 예를 들어 이렇게 씁니다.

<PropertyGroup>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

커맨드라인으로 지정할 수도 있습니다.

dotnet pack -c Release -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg

공개 NuGet 라이브러리라면 PDB를 .nupkg 본체에 넣는 것보다 .snupkg와 Source Link를 쓰는 편이 배포 크기와 디버깅 경험의 균형을 잡기 쉽습니다.

다만 피드나 도구의 대응 상황에는 주의합니다. 사내 NuGet 피드가 .snupkg에 대응하지 않는 경우에는 다른 방법을 고를 필요가 있습니다.

24. PDB를 프로덕션에 두어야 하는가

「PDB를 프로덕션에 두어야 하는가」는 단순한 yes / no가 아닙니다.

판단 축은 네 가지입니다.

장애 조사의 용이성
정보 공개 위험
배포 크기
운영 규칙

사내 시스템이라면 .dll.pdb를 같은 폴더에 두는 운영도 현실적입니다. 예외 로그에 줄 번호가 나오기 쉬워지고, 덤프 분석도 하기 쉬워집니다.

외부 배포 애플리케이션이라면 PDB를 그대로 동봉할지를 신중히 판단합니다. 내부 구조나 로컬 변수명, 소스 경로가 보이는 경우가 있습니다. 필요하다면 public symbols로 한정하거나, 심볼 서버를 경유하거나, 지원용으로 보관만 하는 식의 선택지를 취합니다.

Web 서비스라면 서버에 PDB를 둘지에 더해, 로그에 나오는 스택 트레이스의 취급도 생각합니다. 외부 응답으로 스택 트레이스를 돌려주어서는 안 됩니다. 내부 로그에는 남겨도, 열람 권한과 보존 기간을 정해야 합니다.

실무에서의 권장은 이렇습니다.

PDB는 반드시 생성한다
PDB는 빌드 산출물로서 보관한다
프로덕션 배치는 시스템의 공개 범위에 따라 정한다
외부 공개한다면 정보 공개 범위를 확인한다
덤프 분석에서 꺼낼 수 있는 장소에 둔다

25. PDB는 기밀 정보인가

PDB는 소스 코드 본체나 비밀 키와 같은 취급이 필요하다고 단정할 수는 없습니다. 그러나 무해한 파일로 다루는 것도 위험합니다.

PDB에서 알 수 있는 가능성이 있는 것을 들어 봅니다.

  • 개발자의 로컬 경로
  • 사내 폴더 구성
  • 프로젝트명
  • 클래스명, 메서드명
  • 로컬 변수명
  • 내부 API명
  • 업무 용어
  • Source Link의 리포지토리 URL
  • 임베드된 소스
  • Source Server 설정

특히 오래된 Source Server 방식이나 네이티브 PDB의 일부 기능에서는, 디버거가 소스를 가져오기 위해 명령을 실행하는 메커니즘이 얽히는 경우가 있습니다. 신뢰할 수 없는 PDB나 심볼 서버를 무조건 쓰는 것은 피해야 합니다.

또한 PDB를 분석하는 도구나 라이브러리에 부정한 PDB를 먹이는 경우에도 주의가 필요합니다. 외부에서 받은 PDB를 자동 처리하는 메커니즘에서는 입력을 신뢰하지 않는 설계로 합니다.

정리하면 PDB는 이렇게 다루는 것이 안전합니다.

사내의 완전한 PDB는 내부 산출물로서 보호한다
외부 공개하는 PDB는 내용과 공개 범위를 확인한다
Source Link 대상이 private repository라면 인증과 권한을 관리한다
신뢰할 수 없는 심볼 서버를 디버거에 등록하지 않는다

26. CI/CD에서의 PDB 보관 방침

PDB는 개발자의 로컬 PC에만 남아 있어도 의미가 없습니다. 프로덕션 장애가 났을 때 필요한 것은 실제로 릴리스된 빌드에 대응하는 PDB입니다.

그래서 CI/CD에서는 이런 형태로 보관합니다.

빌드 번호: 2026.06.10.1234
커밋 ID: abcdef123456...
산출물:
  MyApp.dll
  MyApp.pdb
  MyApp.deps.json
  MyApp.runtimeconfig.json
  package.zip
  container image digest

나아가 가능하면 이 근처의 정보도 연결합니다.

Git commit
Git tag
릴리스 번호
환경명
빌드 설정
타깃 프레임워크
RID
컨테이너 이미지 digest
NuGet lock file

중요한 것은 PDB를 단독으로 보관하는 것이 아니라, 어느 바이너리에 대응하는 PDB인지를 잃지 않는 것입니다.

파일 공유에 MyApp.pdb만 계속 두는 운영은 언젠가 무너집니다. 빌드 단위, 버전 단위, 커밋 단위로 꺼낼 수 있게 합니다.

27. PDB의 배치 패턴

PDB 취급에는 몇 가지 실무 패턴이 있습니다.

패턴 1: DLL과 같은 폴더에 PDB를 둔다

가장 단순합니다.

publish/
  MyApp.dll
  MyApp.pdb

장점은 설정이 간단하다는 것입니다. 디버거나 런타임이 찾기 쉽고, 예외 로그의 줄 번호도 내기 쉬워집니다.

단점은 배포물에 디버그 정보가 포함된다는 것입니다. 외부 배포에는 맞지 않는 경우가 있습니다.

패턴 2: PDB는 배치하지 않고 CI 산출물에 보관한다

프로덕션 서버에는 PDB를 두지 않고 CI의 artifact에 남깁니다.

release-artifacts/
  app.zip
  symbols.zip

프로덕션 장애 때는 덤프나 로그를 얻고, 대응하는 빌드 번호의 PDB를 꺼내 분석합니다.

정보 공개 범위를 억누르기 쉬운 한편, 조사 때 꺼내는 절차가 필요합니다.

패턴 3: 사내 심볼 서버로 발행한다

대규모 팀에서는 이것이 다루기 쉽습니다.

CI가 빌드 때 PDB를 심볼 스토어로 발행합니다. 개발자나 조사자의 Visual Studio / WinDbg는 그 심볼 서버를 참조합니다.

장점은 여러 버전의 PDB를 안전하게 다루기 쉽다는 것입니다. 단점은 초기 구축과 접근 제어가 필요하다는 것입니다.

패턴 4: NuGet의 .snupkg로 배포한다

공개 라이브러리에서는 유력합니다.

MyLibrary.1.2.3.nupkg
MyLibrary.1.2.3.snupkg

이용자는 일반 패키지만 복원하고, 디버깅 때 필요한 심볼만 가져옵니다.

Source Link와 조합하면 외부 라이브러리에서도 소스로 step into하기 쉬워집니다.

패턴 5: embedded PDB로 한다

PDB를 빼먹지 않으려면 편리합니다.

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

다만 어셈블리 크기와 정보 공개 범위에 주의합니다.

28. 기존 프로젝트에서 먼저 확인할 것

기존 .NET 프로젝트에서 PDB 취급을 다시 본다면 먼저 여기부터 확인합니다.

Release 빌드에서 PDB가 생성되고 있는가
생성된 PDB는 어디에 저장되어 있는가
프로덕션에 낸 DLL과 PDB를 대응시켜 보관하고 있는가
덤프 분석 때 PDB를 꺼낼 수 있는가
Source Link가 유효한가
NuGet 라이브러리라면 .snupkg를 내고 있는가
PDB에 불필요한 정보를 너무 많이 넣고 있지 않은가

csproj에서는 이 근처의 설정을 확인합니다.

<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <DebugType>portable</DebugType>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

NuGet 패키지라면 이것도 후보입니다.

<PropertyGroup>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

다만 모든 프로젝트에 같은 설정을 넣으면 되는 것은 아닙니다. 내부 앱, 외부 라이브러리, 온프레 제품, SaaS, OSS에서 최적 설정은 달라집니다.

29. PDB가 로드되지 않을 때의 확인 방법

PDB가 로드되지 않을 때는 감각으로 다시 빌드하기 전에 순서대로 원인을 가릅니다.

1. 대상 모듈을 확인한다

Visual Studio의 Modules 창에서 대상 DLL / EXE를 찾습니다.

Debug > Windows > Modules

여기서 봐야 할 열은 이것입니다.

Module
Path
Symbol Status
Symbol File
Version
Timestamp

2. 심볼 상태를 본다

각각의 표시는 대체로 이렇게 읽습니다.

표시 의미
Symbols loaded 로드됨
Cannot find or open the PDB file PDB를 찾지 못함
PDB does not match image PDB는 있지만 대상 바이너리와 일치하지 않음
Skipped loading symbols 설정에 따라 로드하지 않았을 가능성

3. 로컬 PDB가 같은 빌드의 것인지 확인한다

흔한 실수는 로컬에서 다시 빌드한 PDB를 써 버리는 것입니다.

같은 소스라도 빌드 조건이 다르면 일치하지 않는 경우가 있습니다. CI 산출물에서 프로덕션에 낸 것과 같은 PDB를 가져옵니다.

4. 심볼 경로를 확인한다

Visual Studio라면 여기를 확인합니다.

Tools > Options > Debugging > Symbols

WinDbg라면 예를 들어 이렇게 확인합니다.

.sympath
.reload
!sym noisy

Microsoft의 공개 심볼을 쓴다면 로컬 캐시도 지정해 두면 편리합니다.

srv*C:\\Symbols*https://msdl.microsoft.com/download/symbols

5. 캐시를 의심한다

오래된 PDB나 깨진 캐시를 잡고 있는 경우도 있습니다. 심볼 캐시를 지우고, 다른 캐시를 지정하고, 로드 로그를 자세히 보는 식의 확인을 합니다.

Visual Studio 설정은 어디에 있는가

여기까지 나온 설정의 위치를 정리합니다. Visual Studio 버전에 따라 옵션 화면의 계층이 바뀌어 있으므로, 신구 경로를 모두 병기합니다.

하고 싶은 일 메뉴의 전체 경로
모듈과 심볼 상태를 본다 Debug > Windows > Modules(디버깅 중에만 유효. Ctrl + Alt + U)
그 모듈의 심볼을 수동으로 로드한다 Modules 창에서 모듈을 오른쪽 클릭 > Load Symbols
왜 로드하지 못했는지(어디를 찾았는지)를 본다 Modules 창에서 오른쪽 클릭 > Symbol Load Information
심볼 검색 장소를 편집한다 Tools(또는 Debug) > Options > Debugging > Symbols
새 화면에서는 Tools > Options > All Settings > Debugging > General > Symbols > Search Locations
Microsoft / NuGet.org 심볼 서버를 켠다 위와 같은 화면의 체크박스 Microsoft Symbol Servers / NuGet.org Symbol Server
심볼의 로컬 캐시 위치를 지정한다 위와 같은 화면의 Cache symbols in this directory
Modules 창에서 심볼 설정 화면으로 이동한다 Modules 창에서 오른쪽 클릭 > Symbol Settings
Just My Code를 켜거나 끈다 Tools(또는 Debug) > Options > Debugging > General > Enable Just My Code
새 화면에서는 All Settings > Debugging > General 아래
Source Link를 켠다 위와 같은 Debugging > GeneralEnable Source Link support

캐시 위치에 대해서는 공식 문서에 두 가지 주의가 있습니다.

  • C:\\Windows처럼 보호된 폴더를 지정하지 않는다. 읽고 쓸 수 있는 폴더를 쓴다
  • 환경 변수 _NT_SYMBOL_PATH가 설정되어 있으면 그쪽이 Cache symbols in this directory 설정을 덮어쓴다

한 가지 더, Enable Just My Code는 Visual Studio 전체의 전역 설정입니다. 프로젝트 단위가 아니므로, 어떤 건 때문에 바꾼 채로 잊으면 다른 건의 디버깅에서 「왠지 step into가 안 된다」는 형태로 되돌아옵니다.

참고로 DebugType은 GUI에서 만지기보다 csproj의 <PropertyGroup>에 직접 쓰는 것을 권장합니다. 프로젝트 속성 화면에도 해당하는 항목은 있지만, SDK 스타일 여부와 Visual Studio 버전에 따라 항목명도 위치도 바뀝니다. csproj에 써 두면 리뷰에도 차이에도 남습니다(14장).

30. PDB와 「Just My Code」

Visual Studio에는 Just My Code라는 설정이 있습니다.

이것은 디버깅 대상을 「내 코드」로 좁혀 외부 코드를 스텝 실행하기 어렵게 하는 기능입니다. 평소 개발에서는 편리하지만, PDB나 Source Link 확인 때는 혼란의 원인이 되는 경우가 있습니다.

예를 들어 외부 NuGet 라이브러리에 PDB와 Source Link가 있는데도 step into하지 못하는 경우에는 이 근처를 확인합니다.

Just My Code가 켜져 있어 외부 코드 취급이 되어 있지 않은가
Enable Source Link support가 켜져 있는가
NuGet.org Symbol Server가 켜져 있는가
대상 패키지가 PDB / .snupkg를 공개하고 있는가
소스 가져오기 대상에 접근할 수 있는가

「PDB가 나쁘다」고 단정하기 전에 디버거 설정도 확인합니다. 설정 화면의 위치는 29장의 「Visual Studio 설정은 어디에 있는가」에 정리해 두었습니다. Enable Just My CodeEnable Source Link support는 둘 다 Tools(또는 Debug) > Options > Debugging > General에 있습니다.

31. PDB와 디컴파일

최근 Visual Studio는 .NET 어셈블리를 디컴파일해 디버깅에 쓸 수 있습니다.

이에 따라 PDB나 소스가 없는 외부 라이브러리에서도 내용을 어느 정도 따라갈 수 있는 경우가 있습니다.

다만 디컴파일은 만능이 아닙니다.

원래 주석은 돌아오지 않는다
원래 공백이나 구조는 돌아오지 않는다
로컬 변수명이 바뀌는 경우가 있다
async / iterator / pattern matching 등은 원래 형태와 다르게 보인다
최적화된 코드에서는 대응이 알기 어렵다

PDB와 Source Link가 있다면 기본적으로는 그것을 쓰는 편이 자연스럽습니다. 디컴파일은 「PDB나 소스가 없을 때의 보조 수단」으로 생각하면 됩니다.

32. 로그 설계와 PDB

PDB는 로그 설계와도 관계가 있습니다.

예를 들어 예외 로그에 파일명·줄 번호가 나오면 조사하기 쉬워집니다. 그러나 로그만 의지하는 것은 위험합니다.

프로덕션 장애에서는 이런 일이 일어납니다.

로그에 나온 줄 번호가 지금의 main 브랜치와 맞지 않는다
hotfix 후에 같은 버전 번호로 다시 배포되어 있었다
PDB가 남아 있지 않아 줄 번호의 의미를 검증할 수 없다
컨테이너 이미지는 남아 있지만 대응하는 소스 커밋을 모른다

그래서 로그에는 줄 번호뿐 아니라 빌드 정보도 내는 것이 좋습니다.

ApplicationVersion: 1.8.3
GitCommit: abcdef1234567890
BuildNumber: 20260610.12
Environment: Production

PDB, 소스, 로그, 덤프, 배포 이력이 연결되면 장애 조사가 상당히 편해집니다.

33. 컨테이너 운영에서의 PDB

컨테이너에서 .NET 앱을 돌리는 경우 PDB 취급을 명확히 해 둘 필요가 있습니다.

예를 들어 Docker 이미지에 PDB를 포함할지입니다.

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY publish/ .
ENTRYPOINT ["dotnet", "MyApp.dll"]

publish/에 PDB가 들어 있으면 그대로 이미지에도 들어갑니다.

여기에는 장점이 있습니다.

  • 컨테이너 안에서 스택 트레이스의 줄 번호가 나오기 쉽다
  • 덤프 취득 때 같은 파일 시스템 위에서 찾기 쉽다
  • 조사 때의 대응이 간단하다

한편 우려도 있습니다.

  • 이미지 크기가 늘어난다
  • 디버그 정보가 프로덕션 이미지에 포함된다
  • 이미지를 외부 배포하는 경우 정보 공개 범위가 넓어진다

사내 전용 SaaS라면 PDB를 이미지에 포함하는 선택은 충분히 있을 수 있습니다. 외부 고객에게 넘기는 온프레 제품이라면 PDB는 따로 보관하는 편이 좋은 경우가 있습니다.

어느 쪽이든 PDB를 이미지에 넣지 않는 경우에도, 대응하는 PDB를 artifact로 보관하는 것은 필수입니다.

34. 단일 파일 발행과 PDB

.NET에는 단일 파일 발행이 있습니다.

dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true

이때도 디버그 정보 취급을 확인할 필요가 있습니다.

단일 파일로 만들었다고 해서 장애 조사가 불필요해지는 것은 아닙니다. 오히려 배포 형태가 특수해질수록, 대응하는 심볼과 소스를 어떻게 저장할지가 중요합니다.

방침으로 정하는 것은 이 근처입니다.

PDB를 별도 파일로 배포하는가
DebugType=embedded로 하는가
심볼은 사내에만 저장하는가
크래시 덤프를 어떻게 분석하는가

단일 파일 발행, 트리밍, AOT 등을 쓰는 경우에는 일반적인 IL 어셈블리와 조사 경험이 달라지는 경우가 있습니다. 릴리스 전에 「크래시하면 어떻게 읽을까」를 한 번 시험해 두는 것이 안전합니다.

35. PDB와 트리밍 / AOT

현행 .NET에서는 트리밍이나 Native AOT를 쓰는 경우가 있습니다.

이 경우 PDB뿐 아니라, 생성되는 네이티브 심볼이나 플랫폼별 디버그 정보도 관계합니다.

예를 들어 Linux라면 DWARF, macOS라면 dSYM, Windows라면 PDB처럼 네이티브 쪽 디버그 정보도 생각할 필요가 있습니다.

.NET 애플리케이션에서도 이런 구성에서는 심볼 설계가 복잡해집니다.

Native AOT
Self-contained publish
PublishSingleFile
ReadyToRun
P/Invoke로 네이티브 DLL을 호출한다
C++/CLI를 포함한다

일반적인 Web 앱이나 클래스 라이브러리에서는 먼저 Portable PDB와 Source Link를 이해하면 충분합니다. 다만 배포 형태를 고도화할수록 「이 크래시를 어떻게 분석하는가」를 빌드 설계에 넣을 필요가 있습니다.

36. .NET Framework 프로젝트에서의 주의점

.NET Framework의 오래된 프로젝트에서는 SDK 스타일 프로젝트와 설정이나 기본값이 다른 경우가 있습니다.

예를 들어 이런 차이입니다.

csproj 형식이 오래되었다
packages.config를 쓰고 있다
DebugType 기본값이 현행 .NET과 다르다
Windows PDB가 쓰이고 있다
Source Link 설정에 추가 패키지나 MSBuild 설정이 필요하다
CI의 MSBuild 버전이 오래되었다

.NET Framework에서도 PDB의 사고방식은 같습니다.

실행에는 필수가 아니다
디버깅과 장애 조사에는 중요하다
대상 바이너리와 일치하는 PDB가 필요하다
Release 빌드의 PDB를 보관해야 한다

다만 현행 .NET의 설명을 그대로 복사하면 오래된 프로젝트에서는 기대대로 동작하지 않는 경우가 있습니다.

기존 자산에서는 먼저 실제 출력을 확인합니다.

msbuild MyApp.csproj /p:Configuration=Release
Get-ChildItem bin\\Release -Filter *.pdb -Recurse

그다음에 빌드 설정과 CI artifact를 정비합니다.

37. OSS 라이브러리에서는 어떻게 하는가

OSS의 .NET 라이브러리라면 기본적으로는 이 구성을 권장합니다.

Release에서도 PDB를 생성한다
Portable PDB를 쓴다
Source Link를 켠다
NuGet에 .snupkg를 공개한다
Repository 메타데이터를 설정한다
결정적 빌드를 의식한다

예입니다.

<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <DebugType>portable</DebugType>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

SDK나 호스팅 환경에 따라 Source Link 추가 패키지가 필요 없는 경우도 있습니다. 오래된 SDK나 특수한 호스팅에서는 대응하는 Microsoft.SourceLink.* 패키지를 추가합니다.

OSS에서는 이용자가 라이브러리 안으로 step into할 수 있는 것 자체가 품질이 됩니다. 「문제가 났을 때 읽을 수 있는 라이브러리」는 그것만으로 신뢰받기 쉬워집니다.

38. 사내 라이브러리에서는 어떻게 하는가

사내 라이브러리에서도 Source Link와 PDB는 유효합니다.

오히려 사내 라이브러리일수록 업무 앱에서 step into할 수 있으면 도움이 됩니다.

사내 NuGet 피드를 쓰고 있다면 이 근처를 검토합니다.

사내 피드가 .snupkg에 대응하는가
대응하지 않는다면 PDB를 .nupkg에 넣을 것인가
사내 심볼 서버를 마련할 것인가
Git 리포지토리 인증을 어떻게 할 것인가
퇴사자나 이동 후에도 소스에 접근할 수 있는가

내부용이라고 해서 로컬 PC에만 PDB가 있는 상태는 피합니다.

CI에서 생성하고, 팀이 꺼낼 수 있는 장소에 저장합니다.

39. 온프레 제품에서는 어떻게 하는가

고객 환경으로 배포하는 온프레 제품에서는 PDB 취급이 어려워집니다.

PDB를 동봉하면 고객 측에서 얻은 덤프나 로그를 읽기 쉬워집니다. 그러나 내부 구조가 보이기 쉬워집니다.

흔한 선택지는 이 근처입니다.

PDB는 동봉하지 않지만 벤더 측에서 완전한 PDB를 보관한다
고객 지원용으로 public symbols만 제공한다
장애 때 덤프를 회수하고 벤더 측에서 PDB를 써서 분석한다
중요 고객에게 한정적인 심볼 패키지를 제공한다

중요한 것은 릴리스 후에 PDB를 잃지 않는 것입니다.

온프레 제품에서는 몇 년 전 버전의 장애를 조사하는 경우도 있습니다. 그때 대응하는 PDB가 없으면 조사 능력이 크게 떨어집니다.

40. PDB를 지워 버리면 어떻게 되는가

PDB를 지워 버려도 애플리케이션은 동작합니다. 그러나 나중에 곤란합니다.

같은 커밋에서 다시 빌드하면 재현할 수 있다고 생각할 수 있습니다. 그러나 완전한 재현은 의외로 어렵습니다.

SDK 버전이 다르다
NuGet 해석 결과가 다르다
빌드 시각이나 환경 변수가 다르다
생성 코드가 다르다
조건부 컴파일이 다르다
CI에서만 들어가는 설정이 있다
의존하는 네이티브 도구가 다르다

결정적 빌드를 갖추면 재현성은 높아지지만, 그래도 「처음부터 산출물로서 저장해 두는」 편이 안전합니다.

PDB는 보험에 가깝습니다. 필요해졌을 때 비로소 가치가 나옵니다. 필요해졌을 때 없으면 되돌릴 수 없습니다.

41. 실무에서의 권장 설정

모든 프로젝트에 해당하는 유일한 설정은 없습니다. 다만 일반적인 .NET 애플리케이션이라면 먼저 이것을 기준으로 할 수 있습니다.

사내 애플리케이션

<PropertyGroup>
  <DebugType>portable</DebugType>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

방침은 이렇습니다.

Release에서도 PDB를 생성한다
프로덕션에 둘지는 운영 정책으로 정한다
CI artifact에 반드시 저장한다
덤프 분석 절차를 마련한다

공개 NuGet 라이브러리

<PropertyGroup>
  <DebugType>portable</DebugType>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

방침은 다음과 같습니다.

Source Link를 켠다
.snupkg를 공개한다
불필요한 소스 임베드는 피한다
공개되는 메타데이터를 확인한다

작은 내부 도구

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

방침입니다.

PDB를 빼먹지 않는다
배포물 크기 증가는 허용한다
내부 이용으로 한정한다

외부 배포 제품

완전한 PDB를 사내에서 보관한다
필요하면 public symbols를 따로 만든다
고객 배포물에 넣을 PDB 내용을 확인한다
지원 때의 덤프 회수와 분석 절차를 정한다

42. PDB를 볼 때의 체크리스트

마지막으로 PDB 취급에서 헤맸을 때의 체크리스트를 둡니다.

이 PDB는 어느 DLL / EXE에 대응하는가
그 DLL / EXE는 어느 커밋에서 빌드되었는가
Debug 빌드용이 아니라 프로덕션 Release용 PDB인가
PDB는 CI artifact로서 보관되어 있는가
심볼 서버 또는 가져오기 절차가 있는가
Source Link는 유효한가
소스 가져오기 대상의 권한은 적절한가
PDB에 공개하고 싶지 않은 정보가 포함되어 있지 않은가
외부 배포라면 public/private symbol 방침이 있는가
덤프 분석 때 PDB를 로드할 수 있음을 확인했는가

특히 중요한 것은 이 세 가지입니다.

PDB는 실행 파일이 아니라 조사용 정보이다
PDB는 대상 바이너리와 일치해야 한다
PDB는 프로덕션 장애가 나기 전에 보관해 둘 필요가 있다

43. 정리

PDB는 .dll이나 .exe 옆에 나오는 「잘 모르는 파일」이 아니라, 빌드 후 바이너리와 개발자가 읽을 수 있는 소스 코드를 연결하기 위한 디버그 정보입니다.

애플리케이션의 일반 실행에는 필수가 아닙니다. 그러나 디버깅, 예외 조사, 덤프 분석, 프로파일링, 외부 라이브러리로 step into할 때는 중요합니다.

Release 빌드에서도 PDB는 유용합니다. 오히려 프로덕션 장애에서 필요해지는 것은 Release 빌드에 대응하는 PDB입니다.

PDB를 프로덕션 환경에 둘지는 정보 공개 범위나 운영 방침에 따라 정합니다. 그러나 PDB를 생성하고 빌드 산출물로서 보관하는 것은 대부분의 프로젝트에서 필요합니다.

실무에서는 이 방침을 기본으로 하면 됩니다.

Release에서도 PDB를 생성한다
PDB를 CI/CD 산출물로서 보관한다
바이너리, PDB, 커밋 ID, 빌드 번호를 연결한다
Source Link로 소스까지 따라갈 수 있게 한다
NuGet 라이브러리에서는 .snupkg를 검토한다
외부 배포에서는 공개하는 심볼 정보를 확인한다

PDB는 문제가 나지 않았을 때는 눈에 띄지 않습니다. 그러나 문제가 났을 때 조사하는 사람을 소스 코드로 되돌려 주는 중요한 실마리입니다.

「PDB는 지워도 동작한다」가 아니라 이렇게 생각하는 편이 좋습니다.

PDB는 미래의 장애 조사에 필요한 지도이다.

참고

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

PDB 파일이란 무엇인가요?
PDB는 Program Database(프로그램 데이터베이스)의 약어로, 심볼 파일이라고도 불리는 디버그 정보 파일입니다. 빌드된 .dll이나 .exe와, 개발자가 읽을 수 있는 소스 코드를 연결하는 역할을 합니다. 함수명과 메서드명, 로컬 변수명, 소스 파일명과 줄 번호, 소스에서의 위치와 컴파일 후 명령의 대응, Source Link용 소스 가져오기 정보 등이 들어 있으며, 디버거나 진단 도구가 '이 명령이 어느 소스 줄에 대응하는지'를 판단하는 데 사용합니다.
PDB 파일이 없어도 앱은 동작하나요? 삭제해도 괜찮나요?
일반적으로 PDB는 애플리케이션 실행에 필요하지 않으며, .dll이나 .exe만 있으면 실행할 수 있습니다. 다만 PDB가 없으면 breakpoint나 스텝 실행, 예외 스택 트레이스의 파일명·줄 번호 표시, 덤프 분석, 외부 라이브러리로 step into가 어려워집니다. PDB는 '실행하기 위한 파일'이 아니라 '조사하기 위한 파일'이므로, 프로덕션에 둘지 여부와는 별개로 빌드 산출물로서 반드시 보관해 두어야 합니다. 지워 버리면 같은 커밋에서 다시 빌드해도 완전한 재현은 의외로 어려워, 장애 조사에서 되돌릴 수 없게 되는 경우가 있습니다.
Release 빌드에서도 PDB가 필요한가요?
필요합니다. 오히려 프로덕션 장애 조사에서 필요해지는 것은 Release 빌드의 PDB입니다. 프로덕션에서 동작하는 것이 Release 빌드라면 Debug 빌드의 PDB를 가지고 있어도 의미가 없고, 디버거가 필요로 하는 것은 그 프로덕션 바이너리를 만들었을 때 생성된 PDB입니다. PDB는 대상 바이너리와 일치해야 하므로, CI/CD에서는 커밋 ID·빌드 번호·.dll/.exe·.pdb를 세트로 보관하는 것이 기본입니다. 참고로 현재의 .NET SDK 스타일 프로젝트에서는 Debug / Release 모두 기본값으로 Portable PDB가 생성됩니다.
PDB 파일을 프로덕션에 두어도 되나요?
단순한 예/아니오가 아니라, 장애 조사의 편의성, 정보 공개 위험, 배포 크기, 운영 규칙이라는 4가지 축으로 판단합니다. 사내 시스템이라면 .dll과 .pdb를 같은 폴더에 두면 예외 로그에 줄 번호가 나오기 쉬워져 현실적입니다. 외부 배포에서는 PDB로부터 로컬 경로, 사내 폴더 구성, 타입명이나 로컬 변수명, Source Link의 리포지토리 URL 등을 알 수 있는 경우가 있으므로 신중히 판단하고, 필요하다면 public symbols로 한정하거나 심볼 서버를 경유하는 등의 선택지를 취합니다. 배치하지 않는 경우에도 같은 빌드의 PDB 보관은 필수입니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기