How to get proper stack trace despite catch and throw; in std library

Viewed 62

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.

0 Answers
Related