Reputation: 437
I use C++17, GCC, Qt Creator with its integrated GDB debugger.
I have code that simplifies down to this:
#include <iostream>
#include <iomanip>
// Example-implementation
#define assert(Condition) { if (!(Condition)) { std::cerr << "Assert failed, condition is false: " #Condition << std::endl; } }
#include <execinfo.h>
#include <signal.h>
#include <unistd.h>
void printStackTrace()
{
constexpr int requestedFrameCount = 20;
void* frames[requestedFrameCount];
auto actualFrameCount = backtrace(frames, requestedFrameCount);
std::cout << "Stack trace (" << actualFrameCount << " of " << requestedFrameCount << " requested frames):" << std::endl;
backtrace_symbols_fd(frames, actualFrameCount, STDOUT_FILENO);
std::cout << "End of stack trace." << std::endl;
}
void signalHandler(int signalNumber)
{
std::cout << "Signal " << signalNumber << " (" << sys_siglist[signalNumber] << ") happened!" << std::endl;
assert(signalNumber == SIGABRT);
printStackTrace();
}
__attribute_noinline__ void someFunction()
{
throw std::invalid_argument("Bad things happened");
}
__attribute_noinline__ void someFunctionInTheStandardLibraryThatICantChange()
{
try
{
someFunction();
}
catch (...)
{
throw;
}
}
__attribute_noinline__ int main()
{
signal(SIGABRT, signalHandler);
someFunctionInTheStandardLibraryThatICantChange();
return 0;
}
someFunctionInTheStandardLibraryThatICantChange
is a placeholder for this thing:
template<bool _TrivialValueTypes>
struct __uninitialized_copy
{
template<typename _InputIterator, typename _ForwardIterator>
static _ForwardIterator
__uninit_copy(_InputIterator __first, _InputIterator __last,
_ForwardIterator __result)
{
_ForwardIterator __cur = __result;
__try
{
for (; __first != __last; ++__first, (void)++__cur)
std::_Construct(std::__addressof(*__cur), *__first);
return __cur;
}
__catch(...)
{
std::_Destroy(__result, __cur);
__throw_exception_again;
}
}
};
The program's output looks something like this:
On standard output:
Signal 6 (Aborted) happened!
Stack trace (13 of 20 requested frames):
/foo/Test(_Z15printStackTracev+0x1c)[0xaaaab9886d30]
/foo/Test(_Z13signalHandleri+0xbc)[0xaaaab9886e94]
linux-vdso.so.1(__kernel_rt_sigreturn+0x0)[0xffff95f3a5c8]
/lib64/libc.so.6(gsignal+0xc8)[0xffff94e15330]
/lib64/libc.so.6(abort+0xfc)[0xffff94e02b54]
/lib64/libstdc++.so.6(_ZN9__gnu_cxx27__verbose_terminate_handlerEv+0x188)[0xffff950d9358]
/lib64/libstdc++.so.6(_ZN10__cxxabiv111__terminateEPFvvE+0xc)[0xffff950d70ac]
/lib64/libstdc++.so.6(_ZN10__cxxabiv112__unexpectedEPFvvE+0x0)[0xffff950d7100]
/lib64/libstdc++.so.6(__cxa_rethrow+0x60)[0xffff950d7428]
/foo/Test(_Z47someFunctionInTheStandardLibraryThatICantChangev+0x1c)[0xaaaab9886f10]
/foo/Test(main+0x1c)[0xaaaab9886f48]
/lib64/libc.so.6(__libc_start_main+0xe4)[0xffff94e02fac]
/foo/Test(+0x2774)[0xaaaab9886774]
End of stack trace.
On standard error:
terminate called after throwing an instance of 'std::invalid_argument'
what(): Bad things happened
Note how the stack trace goes directly from someFunctionInTheStandardLibraryThatICantChange
to rethrow
. someFunction
was not inlined (call printStackTrace
from someFunction
if you don't trust me).
I can't change the library function, but I need to know where the exception was originally thrown. How do I get that information?
One possible way is to use the debugger and set a "Break when C++ exception is thrown" breakpoint. But that has the significant drawbacks that it only works when debugging, it's external to the program and it is only really viable if you don't throw a bunch of exceptions that you don't care about.
Upvotes: 0
Views: 797
Reputation: 437
What @n.1.8e9-where's-my-sharem. suggested in the comments ended up working. When you throw an exception in C++, behind the scenes the function __cxa_throw
is called. You can replace that function, look at the stack trace, then call the replaced function.
Here is a simple proof-of-concept:
#include <dlfcn.h>
#include <cxxabi.h>
typedef void (*ThrowFunction)(void*, void*, void(*)(void*)) __attribute__ ((__noreturn__));
ThrowFunction oldThrowFunction;
namespace __cxxabiv1
{
extern "C" void __cxa_throw(void* thrownException, std::type_info* thrownTypeInfo, void (*destructor)(void *))
{
if (oldThrowFunction == nullptr)
{
oldThrowFunction = (ThrowFunction)dlsym(RTLD_NEXT, "__cxa_throw");
}
// At this point, you can get the current stack trace and do something with it (e.g. print it, like follows).
// You can also set a break point here to have the debugger stop while the stack trace is still useful.
std::cout << "About to throw an exception of type " << thrownTypeInfo->name() << "! Current stack trace is as follows:" << std::endl;
printStackTrace();
std::cout << std::endl;
oldThrowFunction(thrownException, thrownTypeInfo, destructor);
}
}
Integrated with the example in the question, the output is as follows:
About to throw an exception of type St16invalid_argument! Current stack trace is as follows:
Stack trace (7 of 20 requested frames):
/foo/Test(_Z15printStackTracev+0x3c)[0x55570996b385]
/foo/Test(__cxa_throw+0xa1)[0x55570996b653]
/foo/Test(_Z12someFunctionv+0x43)[0x55570996b555]
/foo/Test(_Z47someFunctionInTheStandardLibraryThatICantChangev+0x12)[0x55570996b581]
/foo/Test(main+0x21)[0x55570996b6ac]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x7fb71560f0b3]
/foo/Test(_start+0x2e)[0x55570996b28e]
End of stack trace.
Signal 6 (Aborted) happened!
Stack trace (13 of 20 requested frames):
/foo/Test(_Z15printStackTracev+0x3c)[0x55570996b385]
/foo/Test(_Z13signalHandleri+0xbd)[0x55570996b50f]
/lib/x86_64-linux-gnu/libc.so.6(+0x46210)[0x7fb71562e210]
/lib/x86_64-linux-gnu/libc.so.6(gsignal+0xcb)[0x7fb71562e18b]
/lib/x86_64-linux-gnu/libc.so.6(abort+0x12b)[0x7fb71560d859]
/lib/x86_64-linux-gnu/libstdc++.so.6(+0x9e911)[0x7fb715893911]
/lib/x86_64-linux-gnu/libstdc++.so.6(+0xaa38c)[0x7fb71589f38c]
/lib/x86_64-linux-gnu/libstdc++.so.6(+0xaa3f7)[0x7fb71589f3f7]
/lib/x86_64-linux-gnu/libstdc++.so.6(__cxa_rethrow+0x4d)[0x7fb71589f6fd]
/foo/Test(_Z47someFunctionInTheStandardLibraryThatICantChangev+0x25)[0x55570996b594]
/foo/Test(main+0x21)[0x55570996b6ac]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x7fb71560f0b3]
/foo/Test(_start+0x2e)[0x55570996b28e]
End of stack trace.
terminate called after throwing an instance of 'std::invalid_argument'
what(): Bad things happened
This can be improved in the following ways:
void*
by examining the type_info
.std::string
field (to plonk the stack trace in) in the inheritance hierarchy of exceptions you are throwing (it's headache-inducing arcane magic, requires changing existing source code, is totally unportable, but works for me); or maybe by using thread-local storage, but I haven't touched that yet.If I find enough time and patience I'll add that to this answer, but I might die of frustration before that happens.
Upvotes: 2