¿Qué es un PDB (Program Database)? — Cómo entender la información de depuración, los símbolos y Source Link
· Actualizado el: · Go Komura · .NET, CSharp, VisualStudio, PDB, Debugging, Symbols, SourceLink, Diagnostics, Operación, Aprovechamiento de activos existentes
1. Lo primero que hay que entender
Al compilar una aplicación de .NET o C++, junto con el .dll o el .exe a veces se genera también un archivo .pdb.
Por ejemplo, una salida como esta.
MyApp.exe
MyApp.dll
MyApp.pdb
Si se sigue desarrollando sin saber qué es ese .pdb, surgen preguntas como estas.
- ¿Se puede colocar el
.pdben el entorno de producción? - ¿La aplicación no funciona sin el
.pdb? - ¿Es raro que aparezca un
.pdbsiendo un build Release? - ¿El
.pdbcontiene todo el código fuente? - ¿Si hay un
.pdb, siempre se pueden colocar puntos de interrupción? - ¿Por qué hace falta el
.pdbpara analizar volcados (dumps) o investigar incidentes? - ¿Cómo se debe tratar el
.pdben un paquete NuGet? - ¿En qué se diferencian Source Link, el servidor de símbolos y el
.snupkg?
El PDB no es protagonista en la implementación del día a día. Pero es bastante importante en la investigación de incidentes, el análisis de volcados, la distribución de bibliotecas, la legibilidad de los logs de operación y la experiencia de depuración.
Antes de nada, la conclusión.
El PDB es un archivo de información de depuración que enlaza el ejecutable o el ensamblado con el código fuente. No es el cuerpo principal que hace funcionar la aplicación, sino aquello que le indica al depurador o a las herramientas de diagnóstico “qué instrucción corresponde a qué línea de código fuente”, “qué es cada variable local” y “qué código fuente hay que mirar”.
En este artículo se aborda el PDB no como un simple “archivo adicional para depurar”, sino desde el punto de vista de cómo debe tratarse en la práctica como un artefacto más del trabajo.
Puntos de lectura según el objetivo
El artículo tiene 43 capítulos en total, pero no hace falta leerlo entero. Si ya sabe qué quiere saber, basta con leer los capítulos siguientes.
| Lo que quiere saber | Capítulos a leer |
|---|---|
| Qué es un PDB, qué contiene | Cap. 2 → Cap. 5 → Cap. 6 |
| Solo quiere decidir si puede colocarlo en producción | Cap. 24 (ejes de decisión) → Cap. 25 (confidencialidad) → Cap. 27 (patrones de ubicación) |
| Solo quiere decidir la política de distribución en NuGet | Cap. 20 (Source Link) → Cap. 22 (embedded) → Cap. 23 (.snupkg) → Cap. 41 (ejemplos de configuración) |
| Quiere decidir la configuración de build | Cap. 13 (tipos de PDB) → Cap. 14 (DebugType) → Cap. 41 (configuración recomendada) |
| No se pueden cargar los símbolos al depurar | Cap. 18 (orden de búsqueda) → Cap. 29 (pasos de diagnóstico) → Cap. 30 (Just My Code) |
| Quiere analizar un volcado de un incidente en producción | Cap. 11 (seguimiento de pila) → Cap. 12 (análisis de volcados) → Cap. 26 (conservación en CI/CD) |
| Quiere despejar antes los malentendidos habituales | Cap. 7 al Cap. 10 |
| Solo quiere saber la configuración, ya está | La siguiente sección y el Cap. 41 |
La conclusión primero — configuración recomendada para el trabajo diario
Sin esperar hasta el capítulo 41, adelantamos ya la conclusión de los 3 patrones más habituales. El razonamiento y las excepciones se explican en cada capítulo correspondiente.
| Destinatario | Configuración recomendada | En una frase |
|---|---|---|
| Aplicación interna | <DebugType>portable</DebugType><ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> |
Generar el PDB incluso en Release y conservarlo siempre como artefacto de CI |
| Biblioteca NuGet pública | Además de lo anterior<PublishRepositoryUrl>true</PublishRepositoryUrl><IncludeSymbols>true</IncludeSymbols><SymbolPackageFormat>snupkg</SymbolPackageFormat> |
Activar Source Link y distribuir el PDB como .snupkg |
| Herramienta interna pequeña | <DebugType>embedded</DebugType> |
Evitar olvidar el PDB en algún sitio; se acepta el aumento de tamaño |
En los productos de distribución externa la situación varía, así que consulte el capítulo 41. Y hay un principio que es común a cualquiera de estos patrones.
Independientemente de si se coloca o no en producción, el PDB generado en ese build siempre debe conservarse.
Volver a generar después el mismo PDB es sorprendentemente difícil incluso volviendo a compilar desde el mismo commit (capítulo 40).
2. Qué es un PDB
PDB es la sigla de Program Database, que en japonés se traduce como プログラムデータベース (base de datos de programa).
El archivo .pdb también se conoce a menudo como archivo de símbolos.
Un símbolo es, dicho de forma sencilla, información sobre los nombres y las posiciones dentro de un programa. Por ejemplo, cosas como estas.
- Nombres de funciones
- Nombres de métodos
- Nombres de variables locales
- Nombres de parámetros
- Información de tipos
- Nombres de archivos fuente
- Números de línea del código fuente
- La correspondencia entre la posición en el código fuente y las instrucciones ya compiladas
- Información para que el depurador coloque puntos de interrupción
- Información de obtención de código fuente para Source Link
Cuando se escribe código fuente, existen estos nombres comprensibles para una persona.
public decimal CalculateTotalPrice(Order order)
{
var subtotal = order.Lines.Sum(x => x.Price * x.Quantity);
var tax = subtotal * 0.10m;
return subtotal + tax;
}
Sin embargo, el .dll o .exe resultante de la compilación no es el código fuente en sí.
En .NET se convierte en IL y metadatos; en C++ nativo, en algo cercano a código máquina.
Como resultado, información como esta no se entiende bien, o resulta difícil de entender, con solo el ejecutable.
A qué línea de qué .cs corresponde este código máquina / IL
A qué función y qué posición corresponde esta dirección
Cuál era el nombre de esta variable local
En qué posición de instrucción hay que colocar realmente este punto de interrupción
A qué código fuente corresponde este marco de pila (stack frame)
El PDB es el archivo que llena este vacío.
3. ¿Es necesario el PDB para ejecutar la aplicación?
Normalmente, el PDB no es necesario para ejecutar la aplicación. Con tener el .dll o el .exe, la aplicación puede iniciarse.
El hecho de que no exista el .pdb no impide que el procesamiento normal se ejecute.
Sin embargo, sin el PDB resultan difíciles las siguientes cosas.
| Lo que quiere hacer | Lo que dificulta la falta de PDB |
|---|---|
| Ejecutar paso a paso con precisión en Visual Studio | No se puede establecer la correspondencia entre la línea de código fuente y la posición de ejecución |
| Colocar un punto de interrupción | No se sabe la posición de instrucción correspondiente y a veces el punto de interrupción queda sin resolver |
| Mostrar el nombre de archivo y el número de línea en el seguimiento de pila de una excepción | No aparece la información de número de línea, o resulta insuficiente |
| Analizar un archivo de volcado | Resulta difícil leer la pila, las variables y los tipos |
| Entrar (step into) dentro de una biblioteca externa | No se puede enlazar con el código fuente de la biblioteca |
| Leer un fallo nativo (native crash) | Solo se tienen direcciones, sin saber el nombre de la función ni la posición |
Es decir, el PDB no es “un archivo para hacer funcionar”, sino “un archivo para investigar”.
Esta diferencia es importante. Cuando ocurre un incidente en producción, la aplicación puede seguir funcionando sin el PDB, pero quien investiga sí lo necesita. Por eso, independientemente de si se coloca en el entorno de ejecución, el PDB siempre debe conservarse como artefacto de build.
4. Qué gana usted al tener el PDB
Cuando existe el PDB, al depurador y a las herramientas de diagnóstico les resulta más fácil devolver el binario a información legible para una persona.
Por ejemplo, sin PDB, la información de un fallo puede mostrarse así.
MyApp.dll!0x00007ff9a1234567
MyApp.dll!0x00007ff9a1234abc
MyApp.dll!0x00007ff9a1234def
Si el PDB se carga correctamente, se llega a ver hasta esto.
MyApp.Services.OrderService.CalculateTotalPrice(Order order) Line 42
MyApp.Controllers.OrderController.Post(CreateOrderRequest request) Line 87
MyApp.Program.Main(string[] args) Line 16
Esta diferencia es enorme.
Lo primero obliga a empezar la investigación a partir de una dirección. Lo segundo permite llegar desde el principio a “qué método, qué línea”.
En la investigación de un incidente, lo importante es si se puede acercarse a la causa en los primeros 30 minutos. El solo hecho de que quede el PDB cambia por completo el punto de partida de la investigación.
5. Qué contiene un PDB
La información que contiene un PDB varía según el lenguaje, el compilador, el formato del PDB y la configuración de build. Por eso no se puede afirmar simplemente que “el PDB siempre contiene tal cosa”.
Aun así, la sensación habitual de un desarrollador de .NET es esperar información principalmente de este tipo.
Correspondencia entre el archivo fuente y el código compilado
Números de línea del código fuente
Símbolos de métodos y funciones
Nombres de variables locales
Información de ámbito (scope)
Ruta y suma de comprobación (checksum) del archivo fuente
Información de Source Link
En algunos casos, el código fuente embebido
Lo especialmente importante es la correspondencia entre la posición en el código fuente y la posición en tiempo de ejecución.
Una línea escrita en C# puede convertirse en varias instrucciones en el IL o en el código nativo posterior al JIT. Y a la inversa, la optimización puede hacer que varias líneas de código fuente se agrupen, desaparezcan, o parezca que cambia su orden.
El depurador usa la información del PDB para determinar “qué línea de código fuente debe mostrarse ahora”.
6. Qué no contiene un PDB
Con el PDB, conviene entender mejor lo que no contiene que lo que sí contiene, para evitar malentendidos.
Normalmente, el PDB no es ninguna de estas cosas.
- No es el cuerpo principal de la aplicación
- No es un archivo de runtime imprescindible para la ejecución
- No es una copia de seguridad completa de todo el código fuente
- No sustituye al repositorio Git
- No reconstruye por completo toda la configuración de build ni la información del entorno
- No explica automáticamente, por sí solo, la causa de un defecto
Sin embargo, hay un matiz importante.
El PDB puede llegar a contener la ruta de los archivos fuente, nombres de tipos, nombres de funciones, nombres de variables locales, y en algunos casos información de Source Link o código fuente embebido.
Por eso no se puede decir que el PDB “no es el código fuente en sí, así que se puede publicar sin ninguna preocupación”.
Puede llegar a contener nombres de proyectos internos, rutas con el nombre de usuario, la estructura de directorios interna de la empresa, nombres de tipos no publicados, o nombres que permiten deducir la lógica de negocio.
7. Malentendido habitual 1: tener el PDB ralentiza la producción
El solo hecho de que el PDB esté colocado al lado no hace que el procesamiento normal de la aplicación se vuelva más lento.
El PDB es algo que usan el depurador o las herramientas de diagnóstico cuando necesitan información de símbolos. El procesamiento normal de la aplicación no lee el PDB en cada operación.
Por supuesto, sí se utiliza en situaciones como resolver el nombre de archivo y el número de línea en un seguimiento de pila de una excepción, adjuntar un depurador, o cuando un profiler o una herramienta de diagnóstico lee símbolos.
Pero entender que “colocar el PDB ralentiza siempre las cosas, así que nunca se debe poner en producción” es una simplificación excesiva.
En la práctica, es más seguro pensar así.
No hay una razón de peso, por motivos de rendimiento en ejecución, para eliminar el PDB
La política de ubicación se decide por el alcance de publicación, el riesgo de fuga de información, el tamaño de los artefactos y las reglas de operación
Aunque no se coloque, el PDB de ese mismo build siempre debe conservarse
8. Malentendido habitual 2: en un build Release no hace falta el PDB
El PDB también es útil en un build Release. De hecho, lo que se necesita para investigar un incidente en producción es precisamente el PDB del build Release.
Si lo que corre en producción es un build Release, tener el PDB del build Debug no sirve de nada. Lo que necesita el depurador es el PDB generado exactamente cuando se creó ese binario de producción.
Aquí es importante esta distinción.
| Elemento | Significado |
|---|---|
| Debug / Release | Configuración de build relacionada con la optimización, la compilación condicional, los ajustes de salida, etc. |
| Presencia del PDB | Si se genera y se conserva o no información de depuración |
| Facilidad de depuración | Depende de si hay optimización, del contenido del PDB, de la coincidencia con el código fuente, del comportamiento del JIT, etc. |
Un build Release suele estar optimizado, por lo que la ejecución paso a paso resulta menos clara que en un build Debug. Puede que variables locales desaparezcan por la optimización, o que la ejecución no se detenga en el orden de las líneas de código fuente.
Aun así, si existe el PDB resulta más fácil obtener este tipo de información.
- La línea de código fuente donde ocurrió la excepción
- Los nombres de métodos en la pila
- La posición correspondiente al analizar un volcado
- Los nombres de funciones en los resultados de profiling
- El cruce entre los logs y el código fuente
No es que en Release el PDB sea innecesario. Precisamente por ser Release, hay que conservar el PDB correspondiente a ese build.
9. Malentendido habitual 3: con el PDB se puede depurar cualquier binario
El PDB no se puede reutilizar indistintamente con cualquier .dll o .exe.
El depurador comprueba si el binario objetivo y el PDB coinciden. Si se fuerza el uso de un PDB que no coincide, la correspondencia de líneas de código fuente, funciones y variables queda desalineada.
Por ejemplo, en situaciones como estas, aunque exista el PDB puede que no se pueda usar, o que no sirva de nada.
Se intenta aplicar al DLL de producción un PDB recompilado localmente
Aunque el número de versión es el mismo, en realidad se compiló desde un commit distinto
Se intenta cargar en el DLL posterior a un hotfix el PDB anterior al hotfix
La configuración de optimización o la compilación condicional son distintas
El PDB no debe entenderse como “si es más o menos el mismo código fuente, sirve”, sino como “debe corresponder al mismo artefacto de build”.
Por eso, en CI/CD lo básico es conservar los elementos con esta granularidad.
ID de commit
Número de build
Versión del artefacto
.dll / .exe
.pdb
Información de referencia del código fuente
Es importante no romper esta combinación.
10. Malentendido habitual 4: con el PDB se puede leer todo aunque no haya código fuente
Aunque exista el PDB, no significa que siempre contenga el código fuente.
El PDB conserva principalmente información que enlaza el código fuente con el binario. Puede tener la ruta y la suma de comprobación de los archivos fuente, e información de Source Link, pero no siempre un PDB normal contiene el cuerpo completo del código fuente.
Por eso, si se quiere entrar (step into) en una biblioteca externa con el depurador, se necesita alguna de estas condiciones.
Tener localmente el mismo archivo fuente
Poder obtener, mediante Source Link, el código fuente del commit correcto
Que el código fuente esté embebido en el PDB
Recurrir a código descompilado como sustituto
Visual Studio también tiene una función para descompilar y mostrar ensamblados .NET. Sin embargo, el resultado de la descompilación no es el código fuente original en sí. Los comentarios, los espacios en blanco, los nombres de variables locales, el estilo de escritura original o las condiciones del preprocesador pueden perderse o cambiar.
Es útil para investigar, pero es mejor no confiar en exceso en él como sustituto del código fuente original.
11. El PDB y el seguimiento de pila (stack trace)
En el seguimiento de pila de una excepción de .NET, la apariencia cambia según exista o no el PDB.
Incluso sin el PDB, a veces se muestran los nombres de métodos y de tipos. Esto se debe a que el ensamblado de .NET tiene metadatos.
Pero si se quiere que aparezca también el nombre de archivo y el número de línea, el PDB es importante.
Por ejemplo, sin el PDB el seguimiento de pila tiende a verse así.
System.InvalidOperationException: Order is invalid
at MyApp.Services.OrderService.Validate(Order order)
at MyApp.Controllers.OrderController.Post(CreateOrderRequest request)
Si existe el PDB y se puede resolver el número de línea, cambia así.
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
Cuando esta diferencia aparece en operación, la velocidad de investigación cambia mucho.
Sin embargo, el hecho de que aparezca la ruta de archivo también puede suponer, en sí mismo, una divulgación de información. Hacen falta otras medidas, como no mostrar el seguimiento de pila en las respuestas de error hacia el exterior, o limitar dónde se conservan los logs.
12. El PDB y el análisis de volcados (dump analysis)
Donde más se agradece el PDB es en el análisis de volcados.
Supongamos que en el entorno de producción ocurre un problema como estos.
- El proceso se bloqueó (crash)
- El uso de CPU se quedó alto de forma sostenida
- Parece un interbloqueo (deadlock)
- La memoria sigue aumentando
- No llega respuesta
- Falla en el límite con una biblioteca nativa
En ese caso, se obtiene un archivo de volcado (dump) y se analiza.
Pero con solo el volcado no basta. Lo que contiene el volcado es el estado del proceso en ese momento. Para convertir la pila y los módulos que aparecen ahí en nombres y líneas de código fuente legibles para una persona, hace falta el PDB correspondiente.
En .NET se analiza usando dotnet-dump, Visual Studio, WinDbg, SOS, etc.
Cuando hay código nativo involucrado, la configuración de símbolos de WinDbg cobra importancia.
Estos son fallos habituales en el análisis de volcados.
El DLL de producción se conserva, pero falta el PDB
Existe un PDB, pero es un objeto distinto recompilado localmente
No se pueden cargar los símbolos del runtime de Windows / .NET
Existe el PDB de la aplicación propia, pero faltan los símbolos de bibliotecas de terceros
No está configurada la ruta de símbolos y el depurador no encuentra el PDB
El análisis de volcados a veces no llega a tiempo si se prepara después de que ocurre el problema. Lo importante es guardar el PDB en el momento del build y dejarlo listo para poder recuperarlo cuando se investigue.
13. Windows PDB y Portable PDB
El PDB tiene varios formatos.
Los dos representantes que hay que conocer en la práctica son estos.
| Tipo | Contexto principal | Características |
|---|---|---|
| Windows PDB | Visual C++, depuración tradicional de Windows | Formato muy usado en el desarrollo nativo de Windows |
| Portable PDB | .NET / .NET Core en adelante | Formato para .NET que se puede manejar de forma multiplataforma |
A partir de .NET Core, el Portable PDB es importante. El Portable PDB es un formato que se puede manejar no solo en Windows, sino también en Linux y macOS.
En proyectos de la época de .NET Framework o en configuraciones antiguas de Visual Studio, todavía se encuentra el Windows PDB. En los proyectos de estilo SDK del .NET actual, cada vez es más habitual partir de la base de que se usa el Portable PDB.
Aquí conviene notar que la extensión, en ambos casos, es .pdb.
La extensión por sí sola no indica la diferencia de formato. Cuando alguien dice “PDB”, conviene confirmar de qué contexto se está hablando.
¿Se habla del Portable PDB de .NET?
¿Se habla del Windows PDB de Visual C++?
¿Se habla de un proyecto antiguo de .NET Framework?
¿Se habla del PDB para distribución en NuGet?
¿Se habla de los símbolos que se usan en WinDbg?
14. El DebugType de .NET
En los proyectos de C# se puede especificar, con DebugType, cómo se genera la información de depuración.
Los valores representativos son estos.
| DebugType | Significado |
|---|---|
portable |
Genera el Portable PDB como archivo separado |
embedded |
Embebe en el .dll / .exe información de depuración equivalente al Portable PDB |
full |
Genera el PDB en el formato predeterminado de la plataforma actual |
pdbonly |
A partir de C# 6.0 se trata, en la práctica, igual que full |
none |
No genera PDB |
En los proyectos de estilo SDK del .NET actual, el DebugType de C# es portable de forma predeterminada tanto en Debug como en Release.
Por eso, normalmente no hace falta especificar explícitamente DebugType solo para generar el Portable PDB.
Tiene sentido escribirlo explícitamente cuando, como proyecto u organización, se quiere dejar fijado “este formato es el que usamos”, cuando se quiere resaltar la diferencia con un proyecto antiguo, o cuando se elige una política distinta a la predeterminada, como embedded o none.
En bibliotecas NuGet o en distribución externa, conviene estudiar cuál de portable, embedded o .snupkg conviene elegir.
Por ejemplo, para generar explícitamente el Portable PDB se escribe así.
<PropertyGroup>
<DebugType>portable</DebugType>
</PropertyGroup>
Para no dejar el PDB en un archivo separado y embeberlo en el ensamblado, sería así.
<PropertyGroup>
<DebugType>embedded</DebugType>
</PropertyGroup>
Si a toda costa no se quiere generar el PDB en un build Release, se puede escribir así.
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugType>none</DebugType>
</PropertyGroup>
Sin embargo, esto debe decidirse con cautela. Configurar que no se genere el PDB de Release puede acabar perjudicando la propia investigación de incidentes en producción.
15. ¿Basta con DebugSymbols=false?
Cuando se quiere detener la generación del PDB, es habitual ver ejemplos que ponen DebugSymbols en false.
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
Sin embargo, si la intención es asegurarse de no generar el PDB, es más claro poner DebugType en none.
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugType>none</DebugType>
</PropertyGroup>
Esta configuración a veces se usa como política de distribución de una biblioteca o aplicación. Pero, insistimos, no generar el PDB reduce la capacidad de investigar incidentes.
En la práctica, la división más habitual es esta.
El PDB se genera siempre como artefacto de build
Si se coloca en el servidor de producción se decide aparte
Aunque no se coloque, se conserva en los artefactos de CI o en un servidor de símbolos
16. El PDB de C++ es algo distinto al de .NET
El PDB de C++ tiene un contexto algo distinto al del PDB de .NET.
En Visual C++, el PDB se genera con opciones como /Zi o /ZI.
Además, intervienen tanto el PDB que usa el compilador como el que genera el enlazador (linker) para el .exe / .dll final.
En C++, el PDB es sumamente importante para leer direcciones de código nativo, funciones, tipos, variables locales, expansión inline y posiciones tras la optimización.
Además, en la investigación de incidentes nativos, sin el PDB se suele terminar en un estado como este.
Se conoce la dirección de la excepción
Se conoce el nombre del módulo
Pero no se conocen el nombre de la función ni la línea de código fuente
En aplicaciones donde se mezclan C++ y C#, con P/Invoke, con C++/CLI, o en aplicaciones .NET que llaman a un DLL nativo, hace falta no solo el PDB del lado .NET, sino también el PDB del lado nativo.
17. Símbolos públicos (public symbols) y símbolos privados (private symbols)
En el mundo de los símbolos de Windows existe la distinción entre public symbols y private symbols.
Dicho de forma sencilla, la diferencia es esta.
| Tipo | Idea de la información que contiene |
|---|---|
| private symbols | Información cercana a completa, que incluye variables locales, tipos, parámetros e información interna detallada |
| public symbols | Información reducida para uso público, como nombres de funciones y direcciones |
En los símbolos que se distribuyen al exterior, a veces se descartan los private symbols y solo se dejan los public symbols.
Esto se hace para equilibrar la capacidad de depuración con el alcance de la divulgación de información.
Por ejemplo, para el análisis de fallos del propio producto se quiere mostrar como mínimo los nombres de funciones. Pero no se quiere mostrar hasta los nombres de variables locales internas ni la información de tipos. En un caso así, se utiliza un PDB “stripped” (despojado).
En el desarrollo nativo para Windows, a veces se usa PDBCopy para crear un PDB del que se han eliminado los símbolos privados.
Por otro lado, para la investigación interna de incidentes hace falta conservar el PDB completo. Si solo queda un PDB reducido para uso externo, la investigación en profundidad se ve limitada.
18. ¿Dónde busca el depurador el PDB?
Visual Studio y WinDbg buscan el PDB en varios lugares.
Los lugares representativos son estos.
La carpeta de salida del proyecto
La misma carpeta que el .dll / .exe
La ruta original del PDB registrada en el .dll / .exe
La carpeta especificada en la configuración de símbolos de Visual Studio
La caché local de símbolos
El servidor de símbolos interno de la empresa
Microsoft Symbol Server
NuGet.org Symbol Server
Servidores de símbolos como Azure Artifacts
Si existe el PDB pero no se carga, conviene revisar estos puntos en orden.
Si el PDB coincide con el binario objetivo
Si el PDB está incluido en la ruta de búsqueda del depurador
Si se puede acceder al servidor de símbolos
Si queda algo antiguo en la caché local
Si la carga de símbolos del módulo objetivo está deshabilitada
Si por la configuración de Just My Code se está tratando como código externo
En Visual Studio, si se mira la ventana Modules durante la depuración, se puede confirmar el estado de carga de símbolos de cada módulo.
Debug
Windows
Modules
Ahí se mira el Symbol Status del DLL objetivo.
Lo habitual es ver alguno de estos 4 mensajes.
Symbols loaded.
Cannot find or open the PDB file.
PDB does not match image.
Skipped loading symbols.
Si se atasca algo relacionado con el PDB, mirar primero la ventana Modules es el atajo más directo.
19. Qué es un servidor de símbolos
A partir de aquí y hasta el capítulo 23 se sigue hablando de “dónde colocar el PDB y cómo hacerlo llegar al depurador”. Como aparecen muchos actores, primero se ilustra la relación general en un diagrama.
flowchart TB
Build["Build<br/>el dll / exe y el pdb se generan a la vez"] --> Bin["dll / exe<br/>lo que se distribuye y ejecuta"]
Build --> Pdb["pdb<br/>información de depuración"]
Build --> Repo["repositorio de código fuente<br/>el commit de ese build"]
Pdb --> Route{Cómo hacer llegar el PDB}
Route -->|"colocarlo en la misma carpeta"| Side["pdb junto al dll<br/>Cap. 27, patrón 1"]
Route -->|"conservarlo como artefacto de CI"| Art["artefacto de CI<br/>Cap. 27, patrón 2"]
Route -->|"publicarlo en el servidor de símbolos"| Sym["servidor de símbolos<br/>Cap. 19"]
Route -->|"distribuirlo con NuGet"| Snup["snupkg<br/>Cap. 23"]
Route -->|"embeberlo en el dll"| Emb["PDB embedded<br/>Cap. 22"]
Side --> Dbg[depurador / herramienta de diagnóstico]
Art --> Dbg
Sym --> Dbg
Snup --> Dbg
Emb --> Dbg
Bin --> Dbg
Dbg --> SL["Source Link<br/>Cap. 20 y 21"]
SL --> Repo
Dbg --> Result["seguimiento de pila con número de línea<br/>ejecución paso a paso<br/>análisis de volcados"]
La mitad izquierda del diagrama es “cómo se conserva y distribuye el PDB”, y la mitad derecha es “cómo lo usa el depurador”. En este diagrama, Source Link es el único con un papel distinto. No es un medio para hacer llegar el PDB, sino la información para ir a buscar el código fuente a partir del PDB ya recibido. El binario, el PDB y el código fuente se gestionan por separado, así que el tema de los capítulos 19 al 23 es precisamente cómo volver a enlazar estos tres elementos.
Empecemos entonces por el servidor de símbolos.
Un servidor de símbolos es un mecanismo que permite que el depurador obtenga, cuando lo necesita, archivos de símbolos como el PDB.
Con solo colocar el PDB en una carpeta compartida ya se puede operar hasta cierto punto. Pero en cuanto aumentan las versiones, este esquema se rompe enseguida.
El PDB de MyApp v1.0.0
El PDB de MyApp v1.0.1
El PDB del hotfix de MyApp v1.0.1
El PDB de MyApp v1.1.0-beta
El PDB con una configuración distinta solo para el cliente A
Aparecen decenas de archivos con el mismo nombre MyApp.pdb.
El servidor de símbolos organiza el PDB no por un simple nombre de archivo, sino a partir de la información de coincidencia con el binario. Por eso, al depurador le resulta más fácil encontrar “el PDB que corresponde a este DLL”.
Un ejemplo de configuración que se puede plantear en la práctica.
Microsoft Symbol Server
se usa para obtener símbolos de Windows o del runtime de .NET
NuGet.org Symbol Server
se usa para obtener símbolos de paquetes NuGet públicos
Servidor de símbolos interno
guarda el PDB de las aplicaciones y bibliotecas propias
Caché local de símbolos
reutiliza el PDB ya obtenido una vez, para acelerar la depuración
Si se investigan incidentes de producción en un servicio interno, resulta útil preparar un mecanismo por el que la CI publique el PDB en un almacén de símbolos interno.
20. Qué es Source Link
Source Link es un mecanismo que enlaza el PDB con el sistema de control de versiones del código fuente.
Aunque el PDB indique “esta posición corresponde a este archivo fuente”, si ese archivo fuente no está disponible localmente, el depurador no puede mostrarlo.
Con Source Link, el depurador puede usar la información contenida en el PDB para obtener, desde GitHub, Azure Repos, GitLab, Bitbucket, etc., el archivo fuente correspondiente al commit adecuado.
Es decir, Source Link resuelve un problema como este.
Se quiere entrar (step into) en una biblioteca obtenida con NuGet
No se ha clonado localmente el código fuente de esa biblioteca
Pero sí se dispone del PDB y de la información del repositorio
El depurador va a buscar el código fuente del commit correcto
Esto mejora enormemente la experiencia de quien usa la biblioteca.
El punto clave de Source Link es que hace referencia no a “la rama main más reciente”, sino a “el commit con el que se construyó ese binario”.
Lo importante no es enlazar con el código fuente más reciente, sino con el código fuente del momento del build.
21. Source Link a partir de .NET 8
A partir del SDK de .NET 8, el manejo de Source Link ha mejorado.
En proveedores muy usados como GitHub, Azure Repos, GitLab o Bitbucket, el propio SDK de .NET ya incluye el mecanismo de Source Link.
Por eso, la idea de que siempre hay que añadir explícitamente algo como Microsoft.SourceLink.GitHub está quedando desactualizada.
Sin embargo, hay casos en los que sigue haciendo falta comprobarlo.
Se está compilando con una versión del SDK de .NET anterior a la 8
Es un proyecto antiguo que no sigue el estilo de SDK
Se usa un alojamiento Git propio on-premise
Se usa un proveedor de Source Link que no está entre los soportados de forma estándar
Se quiere afinar también los metadatos del paquete NuGet
Si se quiere que el paquete NuGet incluya la información del repositorio, se usa esta configuración.
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>
Si hace falta embeber en el PDB archivos que no están en el control de versiones, conviene considerar esta configuración.
<PropertyGroup>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
Sin embargo, embeber código fuente afecta al alcance de divulgación de información. Qué se incluye en el PDB debe decidirse según el destino de publicación y la política de operación.
22. Qué es un embedded PDB
Si se especifica embedded en DebugType, la información de depuración del Portable PDB se embebe en el .dll o .exe.
En este caso no se genera un .pdb como archivo separado.
<PropertyGroup>
<DebugType>embedded</DebugType>
</PropertyGroup>
Esto resulta útil en escenarios como estos.
- Se quiere distribuir algo cercano a un único archivo
- Se quiere evitar olvidar el PDB en algún sitio
- En una herramienta interna pequeña, se quiere llevar junta también la información de depuración
- En un paquete NuGet, se quiere reducir el trabajo de distribuir el PDB
Por otro lado, también tiene inconvenientes.
- Aumenta el tamaño del ensamblado
- El artefacto distribuido siempre incluye información de depuración
- El control del alcance de divulgación de información se vuelve más tosco
- En bibliotecas grandes, afecta al restore y al tamaño de distribución
El embedded PDB es cómodo, pero no es algo que se pueda resolver diciendo simplemente “de momento pongamos todo en embedded”.
Especialmente en bibliotecas de publicación externa, hay que valorar cuál conviene más entre el paquete de símbolos .snupkg, Source Link, incluir el PDB normal junto al binario, o usar embedded.
23. Qué es un .snupkg
.snupkg es el formato de paquete de símbolos de NuGet.
El paquete NuGet normal es .nupkg.
El paquete de símbolos es .snupkg.
MyLibrary.1.2.3.nupkg
MyLibrary.1.2.3.snupkg
El .nupkg contiene el cuerpo de la biblioteca al que hace referencia quien la usa.
El .snupkg se usa para distribuir el PDB de depuración.
Aquí es importante notar que el .snupkg está pensado básicamente para el Portable PDB de código administrado (managed code).
Al menos el servidor de símbolos de NuGet.org solo admite el Portable PDB, y no acepta el Windows PDB que generan los proyectos nativos como los de C++.
Si se quiere distribuir o conservar un Windows PDB, hay que considerar otras vías, como el .symbols.nupkg heredado, un servidor de símbolos interno o un artefacto de CI.
Para crearlo, se puede escribir, por ejemplo, así.
<PropertyGroup>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
También se puede indicar desde la línea de comandos.
dotnet pack -c Release -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
En una biblioteca NuGet pública, en lugar de meter el PDB dentro del propio .nupkg, resulta más fácil equilibrar el tamaño de distribución y la experiencia de depuración usando .snupkg junto con Source Link.
Sin embargo, hay que prestar atención al estado de soporte del feed y de las herramientas.
Si el feed interno de NuGet de la empresa no admite .snupkg, hay que elegir otro método.
24. ¿Se debe colocar el PDB en producción?
“¿Se debe colocar el PDB en producción?” no es un simple sí o no.
Hay cuatro ejes de decisión.
Facilidad de investigar incidentes
Riesgo de divulgación de información
Tamaño de distribución
Reglas de operación
En un sistema interno, la práctica de dejar el .dll y el .pdb en la misma carpeta también resulta realista.
Facilita que aparezcan números de línea en los logs de excepciones, y también facilita el análisis de volcados.
En una aplicación de distribución externa, hay que decidir con cautela si incluir el PDB tal cual. Puede que se vea la estructura interna, los nombres de variables locales o las rutas de código fuente. Si es necesario, se opta por limitarse a los public symbols, distribuir a través de un servidor de símbolos, o simplemente conservarlo para soporte.
En un servicio web, además de si colocar o no el PDB en el servidor, hay que pensar también en el tratamiento del seguimiento de pila que aparece en los logs. No se debería devolver el seguimiento de pila en las respuestas externas. Aunque se conserve en los logs internos, hay que decidir los permisos de visualización y el período de conservación.
En la práctica, la recomendación es esta.
Generar siempre el PDB
Conservar el PDB como artefacto de build
Decidir la ubicación en producción según el alcance de publicación del sistema
Si se publica al exterior, confirmar el alcance de divulgación de información
Colocarlo en un lugar de donde se pueda extraer para el análisis de volcados
25. ¿Es el PDB información confidencial?
El PDB no necesariamente requiere el mismo tratamiento que el propio código fuente o una clave secreta. Pero tratarlo como un archivo totalmente inofensivo también es peligroso.
Veamos lo que puede llegar a revelar un PDB.
- La ruta local del desarrollador
- La estructura de carpetas interna de la empresa
- El nombre del proyecto
- Nombres de clases y de métodos
- Nombres de variables locales
- Nombres de API internas
- Terminología del negocio
- La URL del repositorio de Source Link
- El código fuente embebido
- La configuración de Source Server
En particular, en el antiguo esquema de Source Server o en algunas funciones de los PDB nativos, puede estar involucrado un mecanismo por el que el depurador ejecuta comandos para obtener el código fuente. Conviene evitar usar sin condiciones un PDB o un servidor de símbolos en el que no se confía.
Además, también hay que tener cuidado al pasar un PDB malicioso a herramientas o bibliotecas que analizan PDB. En mecanismos que procesan de forma automática un PDB recibido desde el exterior, conviene diseñarlos para no confiar en la entrada.
En resumen, esta es una forma segura de tratar el PDB.
El PDB completo interno se protege como artefacto interno
El PDB que se publica al exterior debe revisarse en cuanto a contenido y alcance de publicación
Si el destino de Source Link es un repositorio privado, hay que gestionar la autenticación y los permisos
No registrar en el depurador un servidor de símbolos en el que no se confía
26. Política de conservación del PDB en CI/CD
El PDB no sirve de mucho si solo se conserva en el PC local del desarrollador. Lo que hace falta cuando ocurre un incidente en producción es el PDB correspondiente al build que realmente se publicó.
Por eso, en CI/CD se conserva con una forma como esta.
Número de build: 2026.06.10.1234
ID de commit: abcdef123456...
Artefactos:
MyApp.dll
MyApp.pdb
MyApp.deps.json
MyApp.runtimeconfig.json
package.zip
digest de la imagen de contenedor
Además, si es posible, conviene enlazar también información como esta.
Git commit
Git tag
Número de versión de release
Nombre del entorno
Configuración de build
Target framework
RID
Digest de la imagen de contenedor
Archivo de bloqueo (lock file) de NuGet
Lo importante no es conservar el PDB de forma aislada, sino no perder a qué binario corresponde cada PDB.
Una operación en la que se sigue dejando solo un MyApp.pdb en un recurso compartido de archivos acaba rompiéndose tarde o temprano.
Hay que poder recuperarlo por unidad de build, por versión y por commit.
27. Patrones de ubicación del PDB
En el tratamiento del PDB hay varios patrones habituales en la práctica.
Patrón 1: colocar el PDB en la misma carpeta que el DLL
El más simple.
publish/
MyApp.dll
MyApp.pdb
La ventaja es que la configuración es sencilla. Resulta fácil de encontrar para el depurador y el runtime, y también facilita que aparezcan números de línea en los logs de excepciones.
La desventaja es que el artefacto distribuido incluye información de depuración. Puede no ser adecuado para una distribución externa.
Patrón 2: no distribuir el PDB y conservarlo como artefacto de CI
No se coloca el PDB en el servidor de producción, y se conserva en el artefacto de CI.
release-artifacts/
app.zip
symbols.zip
Ante un incidente en producción, se obtienen el volcado o los logs, y se extrae el PDB del número de build correspondiente para analizarlo.
Facilita limitar el alcance de divulgación de información, pero requiere un procedimiento de extracción en el momento de la investigación.
Patrón 3: publicarlo en un servidor de símbolos interno
En equipos grandes, esto resulta manejable.
La CI publica el PDB en el almacén de símbolos en el momento del build. El Visual Studio / WinDbg de los desarrolladores o investigadores hace referencia a ese servidor de símbolos.
La ventaja es que resulta fácil manejar de forma segura el PDB de varias versiones. La desventaja es que hace falta configurarlo inicialmente y controlar el acceso.
Patrón 4: distribuirlo mediante el .snupkg de NuGet
Es una opción sólida en bibliotecas públicas.
MyLibrary.1.2.3.nupkg
MyLibrary.1.2.3.snupkg
Quien la usa restaura solo el paquete normal, y únicamente se obtienen los símbolos necesarios en el momento de depurar.
Combinado con Source Link, resulta más fácil entrar (step into) en el código fuente incluso en bibliotecas externas.
Patrón 5: usar un PDB embedded
Resulta útil para evitar olvidar el PDB en algún sitio.
<PropertyGroup>
<DebugType>embedded</DebugType>
</PropertyGroup>
Sin embargo, hay que prestar atención al tamaño del ensamblado y al alcance de divulgación de información.
28. Qué revisar primero en un proyecto existente
Si en un proyecto .NET existente se quiere revisar el tratamiento del PDB, conviene empezar comprobando esto.
Si se genera el PDB en el build Release
Dónde se guarda el PDB generado
Si se conserva el DLL y el PDB de producción de forma que se correspondan entre sí
Si se puede extraer el PDB en el momento de analizar un volcado
Si Source Link está activo
En el caso de una biblioteca NuGet, si se genera el .snupkg
Si el PDB no está incluyendo información innecesaria
En el csproj conviene revisar configuraciones como estas.
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<DebugType>portable</DebugType>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
Si es un paquete NuGet, esto también es candidato.
<PropertyGroup>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
Sin embargo, no significa que baste con poner la misma configuración en todos los proyectos. La solución óptima cambia entre una aplicación interna, una biblioteca externa, un producto on-premise, un SaaS o un proyecto OSS.
29. Cómo diagnosticar cuando el PDB no se carga
Cuando el PDB no se carga, antes de recompilar por instinto, conviene ir descartando causas en orden.
1. Comprobar el módulo objetivo
En la ventana Modules de Visual Studio, se busca el DLL / EXE objetivo.
Debug > Windows > Modules
Aquí las columnas que hay que mirar son estas.
Module
Path
Symbol Status
Symbol File
Version
Timestamp
2. Ver el estado del símbolo
Cada mensaje se interpreta más o menos así.
| Mensaje | Significado |
|---|---|
| Symbols loaded | Ya está cargado |
| Cannot find or open the PDB file | No se encuentra el PDB |
| PDB does not match image | El PDB existe, pero no coincide con el binario objetivo |
| Skipped loading symbols | Es posible que no se cargue debido a la configuración |
3. Comprobar si el PDB local corresponde al mismo build
Un error habitual es usar un PDB recompilado localmente.
Aunque el código fuente sea el mismo, si las condiciones de build son distintas, puede no coincidir. Hay que obtener, desde el artefacto de CI, el mismo PDB que el que se publicó en producción.
4. Comprobar la ruta de símbolos
En Visual Studio conviene revisar esto.
Tools > Options > Debugging > Symbols
En WinDbg, por ejemplo, se comprueba así.
.sympath
.reload
!sym noisy
Si se usan los símbolos públicos de Microsoft, resulta útil especificar también una caché local.
srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
5. Sospechar de la caché
A veces se está tomando un PDB antiguo o una caché corrupta. Conviene revisar cosas como borrar la caché de símbolos, especificar otra caché distinta, o mirar con detalle el log de carga.
Dónde está cada opción en Visual Studio
A continuación se resume dónde está cada una de las opciones mencionadas hasta aquí. La jerarquía de las pantallas de opciones cambia según la versión de Visual Studio, así que se indican ambas rutas, la antigua y la nueva.
| Lo que quiere hacer | Ruta completa del menú |
|---|---|
| Ver el estado de los módulos y los símbolos | Debug > Windows > Modules (solo disponible durante la depuración; Ctrl + Alt + U) |
| Cargar manualmente los símbolos de ese módulo | En la ventana Modules, clic derecho sobre el módulo > Load Symbols |
| Ver por qué no se pudo cargar (dónde se buscó) | En la ventana Modules, clic derecho > Symbol Load Information |
| Editar los lugares de búsqueda de símbolos | Tools (o Debug) > Options > Debugging > SymbolsEn la pantalla nueva: Tools > Options > All Settings > Debugging > General > Symbols > Search Locations |
| Activar los servidores de símbolos de Microsoft / NuGet.org | La casilla Microsoft Symbol Servers / NuGet.org Symbol Server de la misma pantalla anterior |
| Especificar el destino de la caché local de símbolos | Cache symbols in this directory en la misma pantalla anterior |
| Ir a la pantalla de configuración de símbolos desde la ventana Modules | En la ventana Modules, clic derecho > Symbol Settings |
| Activar / desactivar Just My Code | Tools (o Debug) > Options > Debugging > General > Enable Just My CodeEn la pantalla nueva: bajo All Settings > Debugging > General |
| Activar Source Link | Enable Source Link support, en la misma pantalla Debugging > General |
Sobre el destino de la caché, la documentación oficial incluye dos advertencias.
- No especificar una carpeta protegida como
C:\Windows. Usar una carpeta con permisos de lectura y escritura - Si está definida la variable de entorno
_NT_SYMBOL_PATH, esta tiene prioridad sobre la configuración deCache symbols in this directory
Un punto más: Enable Just My Code es una configuración global de todo Visual Studio. No es por proyecto, así que si se cambia para un caso concreto y se olvida volver a cambiarlo, puede repercutir después, en otro caso, en la forma de “por algún motivo no se puede entrar (step into)” al depurar.
Por lo demás, en cuanto a DebugType, se recomienda escribirlo directamente en el <PropertyGroup> del csproj antes que tocarlo desde la GUI. Existe un elemento equivalente en la pantalla de propiedades del proyecto, pero tanto el nombre como la ubicación cambian según si es un proyecto de estilo SDK y según la versión de Visual Studio. Si está escrito en el csproj, queda reflejado tanto en la revisión de código como en el diff (capítulo 14).
30. El PDB y “Just My Code”
Visual Studio tiene una configuración llamada Just My Code.
Consiste en limitar el objetivo de depuración a “mi propio código”, dificultando la ejecución paso a paso del código externo. Es útil en el desarrollo del día a día, pero puede ser motivo de confusión al verificar el PDB o Source Link.
Por ejemplo, si no se puede entrar (step into) en una biblioteca NuGet externa que sí tiene PDB y Source Link, conviene revisar puntos como estos.
Si Just My Code está activo y se está tratando como código externo
Si Enable Source Link support está activo
Si NuGet.org Symbol Server está activo
Si el paquete objetivo publica el PDB / .snupkg
Si se puede acceder al destino de obtención del código fuente
Antes de dar por hecho que “el problema es el PDB”, conviene revisar también la configuración del depurador. La ubicación de las pantallas de configuración se resume en “Dónde está cada opción en Visual Studio”, en el capítulo 29. Tanto Enable Just My Code como Enable Source Link support se encuentran en Tools (o Debug) > Options > Debugging > General.
31. El PDB y la descompilación
El Visual Studio actual puede descompilar ensamblados .NET y usarlos para depurar.
Gracias a esto, incluso en bibliotecas externas sin PDB ni código fuente, a veces se puede seguir el contenido hasta cierto punto.
Sin embargo, la descompilación no es infalible.
Los comentarios originales no se recuperan
El espaciado y la estructura originales no se recuperan
Los nombres de variables locales pueden cambiar
Construcciones como async / iterator / pattern matching pueden verse distintas a la forma original
En código ya optimizado, la correspondencia resulta difícil de entender
Si se dispone del PDB y de Source Link, en principio resulta más natural usarlos. La descompilación conviene entenderla como “un recurso auxiliar para cuando no hay PDB ni código fuente”.
32. El diseño de logs y el PDB
El PDB también se relaciona con el diseño de logs.
Por ejemplo, si en el log de excepciones aparecen el nombre de archivo y el número de línea, la investigación resulta más fácil. Sin embargo, confiar solo en los logs es peligroso.
En un incidente de producción pueden pasar cosas como estas.
El número de línea que aparece en el log no coincide con la rama main actual
Se volvió a desplegar con el mismo número de versión después de un hotfix
Al no conservarse el PDB, no se puede verificar el significado del número de línea
La imagen de contenedor se conserva, pero no se sabe a qué commit de código fuente corresponde
Por eso conviene que el log muestre, además del número de línea, información del build.
ApplicationVersion: 1.8.3
GitCommit: abcdef1234567890
BuildNumber: 20260610.12
Environment: Production
Cuando el PDB, el código fuente, los logs, los volcados y el historial de despliegues quedan enlazados, la investigación de incidentes se vuelve mucho más sencilla.
33. El PDB en entornos de contenedores
Al ejecutar una aplicación .NET en un contenedor, hace falta dejar claro el tratamiento del PDB.
Por ejemplo, si se incluye o no el PDB en la imagen de Docker.
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY publish/ .
ENTRYPOINT ["dotnet", "MyApp.dll"]
Si el PDB está dentro de publish/, también entra tal cual en la imagen.
Esto tiene ventajas.
- Es más fácil que aparezca el número de línea en el seguimiento de pila dentro del contenedor
- Es más fácil encontrarlo en el mismo sistema de archivos al obtener un volcado
- La correspondencia en el momento de la investigación resulta sencilla
Por otro lado, también hay preocupaciones.
- Aumenta el tamaño de la imagen
- La información de depuración queda incluida en la imagen de producción
- Si la imagen se distribuye al exterior, se amplía el alcance de divulgación de información
En un SaaS exclusivamente interno, la opción de incluir el PDB en la imagen es perfectamente razonable. En un producto on-premise que se entrega a clientes externos, puede ser mejor conservar el PDB por separado.
En cualquier caso, aunque no se incluya el PDB en la imagen, es imprescindible conservar el PDB correspondiente como artefacto (artifact).
34. La publicación de archivo único y el PDB
.NET cuenta con la publicación de archivo único (single-file).
dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true
También en este caso hace falta confirmar el tratamiento de la información de depuración.
El hecho de convertirlo en un archivo único no significa que deje de hacer falta la investigación de incidentes. Más bien, cuanto más particular es la forma de distribución, más importante es decidir cómo se conservan los símbolos y el código fuente correspondientes.
Lo que hay que decidir como política es, entre otras cosas, esto.
Si el PDB se distribuye como archivo separado
Si se pone DebugType=embedded
Si los símbolos se guardan solo internamente
Cómo se analizará el volcado de un fallo
Al usar publicación de archivo único, trimming, AOT, etc., la experiencia de investigación puede cambiar respecto a un ensamblado IL normal. Es más seguro probar una vez, antes del lanzamiento, “cómo se leería esto si se produjera un fallo”.
35. El PDB, el trimming y AOT
En el .NET actual a veces se usan el trimming o el Native AOT.
En ese caso, no solo interviene el PDB, sino también los símbolos nativos generados y la información de depuración propia de cada plataforma.
Por ejemplo, en Linux es DWARF, en macOS es dSYM y en Windows es PDB: hace falta pensar también en la información de depuración del lado nativo, según la plataforma.
Incluso en una aplicación .NET, en configuraciones como estas el diseño de símbolos se vuelve más complejo.
Native AOT
Self-contained publish
PublishSingleFile
ReadyToRun
Se llama a un DLL nativo mediante P/Invoke
Se incluye C++/CLI
En una aplicación web o una biblioteca de clases normal, basta primero con entender el Portable PDB y Source Link. Sin embargo, cuanto más se sofistica la forma de distribución, más hay que incluir en el diseño de build la pregunta de “cómo se analizará este fallo”.
36. Puntos a tener en cuenta en proyectos .NET Framework
En los proyectos antiguos de .NET Framework, la configuración y los valores predeterminados a veces difieren de los de un proyecto de estilo SDK.
Por ejemplo, diferencias como estas.
El formato del csproj es antiguo
Se usa packages.config
El valor predeterminado de DebugType es distinto al del .NET actual
Se usa Windows PDB
La configuración de Source Link necesita paquetes adicionales o ajustes de MSBuild
La versión de MSBuild de la CI es antigua
En .NET Framework, la idea sobre el PDB es la misma.
No es imprescindible para la ejecución
Es importante para la depuración y la investigación de incidentes
Hace falta un PDB que coincida con el binario objetivo
Se debe conservar el PDB del build Release
Sin embargo, si se aplica sin más la explicación del .NET actual, en un proyecto antiguo puede no funcionar como se espera.
En los activos existentes, primero conviene confirmar la salida real.
msbuild MyApp.csproj /p:Configuration=Release
Get-ChildItem bin\Release -Filter *.pdb -Recurse
A partir de ahí, se ajusta la configuración de build y los artefactos de CI.
37. Qué hacer en bibliotecas OSS
En una biblioteca .NET de código abierto (OSS), básicamente se recomienda esta configuración.
Generar el PDB también en Release
Usar el Portable PDB
Activar Source Link
Publicar el .snupkg en NuGet
Configurar los metadatos de Repository
Tener presente el build determinista
Un ejemplo.
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<DebugType>portable</DebugType>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
Según el SDK o el sitio de alojamiento, en algunos casos no hace falta añadir un paquete adicional para Source Link.
En SDK antiguos o alojamientos especiales, hay que añadir el paquete Microsoft.SourceLink.* correspondiente.
En OSS, el hecho de que quien lo usa pueda entrar (step into) dentro de la propia biblioteca es, en sí mismo, una cuestión de calidad. “Una biblioteca que se puede leer cuando surge un problema” gana confianza solo por eso.
38. Qué hacer en bibliotecas internas
También en las bibliotecas internas, Source Link y el PDB son útiles.
Más bien, es precisamente en las bibliotecas internas donde ayuda poder entrar (step into) desde la aplicación de negocio.
Si se usa un feed interno de NuGet, conviene estudiar puntos como estos.
Si el feed interno admite .snupkg
Si no lo admite, si se incluye el PDB dentro del .nupkg
Si se prepara un servidor de símbolos interno
Cómo se gestiona la autenticación hacia el repositorio Git
Si, tras una baja o un cambio de puesto, se sigue teniendo acceso al código fuente
Aunque sea de uso interno, se debe evitar que el PDB solo exista en el PC local de alguien.
Hay que generarlo en la CI y guardarlo en un lugar del que el equipo pueda extraerlo.
39. Qué hacer en productos on-premise
En los productos on-premise que se distribuyen al entorno del cliente, el tratamiento del PDB se vuelve más delicado.
Si se incluye el PDB, resulta más fácil leer los volcados y los logs que se obtienen en el sitio del cliente. Pero también se vuelve más visible la estructura interna.
Las opciones habituales son estas.
No incluir el PDB, pero conservar la versión completa por parte del proveedor
Ofrecer solo public symbols para el soporte al cliente
Recoger el volcado ante un incidente y analizarlo con el PDB por parte del proveedor
Ofrecer un paquete de símbolos limitado para clientes importantes
Lo importante es no perder el PDB después del lanzamiento.
En los productos on-premise, a veces se investiga un incidente de una versión de hace varios años. En ese momento, si no existe el PDB correspondiente, la capacidad de investigación se reduce mucho.
40. Qué ocurre si se elimina el PDB
Aunque se elimine el PDB, la aplicación sigue funcionando. Pero más adelante causa problemas.
Se podría pensar que, si se vuelve a compilar desde el mismo commit, se puede reproducir. Sin embargo, la reproducción completa es sorprendentemente difícil.
La versión del SDK es distinta
El resultado de la resolución de NuGet es distinto
La hora de build o las variables de entorno son distintas
El código generado es distinto
La compilación condicional es distinta
Hay configuración que solo entra en la CI
Las herramientas nativas de las que depende son distintas
Si se tiene bien preparado el build determinista, la reproducibilidad mejora, pero aun así es más seguro “conservarlo como artefacto desde el principio”.
El PDB se parece a un seguro. Solo demuestra su valor cuando hace falta. Y si no está cuando hace falta, ya no hay remedio.
41. Configuración recomendada para el trabajo diario
No existe una única configuración que sirva para todos los proyectos. Sin embargo, para una aplicación .NET general, esto puede tomarse como punto de partida.
Aplicaciones internas
<PropertyGroup>
<DebugType>portable</DebugType>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
La política es esta.
Generar el PDB también en Release
Decidir si se coloca en producción según la política de operación
Conservarlo siempre en el artefacto de CI
Preparar el procedimiento de análisis de volcados
Bibliotecas NuGet públicas
<PropertyGroup>
<DebugType>portable</DebugType>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
La política es la siguiente.
Activar Source Link
Publicar el .snupkg
Evitar embeber código fuente innecesario
Confirmar los metadatos que se publican
Herramientas internas pequeñas
<PropertyGroup>
<DebugType>embedded</DebugType>
</PropertyGroup>
La política.
Evitar olvidar el PDB en algún sitio
Aceptar el aumento de tamaño del artefacto distribuido
Limitarlo al uso interno
Productos de distribución externa
Conservar internamente la versión completa del PDB
Si hace falta, crear aparte los public symbols
Confirmar el contenido del PDB que se incluye en lo que recibe el cliente
Decidir el procedimiento de recogida y análisis de volcados en el soporte
42. Lista de comprobación al revisar un PDB
Por último, dejamos una lista de comprobación para cuando haya dudas sobre el tratamiento del PDB.
A qué DLL / EXE corresponde este PDB
Desde qué commit se compiló ese DLL / EXE
Es el PDB del Release de producción, y no del build Debug
El PDB está conservado como artefacto de CI
Existe un servidor de símbolos o un procedimiento para obtenerlo
Source Link está activo
Los permisos hacia el destino de obtención del código fuente son adecuados
El PDB no contiene información que no se quiere publicar
En una distribución externa, existe una política de public/private symbols
Se ha confirmado que el PDB se puede cargar en el momento de analizar un volcado
Especialmente importantes son estas tres cosas.
El PDB no es el ejecutable, sino información para investigar
El PDB debe coincidir con el binario objetivo
El PDB debe conservarse antes de que ocurra un incidente en producción
43. Resumen
El PDB no es “un archivo que aparece al lado del .dll o el .exe y no se sabe muy bien qué es”, sino información de depuración que enlaza el binario resultante del build con el código fuente que puede leer un desarrollador.
No es imprescindible para la ejecución normal de la aplicación. Sin embargo, es importante en la depuración, la investigación de excepciones, el análisis de volcados, el profiling y para poder entrar (step into) en bibliotecas externas.
El PDB también es útil en un build Release. De hecho, lo que se necesita en un incidente de producción es el PDB correspondiente al build Release.
Si se coloca o no el PDB en el entorno de producción se decide según el alcance de divulgación de información y la política de operación. Pero generar el PDB y conservarlo como artefacto de build es necesario en la mayoría de los proyectos.
En la práctica, conviene tomar como base esta política.
Generar el PDB también en Release
Conservar el PDB como artefacto de CI/CD
Enlazar el binario, el PDB, el ID de commit y el número de build
Hacer posible llegar al código fuente mediante Source Link
En bibliotecas NuGet, considerar el .snupkg
En la distribución externa, confirmar qué información de símbolos se publica
El PDB no llama la atención cuando no hay problemas. Pero cuando ocurre un problema, es una pista fundamental que devuelve a quien investiga al código fuente.
En lugar de pensar “el PDB se puede borrar porque igual funciona”, es mejor pensar así.
El PDB es el mapa necesario para la investigación de incidentes futuros.
Referencias
- Symbols in .NET - Microsoft Learn
- Specify symbol (.pdb) and source files in the Visual Studio debugger - Microsoft Learn
- C# Compiler Options that control code generation - Microsoft Learn
- Portable PDB Symbols - Microsoft Learn
- Source Link included in the .NET SDK - Microsoft Learn
- dotnet/sourcelink - GitHub
- Creating symbol packages (.snupkg) - Microsoft Learn
- Public and Private Symbols - Microsoft Learn
- Using PDBCopy - Microsoft Learn
- Debug only user code with Just My Code - Microsoft Learn
- dotnet-symbol diagnostic tool - Microsoft Learn
- dotnet-dump diagnostic tool - Microsoft Learn
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Cómo manejar correctamente los tokens de suplantación de Windows — préstamo de privilegios por hilo y una forma segura de revertirlos
Guía práctica sobre los tokens de suplantación de Windows: tokens de acceso, primarios y de hilo, niveles de suplantación, RevertToSelf y...
El malentendido de que TCP permite recibir por cada unidad enviada con Send ── diseño de recepción para tratarlo como flujo de bytes
En TCP, suponer que se recibe por cada unidad enviada con Send o Write provoca fragmentación, uniones, texto corrupto y protocolos rotos....
Cómo distinguir la espera de GC de una fuga de memoria en .NET — Procedimiento práctico para observar, comparar y demostrar el crecimiento de memoria
Cómo distinguir, en aplicaciones .NET, si la memoria crece por espera de GC o por una fuga real, usando dotnet-counters, dotnet-gcdump y ...
WinDbg + SOS para leer volcados de memoria — introducción práctica al análisis tras la recolección
Cómo leer volcados de memoria de Windows con WinDbg y SOS: rutas de símbolos, !clrstack, !dumpheap, !analyze -v y diferencias con dotnet-...
¿Qué es Roslyn? ── Leer, corregir y generar código C# desde la perspectiva del compilador
Resumen de Roslyn (.NET Compiler Platform): Syntax Tree, SemanticModel, Workspace, Analyzer, Source Generator, y sus usos y precauciones ...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Investigación de fallos y problemas prolongados
Fallos intermitentes, diagnóstico de comunicaciones, bloqueos prolongados y pruebas de rutas de error.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
Investigación de fallos y causas
Investigamos fallos difíciles de reproducir, problemas tras largos periodos, fugas e interrupciones de comunicación.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Qué es un archivo PDB?
- PDB es la sigla de Program Database (base de datos de programa), y también se conoce como archivo de símbolos, un tipo de archivo de información de depuración. Su función es enlazar el .dll o .exe generado tras la compilación con el código fuente que puede leer un desarrollador. Contiene nombres de funciones y métodos, nombres de variables locales, nombres de archivo fuente y números de línea, la correspondencia entre la posición en el código fuente y las instrucciones ya compiladas, e información de obtención de código fuente para Source Link, entre otras cosas, y el depurador o las herramientas de diagnóstico lo usan para determinar a qué línea de código fuente corresponde una instrucción dada.
- ¿La aplicación funciona sin el archivo PDB? ¿Se puede eliminar sin problema?
- Normalmente el PDB no es necesario para ejecutar la aplicación: basta con tener el .dll o el .exe para poder iniciarla. Sin embargo, sin el PDB resulta difícil colocar puntos de interrupción, ejecutar paso a paso, mostrar el nombre de archivo y el número de línea en el seguimiento de pila (stack trace) de una excepción, analizar volcados (dumps) o entrar (step into) en bibliotecas externas. El PDB no es "un archivo para que la aplicación funcione", sino "un archivo para investigar", así que, independientemente de si se coloca o no en el entorno de producción, siempre debe conservarse como artefacto de build. Si se elimina, reproducirlo por completo volviendo a compilar desde el mismo commit resulta sorprendentemente difícil, y eso puede volverse irreparable durante la investigación de un incidente.
- ¿Hace falta el PDB también en un build Release?
- Sí, hace falta. De hecho, el que se necesita para investigar un incidente en producción es precisamente el PDB del build Release. Si en producción se está ejecutando un build Release, tener el PDB del build Debug no sirve de nada: lo que necesita el depurador es el PDB generado exactamente cuando se creó ese binario de producción. El PDB debe coincidir con el binario correspondiente, así que en CI/CD lo básico es conservar juntos el ID de commit, el número de build, el .dll/.exe y el .pdb. Cabe señalar que, en los proyectos con el estilo de SDK actual de .NET, tanto Debug como Release generan Portable PDB de forma predeterminada.
- ¿Se puede colocar el archivo PDB en el entorno de producción?
- No es un simple sí o no: la decisión se toma según cuatro ejes: la facilidad de investigar incidentes, el riesgo de divulgar información, el tamaño de los artefactos distribuidos y las reglas de operación. En un sistema interno, colocar el .dll y el .pdb en la misma carpeta es una práctica realista, porque facilita que aparezcan números de línea en los logs de excepciones. En una distribución externa, el PDB puede revelar rutas locales, la estructura de carpetas interna, nombres de tipos y de variables locales, o la URL del repositorio de Source Link, así que hay que decidir con cautela y, si es necesario, optar por limitarse a los public symbols o distribuir a través de un servidor de símbolos. Aunque no se coloque, es obligatorio conservar el PDB de ese mismo build.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.