>Number: 59552 >Category: bin >Synopsis: Patch to support the C++ DSO Object Destruction API >Confidential: no >Severity: serious >Priority: medium >Responsible: freebsd-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: change-request >Submitter-Id: current-users >Arrival-Date: Fri Nov 21 07:40:18 PST 2003 >Closed-Date: >Last-Modified: >Originator: Bradley T Hughes >Release: FreeBSD 5.1-CURRENT i386 >Organization: Trolltech AS >Environment: System: FreeBSD rage.troll.no 5.1-CURRENT FreeBSD 5.1-CURRENT #17: Fri Nov 21 14:01:00 CET 2003 root@:/usr/obj/usr/src/sys/RAGE i386 >Description: Below is a test-case and patch to support the cross-vendor C++ DSO Object Destruction API used by GCC 3.x and the Intel C/C++ compilers, defined at http://www.codesourcery.com/cxx-abi/abi.html#dso-dtor Here is a quote from the web-page, describing the motivation/rationale behind this API: The C++ Standard requires that destructors be called for global objects when a program exits in the opposite order of construction. Most implementations have handled this by calling the C library atexit(3) routine to register the destructors. This is problematic because the 1999 C Standard only requires that the implementation support 32 register function, although most implementations support many more. More important, it does not deal at all with the ability in most implementations to remove DSOs (dynamic shared objects) from a running program image by calling dlclose(3) prior to program termination. The API specified below is intended to provide standard-conforming treatment during normal program exit, which includes executing atexit(3)-registered functions in the correct sequence relative to constructor-registered destructors, and reasonable treatment during early DSO unload (e.g. dlclose(3)). >How-To-Repeat: This test case reproduces the problem. Note: remove -fuse-cxa-atexit if testing without the included patch (otherwise the test will fail to link due to undefined symbols). Makefile: ------------------------------------------------------------------------ .if $(CXX) == "icpc" CXXFLAGS = -g # LFLAGS = -Qoption,ld,,-rpath,/usr/obj/usr/src/lib/libc \ # -L/usr/obj/usr/src/lib/libc -lc .else CXXFLAGS = -g -fuse-cxa-atexit # LFLAGS = -rpath /usr/obj/usr/src/lib/libc -L/usr/obj/usr/src/lib/libc -lc .endif all: app lib.so app: app.cpp $(CXX) $(CXXFLAGS) -g -o app app.cpp $(LFLAGS) lib.so: lib.cpp $(CXX) $(CXXFLAGS) -g -shared -o lib.so lib.cpp clean: rm -f app rm -f lib.so rm -f app.core ------------------------------------------------------------------------ app.cpp: ------------------------------------------------------------------------ #include #include #include struct APP { inline ~APP() { fprintf(stderr, "APP::~APP()\n"); } }; typedef void (*fn_t)(); void f() { fprintf(stderr, "app: f()\n"); static APP app; } void x() { printf("app: x()\n"); } void y() { printf("app: y()\n"); } int main(int, char **) { void *lib; fn_t gp; fprintf(stderr, "app: dlopen\n"); lib = dlopen("./lib.so", RTLD_LAZY); if (!lib) return 1; fprintf(stderr, "app: dlfunc\n"); gp = (fn_t) dlfunc(lib, "g"); if (!gp) return 1; fprintf(stderr, "app: calling lib, expect g()\n"); (*gp)(); f(); fprintf(stderr, "app: atexit(x)\n"); atexit(x); fprintf(stderr, "app: atexit(y)\n"); atexit(y); fprintf(stderr, "app: calling dlcose(), expect LIB::~LIB()\n"); dlclose(lib); fprintf(stderr, "app: done, expect y(), x(), and APP::~APP()\n"); return 0; } ------------------------------------------------------------------------ lib.cpp: ------------------------------------------------------------------------ #include struct LIB { inline ~LIB() { fprintf(stderr, "LIB::~LIB()\n"); } }; extern "C" void g() { fprintf(stderr, "lib: g()\n"); static LIB lib;; } ------------------------------------------------------------------------ >Fix: Currently, FreeBSD uses the crtstuff.c distributed with GCC (or the crtx*.o objects distributed with ICC), so the only thing missing is the implementation of __cxa_atexit() and __cxa_finalize(). This patch against src/lib/libc implements these functions and adjusts atexit(3) to use __cxa_atexit() as recommended at the web-page mentioned in the description. A few notes: 1. After applying this patch, the lang/icc port must be modified to not include its own (incorrect) implementations of __cxa_atexit() and __cxa_finalize(). In addition, devel/stlport-icc must be rebuilt with the new icc port to remove a local definitions of these 2 functions (since the icc port links with libcxa.a). In fact, all C++ libraries/programs compiled with icc will need to be (at the very least) relinked (for the same reason that devel/stlport-icc port must be rebuilt). 2. The system compiler does not current use __cxa_atexit(), so programs compiled with the system compiler will not benefit from this fix until the compiler defaults to using __cxa_atexit() (or the user explicitly uses -fuse-cxa-atexit). Once this patch is included in the base system, it should be possible to turn on use of __cxa_atexit() in the system compiler. Additionally, this change will preserve binary compatibility, since _fini() uses a weak reference to __cxa_finalize(). In fact, programs built with -fuse-cxa-atexit will continue to work on systems that do not have __cxa_finalize(), since _fini() works on a weak definition. The only difference is that global destructors will not be called *at all* in such cases. 3. It would probably make sense to bump __FreeBSD_version after applying this (since it is an ABI addition). This would allow (for example) the lang/icc port to detect whether or not its __cxa_atexit()/__cxa_finalize() workarounds are necessary. 4. This is my first patch submission to FreeBSD, so I may not have done somethings correctly. This patch can be used as-is, or with modifications to fit style(9) and other conventions. src-lib-libc.diff: ------------------------------------------------------------------------ Index: stdlib/atexit.c =================================================================== RCS file: /home/ncvs/src/lib/libc/stdlib/atexit.c,v retrieving revision 1.6 diff -b -u -r1.6 atexit.c --- stdlib/atexit.c 22 Mar 2002 21:53:09 -0000 1.6 +++ stdlib/atexit.c 21 Nov 2003 14:44:05 -0000 @@ -41,6 +41,7 @@ __FBSDID("$FreeBSD: src/lib/libc/stdlib/atexit.c,v 1.6 2002/03/22 21:53:09 obrien Exp $"); #include "namespace.h" +#include #include #include #include @@ -55,22 +56,34 @@ #define _MUTEX_LOCK(x) if (__isthreaded) _pthread_mutex_lock(x) #define _MUTEX_UNLOCK(x) if (__isthreaded) _pthread_mutex_unlock(x) -struct atexit *__atexit; /* points to head of LIFO stack */ +struct atexit { + struct atexit *next; /* next in list */ + int ind; /* next index in this table */ + struct { + void (*func)(void *); + void *arg; + void *dso; + } fns[ATEXIT_SIZE]; /* the table itself */ +}; + +static +struct atexit __atexit0; /* one guaranteed table */ +static +struct atexit *__atexit = &__atexit0; /* points to head of LIFO stack */ /* - * Register a function to be performed at exit. + * Register the function 'func' to be called with argument 'arg' when + * the shared object owning 'dso' is unloaded. Note: if 'dso' is + * NULL, the function 'func' will be called at application exit */ int -atexit(fn) - void (*fn)(); +__cxa_atexit(void (*func)(void *), void *arg, void *dso) { - static struct atexit __atexit0; /* one guaranteed table */ struct atexit *p; _MUTEX_LOCK(&atexit_mutex); - if ((p = __atexit) == NULL) - __atexit = p = &__atexit0; - else while (p->ind >= ATEXIT_SIZE) { + assert((p = __atexit) != NULL); + while (p->ind >= ATEXIT_SIZE) { struct atexit *old__atexit; old__atexit = __atexit; _MUTEX_UNLOCK(&atexit_mutex); @@ -89,7 +102,64 @@ p->next = __atexit; __atexit = p; } - p->fns[p->ind++] = fn; + p->fns[p->ind].func = func; + p->fns[p->ind].arg = arg; + p->fns[p->ind].dso = dso; + ++p->ind; + _MUTEX_UNLOCK(&atexit_mutex); + + return 0; +} + +/* + * Call all handlers registered with __cxa_atexit for the shared + * object owning \dso'. Note: if 'dso' is NULL, then all remaining + * handlers are called. + */ +void +__cxa_finalize(void *dso) +{ + struct atexit *p; + int n; + + _MUTEX_LOCK(&atexit_mutex); + for (p = __atexit; p; p = p->next) { + for (n = p->ind; --n >= 0;) { + if (p->fns[n].func == NULL) + continue; /* already been called */ + if (dso != NULL && dso != p->fns[n].dso) + continue; /* wrong DSO */ + _MUTEX_UNLOCK(&atexit_mutex); + /* + pass argument to handler... Note: this + means that we endup passing a NULL + parameter to functions registered with + atexit() + */ + (*p->fns[n].func)(p->fns[n].arg); + _MUTEX_LOCK(&atexit_mutex); + /* + use NULL as a sentinel to indicate that this + particular handler has already been called + */ + p->fns[n].func = NULL; + p->fns[n].arg = NULL; + p->fns[n].dso = NULL; + } + } _MUTEX_UNLOCK(&atexit_mutex); - return (0); +} + +/* + * Register a function to be performed at exit. + */ +int +atexit(void (*func)(void)) +{ + union { + void (*func)(void); + void (*cxa_func)(void *); + } fn; + fn.func = func; + return __cxa_atexit(fn.cxa_func, NULL, NULL); } Index: stdlib/atexit.h =================================================================== RCS file: /home/ncvs/src/lib/libc/stdlib/atexit.h,v retrieving revision 1.2 diff -b -u -r1.2 atexit.h --- stdlib/atexit.h 22 Mar 2002 23:42:03 -0000 1.2 +++ stdlib/atexit.h 21 Nov 2003 14:44:05 -0000 @@ -33,14 +33,8 @@ * @(#)atexit.h 8.2 (Berkeley) 7/3/94 * $FreeBSD: src/lib/libc/stdlib/atexit.h,v 1.2 2002/03/22 23:42:03 obrien Exp $ */ - /* must be at least 32 to guarantee ANSI conformance */ #define ATEXIT_SIZE 32 -struct atexit { - struct atexit *next; /* next in list */ - int ind; /* next index in this table */ - void (*fns[ATEXIT_SIZE])(); /* the table itself */ -}; - -extern struct atexit *__atexit; /* points to head of LIFO stack */ +int __cxa_atexit(void (*func)(void *), void *arg, void *dso); +void __cxa_finalize(void *dso); Index: stdlib/exit.c =================================================================== RCS file: /home/ncvs/src/lib/libc/stdlib/exit.c,v retrieving revision 1.6 diff -b -u -r1.6 exit.c --- stdlib/exit.c 22 Mar 2002 21:53:10 -0000 1.6 +++ stdlib/exit.c 21 Nov 2003 14:44:05 -0000 @@ -61,17 +61,12 @@ exit(status) int status; { - struct atexit *p; - int n; - /* Ensure that the auto-initialization routine is linked in: */ extern int _thread_autoinit_dummy_decl; _thread_autoinit_dummy_decl = 1; - for (p = __atexit; p; p = p->next) - for (n = p->ind; --n >= 0;) - (*p->fns[n])(); + __cxa_finalize(NULL); if (__cleanup) (*__cleanup)(); _exit(status); ------------------------------------------------------------------------ >Release-Note: >Audit-Trail: >Unformatted: _______________________________________________ freebsd-bugs@freebsd.org mailing list http://lists.freebsd.org/mailman/listinfo/freebsd-bugs To unsubscribe, send any mail to "freebsd-bugs-unsubscribe@freebsd.org"