[prev in list] [next in list] [prev in thread] [next in thread] 

List:       freebsd-bugs
Subject:    bin/59552: Patch to support the C++ DSO Object Destruction API
From:       Bradley T Hughes <bhughes () trolltech ! com>
Date:       2003-11-21 15:20:27
[Download RAW message or body]


> 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 <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

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 <stdio.h>

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 <assert.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -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"


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic