Что такое PDB (Program Database) — разбираемся в отладочной информации, символах и Source Link

· · .NET, C#, Visual Studio, PDB, Отладка, Символы, Source Link, Диагностика, Эксплуатация, Использование существующих активов

1. Что нужно понять в первую очередь

Когда вы собираете приложение на .NET или C++, вместе с .dll или .exe иногда генерируется файл .pdb.

Например, вот такой результат сборки.

MyApp.exe
MyApp.dll
MyApp.pdb

Если продолжать разработку, не понимая, что такое этот .pdb, рано или поздно возникают такие вопросы:

  • Можно ли размещать .pdb в продуктивной среде?
  • Перестанет ли приложение работать без .pdb?
  • Странно ли, что .pdb генерируется даже для Release-сборки?
  • Содержит ли .pdb весь исходный код целиком?
  • Гарантирует ли наличие .pdb, что точку останова можно поставить всегда?
  • Почему .pdb нужен для анализа дампов и расследования сбоев?
  • Как обращаться с .pdb в NuGet-пакетах?
  • В чём разница между Source Link, символьным сервером и .snupkg?

В повседневной разработке PDB редко становится главным героем. Но для расследования сбоев, анализа дампов, распространения библиотек, читаемости эксплуатационных логов и удобства отладки он весьма важен.

Сформулируем вывод сразу.

PDB - это файл отладочной информации, который связывает исполняемый файл или сборку с исходным кодом. Это не сама программа, а средство, которое сообщает отладчику и диагностическим инструментам, «какая инструкция какой строке исходного кода соответствует», «что представляет собой та или иная локальная переменная» и «в какой исходник нужно заглянуть».

В этой статье PDB рассматривается не как «непонятный довесок для отладки», а как результат сборки, которым нужно управлять осознанно в реальной практике.

2. Что такое PDB

PDB - это сокращение от Program Database, что переводится как программная база данных. Файлы .pdb также часто называют файлами символов.

Символы, если говорить упрощённо, - это информация об именах и позициях в программе. Например:

  • имена функций;
  • имена методов;
  • имена локальных переменных;
  • имена параметров;
  • информация о типах;
  • имена исходных файлов;
  • номера строк исходного кода;
  • соответствие между позициями в исходном коде и скомпилированными инструкциями;
  • информация, по которой отладчик расставляет точки останова;
  • информация для получения исходников через 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++ - бинарный код, близкий к машинному.

В результате из одного лишь исполняемого файла становится трудно или вовсе невозможно понять такие вещи:

Какой строке какого .cs соответствует этот машинный код / IL
Какой позиции какой функции соответствует этот адрес
Как называлась эта локальная переменная
В какую именно позицию инструкции на самом деле нужно поставить эту точку останова
Какому исходнику соответствует этот стековый кадр

PDB - это файл, который заполняет этот разрыв.

3. Нужен ли PDB для запуска

Как правило, PDB не требуется для запуска приложения. Если есть .dll или .exe, приложение может стартовать. Отсутствие .pdb само по себе не мешает выполнению обычной обработки.

Однако без PDB усложняются такие вещи:

Что хочется сделать Что мешает без PDB
Точно выполнять пошаговую отладку в Visual Studio Невозможно сопоставить строку исходника и позицию выполнения
Поставить точку останова Позиция соответствующей инструкции неизвестна, точка останова может остаться нерешённой
Показать имя файла и номер строки в трассировке стека исключения Информация о номере строки отсутствует или неполна
Проанализировать файл дампа Становится сложно читать стек, переменные и типы
Зайти внутрь внешней библиотеки Не удаётся привязаться к исходнику библиотеки
Прочитать нативный краш Видны только адреса, без имён функций и позиций

Иными словами, 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 или встроенный исходный код.

Поэтому нельзя сказать, что «раз это не сам исходный код, публиковать его можно, ни о чём не задумываясь».

В нём могут оказаться внутренние имена проектов, пути, включающие имя пользователя, структура внутренних директорий, ещё не опубликованные имена типов и имена, по которым можно догадаться о бизнес-логике.

7. Частое заблуждение 1: PDB замедляет продакшен

То, что PDB просто лежит рядом, само по себе не замедляет обычную обработку приложения.

PDB используется тогда, когда отладчику или диагностическому инструменту нужна информация о символах. Обычная работа приложения не выполняется каждый раз с чтением PDB.

Разумеется, он используется при разрешении имени файла и номера строки в трассировке стека исключения, при подключении отладчика, а также когда профилировщик или диагностический инструмент читает символы.

Но понимание «размещение PDB всегда замедляет всё, поэтому его ни в коем случае нельзя класть в продакшен» - слишком грубое.

На практике безопаснее рассуждать так:

Удалять PDB исключительно ради производительности выполнения смысла почти нет
Решение о размещении принимается по масштабу публикации, риску утечки информации, размеру поставки и политике эксплуатации
Даже если PDB не размещается вместе с приложением, PDB той же сборки нужно обязательно хранить

8. Частое заблуждение 2: Release-сборке PDB не нужен

PDB полезен и для Release-сборок. Более того, для расследования сбоев в продакшене нужен именно PDB от Release-сборки.

Если в продакшене работает Release-сборка, то PDB от Debug-сборки бесполезен. Отладчику нужен именно тот PDB, который был сгенерирован при сборке этого продуктивного бинарника.

Здесь важно провести такое различие:

Пункт Смысл
Debug / Release Конфигурация сборки: оптимизация, условная компиляция, настройки вывода и т. п.
Наличие PDB Генерируется ли и сохраняется ли отладочная информация
Удобство отладки Определяется наличием оптимизации, содержимым PDB, соответствием исходников, поведением JIT и т. п.

Release-сборка обычно оптимизирована, поэтому пошаговая отладка в ней менее наглядна, чем в Debug-сборке. Локальные переменные могут исчезать из-за оптимизации, а остановка может происходить не в порядке строк исходника.

Тем не менее с PDB получить такую информацию гораздо проще:

  • строку исходника, где произошло исключение;
  • имена методов в стеке;
  • соответствующую позицию при анализе дампа;
  • имена функций в результатах профилирования;
  • сопоставление логов с исходным кодом.

Дело не в том, что «Release - значит, PDB не нужен». Именно потому, что это Release, нужно сохранять PDB, соответствующий этой сборке.

9. Частое заблуждение 3: с PDB можно отладить любой бинарник

PDB нельзя использовать произвольно с любым .dll или .exe.

Отладчик проверяет, совпадает ли целевой бинарник с PDB. Если насильно использовать несоответствующий PDB, сопоставление строк исходника, функций и переменных собьётся.

Например, в таких ситуациях PDB может оказаться бесполезным или неприменимым, даже если он у вас есть:

Вы пытаетесь применить локально пересобранный PDB к продуктивной DLL
Номер версии тот же, но фактически сборка сделана из другого коммита
Вы пытаетесь загрузить PDB, собранный до hotfix, для DLL, собранной после hotfix
Настройки оптимизации или условной компиляции отличаются

Не стоит думать «раз исходник тот же, скорее всего подойдёт» - правильнее считать, что «PDB должен соответствовать именно тому же результату сборки».

Поэтому в CI/CD принято сохранять как единое целое:

ID коммита
Номер сборки
Версию артефакта
.dll / .exe
.pdb
Информацию для привязки к исходнику

Важно не нарушать эту комбинацию.

10. Частое заблуждение 4: с PDB можно полностью читать код даже без исходников

Даже если PDB есть, исходный код в нём не обязательно присутствует.

PDB в первую очередь хранит информацию, которая сопоставляет исходник и бинарник. Он может содержать пути к исходным файлам, контрольные суммы и информацию Source Link, но обычный PDB не всегда несёт в себе полный текст исходника.

Поэтому если вы хотите зайти внутрь внешней библиотеки в отладчике, нужно одно из следующего:

У вас под рукой есть те же исходные файлы
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 держится на высоком уровне;
  • похоже на deadlock;
  • память постоянно растёт;
  • ответ не возвращается;
  • падение происходит на границе с нативной библиотекой.

В этом случае снимается файл дампа и анализируется.

Но одного дампа недостаточно. Дамп содержит состояние процесса на тот момент времени. Чтобы превратить попавшие туда стеки и модули в понятные человеку имена и строки исходника, нужен соответствующий 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», стоит уточнить, о каком контексте идёт речь:

Речь о Portable PDB в .NET
Речь о Windows PDB в Visual C++
Речь о старом проекте на .NET Framework
Речь о PDB для распространения через NuGet
Речь о символах, используемых в WinDbg

14. DebugType в .NET

В проектах на C# способ вывода отладочной информации задаётся параметром DebugType.

Основные значения такие:

DebugType Значение
portable Генерирует Portable PDB как отдельный файл
embedded Встраивает отладочную информацию, эквивалентную Portable PDB, в .dll / .exe
full Генерирует PDB в формате по умолчанию для текущей платформы
pdbonly Начиная с C# 6.0 фактически не отличается от full
none Не генерирует PDB

В текущих проектах в стиле .NET SDK значение DebugType для C# по умолчанию равно portable как для Debug, так и для Release. Поэтому обычно нет необходимости явно указывать DebugType только ради генерации Portable PDB.

Указывать его явно имеет смысл, если проект или организация хочет зафиксировать «мы придерживаемся именно этого формата», сделать более заметным отличие от старых проектов, либо выбрать политику, отличную от значения по умолчанию, например 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, иногда встречается пример с DebugSymbols, установленным в false.

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

Но если намерение - надёжно исключить генерацию PDB, понятнее установить DebugType в none.

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

Такую настройку иногда используют как политику распространения библиотеки или приложения. Но повторим ещё раз: отказ от генерации PDB снижает способность расследовать сбои.

На практике часто применяют такое разделение:

PDB генерируется обязательно как результат сборки
Размещать ли его на продуктивном сервере - решается отдельно
Даже если не размещается, он хранится в артефактах CI или на символьном сервере

16. PDB в C++ немного отличается от .NET

Контекст PDB в C++ немного отличается от контекста PDB в .NET.

В Visual C++ PDB генерируется такими опциями, как /Zi или /ZI. Также имеет значение, что есть PDB, который использует компилятор, и PDB, который линкер формирует для итогового .exe / .dll.

В C++ PDB критически важен для чтения адресов нативного кода, функций, типов, локальных переменных, инлайнинга и позиций после оптимизации.

При расследовании нативных сбоев без PDB картина обычно такая:

Адрес исключения известен
Имя модуля известно
Но имя функции и строка исходника неизвестны

В приложениях, где смешаны C++ и C#, при использовании P/Invoke, C++/CLI, а также в .NET-приложениях, вызывающих нативные DLL, нужны не только PDB со стороны .NET, но и PDB со стороны нативного кода.

17. Public symbols и private symbols

В мире символов Windows есть разделение на public symbols и private symbols.

Упрощённо разница такая:

Вид Приблизительное содержимое
private symbols Почти полная информация, включая локальные переменные, типы, параметры и подробные внутренние детали
public symbols Информация, урезанная для публикации: имена функций, адреса и т. п.

В символах, распространяемых вовне, иногда убирают private symbols, оставляя только public symbols.

Это делается ради баланса между возможностью отладки и масштабом раскрытия информации.

Например: для анализа сбоев собственного продукта хочется показывать хотя бы минимальные имена функций, но не хочется раскрывать имена внутренних локальных переменных и информацию о типах. В таких случаях используют urезанный (stripped) PDB.

В нативной разработке под Windows иногда создают PDB без private symbols с помощью инструментов вроде PDBCopy.

При этом для внутреннего расследования сбоев нужно хранить полный PDB. Если остался только PDB, урезанный для внешнего распространения, при глубоком расследовании вы столкнётесь с трудностями.

18. Где отладчик ищет PDB

Visual Studio и WinDbg ищут PDB в нескольких местах.

Основные из них такие:

Папка вывода проекта
Та же папка, что и .dll / .exe
Исходный путь PDB, записанный внутри .dll / .exe
Папки, указанные в настройках символов Visual Studio
Локальный кэш символов
Внутренний символьный сервер
Microsoft Symbol Server
NuGet.org Symbol Server
Символьные серверы вроде Azure Artifacts

Если PDB есть, но не загружается, стоит по порядку проверить следующее:

Соответствует ли PDB целевому бинарнику
Находится ли PDB на пути поиска отладчика
Доступен ли символьный сервер
Не осталась ли в локальном кэше устаревшая копия
Не отключена ли загрузка символов для целевого модуля
Не считается ли он внешним кодом из-за настройки Just My Code

В Visual Studio во время отладки в окне Modules можно увидеть состояние загрузки символов для каждого модуля.

Debug
  Windows
    Modules

Здесь смотрят на Symbol Status нужной DLL.

Часто встречаются такие четыре варианта отображения:

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

Если возникли проблемы с PDB, кратчайший путь - сначала заглянуть в окно Modules.

19. Что такое символьный сервер

Символьный сервер - это механизм, который позволяет отладчику получать файлы символов, такие как PDB, когда они нужны.

Просто разместить 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

Появляются десятки файлов с одним и тем же именем MyApp.pdb.

Символьный сервер организует PDB не просто по имени файла, а по информации о соответствии бинарнику. Благодаря этому отладчику проще найти «PDB, который подходит именно к этой DLL».

Пример конфигурации, применимой на практике:

Microsoft Symbol Server
  Используется для получения символов Windows, среды выполнения .NET и т. п.

NuGet.org Symbol Server
  Используется для получения символов публичных NuGet-пакетов

Внутренний символьный сервер
  Хранит PDB собственных приложений и библиотек

Локальный кэш символов
  Повторно использует однажды полученные PDB, ускоряя отладку

Если вы расследуете продуктивные сбои во внутренних сервисах, удобно настроить публикацию PDB из CI во внутреннее символьное хранилище.

Source Link - это механизм, который связывает PDB с системой управления версиями исходного кода.

Даже если PDB «знает», что эта позиция соответствует такому-то исходному файлу, отладчик не сможет его показать, если этого файла нет под рукой.

С Source Link отладчик может использовать информацию, встроенную в PDB, чтобы получить исходные файлы нужного коммита из GitHub, Azure Repos, GitLab, Bitbucket и т. п.

Иными словами, Source Link решает такую проблему:

Хочется зайти внутрь библиотеки, полученной через NuGet
Исходник этой библиотеки локально не клонирован
Но есть PDB и информация о репозитории
Отладчик сам отправляется за исходником нужного коммита

Это заметно улучшает опыт для пользователей библиотек.

Суть Source Link в том, что он ссылается не на «последнюю ветку main», а на «коммит, из которого собран этот бинарник».

Важно именно то, что связь идёт не с самым свежим исходником, а с исходником на момент сборки.

В SDK .NET 8 и новее работа с Source Link улучшена.

Для часто используемых провайдеров - GitHub, Azure Repos, GitLab, Bitbucket - механизм Source Link теперь включён прямо в .NET SDK.

Поэтому прежнее понимание, что нужно обязательно явно добавлять Microsoft.SourceLink.GitHub и подобные пакеты, постепенно устаревает.

Тем не менее проверка иногда всё же нужна:

Сборка выполняется на SDK старше .NET 8
Проект старый, не в стиле SDK
Используется собственный self-hosted Git-хостинг
Используется провайдер Source Link вне стандартного набора
Хочется дополнительно привести в порядок метаданные NuGet-пакета

Если нужно вывести информацию о репозитории в NuGet-пакет, используется такая настройка.

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

Если нужно встроить неотслеживаемые файлы в PDB, стоит рассмотреть такую настройку.

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

Однако встраивание влияет на масштаб раскрытия информации. То, что именно попадёт в PDB, нужно решать в соответствии с аудиторией публикации и политикой эксплуатации.

22. Что такое embedded PDB

Если для DebugType указать embedded, отладочная информация Portable PDB встраивается в .dll или .exe. В этом случае отдельный файл .pdb не генерируется.

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

Это удобно в таких случаях:

  • хочется распространять что-то близкое к единому файлу;
  • хочется избежать ситуации, когда PDB забыли приложить;
  • небольшой внутренний инструмент, для которого хочется держать отладочную информацию вместе с бинарником;
  • хочется уменьшить хлопоты с распространением PDB для NuGet-пакета.

Но есть и недостатки:

  • увеличивается размер сборки;
  • отладочная информация всегда присутствует в поставляемом артефакте;
  • контроль над масштабом раскрытия информации становится более грубым;
  • для крупных библиотек это влияет на 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, а Windows PDB, которые генерируют нативные проекты вроде C++, не принимаются. Если нужно распространять или хранить Windows PDB, стоит рассмотреть другие пути: устаревший .symbols.nupkg, внутренний символьный сервер или артефакты CI.

Чтобы создать такой пакет, пишут, например, так:

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

Это же можно указать в командной строке.

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

Для публичной NuGet-библиотеки использование .snupkg вместе с Source Link обычно даёт лучший баланс между размером дистрибутива и удобством отладки, чем включение PDB прямо в основной .nupkg.

Однако стоит учитывать поддержку со стороны фидов и инструментов. Если внутренний NuGet-фид не поддерживает .snupkg, придётся выбрать другой способ.

24. Стоит ли размещать PDB в продакшене

«Стоит ли выкладывать PDB в продуктивную среду» - вопрос не с простым ответом «да» или «нет».

Есть четыре критерия для решения:

Удобство расследования сбоев
Риск раскрытия информации
Размер дистрибутива
Правила эксплуатации

Для внутренней системы вполне реалистично хранить .dll и .pdb в одной папке. Тогда в логах исключений легче увидеть номера строк, а анализ дампов упрощается.

Для приложения с внешним распространением решение о том, включать ли PDB как есть, стоит принимать осторожно. Могут стать видны внутренняя структура, имена локальных переменных, пути к исходникам. При необходимости стоит выбрать один из вариантов: ограничиться public symbols, распространять через символьный сервер или хранить только для нужд поддержки.

Для веб-сервиса, помимо вопроса о размещении PDB на сервере, стоит подумать и об обработке трассировки стека, попадающей в логи. Трассировку стека не следует возвращать во внешнем ответе. Во внутренних логах её можно оставить, но нужно определить права доступа и срок хранения.

Практическая рекомендация такая:

PDB генерируется обязательно
PDB хранится как результат сборки
Размещение в продакшене решается в зависимости от масштаба публикации системы
При внешней публикации проверяется масштаб раскрываемой информации
PDB хранится там, откуда его можно извлечь для анализа дампов

25. Является ли PDB конфиденциальной информацией

PDB не обязательно требует того же обращения, что и сам исходный код или приватные ключи. Но относиться к нему как к безобидному файлу тоже опасно.

Вот что потенциально может раскрыть PDB:

  • локальные пути разработчиков;
  • структуру внутренних папок;
  • имена проектов;
  • имена классов и методов;
  • имена локальных переменных;
  • имена внутренних API;
  • бизнес-терминологию;
  • URL репозитория Source Link;
  • встроенный исходный код;
  • настройки Source Server.

В частности, в старом механизме Source Server и в некоторых функциях нативного PDB может быть задействовано выполнение команд отладчиком для получения исходника. Не стоит бездумно использовать недоверенные PDB или символьные серверы.

Осторожность нужна и при передаче недоверенных PDB инструментам и библиотекам, которые их разбирают. Для систем, автоматически обрабатывающих полученные извне PDB, стоит проектировать их так, чтобы входные данные не считались доверенными.

Подытоживая, безопасное обращение с PDB выглядит так:

Полные внутренние PDB защищаются как внутренние артефакты
Содержимое и аудиторию любых публикуемых вовне PDB нужно проверять
Если целью Source Link является приватный репозиторий, нужно управлять аутентификацией и правами доступа
Недоверенные символьные серверы не регистрируются в отладчике

26. Политика хранения PDB в CI/CD

PDB, который остался только на локальном компьютере разработчика, бесполезен. При продуктивном сбое нужен именно PDB, соответствующий фактически выпущенной сборке.

Поэтому в CI/CD PDB хранят примерно в такой форме:

Номер сборки: 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-файл

Важно не хранение PDB само по себе, а то, чтобы не потерять, какому именно бинарнику соответствует данный PDB.

Практика, при которой в файловом хранилище постоянно перезаписывается один и тот же MyApp.pdb, рано или поздно сломается. Нужно, чтобы PDB можно было извлечь по конкретной сборке, версии или коммиту.

27. Паттерны размещения PDB

Есть несколько практических паттернов обращения с PDB.

Паттерн 1: разместить PDB в той же папке, что и DLL

Самый простой вариант.

publish/
  MyApp.dll
  MyApp.pdb

Плюс - простота настройки. Отладчику и среде выполнения легко его найти, а номера строк в логах исключений появляются проще.

Минус - в поставляемом артефакте оказывается отладочная информация. Для внешнего распространения это может не подойти.

Паттерн 2: не размещать PDB, а хранить его в артефактах CI

PDB не выкладывается на продуктивный сервер, а сохраняется в артефактах CI.

release-artifacts/
  app.zip
  symbols.zip

При продуктивном сбое собирают дампы и логи, извлекают PDB соответствующего номера сборки и проводят анализ.

Это позволяет ограничить масштаб раскрытия информации, но требует процедуры извлечения во время расследования.

Паттерн 3: публиковать во внутренний символьный сервер

Для крупных команд это самый удобный вариант.

CI публикует PDB в символьное хранилище во время сборки. Visual Studio / WinDbg у разработчиков и исследователей обращаются к этому символьному серверу.

Плюс - можно безопасно работать с PDB для множества версий. Минус - нужна первоначальная настройка и контроль доступа.

Паттерн 4: распространять через .snupkg в NuGet

Сильный вариант для публичных библиотек.

MyLibrary.1.2.3.nupkg
MyLibrary.1.2.3.snupkg

Потребитель восстанавливает только обычный пакет, а символы, нужные при отладке, подгружаются отдельно.

В сочетании с Source Link становится проще заходить в исходник даже для внешних библиотек.

Паттерн 5: использовать embedded PDB

Удобно, чтобы избежать ситуации, когда PDB забыли приложить.

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

Но стоит следить за размером сборки и масштабом раскрытия информации.

28. Что проверить в первую очередь в существующем проекте

Если вы пересматриваете обращение с PDB в существующем .NET-проекте, начните с этих проверок:

Генерируется ли PDB в Release-сборке
Где хранятся сгенерированные 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>

Впрочем, добавлять одни и те же настройки во все проекты подряд - не всегда правильно. Оптимальное решение отличается для внутренних приложений, внешних библиотек, on-premise продуктов, SaaS и OSS.

29. Как разбираться, если PDB не загружается

Если PDB не загружается, не стоит пересобирать проект наугад - лучше разбираться по шагам.

1. Проверить целевой модуль

В окне Modules Visual Studio найдите нужный 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 или повреждённом кэше. Стоит очистить кэш символов, указать другой кэш или внимательно изучить подробный лог загрузки.

30. PDB и «Just My Code»

В Visual Studio есть настройка Just My Code.

Она сужает цель отладки до «своего кода», затрудняя пошаговую отладку внешнего кода. В повседневной разработке это удобно, но при проверке PDB или Source Link может стать источником путаницы.

Например, если во внешней NuGet-библиотеке есть PDB и Source Link, но зайти внутрь не получается, стоит проверить следующее:

Включён ли Just My Code и не считается ли код внешним
Включена ли поддержка Enable Source Link support
Включён ли NuGet.org Symbol Server
Публикует ли целевой пакет PDB / .snupkg
Доступен ли источник получения исходника

Прежде чем решить, что «дело в PDB», стоит проверить и настройки отладчика.

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.

Например, стоит решить, включать ли PDB в образ Docker.

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

Если PDB есть в publish/, они попадут и в образ как есть.

У этого есть плюсы:

  • в контейнере проще получить номера строк в трассировке стека;
  • при снятии дампа проще найти соответствующие файлы в той же файловой системе;
  • сопоставление во время расследования упрощается.

Но есть и опасения:

  • увеличивается размер образа;
  • отладочная информация оказывается в продуктивном образе;
  • если образ распространяется вовне, масштаб раскрытия информации расширяется.

Для внутреннего SaaS включение PDB в образ - вполне допустимый выбор. Для on-premise продукта, передаваемого внешним клиентам, PDB иногда лучше хранить отдельно.

В любом случае, даже если PDB не включён в образ, хранить соответствующий PDB как артефакт обязательно.

34. Публикация в один файл и PDB

В .NET есть публикация в единый файл.

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

И здесь нужно проверить, как обрабатывается отладочная информация.

Переход на единый файл не отменяет необходимости расследования сбоев. Наоборот, чем специфичнее формат распространения, тем важнее продумать, как сохранять соответствующие символы и исходник.

Как политику стоит определить примерно следующее:

Распространять ли PDB отдельным файлом
Использовать ли DebugType=embedded
Хранить ли символы только внутри компании
Как анализировать дампы после краша

При использовании публикации в один файл, trimming, AOT и подобных приёмов опыт расследования может отличаться от работы с обычными IL-сборками. Перед релизом безопаснее один раз проверить, «как читать краш, если он произойдёт».

35. PDB, trimming и AOT

В текущем .NET иногда используются trimming и Native AOT.

В этом случае имеют значение не только PDB, но и генерируемые нативные символы, а также отладочная информация для конкретной платформы.

Например, нужно учитывать и отладочную информацию на стороне нативного кода: DWARF в Linux, dSYM в macOS, PDB в Windows.

Даже для .NET-приложений в таких конфигурациях проектирование символов усложняется:

Native AOT
Self-contained publish
PublishSingleFile
ReadyToRun
Вызов нативной DLL через P/Invoke
Наличие C++/CLI

Для обычных веб-приложений и библиотек классов на первое время достаточно понимать Portable PDB и Source Link. Но чем сложнее формат распространения, тем важнее заранее включить в проектирование сборки вопрос «как анализировать этот краш».

36. Нюансы для проектов на .NET Framework

В старых проектах на .NET Framework настройки и значения по умолчанию иногда отличаются от проектов в стиле SDK.

Например, такие различия:

Старый формат csproj
Используется packages.config
Значение DebugType по умолчанию отличается от текущего .NET
Используется Windows PDB
Для настройки Source Link нужны дополнительные пакеты или настройки MSBuild
Версия MSBuild в CI старая

В .NET Framework подход к PDB остаётся тем же:

Не обязателен для выполнения
Важен для отладки и расследования сбоев
Нужен PDB, соответствующий целевому бинарнику
PDB Release-сборки стоит хранить

Однако если просто скопировать объяснения для текущего .NET, в старом проекте всё может работать не так, как ожидается.

Для существующих активов сначала стоит проверить фактический результат сборки.

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

После этого уже приводить в порядок настройки сборки и артефакты CI.

37. Как быть с OSS-библиотеками

Для OSS-библиотеки на .NET в целом рекомендуется такая конфигурация:

Генерировать PDB даже для Release
Использовать Portable PDB
Включить Source Link
Публиковать .snupkg в NuGet
Настроить метаданные 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 сама возможность для пользователя зайти внутрь библиотеки - это часть качества. «Библиотека, которую можно прочитать, когда что-то идёт не так», уже за счёт этого вызывает больше доверия.

38. Как быть с внутренними библиотеками

Source Link и PDB полезны и для внутренних библиотек.

Более того, именно во внутренних библиотеках особенно помогает возможность заходить внутрь прямо из бизнес-приложения.

Если вы используете внутренний NuGet-фид, стоит рассмотреть такие моменты:

Поддерживает ли внутренний фид .snupkg
Если нет - включать ли PDB прямо в .nupkg
Настраивать ли внутренний символьный сервер
Как организовать аутентификацию к Git-репозиторию
Останется ли доступ к исходнику после увольнения или перевода сотрудника

Не стоит допускать ситуации, когда PDB есть только на чьём-то локальном компьютере, - даже если библиотека внутренняя.

Их нужно генерировать в CI и хранить там, откуда команда сможет их извлечь.

39. Как быть с on-premise продуктами

Для on-premise продуктов, распространяемых в среды клиентов, обращение с PDB усложняется.

Если приложить PDB, дампы и логи, снятые у клиента, будет проще читать. Но при этом внутренняя структура становится более заметной.

Часто рассматриваются такие варианты:

PDB не прикладывается, но полная версия хранится на стороне вендора
Для клиентской поддержки предоставляются только public symbols
При сбое дамп собирается и анализируется на стороне вендора с использованием PDB
Для важных клиентов предоставляется ограниченный пакет символов

Важно не потерять PDB после релиза.

С on-premise продуктами иногда приходится расследовать сбой в версии, выпущенной несколько лет назад. Если к этому моменту соответствующего PDB нет, возможности расследования резко падают.

40. Что будет, если удалить PDB

Если удалить PDB, приложение продолжит работать. Но позже вы столкнётесь с трудностями.

Может показаться, что пересборка из того же коммита позволит всё восстановить. Но полное воспроизведение оказывается на удивление сложным делом:

Отличается версия SDK
Отличается результат разрешения NuGet
Отличается время сборки или переменные окружения
Отличается сгенерированный код
Отличается условная компиляция
Есть настройки, которые применяются только в CI
Отличаются зависимые нативные инструменты

Детерминированная сборка повышает воспроизводимость, но даже в этом случае безопаснее «сохранять как результат сборки с самого начала».

PDB - это что-то вроде страховки. Его ценность проявляется только тогда, когда он действительно нужен. А если его нет именно в тот момент, когда он понадобился, это уже непоправимо.

41. Рекомендуемые настройки на практике

Единой настройки, подходящей для всех проектов, не существует. Но для типичного .NET-приложения можно взять за основу следующее.

Внутренние приложения

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

Политика такая:

Генерировать PDB даже для Release
Решение о размещении в продакшене принимать по политике эксплуатации
Обязательно сохранять в артефактах CI
Подготовить процедуру анализа дампов

Публичные 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.

Какому DLL / EXE соответствует этот PDB
Из какого коммита была собрана эта DLL / EXE
Это PDB для продуктивной Release-сборки, а не для Debug
Хранится ли PDB как артефакт CI
Есть ли символьный сервер или процедура его получения
Включён ли Source Link
Уместны ли права доступа к источнику получения исходника
Не содержит ли PDB информацию, которую нежелательно раскрывать
Определена ли политика public/private symbols для внешнего распространения
Проверено ли, что PDB загружается при анализе дампа

Особенно важны эти три пункта:

PDB - это информация для расследования, а не исполняемый файл
PDB должен совпадать с целевым бинарником
PDB нужно сохранить до того, как случится продуктивный сбой

43. Заключение

PDB - это не «непонятный файл», появляющийся рядом с .dll или .exe, а отладочная информация, которая связывает собранный бинарник с исходным кодом, доступным для чтения разработчику.

Он не обязателен для обычного выполнения приложения. Но он важен для отладки, расследования исключений, анализа дампов, профилирования и захода внутрь внешних библиотек.

PDB полезен и для Release-сборок. Более того, при продуктивном сбое нужен именно PDB, соответствующий Release-сборке.

Размещать ли PDB в продуктивной среде - решается исходя из масштаба раскрытия информации и политики эксплуатации. Но генерировать PDB и хранить его как результат сборки необходимо практически в любом проекте.

На практике за основу стоит взять такую политику:

Генерировать PDB даже для Release
Хранить PDB как артефакт CI/CD
Связывать бинарник, PDB, ID коммита и номер сборки
Обеспечивать доступ к исходнику через Source Link
Для NuGet-библиотек рассматривать .snupkg
При внешнем распространении проверять раскрываемую информацию о символах

PDB не привлекает внимания, пока всё в порядке. Но когда что-то идёт не так, именно он становится той решающей зацепкой, которая возвращает исследователя к исходному коду.

Вместо «PDB можно удалить, и всё будет работать» лучше думать так:

PDB - это карта, которая понадобится для расследования будущих сбоев.

Источники

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Заблуждение, что TCP отдаёт данные через Receive такими же порциями, какими их отправили через Send — как проектировать приём, работая с байтовым потоком

Если считать, что в TCP-соединении данные можно принимать такими же порциями, какими их отправили через Send или Write, это приводит к ра...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Что такое файл PDB?
PDB - это сокращение от Program Database (программная база данных), файл отладочной информации, который также называют файлом символов. Он связывает собранные .dll или .exe с исходным кодом, который может прочитать разработчик. В нём хранятся имена функций и методов, имена локальных переменных, имена исходных файлов и номера строк, соответствие между позициями в исходном коде и скомпилированными инструкциями, а также информация для получения исходников через Source Link. Отладчики и диагностические инструменты используют эти данные, чтобы определить, «какой строке исходного кода соответствует данная инструкция».
Будет ли приложение работать без файла PDB? Можно ли его удалить?
Как правило, PDB не требуется для запуска приложения - оно стартует, если есть .dll или .exe. Однако без PDB становится сложно ставить точки останова и выполнять пошаговую отладку, показывать имя файла и номер строки в трассировке стека исключений, анализировать дампы и заходить внутрь внешних библиотек. PDB - это «файл для расследования», а не «файл для запуска», поэтому его нужно обязательно сохранять как результат сборки независимо от того, размещаете вы его в продуктивной среде или нет. Если PDB удалён, полное восстановление даже путём пересборки из того же коммита оказывается на удивление сложным, и при расследовании сбоя это может стать непоправимой потерей.
Нужен ли PDB для Release-сборки?
Да, необходим. Более того, для расследования сбоев в продуктивной среде нужен именно PDB от Release-сборки. Если в продакшене работает Release-сборка, то PDB от Debug-сборки бесполезен: отладчику нужен именно тот PDB, который был сгенерирован при сборке конкретного продуктивного бинарника. PDB должен совпадать с целевым бинарным файлом, поэтому в CI/CD базовая практика - хранить вместе ID коммита, номер сборки, .dll/.exe и .pdb. При этом в современных проектах в стиле .NET SDK Portable PDB по умолчанию генерируется и для Debug, и для Release.
Можно ли размещать файлы PDB в продуктивной среде?
Однозначного «да» или «нет» здесь нет - решение принимается по четырём критериям: удобство расследования сбоев, риск раскрытия информации, размер поставки и правила эксплуатации. Для внутренних систем размещение .pdb в той же папке, что и .dll, - вполне реалистичный вариант: в логах исключений будет удобнее видеть номера строк. При внешнем распространении стоит быть осторожнее, поскольку PDB может раскрыть локальные пути, структуру внутренних папок, имена типов и локальных переменных, URL репозитория Source Link и так далее; при необходимости стоит ограничиться public symbols или распространять символы через символьный сервер. Даже если PDB не размещается вместе с приложением, хранить PDB той же сборки обязательно.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог