October 27, 2007

Where Did My Exception Occur?

A couple of times a day, this blog takes a hit from someone trying to figure out how to programmatically determine on what line number an exception occurred. Determining this information actually turns out to be a fairly straight forward operation.

The one caveat for retrieving exception line numbers is that your application must have access to the portable database (pdb) file generated when the code is compiled. In C#, the default is to create this file for both a release and debug build. Unless you actively make a decision to exclude this file, you should be able to find it alongside your application or dll in your bin/<buildtype> directory.

The key to determining the line number on which an exception occurs is to check out the stack trace. A stack trace contains the history of all of the method calls made in your application up to a given point in time. If debug information exists, the stack trace can also display the file, module, and line number on which an exception occured. There are two ways to access the stack trace information: a string directly from the exception, and StackTrace object from the System.Diagnostics namespace.

At the bottom of this post is sample code which accesses stack trace information directly from an exception object. The Main method calls GetValue. GetValue calls DivideByZero, which throws an exception. The exception is caught in the Divide method and is displayed on the console. When I run the code on my computer, the following stack trace is returned:

This StackTrace contains a snapshot of my application at the point in time the error occured. The most recent call exists at the top of the stack. Reading from the bottom up, we can see that GetValue (line 23) called Divide (line 36). Our application triggered three method calls into the Decimal object, itself.

Viewing the stack trace string directly from an Exception object has a couple of drawbacks. For starters, we can only see the stack trace to the point in time that the exception was handled in our code. The stack trace does not show that Main initially called GetValue(). In our application, this is trivial to figure out, but if GetValue() can be called from multiple methods, we may want to be able to see higher up the stack. Also, we may want to persist our exception information by file, module and line number. We could probably come up with some crazy regular expression to pull this information out, but it wouldn’t be much fun to say the least.

The StackTrace object in the System.Diagnostics namespace allows us to see the entire stack trace above an exception when it occurs. Let’s modify the code in the Catch block to use a StackTrace object. The ‘true’ parameter indicates that we do want access to the file name, module name, and line number.

If we want to pull out line or module information on a specific frame, we can iterate over the Frames object exposed by the stack trace. If we tweak the Catch block one more time, we can see how we can pull out individual pieces of data. This could potentially be used to automatically log our exceptions to a database should a problem occur. Note that in this example, I’m going to pass in my exception to the constructor of the StackTrace object, indicating that I want to take a look at the StackTrace of this particular exception.

I use a release build PDB file to help identify the location of errors. However, I found that the line number reported from the release build does not correspond exactly with the line number in my code.

I had one particulate error (the ubiquitous ‘Object reference not set to an instance of an object’ occurring at a line that was simply assigning a value to a property; all the property was doing was assigning a value to a ViewState variable. In the end the error was 4 lines on fom that point. It happened that prior to the reported line number there were 4 blank lines in the function. So it appears that blank lines are ignored when line numbers are reported in the stack trace.

@Phil
I’ve seen this behavior in the past because of compiler optimizations. Sometimes, the resulting code gets optimized to the point where it’s tough for the compiler to correlate the original source line number with actual generated code.

If you add blank lines, does the line number not change? Just curious.

To anyone in general:
While it’s pretty trivial to generate the PDB, has anyone ever heard of any way to “embed” the PDB into the compiled EXE/DLL. I thought that was an option of the VS6 compilers/linkers, but I’m not finding any info on doing anything like that with VS2008.

I’m just thinking from a distribution standpoint, I’d rather not have this “extra” file that I have to distribute and that could easily get out of synch with the actual DLL/EXE.

getErrorLineNo() function realy helped me to get what I want. Thanks,
I would like to add few of my code which would be more helpfull in this prospect. Here, you get the filename, LineNumber and the MethodName where the Exception Orginated.

When debugging sometimes the stacktrace does not contain the line number, I dont know why, it shows me the method the started the whole mess (onpacketrecieved), Other times the ex shows me everyting I need to know. I’m clueless why sometimes when I degub the app I get all the info, and other times the stack trace lacks the line number in the same try catch block. I solve this by adding a few more try catches to isolate which section is causing the problem. Maybe this will help someone else.

[…] Where Did My Exception Occur? | Dot Net Thoughts – Oct 27, 2007. The one caveat for retrieving exception line numbers is that your. contains a snapshot of my application at the point in time the error occured. […]