On ARM EABI, the function "exit" needs to be marked with EXIDX_CANTUNWIND so that exceptions thrown from functions registered with atexit properly stop unwinding at that point. This may need a GCC change to add a "nounwind" attribute so that C functions can be marked that way. (_start is defined in assembly with ".cantunwind" which deals with the true end of the stack, but that doesn't suffice; the C++ standard requires unwinding to stop and terminate to be called on leaving an atexit function.) The C++ testcase is as follows; it should call "terminate" but instead segfaults. extern "C" int puts(const char *); struct S { int i; S() : i(0) { } ~S() { puts("dtor"); throw "s"; // no handler anywhere } }; S xo; int main(int argc, char *argv[]) { puts("main"); return 0; }
Mine, if it's ARM-specific. Joseph, I assume this is a general C++ requirement? How does this work on other targets? Using unwind control for this seems strange, since in theory the standard personality tables could be used by a debugger, and then it's desirable to reach exit's caller.
Yes, it's a general C++ requirement from 18.3 [lib.support.start.term] paragraph 5. If on a given platform you can tell that there isn't unwind information for the stack frame for exit, then the unwinding will stop there; it's a peculiarity of ARM unwind information that you can't tell that and need unwind information all the way up the stack until explicitly marked to stop unwinding. I don't know how or whether this (avoiding unwinding through exit) works on IA64 where everything has unwind information by default.
Subject: Re: On ARM EABI, exit should be marked EXIDX_CANTUNWIND On Mon, May 22, 2006 at 11:33:55AM -0000, jsm28 at gcc dot gnu dot org wrote: > Yes, it's a general C++ requirement from 18.3 [lib.support.start.term] paragraph 5. > > If on a given platform you can tell that there isn't unwind information for the > stack frame for exit, then the unwinding will stop there; it's a peculiarity of > ARM unwind information that you can't tell that and need unwind information all > the way up the stack until explicitly marked to stop unwinding. I don't know > how or whether this (avoiding unwinding through exit) works on IA64 where > everything has unwind information by default. x86-64 also produces unwind tables by default; and my system libc has one for exit. Here's a modified testcase: #include <stdlib.h> extern "C" int puts(const char *); struct S { int i; S() : i(0) { } ~S() { puts("dtor"); throw "s"; // no handler anywhere } }; S xo; int main(int argc, char *argv[]) { puts("main"); try { exit (0); } catch (...) { puts ("caught"); abort (); } } It generates: #0 0x00002aaaf66e8770 in std::terminate () from /usr/lib/libstdc++.so.6 #1 0x00002aaaf66e886a in __cxa_throw () from /usr/lib/libstdc++.so.6 #2 0x00000000004007b8 in ~S (this=0x500d54) at exit-exc2.cc:12 #3 0x0000000000400712 in __tcf_0 () at exit-exc2.cc:16 #4 0x00002aaaf6af257d in exit () from /lib/libc.so.6 #5 0x00000000004006fb in main (argc=1, argv=0x7fffb4593918) at exit-exc2.cc:22 ~S has unwind info. __tcf_0 has unwind info. exit has unwind info. Yet somehow, terminate gets called anyway. I do not have a debuggable libgcc handy; I have not yet managed to work out why this happens. There's nothing obviously special in exit to account for this. As far as I can tell it ought to have no 'L' augmentation, and thus no LSDA, and thus fall through... ahh! 000000b0 0000002c 000000b4 FDE cie=00000000 pc=00400878..00400890 Augmentation data: 00 00 00 00 00 00 00 00 That is an empty augmentation entry for __tcf_0, which causes __cxa_throw to call terminate. You might want to check what the ARM compiler and runtime do for this case, for compatibility, but it looks to me as if the g++-generated stubs which call destructors ought to be marked .cantunwind if anything should. // If ip is not present in the table, call terminate. This is for // a destructor inside a cleanup, or a library routine the compiler // was not expecting to throw. found_type = found_terminate; goto do_something; There has to be an unwind entry for these functions anyway, or we wouldn't have a hope of reaching exit, so I hope ARM didn't try to save space by consolidating the can't unwind marker into exit.
Marking destructor stops doesn't suffice since the same applies to functions registered explicitly from atexit. Such functions can be called normally, and throwing is then OK; they can be called from atexit, when throwing must result in terminate being called. Thanks for the x86_64 pointer, here's an example which does fail there. (The indirection about how and when exit is called is to avoid problems with the compiler knowing that exit can't throw.) #include <stdlib.h> #include <stdio.h> void f(void) { puts("in atexit handler"); throw "s"; } volatile int v; void e(void) { if (v) throw "s"; exit (0); } int main(void) { atexit(f); try { e (); } catch (...) { puts ("caught"); abort (); } }
OK, then this is a generic libc bug. It will probably require different solutions on the different ABIs, though. Should nothrow do this? i.e. when the definition of a function is marked nothrow, arrange unwind tables so that unwinding through it will cause a call to terminate?
You're distinguishing nothrow (the GCC attribute on C functions) from the standard C++ throw()? For the former we could define it to terminate unwinding, for the latter unexpected() should be called. There's also the possibility of calling all atexit functions via an assembly wrapper __call_atexit_function, which does whatever (target-specific) is needed to stop unwinding.
Subject: Re: On ARM EABI, exit should be marked EXIDX_CANTUNWIND On Mon, May 22, 2006 at 02:35:00PM -0000, jsm28 at gcc dot gnu dot org wrote: > You're distinguishing nothrow (the GCC attribute on C functions) from the > standard C++ throw()? For the former we could define it to terminate unwinding, > for the latter unexpected() should be called. No, I'm forgetting about throw(). OK, so that was not a good idea.
The testcase in comment #4 fails on x86_64 also; this is not a ports-specific bug.
As discussed (see comment#4) this is a generic libc problem that can be demonstrated on x86_64, although there will be target-specific aspects to a fix.