Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recreate stack trace with line numbers from user bug-report in .net?

First, the problem: I have several free projects, and as any software they contains bugs. Some fellow users when encounter bug send me a bug-reports with stack traces. In order to simplify finding fault place, I want to see line numbers in this stack traces. If application shipped without .pdb files, then all line information is lost, so currently all my projects deployed with .pdb files, and so generated stack traces has this numbers. But! But I do not want to see this files in distribution and want to remove all .pdb. They confuse users, consume space in installer, etc.

Delphi solution: Long time ago when I was a delphi programmer, I used the following technique: on exception my application walk on stack and collect addresses. Then, when I receive bug-report, I used a tool that reconstruct valid stack trace with function names and line numbers based on collected addresses and corresponding symbol files located on MY machine.

Question: Is there any lib, or technique or whatever to do the same in .NET?

Status Update: Very interesting, that often asking a question is the best way to start your own investigation. For example I think about this problem for some time, but start looking for answer only several days ago.

Option 1: MiniDumps. After a lot googling I have found a way to create mini dump from code, and how to recreate stack from managed mini dump.

  • Redistributable assembly to create mini dump form code - clrdump
  • Blog post about using previous assembly - Creating and analyzing minidumps in .NET production applications

This solution however need to redistribute two additional assemblies (~1mb in size), and mini dumps takes some space, and it is uncomfortable for user to send them by email. So for my purposes, right now, it is unacceptable.

Option 2: Thanks to weiqure for clue. It is possible to extract managed IL offset for every stack frame. Now the problem is how to get line numbers from .pdb based on this offsets. And what I have found:

  • PDB File Internals, just for information because:
  • ISymbolReader - managed interface to read program database files
  • And finally a tool to convert .pdb files to structured xml for easy xpath processing

Using this tool, it is possible to create xml files for every release build and put them into repositary. When exception occurs on user's machine, it is possible to create formatted error message with IL offsets. Then user send this message (very small) by mail. And finally, it is possible to create a simple tool that recreate resulting stack from formatted error message.

I only wondering why nobody else does not implement a tool like this? I don't believe that this is interesting for me only.

like image 754
arbiter Avatar asked Jun 24 '09 12:06

arbiter


2 Answers

You can get the offset of the last MSIL instruction from the Exception using System.Diagnostics.StackTrace:

// Using System.Diagnostics
static void Main(string[] args)
{
    try { ThrowError(); }
    catch (Exception e)
    {
        StackTrace st = new System.Diagnostics.StackTrace(e);
        string stackTrace = "";
        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;
        }
        Console.Write(stackTrace);
        Console.WriteLine("Message: " + e.Message);
    }
    Console.ReadLine();
}

static void ThrowError()
{
    DateTime myDateTime = new DateTime();
    myDateTime = new DateTime(2000, 5555555, 1); // won't work
    Console.WriteLine(myDateTime.ToString());
}

Output:

at ConsoleApplicationN.exe.Program.Main (IL offset: 0x7)
at ConsoleApplicationN.exe.Program.ThrowError (IL offset: 0x1b)
at mscorlib.dll.DateTime..ctor (IL offset: 0x9)
at mscorlib.dll.DateTime.DateToTicks (IL offset: 0x61)
Message: Year, Month, and Day parameters describe an un-representable DateTime.

You can then use Reflector or ILSpy to interpret the offset:

.method private hidebysig static void ThrowError() cil managed
{
    .maxstack 4
    .locals init (
        [0] valuetype [mscorlib]System.DateTime myDateTime)
    L_0000: nop 
    L_0001: ldloca.s myDateTime
    L_0003: initobj [mscorlib]System.DateTime
    L_0009: ldloca.s myDateTime
    L_000b: ldc.i4 0x7d0
    L_0010: ldc.i4 0x54c563
    L_0015: ldc.i4.1 
    L_0016: call instance void [mscorlib]System.DateTime::.ctor(int32, int32, int32)
    L_001b: nop 
    L_001c: ldloca.s myDateTime
    L_001e: constrained [mscorlib]System.DateTime
    L_0024: callvirt instance string [mscorlib]System.Object::ToString()
    L_0029: call void [mscorlib]System.Console::WriteLine(string)
    L_002e: nop 
    L_002f: ret 
}

You know that the instruction before 0x1b threw the exception. It's easy to find the C# code for that:

 myDateTime = new DateTime(2000, 5555555, 1);

You could map the IL code to your C# code now, but I think the gain would be too little and the effort too big (though there might be a reflector plugin). You should be fine with the IL offset.

like image 98
weiqure Avatar answered Oct 18 '22 00:10

weiqure


You should use Environment.FailFast, call FailFast in the Application.UnhandledException and a dump file will be created for you.

From the MSDN:

The FailFast method writes a log entry to the Windows Application event log using the message parameter, creates a dump of your application, and then terminates the current process.

Use the FailFast method instead of the Exit method to terminate your application if the state of your application is damaged beyond repair, and executing your application's try-finally blocks and finalizers will corrupt program resources. The FailFast method terminates the current process and executes any CriticalFinalizerObject objects, but does not execute any active try-finally blocks or finalizers.

You can write a simple app that will collect the log files and send them to you.

Now, opening the dump file is a bit tricky, Visual Studio cannot handle managed dump file (fixed in .NET 4.0) you can use WinDBG but you need to use SOS.

like image 41
Shay Erlichmen Avatar answered Oct 18 '22 00:10

Shay Erlichmen