I suppose that I don’t need to stress how important it is to have valid PDB files while debugging. Normally the PDB files are silently loaded by the debugger and you are happy to see all the symbols resolved in your modules window. Unfortunately you may also run into situation when the debugger will not be able to find matching symbols. The reason may be as trivial as a broken Internet connection or much more complicated like mismatched signatures. In this post I’m going to show you how to check your symbol files before debugging as well as how to extract the source file information from them. As there are different ways (and tools) to manipulate the symbol files I will present the ones that I am aware of, but feel free to leave comments about tools that I might have missed and I will try to update the post.
Downloading a PDB file for a given PE file
Some time ago Mike Stall blogged about different Symbol APIs available for developers. As the PDB file format is a Microsoft’s secret all the tools that I’m presenting are just wrappers over those APIs. To work with PDB files we first need to get them. Let’s list the tools that will help us in it.
symchk.exe
Symbol Checker (Symchk.exe) is an application that compares executable files to symbol files to verify that the matching symbols are available. Symchk may also be used to populate your symbol cache. It can read symbol information from PE files (exe, dll), dump files and processes. It also supports recursive directory search and batch files.
We will start from loading symbols for the kernel32.dll library:
c:\Windows\System32>echo %_NT_SYMBOL_PATH% SRV*C:\Symbols\MSS*http://referencesource.microsoft.com/symbols;SRV*C:\Symbols\MSS*http://msdl.microsoft.com/download/symbols c:\Windows\System32>symchk /v /os kernel32.dll [SYMCHK] Searching for symbols to c:\Windows\System32\kernel32.dll in path SRV*C:\Symbols\MSS*http://referencesource.microsoft.com/symbols;SRV*C:\Symbols\MSS*http://msdl.microsoft.com/download/symbols DBGHELP: Symbol Search Path: SRV*C:\Symbols\MSS*http://referencesource.microsoft.com/symbols;SRV*C:\Symbols\MSS*http://msdl.microsoft.com/download/symbols [SYMCHK] Using search path "SRV*C:\Symbols\MSS*http://referencesource.microsoft.com/symbols;SRV*C:\Symbols\MSS*http://msdl.microsoft.com/download/symbols" DBGHELP: No header for c:\Windows\System32\kernel32.dll. Searching for image on disk DBGHELP: c:\Windows\System32\kernel32.dll - OK SYMSRV: C:\Symbols\MSS\kernel32.pdb\9B30FD7CD6B44975BF34B43B6EF668212\kernel32.pdb not found SYMSRV: http://referencesource.microsoft.com/symbols/kernel32.pdb/9B30FD7CD6B44975BF34B43B6EF668212/kernel32.pdb not found SYMSRV: kernel32.pdb from http://msdl.microsoft.com/download/symbols: 704453 bytes - copied DBGHELP: kernel32 - public symbols C:\Symbols\MSS\kernel32.pdb\9B30FD7CD6B44975BF34B43B6EF668212\kernel32.pdb [SYMCHK] MODULE64 Info ---------------------- [SYMCHK] Struct size: 1680 bytes [SYMCHK] Base: 0x0000000078D20000 [SYMCHK] Image size: 1175552 bytes [SYMCHK] Date: 0x4e21213b [SYMCHK] Checksum: 0x0012386d [SYMCHK] NumSyms: 0 [SYMCHK] SymType: SymPDB [SYMCHK] ModName: kernel32 [SYMCHK] ImageName: c:\Windows\System32\kernel32.dll [SYMCHK] LoadedImage: c:\Windows\System32\kernel32.dll [SYMCHK] PDB: "C:\Symbols\MSS\kernel32.pdb\9B30FD7CD6B44975BF34B43B6EF668212\kernel32.pdb" [SYMCHK] CV: RSDS [SYMCHK] CV DWORD: 0x53445352 [SYMCHK] CV Data: kernel32.pdb [SYMCHK] PDB Sig: 0 [SYMCHK] PDB7 Sig: {9B30FD7C-D6B4-4975-BF34-B43B6EF66821} [SYMCHK] Age: 2 [SYMCHK] PDB Matched: TRUE [SYMCHK] DBG Matched: TRUE [SYMCHK] Line nubmers: FALSE [SYMCHK] Global syms: FALSE [SYMCHK] Type Info: FALSE [SYMCHK] ------------------------------------ SymbolCheckVersion 0x00000002 Result 0x00030001 DbgFilename DbgTimeDateStamp 0x4e21213b DbgSizeOfImage 0x0011f000 DbgChecksum 0x0012386d PdbFilename C:\Symbols\MSS\kernel32.pdb\9B30FD7CD6B44975BF34B43B6EF668212\kernel32.pdb PdbSignature {9B30FD7C-D6B4-4975-BF34-B43B6EF66821} PdbDbiAge 0x00000002 [SYMCHK] [ 0x00000000 - 0x00030001 ] Checked "c:\Windows\System32\kernel32.dll" SYMCHK: FAILED files = 0 SYMCHK: PASSED + IGNORED files = 1
As you can see in the verbose mode (/v switch) you receive a lot of information about what symchk is doing. We can even read which symbols API it is using (dbghelp messages). The /os switch informs symchk to print full paths of the symbol files in the output messages. After running this command the kernel32.pdb file should be in our symbol store. If you would like to index the whole System32 directory you would like to use the /r switch which informs symchk to recursively step through the provided directory and download symbols for all the files found, eg. symchk /r /v c:\windows\system32\*.dll
Let’s try to load symbols for the notepad.exe process:
c:\temp\symtest>tasklist /FI "IMAGENAME eq notepad.exe" Image Name PID Session Name Session# Mem Usage ========================= ======== ================ =========== ============ notepad.exe 2264 Console 1 6 036 K c:\temp\symtest>symchk /ip 2264 /s SRV*.*http://msdl.microsoft.com/download/symbols SYMCHK: FAILED files = 0 SYMCHK: PASSED + IGNORED files = 26
With the /ip switch we can provide just process ID and symchk will download symbol files for all the modules loaded in the process. In this example we also used the /s switch which provides symchk with symbol path that it should use (overriding _NT_SYMBOL_PATH if set). In our case we were downloading symbol files from the Microsoft public server to the current directory. The listing of this directory after running this command will look as follows:
c:\temp\symtest>tree . Folder PATH listing Volume serial number is 00000002 C622:C13F C:\TEMP\SYMTEST ├───advapi32.pdb │ └───6AEFDCFF7F2A429B8532CD2BFDDF85D12 ├───CLBCatQ.pdb │ └───60B9D310C472440BA13F66BFF0FC39E32 ├───comctl32.pdb │ └───943BA638A2CD4D88A1C7E7418EAF796C1 ├───comdlg32.pdb │ └───631B57376F8549FDB2E7A8AB3D2D1FDF2 ├───cryptbase.pdb │ └───F03E074BB9E74C9F9BBFB0E42EF3A0AB2 ├───dwmapi.pdb │ └───8683ED0C3DBE4053883EC22FD9B4F2102 ├───gdi32.pdb │ └───FB9403C3B1304DA192C4D0E3485E25ED2 ├───imm32.pdb │ └───98F27BA5AEE541ECBEE00CD03AD50FEE2 ├───kernel32.pdb │ └───9B30FD7CD6B44975BF34B43B6EF668212 ├───kernelbase.pdb │ └───61044362232B410AA600843CEBFD11612 ...
Another interesting switch is (/id) which enables you to debug dump files.
Another great functionality of symchk.exe is something called manifest files. Manifest files contain information about all symbols that must be downloaded. You may then run symchk with /om switch which will produce the manifest file without downloading any symbols. Then you can copy the manifest file to any computer that has the Internet connection and download the symbol files using /im switch. The snippet below shows an usage example:
c:\temp\symtest>symchk /om notepad-symbols.man /ip 2264 SYMCHK: FAILED files = 0 SYMCHK: PASSED + IGNORED files = 26 c:\temp\symtest>type notepad-symbols.man notepad.pdb,36CFD5F9888C4483B522B9DB242D84782,1 notepad.exe,4a5bc9b335000,1 ntdll.pdb,6192BFDB9F04442995FFCB0BE95172E12,1 ntdll.dll,4ce7c8f91a9000,1 kernel32.pdb,9B30FD7CD6B44975BF34B43B6EF668212,1 kernel32.dll,4e21213b11f000,1 kernelbase.pdb,61044362232B410AA600843CEBFD11612,1 KernelBase.dll,4e21213c6c000,1 ... c:\temp\symtest>symchk /im notepad-symbols.man /s SRV*.*http://msdl.microsoft.com/download/symbols SYMCHK: FAILED files = 0 SYMCHK: PASSED + IGNORED files = 52
dbh.exe
This tool is a wrapper over the DbgHelp.dll library and uncovers almost all of its functionality. We will just look at the one usage example so if you would like to go deeper have a look at the Debugging Tools for Windows help. When you run dbh.exe with a module name as an argument it will automatically download the symbol files. So by simply calling dbh c:\windows\system32\kernel32.dll info
you will download the symbol file (_NT_SYMBOL_PATH environment variable is used) and print information about it and its PE file (kernel32.dll):
c:\temp>dbh c:\windows\system32\kernel32.dll info SizeOfStruct : 0x690 BaseOfImage : 0x1677721664x ImageSize : 0x1000000 TimeDateStamp : 0x4e21213b CheckSum : 0x12386d NumSyms : 0x0 SymType : SymPdb ModuleName : kernel32 ImageName : c:\windows\system32\kernel32.dll LoadedImageName : c:\windows\system32\kernel32.dll LoadedPdbName : C:\Symbols\MSS\kernel32.pdb\9B30FD7CD6B44975BF34B43B6EF668212\kernel32.pdb CVSig : 0x53445352 CVData : kernel32.pdb PdbSig : 0x0 PdbSig70 : 0x9b30fd7c, 0xd6b4, 0x4975, 0xbf, 0x34, 0xb4, 0x3b, 0x6e, 0xf6, 0x68, 0x21 PdbAge : 0x2 PdbUnmatched : false DbgUnmatched : false LineNumbers : false GlobalSymbols : false TypeInfo : false SourceIndexed : false PublicSymbols : true MachineType : X64
If you would like to see some verbose information about PDB files loaded use -n switch. To change the default symbol path (or override _NT_SYMBOL_PATH settings) use the -s: switch.
dumpbin.exe
You might be surprised that this tool appears here, but have you ever noticed its /PDBPATH[:VERBOSE] switch? Issuing the dumpbin /pdbpath:verbose
on our kernel32.dll library will result in downloading the PDB file from the public symbol store:
c:\temp\symtest>dumpbin /pdbpath:verbose c:\windows\system32\kernel32.dll Microsoft (R) COFF/PE Dumper Version 10.00.40219.01 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file c:\windows\system32\kernel32.dll File Type: DLL PDB file 'c:\windows\system32\kernel32.pdb' checked. (File not found) PDB file 'c:\temp\symtest\kernel32.pdb' checked. (File not found) PDB file found at 'C:\Symbols\MSS\kernel32.pdb\9B30FD7CD6B44975BF34B43B6EF668212\kernel32.pdb' Summary 2000 .data A000 .pdata 6E000 .rdata 8000 .reloc 1000 .rsrc 9B000 .text
Downloading source code from the source server
srctool.exe
This tool is quite interesting as it allows you to check which source files are indexed in the PDB file and eventually extract them. With -r switch you may check which source code paths were hardcoded in the PDB file, eg.
c:\temp>srctool -r ConsoleApplication1.pdb D:\lab\symbols-lab\symbols\ConsoleApplication1\Program.cs D:\lab\symbols-lab\symbols\ConsoleApplication1\AdvertQuickView.cs
Without any switch srctool will examine the source server stream in the PDB file (if it exists) and print commands that will be executed to extract the source files, eg:
c:\temp>srctool ConsoleApplication1.pdb [D:\lab\symbols-lab\symbols\ConsoleApplication1\Program.cs] cmd: cmd /c svn.exe cat "svn://localhost/test2/Program.cs@1" --non-interactive --username admin --password admin > " [D:\lab\symbols-lab\symbols\ConsoleApplication1\AdvertQuickView.cs] cmd: cmd /c svn.exe cat "svn://localhost/test2/AdvertQuickView.cs@1" --non-interactive --username admin --password admin > " ConsoleApplication1.pdb: 2 source files are indexed
To run the commands and thus extract the source files you just need to add the -x switch to the above call. Additionally using the -d switch you may specify the directory to which the source files will be extracted (by default it’s the current directory).
pdbstr
If you would like to have a better control over the source server stream you may check the pdbstr command. With its aid you can read and update the source server information in the PDB file. The source server stream is actually a text block with predefined sections (more info can be found here). You can dump its content by issuing:
c:\temp>pdbstr -r -p:ConsoleApplication1.pdb -s:srcsrv > stream.txt
The stream.txt file should contain something like:
SRCSRV: ini ------------------------------------------------ VERSION=1 INDEXVERSION=2 VERCTRL=Subversion DATETIME=Thu Nov 17 13:31:46 2011 SRCSRV: variables ------------------------------------------ SVNUSER=admin SVNPASS=admin SVN_EXTRACT_TARGET=%targ%\%fnbksl%(%var3%)\%var4%\%fnfile%(%var1%) SVN_EXTRACT_CMD=cmd /c svn.exe cat "%var2%%var3%@%var4%" --non-interactive --username %svnuser% --password %svnpass% > " %svn_extract_target%" SRCSRVTRG=%SVN_extract_target% SRCSRVCMD=%SVN_extract_cmd% SRCSRV: source files --------------------------------------- D:\lab\symbols-lab\symbols\ConsoleApplication1\Program.cs*svn://localhost/*test2/Program.cs*1 D:\lab\symbols-lab\symbols\ConsoleApplication1\AdvertQuickView.cs*svn://localhost/*test2/AdvertQuickView.cs*1 SRCSRV: end ------------------------------------------------
You can then apply the changes you’d like and save the new source stream definition back to the PDB file:
c:\temp>pdbstr -w -p:ConsoleApplication1.pdb -s:srcsrv -i:stream.txt
Pdbstr will not extract the source code files for you but from the source stream content you can easily read where to find them.
That will end our list of PDB tools – I hope that you will find it useful. Please remember to leave a comment if you know any other tool that should be enlisted here.
Hi Sebastian,
Thanks for your article. It’s very insteresting.
I am trying to inspect the symbols of a .NET program but I only can see the public methods. I can’t see any variable (at class or local level).
I tried with DBH and DIA2DUMP with the same result.
I am targeting NET 4.0.
I am building in debug mode.
Do you have any idea why this is happening ?
Thanks in advance. Regards.