How the Visual Studio Debugger Finds Debug Information
When you build your source code locally on your development machine and then run it under the debugger, the debugger finds the debugging information automatically. What about when you didn’t build it yourself?
(Part 2 of a series on Windows debugging.)
Local build
At build time the debug information is placed on disk adjacent to the binary; for example C:\source\myprogram\bin\Debug\foo.exe
and C:\source\myprogram\bin\Debug\foo.pdb
. If you debug the program the debugger will just find the PDB file right there.
If you move the build binaries to another directory (e.g. C:\bin\foo.exe
), the debugger just finds the PDB file anyway. How? Because the compiler records the generated PDB path (C:\source\myprogram\bin\Debug\foo.pdb
) in the binary.
We can describe this as a search sequence:
- You are debugging
C:\bin\foo.exe
, so look forC:\bin\foo.pdb
. - It was originally compiled to
C:\source\myprogram\bin\Debug\foo.exe
so look forC:\source\myprogram\bin\Debug\foo.pdb
.
Remote build
It’s common for the “official” build to happen in some kind of official build environment, like CI/CD or a build lab. As a developer if you copy those binaries to your development machine and try to debug them, you’ll quickly get stuck if you didn’t copy the PDB with it. The debugger searches like this:
- You are debugging
C:\bin\foo.exe
, so it looks forC:\bin\foo.pdb
. - It was originally compiled to
E:\builds\myprogram\bin\Debug\foo.exe
so it looks forE:\builds\myprogram\bin\Debug\foo.pdb
.
You don’t even have an E:
drive, so that definitely doesn’t work! What next?
Hopefully when your official build saved away the outputs it also saved away that debug info. Maybe it’s on a network share, \\files\builds\mprogram\2023-02-01\123450\bin\foo.pdb
. You can tell the debugger to look in that directory and find the matching PDB file. Either use the Load Symbols
menu item in the Modules or Callstack window:
Or add to the symbol search paths in debugger settings:
Now the search sequence is:
- You are debugging
C:\bin\foo.exe
, so it looks forC:\bin\foo.pdb
. - It was originally compiled to
E:\builds\myprogram\bin\Debug\foo.exe
so it looks for ``E:\builds\myprogram\bin\Debug\foo.pdb`. - Each of the symbol paths you added in in debugger settings
You can see the log of this searching activity in the Symbol Load Information
context menu item that is visible in the menu on the above screenshot of the Modules window. It’s great for diagnosing why symbols won’t load.
I said “matching PDB”. What does it mean to “match”? What if you accidentally told it to look in the 2023-01-02\
directory instead of 2023-02-01\
? The PDB file with the right name might be found at that location, but it’s the wrong file - all the variables and functions are probably laid out differently, and the debugger wouldn’t be able to find them correctly. When the compiler records the path to the PDB file in the output binary, it also records PDB signature so the debugger can later ensure that it only loads an exact-match PDB file.
Symbol Server.
Finding PDB files this way can be a hassle. Can we do better?
If we know the PDB signature, can we use it as a key to find the matching PDB file?. Yes. When the official build runs, it can follow up with a “symbol publishing” step which places the PDB files on a network share using the signature as a directory name. For example if the PDB signature is 1234ABCD
then the PDB could be found at \\symbols\symbols\1234ABCD\foo.pdb
. You can read more about this feature in the official documentation.
Later this technology was extended to allow HTTP(S) connections to a symbol server over the internet. You can use this today to get debugging information for Windows and .NET binaries:
Symbol Cache
You see that cache entry in the symbol settings? You should set it to some local directory (e.g. C:\symbols
), so the debugger doesn’t have to go over the network every time. That joins the search sequence:
- You are debugging
C:\bin\foo.exe
, so it looks forC:\bin\foo.pdb
. - It was originally compiled to
E:\builds\myprogram\bin\Debug\foo.exe
so it looks for ``E:\builds\myprogram\bin\Debug\foo.pdb`. - The local symbol cache in debugger settings.
- Each of the symbol paths in debugger settings.