Bug 2682 - Avoid unwinding from atexit handlers
Summary: Avoid unwinding from atexit handlers
Status: NEW
Alias: None
Product: glibc
Classification: Unclassified
Component: libc (show other bugs)
Version: 2.4
: P3 minor
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2006-05-21 22:50 UTC by Joseph Myers
Modified: 2019-04-10 07:16 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Joseph Myers 2006-05-21 22:50:27 UTC
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;
}
Comment 1 Daniel Jacobowitz 2006-05-22 03:43:30 UTC
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.
Comment 2 Joseph Myers 2006-05-22 11:33:55 UTC
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.
Comment 3 Daniel Jacobowitz 2006-05-22 13:56:20 UTC
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.

Comment 4 Joseph Myers 2006-05-22 14:19:20 UTC
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 ();
  }
}
Comment 5 Daniel Jacobowitz 2006-05-22 14:23:22 UTC
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?
Comment 6 Joseph Myers 2006-05-22 14:35:00 UTC
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.
Comment 7 Daniel Jacobowitz 2006-05-22 14:45:31 UTC
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.

Comment 8 Daniel Jacobowitz 2006-09-09 18:25:37 UTC
The testcase in comment #4 fails on x86_64 also; this is not a ports-specific bug.
Comment 9 Joseph Myers 2012-02-15 17:50:33 UTC
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.