exception handling and conservative GC

Filip Pizlo pizlo@mac.com
Thu Jan 19 01:18:00 GMT 2006


Hello,

The method of allocation used for exceptions in flight (see  
__cxa_allocate_exception and __cxa_free_exception) makes things  
difficult for garbage collector users.  The problem appears whether  
or not the collector overrides malloc/free.  In particular:

1) If the collector overrides malloc/free with its own allocation  
routines, we run into the deeper problem that the collector has no  
way of finding thread-local storage.  So, we run the risk of freeing  
the exception object prematurely.  (This is a problem in other  
libraries as well.)

2) If the collector does not override malloc/free, then the collector  
doesn't see the exception object.  The result is that if the user's  
exception contained pointers to objects allocated by the GC, those  
objects may be freed prematurely.

I attach a program that demonstrates this.  It prints the wrong  
output, "foobar!", on all of the platforms that I've tested (Mac OS X  
with GCC 3.3 and 4.0; I tested both FSF and Apple versions of 4.0;  
and also Linux on AMD64 using GCC 3.4).  In this program, the  
exception object is the char* pointer; its target is allocated using  
the GC.

What I have implemented is a hook in eh_alloc.cc (see attached patch)  
that resolves this issue.  It works regardless of whether the  
collector is overriding malloc/free.  It allows the collector to  
insert its own allocation routines specifically for use with  
exceptions; in this case the collector can replace malloc with  
GC_malloc_uncollectable and free with GC_free.  The result is that:

1) the collector can scan the exception object, and

2) the collector knows not to free the exception object until  
explicitly told to do so by libstdc++.  Hence, even without a general  
solution to the problem of thread-local storage, this resolves the  
exception handling issue.

This patch is imperfect:  there is no safe way to change the  
allocation routines if an exception is already in flight, and I'm not  
sure about my namespacing decisions.

Regards,
Filip Pizlo
Computer Science Dept, Purdue University, Lafayette, IN.


The Program:

#include <gc/gc.h>
#include <string.h>
#include <stdio.h>

struct myexc {
     char *str;
};

void f() {
     myexc e;
     e.str=(char*)GC_MALLOC_ATOMIC(100);
     strcpy(e.str,"hello, world!");
     // uncomment for a cheap workaround
     //char **strptr=(char**)GC_MALLOC_UNCOLLECTABLE(sizeof(char**));
     //*strptr=e.str;
     throw e;
}

void g() {
     try {
         f();
     } catch (...) {
         // if you don't see the bug, change 1000 to a bigger number  
or do something else here
         // that would force a GC.  to see correct behavior, comment  
this loop out.
         for (unsigned i=0;i<1000;++i) {
             char *str=(char*)GC_MALLOC_ATOMIC(100);
             strcpy(str,"foobar!");
         }
         throw;
     }
}

int main() {
     GC_INIT();
     try {
         g();
     } catch (const myexc &e) {
         printf("string is: %s\n",e.str);
     }
     return 0;
}


The Patch:

*** /Users/pizlo/Programs/cvs_hacking/gcc-4.0.2/libstdc++-v3/libsupc+ 
+/eh_alloc.cc      Fri Feb 18 21:35:24 2005
--- libsupc++/eh_alloc.cc       Tue Jan 17 01:28:21 2006
***************
*** 52,57 ****
--- 52,80 ----
   extern "C" int memset (void *, int, std::size_t);
   #endif

+ namespace __gnu_cxx {
+     void *(*__cxa_eh_malloc)(std::size_t)=0;
+     void (*__cxa_eh_free)(void*)=0;
+ }
+
+ using namespace __gnu_cxx;
+
+ static void *my_malloc(std::size_t s) {
+     if (__cxa_eh_malloc) {
+       return __cxa_eh_malloc(s);
+     } else {
+       return malloc(s);
+     }
+ }
+
+ static void my_free(void *p) {
+     if (__cxa_eh_free) {
+       return __cxa_eh_free(p);
+     } else {
+       return free(p);
+     }
+ }
+
   using namespace __cxxabiv1;

   // ??? How to control these parameters.
***************
*** 112,118 ****
     void *ret;

     thrown_size += sizeof (__cxa_exception);
!   ret = malloc (thrown_size);

     if (! ret)
       {
--- 135,141 ----
     void *ret;

     thrown_size += sizeof (__cxa_exception);
!   ret = my_malloc (thrown_size);

     if (! ret)
       {
***************
*** 178,182 ****
   #endif
       }
     else
!     free (ptr - sizeof (__cxa_exception));
   }
--- 201,205 ----
   #endif
       }
     else
!     my_free (ptr - sizeof (__cxa_exception));
   }




More information about the Libstdc++ mailing list