Windows/C++: How to Find the Line of Code Where Exception Was Thrown Having "Exception Offset"

Windows/C++: Is it possible to find the line of code where exception was thrown having Exception Offset

Yes, that's possible. Start debugging with the exact same binaries as ran by your user, make sure the DLL is loaded and you've got a matching PDB file for it. Look in Debug + Windows + Modules for the DLL base address. Add the offset. Debug + Windows + Disassembly and enter the calculated address in the Address field (prefix with 0x). That shows you the exact machine code instruction that caused the exception. Right-click + Go To Source code to see the matching source code line.

While that shows you the statement, this isn't typically good enough to diagnose the cause. The 0xc0000005 exception is an access violation, it has many possible causes. Often you don't even get any code, the program may have jumped into oblivion due to a corrupted stack. Or the real problem is located far away, some pointer manipulation that corrupted the heap. You also typically really need a stack trace that shows you how the program ended up at the statement that bombed.

What you need is a minidump. You can easily get one from your user if she runs Vista or Win7. Start TaskMgr.exe, Processes tab, select the bombed program while it is still displaying the crash dialog. Right-click it and Create Dump File.

To make this smooth, you really want to automate this procedure. You'll find hints in my answer in this thread.

How do I find where an exception was thrown in C++?

Here's some info that may be of use in debugging your problem

If an exception is uncaught, the special library function std::terminate() is automatically called. Terminate is actually a pointer to a function and default value is the Standard C library function std::abort(). If no cleanups occur for an uncaught exception, it may actually be helpful in debugging this problem as no destructors are called.

†It is implementation-defined whether or not the stack is unwound before std::terminate() is called.


A call to abort() is often useful in generating a core dump that can be analyzed to determine the cause of the exception. Make sure that you enable core dumps via ulimit -c unlimited (Linux).


You can install your own terminate() function by using std::set_terminate(). You should be able to set a breakpoint on your terminate function in gdb. You may be able to generate a stack backtrace from your terminate() function and this backtrace may help in identifying the location of the exception.

There is a brief discussion on uncaught exceptions in Bruce Eckel's Thinking in C++, 2nd Ed that may be helpful as well.


Since terminate() calls abort() by default (which will cause a SIGABRT signal by default), you may be able to set a SIGABRT handler and then print a stack backtrace from within the signal handler. This backtrace may help in identifying the location of the exception.


Note: I say may because C++ supports non-local error handling through the use of language constructs to separate error handling and reporting code from ordinary code. The catch block can be, and often is, located in a different function/method than the point of throwing. It has also been pointed out to me in the comments (thanks Dan) that it is implementation-defined whether or not the stack is unwound before terminate() is called.

Update: I threw together a Linux test program called that generates a backtrace in a terminate() function set via set_terminate() and another in a signal handler for SIGABRT. Both backtraces correctly show the location of the unhandled exception.

Update 2: Thanks to a blog post on Catching uncaught exceptions within terminate, I learned a few new tricks; including the re-throwing of the uncaught exception within the terminate handler. It is important to note that the empty throw statement within the custom terminate handler works with GCC and is not a portable solution.

Code:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <string.h>

#include <iostream>
#include <cstdlib>
#include <stdexcept>

void my_terminate(void);

namespace {
// invoke set_terminate as part of global constant initialization
static const bool SET_TERMINATE = std::set_terminate(my_terminate);
}

// This structure mirrors the one found in /usr/include/asm/ucontext.h
typedef struct _sig_ucontext {
unsigned long uc_flags;
struct ucontext *uc_link;
stack_t uc_stack;
struct sigcontext uc_mcontext;
sigset_t uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) {
sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;

// Get the address at the time the signal was raised from the EIP (x86)
void * caller_address = (void *) uc->uc_mcontext.eip;

std::cerr << "signal " << sig_num
<< " (" << strsignal(sig_num) << "), address is "
<< info->si_addr << " from "
<< caller_address << std::endl;

void * array[50];
int size = backtrace(array, 50);

std::cerr << __FUNCTION__ << " backtrace returned "
<< size << " frames\n\n";

// overwrite sigaction with caller's address
array[1] = caller_address;

char ** messages = backtrace_symbols(array, size);

// skip first stack frame (points here)
for (int i = 1; i < size && messages != NULL; ++i) {
std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
}
std::cerr << std::endl;

free(messages);

exit(EXIT_FAILURE);
}

void my_terminate() {
static bool tried_throw = false;

try {
// try once to re-throw currently active exception
if (!tried_throw++) throw;
}
catch (const std::exception &e) {
std::cerr << __FUNCTION__ << " caught unhandled exception. what(): "
<< e.what() << std::endl;
}
catch (...) {
std::cerr << __FUNCTION__ << " caught unknown/unhandled exception."
<< std::endl;
}

void * array[50];
int size = backtrace(array, 50);

std::cerr << __FUNCTION__ << " backtrace returned "
<< size << " frames\n\n";

char ** messages = backtrace_symbols(array, size);

for (int i = 0; i < size && messages != NULL; ++i) {
std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
}
std::cerr << std::endl;

free(messages);

abort();
}

int throw_exception() {
// throw an unhandled runtime error
throw std::runtime_error("RUNTIME ERROR!");
return 0;
}

int foo2() {
throw_exception();
return 0;
}

int foo1() {
foo2();
return 0;
}

int main(int argc, char ** argv) {
struct sigaction sigact;

sigact.sa_sigaction = crit_err_hdlr;
sigact.sa_flags = SA_RESTART | SA_SIGINFO;

if (sigaction(SIGABRT, &sigact, (struct sigaction *)NULL) != 0) {
std::cerr << "error setting handler for signal " << SIGABRT
<< " (" << strsignal(SIGABRT) << ")\n";
exit(EXIT_FAILURE);
}

foo1();

exit(EXIT_SUCCESS);
}

Output:


my_terminate caught unhanded exception. what(): RUNTIME ERROR!
my_terminate backtrace returned 10 frames

[bt]: (0) ./test(my_terminate__Fv+0x1a) [0x8048e52]
[bt]: (1) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa]
[bt]: (2) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5]
[bt]: (3) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf]
[bt]: (4) ./test(throw_exception__Fv+0x68) [0x8049008]
[bt]: (5) ./test(foo2__Fv+0xb) [0x8049043]
[bt]: (6) ./test(foo1__Fv+0xb) [0x8049057]
[bt]: (7) ./test(main+0xc1) [0x8049121]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__eh_alloc+0x3d) [0x8048b21]

signal 6 (Aborted), address is 0x1239 from 0x42029331
crit_err_hdlr backtrace returned 13 frames

[bt]: (1) ./test(kill+0x11) [0x42029331]
[bt]: (2) ./test(abort+0x16e) [0x4202a8c2]
[bt]: (3) ./test [0x8048f9f]
[bt]: (4) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa]
[bt]: (5) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5]
[bt]: (6) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf]
[bt]: (7) ./test(throw_exception__Fv+0x68) [0x8049008]
[bt]: (8) ./test(foo2__Fv+0xb) [0x8049043]
[bt]: (9) ./test(foo1__Fv+0xb) [0x8049057]
[bt]: (10) ./test(main+0xc1) [0x8049121]
[bt]: (11) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (12) ./test(__eh_alloc+0x3d) [0x8048b21]

How to match a crash's Fault offset to the source code?

You also need to know what module the offset belongs too, if you are getting 0xC0000008 (STATUS_INVALID_HANDLE), then the exception is likely thrown from ntdll.dll, which isn't going to help you debug your program, since what you care about is deeper in the stack.

What you should be doing is have your customer enable LocalDumps, and then send you a minidump file which you can debug.

Sample registry setup:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps]
"DumpFolder"="d:\\miniDumps"
"DumpType"=dword:00000002
"CustomDumpFlags"=dword:00001124

How to get full stack trace from exception object, when debugging in windbg?

TLDR;

You can show the stack trace of a thrown exception using !pe. In probably more than 90% of the cases that's all you need.

Simple and complete example

Here's a minimal reproducible example:

using System;

namespace ConsoleNetFramework
{
class Program
{
static void Main()
{
throw new ApplicationException("An exception");
}
}
}

and the debugging session:

0:000> .loadby sos clr
0:000> !pe
Exception object: 0335246c
Exception type: System.ApplicationException
Message: An exception
InnerException: <none>
StackTrace (generated):
SP IP Function
012FEFB0 01720897 ConsoleNetFramework!ConsoleNetFramework.Program.Main()+0x4f

StackTraceString: <none>
HResult: 80131600

Complex example

Since I considered the answer to be too simple, I felt free to make it more interesting by adding more seldom cases to the example like so:

static void Main()
{
var b = new BadImageFormatException("Bad exception");
ApplicationException a;
try
{
throw new ApplicationException("An exception");
}
catch (ApplicationException aex)
{
a = aex;
}

var t = new Thread(ThrowAnotherException);
t.Start();
throw new DataException("Data exception");

Console.WriteLine(b.Message);
Console.WriteLine(a.Message);
}

private static void ThrowAnotherException()
{
throw new ConfigurationException("Config exception");
}

In the end we'll have 4 exception objects in memory:

  • A...Exception
  • B...Exception
  • C...Exception
  • D...Exception

and we can have a look at all of them.

The debugging session is:

0:000> .loadby sos clr
0:000> !pe
Exception object: 02eb2ca8
Exception type: System.Data.DataException
Message: Data exception
[...]

So far so normal, we stopped at the first exception that was thrown and we can have a look at that exception with !pe.

If we switch to another thread, we can see that the exception is indicated with a # in the list of threads, as long as it's not overwritten by the . (current thread).

0:000> ~1s
0:001> ~
# 0 Id: 28a8.501c Suspend: 1 Teb: 00cc6000 Unfrozen
. 1 Id: 28a8.1044 Suspend: 1 Teb: 00cc9000 Unfrozen
[...]

Here's an interesting part: let's freeze thread 0 and run the application so that a second exception is thrown.

0:001> ~0f
0:001> g
System 0: 1 of 7 threads are frozen
WARNING: Continuing a non-continuable exception
System 0: 1 of 8 threads were frozen
System 0: 1 of 8 threads are frozen
(28a8.4228): CLR exception - code e0434352 (!!! second chance !!!)
System 0: 1 of 8 threads were frozen
[...]

Now we have another exception that caused the debugger to stop.

0:006> ~1s
[...]
0:001> ~
0 Id: 28a8.501c Suspend: 1 Teb: 00cc6000 Frozen
. 1 Id: 28a8.1044 Suspend: 1 Teb: 00cc9000 Unfrozen
2 Id: 28a8.1a2c Suspend: 1 Teb: 00ccc000 Unfrozen
3 Id: 28a8.5cfc Suspend: 1 Teb: 00ccf000 Unfrozen
4 Id: 28a8.490 Suspend: 1 Teb: 00cd2000 Unfrozen
5 Id: 28a8.2b60 Suspend: 1 Teb: 00cd5000 Unfrozen
# 6 Id: 28a8.4228 Suspend: 1 Teb: 00cd8000 Unfrozen
[...]

That exception happened on thread 6. We can also see that we only have one # mark in that list, not two.

On the correct thread, we can print the exception:

0:006> !pe
Exception object: 02eb3ff4
Exception type: System.Configuration.ConfigurationException
Message: Config exception
[...]

On the wrong thread, we can't:

0:006> ~1s
[...]
0:001> !pe
The current thread is unmanaged

Lesson learned: the !pe command is thread sensitive.

Now, what about the other two exceptions, A... and B...? We can find them on the heap:

0:000> !dumpheap -type BadImageFormatException
Address MT Size
02eb2a6c 6e3983c4 92
[...]
Fields:
MT Field Offset Type VT Attr Value Name
[...]
6e342734 40002ab 20 System.Object 0 instance 00000000 _stackTrace
[...]

The exception that was not thrown at all, does not have a stack trace. It's null.

0:000> !dumpheap -type ApplicationException
Address MT Size
02eb2ae4 6e388090 84

Statistics:
MT Count TotalSize Class Name
6e388090 1 84 System.ApplicationException
Total 1 objects
0:000> !DumpObj /d 02eb2ae4
Name: System.ApplicationException
[...]
Fields:
MT Field Offset Type VT Attr Value Name
[...]
6e342734 40002ab 20 System.Object 0 instance 02eb2b7c _stackTrace
[...]

The exception that was thrown but caught, still has its stack trace.

0:000> !pe 02eb2ae4
Exception object: 02eb2ae4
Exception type: System.ApplicationException
Message: An exception
InnerException: <none>
StackTrace (generated):
SP IP Function
00EFEEA0 052F08D2 ConsoleNetFramework!ConsoleNetFramework.Program.Main()+0x8a

StackTraceString: <none>
HResult: 80131600

Lesson learned: !pe can take an arbitrary exception as parameter and print the exception with its call stack.

Looking at the output of the managed !threads, both exceptions will be listed (unlike ~ which shows only one #):

0:001> !threads
ThreadCount: 3
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 501c 00fb99c0 2a020 Preemptive 02EC0714:00000000 00f815a0 0 MTA System.Data.DataException 02eb2ca8
5 2 2b60 00fc4728 2b220 Preemptive 00000000:00000000 00f815a0 0 MTA (Finalizer)
6 3 4228 00fec9a0 2b020 Preemptive 02EC5B70:00000000 00f815a0 0 MTA System.Configuration.ConfigurationException 02eb3ff4

what steps can I take to find out the origin of NullReferenceException?

With the call stack and symbols you should be able to identify a single line of code. Next step is to do a code review there. Figure out which variable is null. Write a unit test to reproduce the issue. Then fix the bug. Like in red/green refactoring.

C++ display stack trace on exception

It depends which platform.

On GCC it's pretty trivial, see this post for more details.

On MSVC then you can use the StackWalker library that handles all of the underlying API calls needed for Windows.

You'll have to figure out the best way to integrate this functionality into your app, but the amount of code you need to write should be minimal.

Displaying exception debug information to users

Wrapping all your code in one try/catch block is a-ok. It won't slow down the execution of anything inside it, for example. In fact, all my programs have (code similar to) this framework:

int execute(int pArgc, char *pArgv[])
{
// do stuff
}

int main(int pArgc, char *pArgv[])
{
// maybe setup some debug stuff,
// like splitting cerr to log.txt

try
{
return execute(pArgc, pArgv);
}
catch (const std::exception& e)
{
std::cerr << "Unhandled exception:\n" << e.what() << std::endl;
// or other methods of displaying an error

return EXIT_FAILURE;
}
catch (...)
{
std::cerr << "Unknown exception!" << std::endl;

return EXIT_FAILURE;
}
}


Related Topics



Leave a reply



Submit