The first key for a successful debug on Windows : the Symbols
I have long pondered about the subject of my first post. Debugging subtle bugs on Windows can be easy or not, but one thing is sure: it is really a pain to understand what happen in a software (at least a native one) without the Symbols. There is lot to say about it, and you can find lots of article on the internet describing the contents and the benefits. My approach will be much more practical: a short description, and why you definitely have to create and use them.
What is a symbol file?
A symbol file has the extension “.pdb” (Program DataBase) on Windows systems. Every compiler that I know can generate it for you (Visual C++/.NET, Delphi, even VB6 IDE). Even if it can be a large file (especially with C++), they are worth being generated for each piece of dll/exe, because they are the difference between be able to fix your issues, or stand in front of a customer issue without having the first clue about what happened.
ALWAYS generate symbols for ANYTHING that can be executed. Don’t think. Do it. And keep them in a safe place (I will soon write a post about symbol servers, a handful way to manage them).
I generated once a symbol file for my dll. Do I have to recreate it each time?
Hell yes. A symbol file is closely related to its executable file. It contains a unique ID that matches your dll/exe, and changes every time you compile it. WinDbg have a few commands to bypass this check (but by doing it, you’re not sure of the behavior, I warned you), but Visual Studio cannot. Just remember that you cannot use a symbol file that has not been generated while you compiled your code.
Ok, I generate my symbol files each time I compile. What can I do with them?
Any decent debugger on Windows (Visual Studio, WinDbg, etc.) can use symbols to retrieve useful information about your live application, or from a crash dump. You will have a callstack, you will have the value of each local variable (well, most of the time). You will also have the file and the line matching each single assembler instructions if the guy who compiled the binary chose to insert them. Nice, isn’t it?
An easy way to make a debugger load the symbol is…copying it next to your binary. That’s all. Even with complex configurations of your symbol directories, every debugger that I know will always have a look right next you exe/dll.
Well, I’m not convinced yet
I wrote a very simple and dumb program in C++, whose main feature is to trigger a division by zero exception. Here is my debugger view (Visual Studio 2010) without symbols:
All you can see is a bunch of assembler code. Pretty sad. But with symbols, you have this:
Your code, a decent call stack (with function names!) and….the local variable. Convinced now?