Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does stack get truncated in Exception.StackTrace?

Why does the high part of the stack (in Exception.StackTrace) gets truncated? Let's see a simple example:

public void ExternalMethod()
{
  InternalMethod();
}

public void InternalMethod()
{
  try
  {
    throw new Exception();
  }
  catch(Exception ex)
  {
    // ex.StackTrace here doesn't contain ExternalMethod()!
  }
}

It seems like this is "by design". But what are the reasons for such a strange design? It only makes debugging more complex, because in log messages I can't understand who called InternalMethod() and often this information is very necessary.

As for solutions (for those who don't know), there are 2 general solutions as I understand:
1) We can log static Environment.StackTrace property, which contains the whole stack (for example, starting at the hiest level (message queue) and ending at the deepest method in which exception occurs).
2) We have to catch and log exceptions on highest levels. When we need to catch exceptions on lower levels to do something, we need to rethrow (with "throw" statement in C#) it further up.

But the question is about reasons of such design.

like image 429
nightcoder Avatar asked Nov 11 '09 22:11

nightcoder


People also ask

What is exception StackTrace?

A trace of the method calls is called a stack trace. The stack trace listing provides a way to follow the call stack to the line number in the method where the exception occurs. The StackTrace property returns the frames of the call stack that originate at the location where the exception was thrown.

What is the use of StackTrace?

In computing, a stack trace (also called stack backtrace or stack traceback) is a report of the active stack frames at a certain point in time during the execution of a program. When a program is run, memory is often dynamically allocated in two places; the stack and the heap.

What is StackTrace C#?

Whereas stack trace in C# is the execution stack keeps track of all the methods that are in execution at a given instant. A trace of the method calls is termed as a stack trace. A stack trace listing provides us with a way to view the call stack to the line number in the method where the exception occurs.

How do I recover stack trace?

You can obtain a stack trace from a thread – by calling the getStackTrace method on that Thread instance. This invocation returns an array of StackTraceElement, from which details about stack frames of the thread can be extracted.


2 Answers

Ok, now I see what your getting at... Sorry for my confusion on the inlining thing.

The 'stack' in a caught exception is only a delta from the currently executing catch block to where the exception was thrown. Conceptually this behavior is correct in that the Exception.StackTrack tells you where the exception occurred within the context of this try/catch block. This allows exception stacks to be forwarded across 'virtual' calls and still maintain accuracy. One classic example of this being done is .Net Remoting exceptions.

Thus if you want a complete stack report in the catch block you would add the current stack to the exception's stack as in the example below. The only problem is this can be more expensive.

    private void InternalMethod()
    {
        try
        {
            ThrowSomething();
        }
        catch (Exception ex)
        {
            StackTrace currentStack = new StackTrace(1, true);
            StackTrace exceptionStack = new StackTrace(ex, true);
            string fullStackMessage = exceptionStack.ToString() + currentStack.ToString();
        }
    }
like image 143
csharptest.net Avatar answered Oct 29 '22 02:10

csharptest.net


As csharptest said this is by design. The StackTrace stops at the try block. Further more there is no hook in the framework that is called when an exception is thrown.

So the best you can do is something along these lines, it its an absolute requirement to get full stack traces (store a full trace on exceptions creation):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;
using System.Diagnostics;

namespace ConsoleApplication15 {

    [global::System.Serializable]
    public class SuperException : Exception {

        private void SaveStack() {
            fullTrace = Environment.StackTrace;
        }

        public SuperException() { SaveStack(); }
        public SuperException(string message) : base(message) { SaveStack();  }
        public SuperException(string message, Exception inner) : base(message, inner) { SaveStack(); }
        protected SuperException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }

        private string fullTrace; 
        public override string StackTrace {
            get {
                return fullTrace;
            }
        }
    }

    class Program {

        public void ExternalMethod() {
            InternalMethod();
        }

        public void InternalMethod() {
            try {
                ThrowIt();
            } catch (Exception ex) {
                Console.WriteLine(ex.StackTrace);
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void ThrowIt() {
            throw new SuperException();
        }


        static void Main(string[] args) {
            new Program().ExternalMethod();
            Console.ReadKey();
        }
    }
}

Outputs:

 
     at System.Environment.get_StackTrace()
   at ConsoleApplication15.SuperException..ctor() in C:\Users\sam\Desktop\Source
\ConsoleApplication15\ConsoleApplication15\Program.cs:line 17
   at ConsoleApplication15.Program.ThrowIt() in C:\Users\sam\Desktop\Source\Cons
oleApplication15\ConsoleApplication15\Program.cs:line 49
   at ConsoleApplication15.Program.InternalMethod() in C:\Users\sam\Desktop\Sour
ce\ConsoleApplication15\ConsoleApplication15\Program.cs:line 41
   at ConsoleApplication15.Program.Main(String[] args) in C:\Users\sam\Desktop\S
ource\ConsoleApplication15\ConsoleApplication15\Program.cs:line 55
   at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, C
ontextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

It is not possible to inject this behaviour into the existing System defined exceptions, but .Net has a rich infrastructure for wrapping exceptions and rethrowing so it should not be a huge deal.

like image 28
Sam Saffron Avatar answered Oct 29 '22 01:10

Sam Saffron