Dot Net Thoughts

October 27, 2007

Where Did My Exception Occur?

Filed under: Debugging — dotnetthoughts @ 7:19 am
Tags: , , , , ,

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:


at System.Decimal.FCallDivide(Decimal& result, Decimal d1, Decimal d2)
at System.Decimal.Divide(Decimal d1, Decimal d2)
at System.Decimal.op_Division(Decimal d1, Decimal d2)
at StackTrace.Program.Divide(Decimal a, Decimal b) in C:\DebuggingPresentation\StackTrace\StackTrace\Program.cs:line 36
at StackTrace.Program.GetValue() in C:\DebuggingPresentation\StackTrace\StackTrace\Program.cs:line 23

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.

catch (DivideByZeroException ex) 
{ 
   System.Diagnostics.StackTrace st = 
      new System.Diagnostics.StackTrace(true); 

   result = st.ToString(); 
}

When we call the ToString() method of this StackTrace object, we now get to see the entire stack up to the point the exception was handled.


at StackTrace.Program.GetValue() in C:\DebuggingPresentation\StackTrace\StackTrace\Program.cs:line 29
at StackTrace.Program.Main(String[] args) in C:\DebuggingPresentation\StackTrace\StackTrace\Program.cs:line 15
at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile,
Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

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.

catch (DivideByZeroException ex) 
{ 
   StringBuilder sb = new StringBuilder(); 
   System.Diagnostics.StackTrace st = 
      new System.Diagnostics.StackTrace(ex, true);     

   foreach (System.Diagnostics.StackFrame frame in st.GetFrames()) 
   { 
       sb.AppendLine( frame.GetFileName() + ": " + 
         frame.GetMethod() + ": " + frame.GetFileLineNumber()); 
    } 
    result = sb.ToString(); 
}

Using this code gives me my “custom” Stack trace information.


: Void FCallDivide(System.Decimal ByRef, System.Decimal, System.Decimal): 0
: System.Decimal Divide(System.Decimal, System.Decimal): 0
: System.Decimal op_Division(System.Decimal, System.Decimal): 0
C:\DebuggingPresentation\StackTrace\StackTrace\Program.cs: System.Decimal Divide(System.Decimal, System.Decimal): 45
C:\DebuggingPresentation\StackTrace\StackTrace\Program.cs: System.String GetValue(): 25

So, there you have it. A fairly simple way to get your error line numbers! Good luck with it, and code safe!

Mike

——————-Sample Code here!——————————————–

    class Program 
    { 
        static void Main(string[] args) 
        { 
            Console.WriteLine(GetValue()); 
            Console.ReadLine(); 
        }          

        static string GetValue() 
        { 
            string result = String.Empty;          

            try 
            { 
                result = Divide(3M, 0M).ToString(); 
            } 
            catch (DivideByZeroException ex) 
            { 
               result = ex.StackTrace; 
            }          

            return result; 
        }          

        static decimal Divide(decimal a, decimal b) 
        { 
            return (a / b); 
        } 
    }

Create a free website or blog at WordPress.com.