When people post the output from the WinDbg !analyze -v command to our NTDEV support forum, it’s often useless. The kernel stack doesn’t make sense. And there are banners and error messages saying “the symbols are wrong.” When this happens, the next thing that occurs is often a chorus of replies saying little more than “Fix your symbols and then repost your output!”
A while back, it occurred to me that just telling people to “Fix your symbols!” probably isn’t the greatest advice. If they knew anything about fixing their symbols, they probably wouldn’t have posted the useless output in the first place.
So, in this short video and article, I’m going to describe how to properly set the WinDbg symbol search path.
Symbols are what allow the debugger to show you symbolic debug information. The visual C compiler takes all the symbolic debug information and it stores it in a separate file called a program database file, which we refer to as a PDB file. It’s in that PDB file that we find things like our data types, function names, and global variable names. It’s also where we find our source line information. Without that file, all the debugger has is a bunch of virtual addresses and a bunch of assembly language instructions.
It’s the PDB file that allows the debugger to show us our data types, allow us to set break points on a particular function by name, to bring up the source code when we hit that break point, and to allow us to actually do effective debugging.
One thing it’s important to understand about PDB files is that they are generated on a per build basis. Every time you build your driver you get a new PDB file, even if you didn’t change the source code prior to that build. The PDB file exactly matches the binary that was generated for that build. The way the operating system determines which PDB goes with which binary is by stamping a globally unique identifier (GUID) into both the PDB and the executable.
When we set our symbol search path, we tell WinDbg, the debugger, where these PDB files are located. We can do that in a several different ways:
- One way we can tell WinDbg where our PDB file resides is by doing nothing. This can work, because your executable image header (the header on your driver’s .sys file) contains a fully qualified path to your PDB. When you’re debugging, if your PDB is in exactly the same place it was when you built your driver, the debugger can find your executable image header, locate the path, open the PDB, and you’re done!
- The next way we can tell WinDbg where our PDBs are is by explicitly specifying a local directory to look in to find the file. You can specify this directory in WinDbg’s Symbol Search Path dialog box, or with the .SYMPATH command.
- The last way that we can tell WinDbg where to find PDBs for our driver is through a symbol server. As you can imagine, if a new PDB gets generated for every single build, then that’s a lot of PDBs to keep around. That could be an awfully big directory, plus we have a naming problem because the PDB has the same name every time.To help with this, we can use a utility called SymStore to index our PDBs onto a symbol server. Then, we can point WinDbg to our symbol server, and WinDbg will just automatically locate (and make a local copy of) the appropriate PDB for our driver image running on the target.How do we actually specify a symbol server? It’s a simple syntax, that looks like the following:
In the string above, the first thing we specify is the 3 characters SRV. The next thing we have to specify is a local directory that we want to cache our PDBs in. In the example above, this is the directory “C:\debug”. WinDbg is going to take the PDBs from the symbol server and copy them to the local cache. Next, we have another star and then the symbol server UNC path, which in our example above is \\symsrv\symbols.Symbol servers are actually kind of cool because there’s no special software that you have to run on the server. That SymStore utility simply copies the PDBs into a particular directory structure, and the directory structure becomes the index.
In addition to having symbols for our driver, we’re also required to have symbols for the operating system. Note that this isn’t just recommended, it’s actually required to enable correct debugging. Thankfully, Microsoft provides a public symbol server on to which they index all of the Microsoft PDBs. In addition to specifying potentially a local symbol server with our own PDBs, we could also specify the Microsoft symbol sever. The syntax is pretty much the same as that shown above. For example, to specify “C:\debug” as the local symbol cache, and point to the Microsoft symbol server, you would use the syntax:
It’s best to see this next part in the video that accompanies this article, but I’ll still explain the steps for you here. We’re going to go through the steps of having broken symbols and then actually fixing them.
The first thing I’ll do is I will open up a crash dump from within WinDbg. Obviously, I’m going to get an endless stream of errors and warnings about how my symbols are wrong. Warnings you’ll see include “Please fix your symbols” and “Your kernel symbols are wrong.” It should be pretty clear that we can’t do analysis with our symbols like this.
If we scroll down to the end though, we see something interesting. We see that the symbols for my driver have actually been found. The debugger knows about the symbols for my driver. It’s giving me a name for my function, and it found that through the executable image header as a path the PDB which it used. So, that much worked at least. And it worked because the PDB file for my driver is still located in the same place it was when my driver was built. So I was able to “do nothing” and WinDbg was able to find my driver’s PDB file just as I described earlier.
WinDbg was not able to find the OS symbols, however. For the operating system symbols, I’m going to point the debugger off to the Microsoft symbol server, which again is the big index of all of the Microsoft symbols. I can do that many different ways, but I’ll bring up the symbol search path from the file menu, and within this symbol search path, I’m going to type out a path to the Microsoft symbol server.
Once I’ve set my symbol search path to point to the Microsoft symbol server, I can hit OK to close the dialog box, but note that all I did was change my symbol search search path. This doesn’t cause WinDbg to actively reevaluate symbols using this new path or to re-download symbols to the local directory.
So, to get WinDbg to use the new symbol search path, we issue the .reload WinDbg command. This forces WinDbg to go out onto the internet to the Microsoft symbol server and download the symbols. In doing this, I received no errors or warnings, and those big banners about my kernel symbols being wrong are now gone.
And now I can start my analysis, which always begins with !analyze -v.