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); 
        } 
    }

Advertisements

10 Comments »

  1. I would like to see a continuation of the topic

    Comment by Maximus — December 19, 2007 @ 9:58 pm | Reply

  2. Maximus–

    I appreciate knowing that you found the article helpful! Many of my article ideas come from blog feedback, emails from readers, and search parameters people use to find the blog.

    What, specifically, do you think would be helpful if I were to write another article along these lines?

    MW

    Comment by dotnetthoughts — December 21, 2007 @ 9:43 pm | Reply

  3. 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.

    Has anyone else seen this occuring?

    Comment by Phil Morris — May 10, 2008 @ 2:02 pm | Reply

  4. @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.

    Just wondering if anyone’s seen anything about doing that?

    Thanks
    Darin

    Comment by Darin — June 15, 2008 @ 8:45 am | Reply

  5. There is a better way to get exact line number.You sample code was great,i just tweaked it to get just the line number

    here is my function

    private int getErrorLineNo(Exception ex)
    {
    System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace(ex, true);
    System.Diagnostics.StackFrame frame = st.GetFrame(st.FrameCount – 1);
    return frame.GetFileLineNumber();
    }

    Comment by Santh — August 12, 2008 @ 1:07 am | Reply

  6. hi santh thank u

    Comment by munna — September 5, 2008 @ 4:22 am | Reply

  7. Hi,

    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.

    public static void LogException(Exception ex)
    {
    System.Diagnostics.StackTrace Trace = new System.Diagnostics.StackTrace(ex, true);
    System.Diagnostics.StackFrame frame = Trace.GetFrame(Trace.FrameCount – 1);
    string FileName = frame.GetMethod().ToString();
    string MethodName = frame.GetMethod().ToString();
    string LineNumber = frame.GetFileLineNumber().ToString();
    }

    Thanks,
    Franklin

    Comment by Franklin — November 5, 2008 @ 9:47 pm | Reply

  8. 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.

    Comment by Me — January 13, 2009 @ 1:35 pm | Reply

  9. There is obviously a lot to learn. There are some good points here.

    Robert Shumake

    Comment by Robert Shumake — February 2, 2010 @ 4:48 pm | Reply

  10. Right now it looks like WordPress is the top blogging platform available right now. from what I’ve read Is that what you’re using on your blog? bcdgekkckade

    Comment by Johnk370 — May 19, 2014 @ 6:27 pm | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: