ps3dev / ps3toolchain

A script to autobuild an open source toolchain for the PS3.
BSD 2-Clause "Simplified" License
283 stars 92 forks source link

using exceptions crash in global_dtor #47

Closed kazzmir closed 13 years ago

kazzmir commented 13 years ago

If I use an exception at any point in my program a segfault will occur when the program finishes in the global_dtor_aux stuff. Compiled like so:

ppu-g++ -mcpu=cell -mhard-float -fmodulo-sched -ffunction-sections -fdata-sections -maltivec -Woverloaded-virtual -fno-rtti -O2 -mcpu=cell -mhard-float -fmodulo-sched -ffunction-sections -fdata-sections -maltivec -Wall -Wno-unused-variable -Wno-unused-function -fexceptions -G0 x.cpp -o x

include

include

using namespace std;

define xdebug printf("%s:%d\n", FILE, LINE)

int main(){ try{ xdebug; throw std::exception(); xdebug; } catch (const std::exception & f){ xdebug; } xdebug; return 0; }

Will print Launching... y.cpp:10 y.cpp:14 y.cpp:16 slb_refill_handler(DSI): invalid access address 0x400004b0! SRR0: 0x000000000002143c, SRR1: 0x800000000000e032

Srr0: 0x000000000002143c Which gdb says is in the function __deregister_frame_info_bases:

Backtrace:

0x0000000010000024 (_ZTSSt9exception)

0x00000000000102c8 (.__do_global_dtors_aux)

0x0000000000039d70 (._fini)

0x0000000000030c48 (.__librt_exit)

0x0000000000021770 (_exit)

0x00000000000217dc (exit)

0x000000000001054c (._initialize)

0x00000000000103ac (._start)

Is there some compiler flag I'm missing to make exceptions work?

kazzmir commented 13 years ago

I also just tried using no flags at all and I get the same behavior

ppu-g++ x.cpp -o x
kazzmir commented 13 years ago

I looked into this issue and have turned up the following. The short story is sbrk_deinit from librt/sbrk.c is at fault. When the program exits it goes through this call chain: exit -> _exit -> .fini -> global_dtors_aux -> deregister_frame_info

With possibly some more functions in there. global_dtors_aux is implemented in gcc at gcc/crtstuff.c. First it calls all globally registered destructor functions that exist in the DTOR_LIST (line 300-304 in that file), reproduced here

while (dtor_idx < max_idx) { f = DTOR_LIST[++dtor_idx]; f (); }

Where sbrk_deinit (from librt/sbrk.c) is in that list because it was declared with attribute(destructor). sbrk_deinit deallocates all available memory or something, I'm not 100% sure, but after it runs it seems that nothing else should happen. What does happen is the global c++ object destructors run. A few lines later in crtstuff.c is this call

if (deregister_frame_info) deregister_frame_info (EH_FRAME_BEGIN);

Implemented in gcc/unwind-dw2-fde.c. Any usage of memory (allocation, deallocation, dereference) will segfault in deregister_frame_info.

I'm guessing things work in C because there is nothing that gets called after sbrk_deinit. I'm not sure what the solution is but maybe sbrk_deinit can be more careful about cleaning up?

kazzmir commented 13 years ago

I have no idea why github keeps closing this issue. Argh..

kazzmir commented 13 years ago

A workaround is to call the global object destructors first and then the functions in DTOR_LIST. This works with the test case above and my real application. Here is the code in full from gcc/crtstuff.c


static void __attribute__((used))
__do_global_dtors_aux (void)
{
  static _Bool completed;

  if (__builtin_expect (completed, 0))
    return;

#ifdef CRTSTUFFS_O
  if (__cxa_finalize)
    __cxa_finalize (__dso_handle);
#endif

/* NOTE: kazzmir moved the call to deregister_frame_info so it appears before the __DTOR_LIST__ */
#ifdef USE_EH_FRAME_REGISTRY
#ifdef CRT_GET_RFIB_DATA
  /* If we used the new __register_frame_info_bases interface,
     make sure that we deregister from the same place.  */
  if (__deregister_frame_info_bases)
    __deregister_frame_info_bases (__EH_FRAME_BEGIN__);
#else
  if (__deregister_frame_info)
    __deregister_frame_info (__EH_FRAME_BEGIN__);
#endif
#endif

#ifdef FINI_ARRAY_SECTION_ASM_OP
  /* If we are using .fini_array then destructors will be run via that
     mechanism.  */
#elif defined(HIDDEN_DTOR_LIST_END)
  {
    /* Safer version that makes sure only .dtors function pointers are
       called even if the static variable is maliciously changed.  */
    extern func_ptr __DTOR_END__[] __attribute__((visibility ("hidden")));
    static size_t dtor_idx;
    const size_t max_idx = __DTOR_END__ - __DTOR_LIST__ - 1;
    func_ptr f;

    while (dtor_idx < max_idx)
      {
    f = __DTOR_LIST__[++dtor_idx];
    f ();
      }
  }
#else /* !defined (FINI_ARRAY_SECTION_ASM_OP) */
  {
    static func_ptr *p = __DTOR_LIST__ + 1;
    func_ptr f;

    while ((f = *p))
      {
    p++;
    f ();
      }
  }
#endif /* !defined(FINI_ARRAY_SECTION_ASM_OP) */

  completed = 1;
}
zeldin commented 13 years ago

Thanks for looking deeper into this.

A potential problem with moving the __deregister_frame_info call is that it could break use of exceptions in global destructors.

I think another way of fixing it, without changing gcc at all, is to modify sbrk.c to place code in .fini in instead of using attribute((destructor)). That will assure that it is executed after __do_global_dtors_aux (which is the first thing called from .fini) has completed.

If nobody beats be to it, I'll make the fix tomorrow.

kazzmir commented 13 years ago

Ah yes you are probably right. Someone in #gcc on freenode mentioned that I should use .fini instead but I wasn't entirely sure what they meant.

zeldin commented 13 years ago

Fix pushed to PSL1GHT, closing the issue.