A while ago I wrote a blog-post about not breaking the stack trace while throwing an exception up the call stack. Stack traces are the very first place you look when finding the cause of an unexpected exception. Today I was once again saved by a stack trace. A project I am currently working on is starting a pilot in Cairo next week and had some trouble with a NullRefrenceException that was blocking one of the main scenarios. The entire pilot should be called-off, if this didn't get fixed right away.

Luckily the application's crash screen did not only allow me to view the exceptions stack trace (which is pretty common), but we had also deployed the compilers .PDB files along with the application's assemblies. PDB files contain (among other things) information about which IL-instruction corresponds to which source file and line number. The Visual Studio debugger uses this to place the yellow arrow at the next line of code that will be executed. Because most people don't debug on their production environment why should you deploy these files? By default Visual Studio doesn't even generate them for a Release build.

Besides assisting the debugger, the information in the .PDB files are also used at runtime when you call the Exception.ToString() method. At each level in the stack trace after the name of the executing method, the source file and the line number of the executing statement are added, if a PDB file is present for the assempley containing the method. This looks something like this.

System.NullReferenceException: Object reference not set to an instance of an object.
   at Demo.Class1.Foo(Object parameter) in c:\projects\Demo\class1.cs:line 48 
   at Demo.Class1.Main(String[] args) in c:\projects\Demo\class1.cs:line 21

With this information I retrieved the correct version of the source file from source control (of course we label the sources with the build-number in our build cycle) and scrolled to the line number I had found in the stack trace. This showed me the exact statement that caused the NullReferenceException in less than five minutes, without having to reproduce the error in the development environment (which would have been quite tricky in this case). By only looking at the code I was able to see that someone had made a configuration error while installing the pilot environment. Correcting the configuration error fixed the problem and the pilot was ready to go.

So unless you have good reasons to hide information about your source code from your end users, you should always set the compilers 'Generate Debugging Information' option to true for Release builds and keep the PDB files along side the assemblies wherever they go.

9 thoughts on “Show source file and line number information in your Stack traces

  1. It’s a best practice as well to do defensive checking of configuration parameters as early as possible in the application life cycle, preferably during start-up. You can throw some more informative exceptions about the problems, which then usually can be solved first-hand (ASP.Net has the very nice ConfigurationException with line-precise error reporting). Without checking the application breaks at odd places at odd moments, which causes more stress and sometimes creates a lack of trust in the stability of the application.

    Most end-users are very intimidated by stack traces, and think the web site is severely broken because of all the weird text on-screen. You can solve this by simply not showing the stack trace in the error page, just log it instead. Of course, you can then still log with full debugging information unless performance requirements demand otherwise. :)

  2. Peterhe is right that you should try to avoid configuration errors from popping up at random while using the application, but that of course is true for any unexpected exception. The problem with unexpected exceptions is; well, you did not expect them. And if something happens that you did not expect your application is indeed severely broken!

    The point of course is not that this problem could have been avoided, but that you should maximize the amount of information available is a crash sitation without overwhelming the end user with technical details.

  3. We obfuscate(sp) our code for our release product and if a user reports a bug to us we are getting a stack trace and other goodies, however all the line numbers are zero. If we include the pdb and instruct the compiler to include debugging information, will we be allowing people to reverse engineer our code?

    If that is the case, can the pdb wbe analysed with a stack trace outside a runtime envornonment to work out the line number of the problem?
    BryonBakerAUS@hotmail.com?

  4. In my post I wrote “unless you have good reasons to hide information about your source code from your end users..”. This is true for most of the projects I work on, but as I guess not for your project.

    I think the problem with the obfuscator is that it removes (among other things) all newlines from the sourcecode before it is passed to the compiler. When generating the pdb file, the compiler has no idea which original source line generated which IL instruction. To get the original source lines in the PDB file you would have to somehow get your obfuscator to keep the line numbers in tact (maybe this is an option somewhere?). The information in the PBD might help a bit to reverse enginere the code, it does contains a lot of information about your code.

    Without the BDP file there is no way I know of to get the source line information in the stack trace. Another option alltogether would be to customize the rendering of the stacktrace, and instead of printing the source line numbers, print the adress of the IL instruction. Maybe you can use Reflector on the Exception.ToString method to find out how you should do this. If a user submits this information, you should be able to map it back to the original sources. Don’t think it will be easey, but keep me posted if it works :-)

  5. In VS 2005, there are two options other than NONE – FULL and PDB ONLY for ‘Generate Debug Info’.

    For the purposes that you have discussed in this article, which one is appropriate?

  6. In VS2005 this setting is found in the project properties page (right click the project -> properties). On the build tab choose Advanced…

    The diffrence between FULL en PDB-Only is explained in the MSDN. (press F1 in the dailog) The default in VS 2005 for a release build is PDB only, for a Debug build it is Full. I have not tested it yet, but I think PDB only is enough for the purpose described above, this means that you can just leave the defaults as they are.

  7. #line can be used in the obfuscated code.  It does not have to give real line number and file name, you can have your secret map.