[backport proposed gcc-4.8/trunk patch for ARM EABI unwinding ] Date: Mon, 30 Jul 2012 15:18:40 +0100 From: Julian Brown Subject: [PATCH, ARM] RFC: Backtracing through C++ exception-handling constructs List-Archive: Hi, I've been investigating a patch which we've been using locally to fix an issue with backtraces (using, e.g., glibc's backtrace() function) through C++ exception-handling constructs on ARM. The original author of the patch was Daniel Jacobowitz (please correct me if my understanding is wrong!). There are two issues in play here: 1. Exception-handling is handled in a target-specific way for ARM, defined in the EHABI document ("Exception handling ABI for the ARM architecture", IHI 0038A). However, no mention of "forced unwinding" is made in this document. 2. Backtracing in particular isn't even the "normal" use case for forced unwinding: e.g., http://www.ucw.cz/~hubicka/papers/abi/node25.html#SECTION00923200000000000000 suggests that forced unwinding is "a single-phase process (phase 2 of the normal exception-handling process)", whereas for producing a backtrace, something more like a phase 1 lookup is done (no cleanup handlers are called -- we're merely observing the state of the stack). So, to be clear, we're definitely dealing with a corner case here. The problem is that _Unwind_Backtrace in libgcc will fail to make progress in some cases if it hits a frame with a cleanup associated with it, leading to unhelpful behaviour like (for the attached program): bar calling abort abort handler invoked, depth 25 ./test() [0x8968] ../install/arm-none-linux-gnueabi/libc/lib/libc.so.6(__default_rt_sa_restorer_v2+0) [0x401cf860] ../install/arm-none-linux-gnueabi/libc/lib/libc.so.6(gsignal+0x40) [0x401ce5e0] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] ./test() [0x8a4c] which is clearly wrong, no matter how you look at it. I'll defer to Dan for a better description of the problem/fix (on a private branch, circa February 2010): "This bug was a failure of backtrace() when presented with a C++ abort - in particular, one which inherited throw() but called cout, so needed its own call to __cxa_call_unexpected. We'd get stuck in a loop in _Unwind_Backtrace because the code was not prepared for the handler to return _URC_HANDLER_FOUND. "The GCC ARM unwinders already have _US_FORCED_UNWIND passed to the personality routine. ISTM that forced unwinding when doing essentially a 'phase 1' lookup has no other useful meaning, and this is a useful meaning to assign to it: skip handlers, keep unwinding." The patch still seems to produce a reasonable improvement in behaviour, giving: build$ arm-none-linux-gnueabi-g++ test.cc -o test -g $ ./test bar calling abort abort handler invoked, depth 5 ./test() [0x8968] ../install/arm-none-linux-gnueabi/libc/lib/libc.so.6(__default_rt_sa_restorer_v2+0) [0x401cf860] ../install/arm-none-linux-gnueabi/libc/lib/libc.so.6(gsignal+0x40) [0x401ce5e0] ./test() [0x8a4c] ./test() [0x8a4c] although tbh I'd hope for backtrace_symbols to produce something a little more useful than that (unrelated to this patch), and I'd also expect identical results from the test whether the "throw ()" is present on the declaration of "bar", or not -- which unfortunately isn't the case. Without throw (), we get: bar calling abort abort handler invoked, depth 8 ./test() [0x897c] ../install/arm-none-linux-gnueabi/libc/lib/libc.so.6(__default_rt_sa_restorer_v2+0) [0x401cf860] ../install/arm-none-linux-gnueabi/libc/lib/libc.so.6(gsignal+0x40) [0x401ce5e0] ./test() [0x8a60] ./test() [0x8a60] ./test() [0x8a7c] ./test() [0x8acc] ../install/arm-none-linux-gnueabi/libc/lib/libc.so.6(__libc_start_main+0x114) [0x401b8e44] I.e., it looks like the backtrace progresses all the way to the outermost frame -- which IIUC, was the intended resulting behaviour for the attached patch to start with. So: does anyone have an opinion about whether the attached is a correct fix, or if the spinning-during-backtrace problem might have a better solution? (I'm a little fuzzy on the intricate details of all this stuff!). Thanks, Julian #include #include #include #include using namespace std; void cxx_abort () { raise (SIGABRT); exit (1); } void abort_handler (int signal, siginfo_t* info, void* unused) { void* trace[25]; int depth; int i; char** symbols; depth = backtrace (trace, 25); symbols = backtrace_symbols (trace, depth); cout << "abort handler invoked, depth " << depth << endl; for (i = 0; i < depth; ++i) cout << symbols[i] << endl; } void bar () throw () { cout << "bar calling abort" << endl; cxx_abort (); } void foo () { bar (); } int main (int argc, char **argv) { struct sigaction sa; sa.sa_flags = SA_SIGINFO; sigemptyset (&sa.sa_mask); sa.sa_sigaction = abort_handler; sigaction (SIGABRT, &sa, NULL); foo (); return 0; } ChangeLog Daniel Jacobowitz libstdc++-v3/ * libsupc++/eh_personality.cc (PERSONALITY_FUNCTION): For ARM EABI, skip handlers for _US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND. --- gcc-4.8.0/libstdc++-v3/libsupc++/eh_personality.cc.~1~ 2013-02-03 18:54:05.000000000 +0100 +++ gcc-4.8.0/libstdc++-v3/libsupc++/eh_personality.cc 2013-03-23 17:49:29.685425019 +0100 @@ -378,6 +378,8 @@ PERSONALITY_FUNCTION (int version, switch (state & _US_ACTION_MASK) { case _US_VIRTUAL_UNWIND_FRAME: + if (state & _US_FORCE_UNWIND) + CONTINUE_UNWINDING; actions = _UA_SEARCH_PHASE; break;