atexit and static global destructors

I have a weird issue with the behaviour of atexit. From the output below, you can see that registration with atexit is the last registration that occurs just before main is called. However, it is not the first atexit routine called after the end of main. Has anyone else encountered this before? I wrote a simple test app to check for the ordering and it appears to honour ordering of explicit and implicit (dtor) registration with atexit. However this other application that I am building does not. Same result on both FreeBSD6 and FreeBSD8. Thoughts?
Code:
[david.chappelle@vmachine ~/gpt/test]$ ./main
<DC> constructed: atomicops_x86
<DC> constructed: heapprofiler
<DC> constructed: init_start
[B]<DC> atexit registered heap cleanups[/B]
********** start of main **********
*********** end of main ***********
<DC> destructed: init_start
<DC> destructed: heapprofiler
<DC> destructed: atomicops_x86
[B]<DC> atexit called heap cleanups[/B]
 
Take a look at all of the atexit and __cxa_atexit breakpoints below. Pay close attention to the dso parameter values. For calls to atexit, dso is set to 0x0 for registration.

Code:
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /usr/home/david.chappelle/gpt/test/main
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x8006a6140 <__gnu_cxx::__pool<false>::_M_destroy()+160>, arg=0x0, dso=0x80081d000)
    at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x8008b28a0 <__tcf_0>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x8008ae1e0 <__tcf_0>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x8008ae1c0 <__tcf_1>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x8008aedb0 <__tcf_2>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x8008a0020 <__tcf_0>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x80089a300 <__tcf_0>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x80088d330 <__tcf_0>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x80088d350 <__tcf_1>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x80088d370 <__tcf_2>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 7, atexit (func=0x800891f00 <HeapLeakChecker::RunHeapCleanups()>) at /usr/src/lib/libc/stdlib/atexit.c:124
[/color][/B]124             fn.fn_type = ATEXIT_FN_STD;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x80088d8d0 <__tcf_3>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 6, __cxa_atexit (func=0x80088ab20 <__tcf_0>, arg=0x0, dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:143
[/color][/B]143             fn.fn_type = ATEXIT_FN_CXA;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 7, atexit (func=0x800507eb0 <dlclose+304>) at /usr/src/lib/libc/stdlib/atexit.c:124
[/color][/B]124             fn.fn_type = ATEXIT_FN_STD;
(gdb) c
Continuing.
[B][color="rgb(46, 139, 87)"]Breakpoint 7, atexit (func=0x400848 <_fini>) at /usr/src/lib/libc/stdlib/atexit.c:124
[/color][/B]124             fn.fn_type = ATEXIT_FN_STD;
(gdb) c
Continuing.

********** start main **********
*********** end main ***********

[B][color="DarkOrchid"]Breakpoint 5, __cxa_finalize (dso=0x0) at /usr/src/lib/libc/stdlib/atexit.c:164
[/color][/B]164             _MUTEX_LOCK(&atexit_mutex);
(gdb) bt
#0  __cxa_finalize (dso=0x0) at /usr/src/lib/libc/stdlib/atexit.c:164
#1  0x0000000800c34462 in exit (status=0) at /usr/src/lib/libc/stdlib/exit.c:67
#2  0x0000000000400745 in _start ()
#3  0x0000000800532000 in ?? ()
(gdb) c
Continuing.

[B][color="rgb(153, 50, 204)"]Breakpoint 5, __cxa_finalize (dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:164
[/color][/B]164             _MUTEX_LOCK(&atexit_mutex);
(gdb) bt
[B][color="Red"]#0  __cxa_finalize (dso=0x8009ba180) at /usr/src/lib/libc/stdlib/atexit.c:164
[/color][/B]#1  0x0000000800888863 in __do_global_dtors_aux () from /usr/local/lib/libtcmalloc.so.2
#2  0x00000008008b4b99 in _fini () from /usr/local/lib/libtcmalloc.so.2
#3  0x000000080062f1d0 in ?? () from /libexec/ld-elf.so.1
#4  0x0000000800507d21 in dlsym () from /libexec/ld-elf.so.1
#5  0x0000000800507ede in dlclose () from /libexec/ld-elf.so.1
[B][color="rgb(153, 50, 204)"]#6  0x0000000800cac0b6 in __cxa_finalize (dso=0x0) at /usr/src/lib/libc/stdlib/atexit.c:183
[/color][/B]#7  0x0000000800c34462 in exit (status=0) at /usr/src/lib/libc/stdlib/exit.c:67
#8  0x0000000000400745 in _start ()
#9  0x0000000800532000 in ?? ()
(gdb)


You should have noticed that the atexit and __cxa_atexit calls are interleaved. Now with this in mind consider the two __cxa_finalize breakpoints/backtraces above. The first one is called directly from exit.c and passes dso=0x0:

Code:
 58 void
 59 exit(status)
 60         int status;
 61 {
 62         /* Ensure that the auto-initialization routine is linked in: */
 63         extern int _thread_autoinit_dummy_decl;
 64
 65         _thread_autoinit_dummy_decl = 1;
 66
 67         __cxa_finalize(NULL);
 68         if (__cleanup)
 69                 (*__cleanup)();
 70         _exit(status);
 71 }

This is telling __cxa_finalize to just call all registered functions in LIFO order. Now, look at the second __cxa_finalize context. It is actually being called from within the context of the first __cxa_finalize call. Also note that it is passing dso=0x8009ba180. This is telling __cxa_finalize to only call functions that were registered with this dso. Now recall that atexit calls with dso=0x0 were interleaved with __cxa_atexit calls with dso=0x8009ba180. This is bad news. Effectively what happens is that the order that is supposed to be strictly guaranteed based on order of construction is now invalidated since entries with dso=0x0 are now skipped while entries with dso=0x8009ba180 are processed.

In my opinion, something is buggy/broken here.
 
Thanks for your help. I'll make sure to use proper formatting for future posts. I'll repost to the mailing list as you suggest.

Regards,

Dave
 
Back
Top