10101010101001

0101010101001

Silverlight stack trace line numbers

with 8 comments

As you probably know, to have stack traces line numbers in a .net app you need pdb files.
In Silverlight though, pdb files don’t get included in the XAP. Even if it was possible to include them you probably wouldn’t want them there because they tend to be huge and you want to have a fast loading web-site.

So what will you do when your client calls you and says he received an error message like the one below?
NullReferenceException
at ChaptersView.ChaptersTree_OnMouseMove
at ChaptersView.GetPreviousCommandButton

After I had a look today at the answer to this question, it became obvious:

Step 1)
In the place you now log or show exceptions to the user, you stop writing a normal stack trace using exception.ToString() or exception.StackTrace.
You should write a stacktrace that includes IL offsets using the code below:

...
catch (Exception exception)
{
            string errorDescription = string.Empty;
            try
            {
                StackTrace st = new System.Diagnostics.StackTrace(ex);
                string stackTrace = "";

                if (st != null)
                {
                    foreach (StackFrame frame in st.GetFrames())
                    {
                        stackTrace = "at " + frame.GetMethod().Module.Name + "." + frame.GetMethod().ReflectedType.Name +
                                     "." + frame.GetMethod().Name + "  (IL offset: 0x" +
                                     frame.GetILOffset().ToString("x") + ")\n" + stackTrace;
                    }
                }
                errorDescription = stackTrace;
            } catch //sometimes there are no stackframes, just use ex.ToString() in those cases
            {
                errorDescription = ex.ToString();
            }
            //do something with error description, like show it to the user or send it to your log server  
}

If we pass the exception that generated the stacktrace in our first example, it will look like:

NullReferenceException
at ChaptersView.ChaptersTree_OnMouseMove (IL offset: 0x44)
at ChaptersView.GetPreviousCommandButton (IL offset: 0x28)

Step 2: Client sends you an exception/log that includes IL offsets
You can use Reflector or IL Disassembler (which comes with visual studio) to find out the piece of code that generated the exception.
I prefer IL Disassembler because Reflector will only show you IL code from those IL offsets, and there’s a large chance you don’t read IL as well as you read C#.

So you have to perform the following steps:
1) Go to start menu and start IL Disassembler
2) Inside IL Disassembler go to File->Open and load the dll (not the XAP) where the exception was thrown. (e.g. …\Release\Client.dll)
3) Go to View->Show source lines
4) Now navigate to the last method that appears in the stacktrace and we look after the IL offset from the stacktrace.
In our example above this is ChaptersView.GetPreviousCommandButton and has offset 0x28

Inside IL Disassembler we will see a mix of C# code and corresponding IL code:

  IL_001c:  brtrue.s   IL_0029
//000193:             {
//000194:                 throw new NullReferenceException(“Test Exception”);
  IL_001e:  ldstr      "Test Exception"
  IL_0023:  newobj     instance void [mscorlib]System.Exception::.ctor(string)
  IL_0028:  throw
//000195:             }
//000196:
//000197:             var previousCommandButton =
 

As you can see, at IL_0028 there’s the instruction that caused the exception.
This is IL code, but because we’ve checked “View->Show source lines” we also get the corresponding C# code just a few lines above.

//000193: {
//000194: throw new NullReferenceException();

So, in this case the exception was thrown because I’ve thrown it explicitly in order to show how IL offsets and IL Disassembler can be used to get an alternative to stack trace line numbers in silverlight.

Also please don’t just put that piece of code in every catch block. Create a method like “string GetStackTraceFromException(Exception ex)”
Another good idea would be to have some centralized error service, on the server side. You would then send those exception descriptions automatically along with method parameter values logged with PostSharp, whenever an error happens.
So by the time the client calls you, you have fixed the problem and he’s impressed ;)

Happy Debugging.

Written by Liviu Trifoi

April 21, 2011 at 6:15 pm

8 Responses

Subscribe to comments with RSS.

  1. Thank you. I don’t find IL Disassembler in Visual Web Deleveloper 2010 Express. How to get source line using free developer tools ?

    Andrus

    May 13, 2011 at 3:28 pm

  2. How to extend this method to show method parameter values in dump also like VS debugger shows in stack traces?

    Andrus

    May 13, 2011 at 3:33 pm

  3. I can’t seem to fully load this site from my smartphone!!!

    shamtest

    May 14, 2011 at 11:59 am

  4. You should print out the stack frames for the InnerException as well.

    randomengy

    January 8, 2013 at 5:46 pm

  5. Hello Liviu,

    Thanks for sharing this wonderful code. It helps me a lot. I changed your original code a bit to include the handling of InnerException as randomengy suggested and reverse the order in which the stack frames render themself. Hopefully this change makes sense to you.

    private void createTracableExceptionMsg(Exception err, ref string tracableExceptionMsg)
    {
    string errorDescription = string.Empty;

    try
    {
    System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace(err);
    string stackTrace = string.Empty;

    if (st != null)
    {
    foreach (StackFrame frame in st.GetFrames())
    {
    //StackFrame frame = st.GetFrame(i);

    stackTrace += System.Environment.NewLine + “at ” + frame.GetMethod().Module.Name + “.” + frame.GetMethod().ReflectedType.Name + “.” +
    frame.GetMethod().Name + ” (IL offset: 0x” + frame.GetILOffset().ToString(“x”) + “)”;
    }

    errorDescription = stackTrace;
    }
    }
    catch
    {
    errorDescription = err.ToString();
    }

    errorDescription = err.Message + System.Environment.NewLine + errorDescription;
    tracableExceptionMsg = errorDescription + System.Environment.NewLine + tracableExceptionMsg;

    if (err.InnerException != null)
    createTracableExceptionMsg(err.InnerException, ref tracableExceptionMsg);
    }

    Frank

    February 14, 2013 at 4:23 pm

  6. Thanks for the tip for debugging using IL offsets instead of line numbers. However I would suggest caution relying on a custom implementation of Exception.ToString() for production error logging — you need to think of things like inner exceptions, remote object stack traces, internationalisation, generic method arguments etc. Production exception logging is pretty important and you don’t want to lose something because of a bug in a stack string builder somewhere! It might be simpler just to display the IL offsets separately.

    Rich (@dingwallr)

    September 8, 2014 at 8:34 pm


Leave a comment