How to get a reliable stack trace in release build?

I am currently investigating some weird behaviour regarding stacktraces of Exceptions in release builds.

I understand that the common way of tracking exceptions is to register your own handler as a callback for the unity logger. However the stacktraces for exceptions that get passed to the handler sometimes are incomplete. If they are incomplete they will contain only the first method that was called, or if any call to Debug.Log was made from the current stack the stacktrace will include all calls up to that point. For example if I do this:

void Start ()
{
  ThrowException();
}

void ThrowException()
{
  Debug.Log("");
  DoThrowException();
}

void DoThrowException()
{
  throw new System.NullReferenceException();
}

Then the stacktrace will report that the exception was fired from ThrowException. If I remove the Debug.Log it will only contain the Start() method.

But it gets weirder. So for some exceptions i get the correct stacktrace, and for some I don’t. For instance throwing an IndexOutOfRangeException does not produce the correct stacktrace, but if I actually try to access an array with an index bigger than its length the resulting runtime exception WILL have the correct stacktrace. NullReferenceExceptions never seem to have the correct stacktrace. This seems really weird to me. Is there any explanation for this? (i assume that unity is somehow tampering with some of the exceptions behind the scenes) If not, is there a better way of getting stacktraces for Exceptions in a release build?

If you want to try it yourself you can just use this script and (un)comment the various cases:

using UnityEngine;
using System.Collections;

public class SbsErrorTrackingTest : MonoBehaviour {

    void HandleLog (string message, string stackTrace, LogType type)
    {
        Debug.LogException(null);
    }

	// Use this for initialization
	void Start () {
        Debug.Log("START");    
        ThrowException();
	}
	
	void ThrowException ()
    {       
        DoThrowException();
    }

    void DoThrowException ()
    {   
        // if this line is un-commented the stack trace is probably correct
//        Debug.Log("");

        // doesn't work
        throw new System.NullReferenceException(message);

        // doesn't work
        throw new System.IndexOutOfRangeException("test");

        // does work
        string[] arr = { "" };
        var s = arr[3];

        // doesn't work
        int x = 0;
        int y = 1 / x;

        // does work
        System.Collections.Generic.Dictionary<string,string> dict = new System.Collections.Generic.Dictionary<string,string>();
        var s = dict["key"];

        // does not work
        DummyClass dm = new DummyClass();
        var s = dm.GetStringCopy();
    }
}

public class DummyClass
{
    string s = null;

    public string GetStringCopy ()
    {
        return s.Clone() as string;
    }
}

I tried this with release builds on Android and iOS.

2 Answers

2

I originally pointed out the undocumented UnityEngine.StackTraceUtility.ExtractStackTrace, but I’ve since found that those stack traces are sometimes incomplete or missing. Haven’t fully identified in what cases they are incomplete. Generally they are always complete in debug builds, but it gets strange in release builds.

The most robust way to do this is to use System.Diagnostics.StackTrace. This will get you the full stack trace, even in release code, with a few minor exceptions:

  1. It will not give stack traces for inlined method calls.
  2. If compiled in release mode, it will probably not have file and line numbers.
  3. It works on all the platforms I’ve seen, but there may be an odd platform it doesn’t support.

If you use it, make sure to null check everything.

Adding a comment here because I’m having the same problem: In my Android plugin for Unity sometimes a RuntimeException is generated (e.g. NullPointerException). The normal behavior and expected for any RuntimeException is that the app crashes and the stacktrace is dumped. After all, RuntimeExceptions exist to tell me there’s a developer issue.

But, it looks like the Unity library in Android catches RuntimeExceptions, and instead of dumping the stacktrace of the RuntimeException, I see some odd JNI stacktrace generated by the Unity layer.

I need a way to ensure that RuntimeExceptions are dumped to the console as-is/unmolested and the app crashes when they happen. Is that possible?

Thanks.

Putting try/catch blocks throughout the code is a serious step backwards because I would have to do it everywhere, and it's just compensating for the fact that Unity is obscuring perfectly good information in the form of RuntimeException stack traces. It can turn a five minute fix into a five hour fix because I have to hunt for the block of code to put my try/catch block around. Plain and simple, Unity needs to not catch RuntimeExceptions. I work for a premium support customer of Unity so I'll use that channel to submit a change request.