C
C#12mo ago
bigmazi

Getting call stack that was a thing before exception was thrown

Consider code below. I need to know how I got to the throw, not how I escaped from it.
16 Replies
MODiX
MODiX12mo ago
bigmazi
REPL Result: Success
using System;

class Program
{
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static void f()
{
g();
}

[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static void g()
{
try
{
h();
}
catch (Exception ex)
{
// Expected: Main -> f -> g -> h
// Got: g -> h
Console.WriteLine(ex.StackTrace);
}
}

[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static void h()
{
throw new Exception();
}

public static void Main()
{
f();
}
}

Program.Main();
using System;

class Program
{
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static void f()
{
g();
}

[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static void g()
{
try
{
h();
}
catch (Exception ex)
{
// Expected: Main -> f -> g -> h
// Got: g -> h
Console.WriteLine(ex.StackTrace);
}
}

[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static void h()
{
throw new Exception();
}

public static void Main()
{
f();
}
}

Program.Main();
Console Output
at Submission#0.Program.h()
at Submission#0.Program.g()
at Submission#0.Program.h()
at Submission#0.Program.g()
Compile: 636.305ms | Execution: 72.944ms | React with ❌ to remove this embed.
Chiyoko_S
Chiyoko_S12mo ago
so the idea is, when an exception is thrown, the runtime unwinds the stack frames from where it is thrown one by one, until it finds an appropriate exception handler (the catch block), at which point it will stop unwinding and let the handler run it only has 'h got called from g' because you caught it in g, so the unwinding stops there if you let the exception propagate through and leave the exception unhandled, it'll fully unwind and show the full stacktrace including the entrypoint however if you want the full stacktrace for whatever reason you can use System.Diagnostics.StackTrace and System.Environments.StackTrace former lets you read the data off of the stacktrace info programatically, latter just gives it as a string
bigmazi
bigmaziOP12mo ago
well, thanks, but it doesn't really answer my question if I knew in ahead of time where exactly my app gonna throw a fatal error, I wouldn't use this information to save the call stack right before it, I would solve to error instead in other words, the question was: can I do something such that stack trace is saved right before the exception is raised automatically? ig I can combine stack trace that I get inside handler with the one I get from exception?
catch (Exception ex)
{
ex.StackTrace + Environment.StackTrace;
}
catch (Exception ex)
{
ex.StackTrace + Environment.StackTrace;
}
Chiyoko_S
Chiyoko_S12mo ago
well, just leave the exception unhandled...? if you're using a debugger, one can trivially determine how you got to that point very easily by just looking at the callstack window if you're not using a debugger, what would you really going to do with the stack trace information other than logging
bigmazi
bigmaziOP12mo ago
sometimes I call code made by other people and I know I can recover at a particular point without entering a corrupted state, I just don't know what exactly they gonna throw the intention is to get error reports from customers this one looks promising, I'm gonna try it
Chiyoko_S
Chiyoko_S12mo ago
deciding whether or not to continue or fail depending on the stack trace seems rather fragile
bigmazi
bigmaziOP12mo ago
no, it's not to decide, it's to create report so me and other devs could look at it
Chiyoko_S
Chiyoko_S12mo ago
as for error reports, .NET runtime leaves an entry in the Windows' event log on crash
bigmazi
bigmaziOP12mo ago
ty, I'll look into it I have a catch inside main for unhandled exceptions that creates a report and closes app
Chiyoko_S
Chiyoko_S12mo ago
I see
bigmazi
bigmaziOP12mo ago
also, another thing I don't like is that rethrowing exception changes the line of exception origin even with just bare throw; but it only happens if I throw explicitly from inside the try block, right? I think I need to check whether stuff like null dereference keep original line or not, hmm...
Chiyoko_S
Chiyoko_S12mo ago
isn't line count only for debug builds?
bigmazi
bigmaziOP12mo ago
it's when there is a .pdb file present
Chiyoko_S
Chiyoko_S12mo ago
right
bigmazi
bigmaziOP12mo ago
in release lines may be erased, but some of them still may be present vice versa, sharing debug build without pdb not going produce lines worked fine found this https://stackoverflow.com/questions/21600023/rethrow-an-exception-with-correct-line-numbers-in-the-stack-trace snippets from there: #1
public void PreserveStackTrace(Exception ex)
{
MethodInfo preserve = ex.GetType().GetMethod("InternalPreserveStackTrace",
BindingFlags.Instance | BindingFlags.NonPublic);
preserve.Invoke(ex,null);
}

public void ExceptionTest()
{
try
{
throw new Exception("An Error Happened");
}
catch (Exception ex)
{
PreserveStackTrace(ex);
throw ex;
}
}
public void PreserveStackTrace(Exception ex)
{
MethodInfo preserve = ex.GetType().GetMethod("InternalPreserveStackTrace",
BindingFlags.Instance | BindingFlags.NonPublic);
preserve.Invoke(ex,null);
}

public void ExceptionTest()
{
try
{
throw new Exception("An Error Happened");
}
catch (Exception ex)
{
PreserveStackTrace(ex);
throw ex;
}
}
#2
public static void ReThrow(this Exception ex)
{
var exInfo = ExceptionDispatchInfo.Capture(ex);
exInfo.Throw();
}

public void ExceptionTest()
{
try
{
throw new Exception("An Error Happened");
}
catch (Exception ex)
{
ex.ReThrow();
}
}
public static void ReThrow(this Exception ex)
{
var exInfo = ExceptionDispatchInfo.Capture(ex);
exInfo.Throw();
}

public void ExceptionTest()
{
try
{
throw new Exception("An Error Happened");
}
catch (Exception ex)
{
ex.ReThrow();
}
}
looks promising comment there
ReThrow() is a great idea, but unlike "throw e;" it now gives an error saying "not all code paths return a value" - any idea how to fix properly? – mcmillab Dec 17, 2019 at 2:13
I think it could be fixed like this
public static Exception ReThrow(this Exception ex)
{
var exInfo = ExceptionDispatchInfo.Capture(ex);
exInfo.Throw();

Debug.Assert(false);
return null;
}

public int ExceptionTest()
{
try
{
return GetInt();
}
catch (Exception ex)
{
throw ex.ReThrow();
}
}
public static Exception ReThrow(this Exception ex)
{
var exInfo = ExceptionDispatchInfo.Capture(ex);
exInfo.Throw();

Debug.Assert(false);
return null;
}

public int ExceptionTest()
{
try
{
return GetInt();
}
catch (Exception ex)
{
throw ex.ReThrow();
}
}
Want results from more Discord servers?
Add your server