]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
It appears that NPTL uses a new system for dealing with cleanup
authorTom Hughes <tom@compton.nu>
Sat, 12 Jun 2004 12:58:22 +0000 (12:58 +0000)
committerTom Hughes <tom@compton.nu>
Sat, 12 Jun 2004 12:58:22 +0000 (12:58 +0000)
handlers when a thread is cancelled which has the side effect that
programs linked with librt fail on Fedora Core 2 due to librt having
been built against the NPTL header instead of the old pthread headers.

This change extends valgrind's libpthread.so to handle both the old
and new style cleanup handlers in a similar way to NPTL and seems to
be sufficient to get programs linked with librt working again.

git-svn-id: svn://svn.valgrind.org/valgrind/trunk@2405

corecheck/tests/Makefile.am
corecheck/tests/pth_cancel1.c [new file with mode: 0644]
corecheck/tests/pth_cancel1.stderr.exp [new file with mode: 0644]
corecheck/tests/pth_cancel1.stdout.exp [new file with mode: 0644]
corecheck/tests/pth_cancel1.vgtest [new file with mode: 0644]
coregrind/vg_include.h
coregrind/vg_libpthread.c
coregrind/vg_scheduler.c

index c906df46a641c4c7deb37e34f03c510b670f8cb9..8df94dcec0ee7fcf0ac59678bde30926d6f86f2f 100644 (file)
@@ -18,6 +18,7 @@ EXTRA_DIST = $(noinst_SCRIPTS) \
        fdleak_pipe.stderr.exp fdleak_pipe.vgtest \
        fdleak_socketpair.stderr.exp fdleak_socketpair.vgtest \
        pth_atfork1.stderr.exp pth_atfork1.stdout.exp pth_atfork1.vgtest \
+       pth_cancel1.stderr.exp pth_cancel1.stdout.exp pth_cancel1.vgtest \
        pth_cancel2.stderr.exp pth_cancel2.vgtest \
        pth_cvsimple.stderr.exp pth_cvsimple.stdout.exp pth_cvsimple.vgtest \
        pth_empty.stderr.exp pth_empty.vgtest \
@@ -33,7 +34,7 @@ check_PROGRAMS = \
        erringfds fdleak_cmsg fdleak_creat fdleak_dup fdleak_dup2 \
        fdleak_fcntl fdleak_ipv4 fdleak_open fdleak_pipe \
        fdleak_socketpair sigkill res_search \
-       pth_atfork1 pth_cancel2 pth_cvsimple pth_empty \
+       pth_atfork1 pth_cancel1 pth_cancel2 pth_cvsimple pth_empty \
        pth_exit pth_mutexspeed pth_once \
        as_mmap as_shm \
        vgprintf
@@ -62,6 +63,8 @@ sigkill_SOURCES               = sigkill.c
 # Pthread ones
 pth_atfork1_SOURCES    = pth_atfork1.c
 pth_atfork1_LDADD      = -lpthread
+pth_cancel1_SOURCES    = pth_cancel1.c
+pth_cancel1_LDADD      = -lpthread
 pth_cancel2_SOURCES    = pth_cancel2.c
 pth_cancel2_LDADD      = -lpthread
 pth_cvsimple_SOURCES   = pth_cvsimple.c
diff --git a/corecheck/tests/pth_cancel1.c b/corecheck/tests/pth_cancel1.c
new file mode 100644 (file)
index 0000000..d1b93e3
--- /dev/null
@@ -0,0 +1,66 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <unistd.h>
+
+static void thread_cleanup(void *arg)
+{
+  printf("cleaning up %p\n", arg);
+
+  return;
+}
+
+static void *thread_main(void *arg)
+{
+  pthread_cleanup_push(thread_cleanup, (void *)0x1234);
+  pthread_cleanup_push(thread_cleanup, (void *)0x5678);
+
+  if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL) != 0)
+    {
+      perror("pthread_setcanceltype");
+      return NULL;
+    }
+  
+  if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
+    {
+      perror("pthread_setcancelstate");
+      return NULL;
+    }
+
+  pause();
+
+  pthread_cleanup_pop(0);
+  pthread_cleanup_pop(0);
+
+  return NULL;
+}
+
+int main(int argc, char **argv)
+{
+  pthread_t tid;
+  void *result;
+  
+  if (pthread_create(&tid, NULL, thread_main, NULL) != 0)
+    {
+      perror("pthread_create");
+      exit(1);
+    }
+
+  sleep(1);
+
+  if (pthread_cancel(tid) != 0)
+    {
+      perror("pthread_cancel");
+      exit(1);
+    }
+
+  if (pthread_join(tid, &result) != 0)
+    {
+      perror("pthread_join");
+      exit(1);
+    }
+
+  printf("result = %p\n", result);
+  
+  exit(0);
+}
diff --git a/corecheck/tests/pth_cancel1.stderr.exp b/corecheck/tests/pth_cancel1.stderr.exp
new file mode 100644 (file)
index 0000000..d18786f
--- /dev/null
@@ -0,0 +1,3 @@
+
+
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/corecheck/tests/pth_cancel1.stdout.exp b/corecheck/tests/pth_cancel1.stdout.exp
new file mode 100644 (file)
index 0000000..f0e1e4e
--- /dev/null
@@ -0,0 +1,3 @@
+cleaning up 0x5678
+cleaning up 0x1234
+result = 0xffffffff
diff --git a/corecheck/tests/pth_cancel1.vgtest b/corecheck/tests/pth_cancel1.vgtest
new file mode 100644 (file)
index 0000000..f224af4
--- /dev/null
@@ -0,0 +1 @@
+prog: pth_cancel1
index 50f486d7353a146d7f45068cb9f199d468a712a7..19fae4489d4cd23f67ac942a983a1ab348e6509d 100644 (file)
@@ -705,11 +705,34 @@ typedef
    }
    ThreadStatus;
 
+typedef
+   enum CleanupType {
+      VgCt_None,       /* this cleanup entry is not initialised */
+      VgCt_Function,   /* an old-style function pointer cleanup */
+      VgCt_Longjmp     /* a new-style longjmp based cleanup */
+   }
+   CleanupType;
+
+/* A thread unwind buffer */
+typedef
+   struct {
+      jmp_buf jb;
+   } ThreadUnwindBuf;
+
 /* An entry in a threads's cleanup stack. */
 typedef
    struct {
-      void (*fn)(void*);
-      void* arg;
+      CleanupType type;
+      union {
+         struct {
+            void (*fn)(void*);
+            void* arg;
+         } function;
+         struct {
+            ThreadUnwindBuf *ub;
+            int ctype;
+         } longjmp;
+      } data;
    }
    CleanupEntry;
 
index b3203330d3051c2f83640bc1e639e82bf81502e8..848f8694b87eee44c1333ae1fb0bbd416f185401 100644 (file)
@@ -199,6 +199,11 @@ init_global_thread_specific_state ( void );
 static void
 init_thread_specific_state ( void );
 
+static void
+set_ret_val ( void* );
+static void *
+get_ret_val ( void );
+
 /* ---------------------------------------------------------------------
    Helpers.  We have to be pretty self-sufficient.
    ------------------------------------------------------------------ */
@@ -675,17 +680,6 @@ void thread_exit_wrapper ( void* ret_val )
    pthread_key_t key;
    void**        specifics_ptr;
 
-   /* Run this thread's cleanup handlers. */
-   while (1) {
-      VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
-                              VG_USERREQ__CLEANUP_POP,
-                              &cu, 0, 0, 0);
-      if (res == -1) break; /* stack empty */
-      my_assert(res == 0);
-      if (0) printf("running exit cleanup handler");
-      cu.fn ( cu.arg );
-   }
-
    /* Run this thread's key finalizers.  Really this should be run
       PTHREAD_DESTRUCTOR_ITERATIONS times. */
    for (key = 0; key < VG_N_THREAD_KEYS; key++) {
@@ -694,9 +688,10 @@ void thread_exit_wrapper ( void* ret_val )
                               key, &cu, 0, 0 );
       if (res == 0) {
          /* valid key */
-         if (cu.fn && cu.arg)
-            cu.fn /* destructor for key */ 
-                  ( cu.arg /* specific for key for this thread */ );
+         my_assert(cu.type == VgCt_Function);
+         if (cu.data.function.fn && cu.data.function.arg)
+            cu.data.function.fn /* destructor for key */ 
+                  ( cu.data.function.arg /* specific for key for this thread */ );
          continue;
       }
       my_assert(res == -1);
@@ -775,13 +770,14 @@ static
 __attribute__((noreturn))
 void thread_wrapper ( NewThreadInfo* info )
 {
-   int           attr__detachstate;
-   void*         tls_data;
-   int           tls_segment;
-   unsigned long sysinfo;
-   void*         (*root_fn) ( void* );
-   void*         arg;
-   void*         ret_val;
+   int             attr__detachstate;
+   void*           tls_data;
+   int             tls_segment;
+   unsigned long   sysinfo;
+   void*           (*root_fn) ( void* );
+   void*           arg;
+   void*           ret_val;
+   ThreadUnwindBuf ub;
 
    attr__detachstate = info->attr__detachstate;
    tls_data          = info->tls_data;
@@ -836,16 +832,238 @@ void thread_wrapper ( NewThreadInfo* info )
    /* Free up the arg block that pthread_create malloced. */
    my_free(info);
 
-   /* The root function might not return.  But if it does we simply
-      move along to thread_exit_wrapper.  All other ways out for the
-      thread (cancellation, or calling pthread_exit) lead there
-      too. */
-   ret_val = root_fn(arg);
+
+   if (setjmp(ub.jb) == 0) {
+      CleanupEntry cu;
+      int          res;
+      
+      cu.type = VgCt_Longjmp;
+      cu.data.longjmp.ub = &ub;
+      VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                              VG_USERREQ__CLEANUP_PUSH,
+                              &cu, 0, 0, 0);
+
+      /* The root function might not return.  But if it does we simply
+         move along to thread_exit_wrapper.  All other ways out for the
+         thread (cancellation, or calling pthread_exit) lead there
+         too. */
+      ret_val = root_fn(arg);
+   }
+   else {
+      ret_val = get_ret_val();
+   }
+   
    thread_exit_wrapper(ret_val);
    /* NOTREACHED */
 }
 
 
+/* ---------------------------------------------------
+   CLEANUP STACKS
+   ------------------------------------------------ */
+
+void _pthread_cleanup_push (struct _pthread_cleanup_buffer *__buffer,
+                            void (*__routine) (void *),
+                            void *__arg)
+{
+   int          res;
+   CleanupEntry cu;
+   ensure_valgrind("_pthread_cleanup_push");
+   cu.type = VgCt_Function;
+   cu.data.function.fn = __routine;
+   cu.data.function.arg = __arg;
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__CLEANUP_PUSH,
+                           &cu, 0, 0, 0);
+   my_assert(res == 0);
+}
+
+
+void _pthread_cleanup_push_defer (struct _pthread_cleanup_buffer *__buffer,
+                                  void (*__routine) (void *),
+                                  void *__arg)
+{
+   /* As _pthread_cleanup_push, but first save the thread's original
+      cancellation type in __buffer and set it to Deferred. */
+   int orig_ctype;
+   ensure_valgrind("_pthread_cleanup_push_defer");
+   /* Set to Deferred, and put the old cancellation type in res. */
+   my_assert(-1 != PTHREAD_CANCEL_DEFERRED);
+   my_assert(-1 != PTHREAD_CANCEL_ASYNCHRONOUS);
+   my_assert(sizeof(struct _pthread_cleanup_buffer) >= sizeof(int));
+   VALGRIND_MAGIC_SEQUENCE(orig_ctype, (-1) /* default */,
+                           VG_USERREQ__SET_CANCELTYPE,
+                           PTHREAD_CANCEL_DEFERRED, 0, 0, 0);   
+   my_assert(orig_ctype != -1);
+   *((int*)(__buffer)) = orig_ctype;
+   /* Now push the cleanup. */
+   _pthread_cleanup_push(NULL, __routine, __arg);
+}
+
+
+void _pthread_cleanup_pop (struct _pthread_cleanup_buffer *__buffer,
+                           int __execute)
+{
+   int          res;
+   CleanupEntry cu;
+   ensure_valgrind("_pthread_cleanup_push");
+   cu.type = VgCt_None; /* paranoia */
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__CLEANUP_POP,
+                           &cu, 0, 0, 0);
+   my_assert(cu.type == VgCt_Function);
+   if (res == 0) {
+      /* pop succeeded */
+     if (__execute) {
+        cu.data.function.fn ( cu.data.function.arg );
+     }
+     return;
+   }   
+   if (res == -1) {
+      /* stack underflow */
+      return;
+   }
+   barf("_pthread_cleanup_pop");
+}
+
+
+void _pthread_cleanup_pop_restore (struct _pthread_cleanup_buffer *__buffer,
+                                   int __execute)
+{
+   int orig_ctype, fake_ctype;
+   /* As _pthread_cleanup_pop, but after popping/running the handler,
+      restore the thread's original cancellation type from the first
+      word of __buffer. */
+   _pthread_cleanup_pop(NULL, __execute);
+   orig_ctype = *((int*)(__buffer));
+   my_assert(orig_ctype == PTHREAD_CANCEL_DEFERRED
+          || orig_ctype == PTHREAD_CANCEL_ASYNCHRONOUS);
+   my_assert(-1 != PTHREAD_CANCEL_DEFERRED);
+   my_assert(-1 != PTHREAD_CANCEL_ASYNCHRONOUS);
+   my_assert(sizeof(struct _pthread_cleanup_buffer) >= sizeof(int));
+   VALGRIND_MAGIC_SEQUENCE(fake_ctype, (-1) /* default */,
+                           VG_USERREQ__SET_CANCELTYPE,
+                           orig_ctype, 0, 0, 0); 
+   my_assert(fake_ctype == PTHREAD_CANCEL_DEFERRED);
+}
+
+
+__attribute ((regparm (1)))
+void __pthread_register_cancel (ThreadUnwindBuf *ub)
+{
+   int          res;
+   CleanupEntry cu;
+   ensure_valgrind("__pthread_register_cancel");
+   cu.type = VgCt_Longjmp;
+   cu.data.longjmp.ub = ub;
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__CLEANUP_PUSH,
+                           &cu, 0, 0, 0);
+   my_assert(res == 0);
+}
+
+
+__attribute ((regparm (1)))
+void __pthread_register_cancel_defer (ThreadUnwindBuf *ub)
+{
+   /* As __pthread_register cancel, but save the thread's original
+      cancellation type and set it to Deferred. */
+   int          res;
+   CleanupEntry cu;
+   ensure_valgrind("__pthread_register_cancel_defer");
+   cu.type = VgCt_Longjmp;
+   cu.data.longjmp.ub = ub;
+   /* Set to Deferred, and save the old cancellation type. */
+   my_assert(-1 != PTHREAD_CANCEL_DEFERRED);
+   my_assert(-1 != PTHREAD_CANCEL_ASYNCHRONOUS);
+   my_assert(sizeof(struct _pthread_cleanup_buffer) >= sizeof(int));
+   VALGRIND_MAGIC_SEQUENCE(cu.data.longjmp.ctype, (-1) /* default */,
+                           VG_USERREQ__SET_CANCELTYPE,
+                           PTHREAD_CANCEL_DEFERRED, 0, 0, 0);   
+   my_assert(cu.data.longjmp.ctype != -1);
+   /* Now push the cleanup. */
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__CLEANUP_PUSH,
+                           &cu, 0, 0, 0);
+   my_assert(res == 0);
+}
+
+
+__attribute ((regparm (1)))
+void __pthread_unregister_cancel (ThreadUnwindBuf *ub)
+{
+   int          res;
+   CleanupEntry cu;
+   ensure_valgrind("__pthread_unregister_cancel");
+   cu.type = VgCt_None; /* paranoia */
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__CLEANUP_POP,
+                           &cu, 0, 0, 0);
+   my_assert(cu.type == VgCt_Longjmp);
+   my_assert(cu.data.longjmp.ub == ub);
+   return;
+}
+
+
+__attribute ((regparm (1)))
+void __pthread_unregister_restore (ThreadUnwindBuf *ub)
+{
+   int          res;
+   CleanupEntry cu;
+   int          fake_ctype;
+   /* As __pthread_unregister_cancel, but after popping/running the
+      handler, restore the thread's original cancellation type. */
+   ensure_valgrind("__pthread_unregister_cancel_restore");
+   cu.type = VgCt_None; /* paranoia */
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__CLEANUP_POP,
+                           &cu, 0, 0, 0);
+   my_assert(cu.type == VgCt_Longjmp);
+   my_assert(cu.data.longjmp.ub == ub);
+   /* Restore the original cancellation type. */
+   my_assert(cu.data.longjmp.ctype == PTHREAD_CANCEL_DEFERRED
+          || cu.data.longjmp.ctype == PTHREAD_CANCEL_ASYNCHRONOUS);
+   my_assert(-1 != PTHREAD_CANCEL_DEFERRED);
+   my_assert(-1 != PTHREAD_CANCEL_ASYNCHRONOUS);
+   VALGRIND_MAGIC_SEQUENCE(fake_ctype, (-1) /* default */,
+                           VG_USERREQ__SET_CANCELTYPE,
+                           cu.data.longjmp.ctype, 0, 0, 0); 
+   my_assert(fake_ctype == PTHREAD_CANCEL_DEFERRED);
+   return;
+}
+
+__attribute ((regparm (1)))
+__attribute ((__noreturn__))
+void __pthread_unwind (ThreadUnwindBuf *ub)
+{
+   int           res;
+   CleanupEntry  cu;
+   while (1) {
+      VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                              VG_USERREQ__CLEANUP_POP,
+                              &cu, 0, 0, 0);
+      my_assert(res == 0);
+      if (cu.type == VgCt_Longjmp) break;
+      if (0) printf("running cleanup handler");
+      my_assert(cu.type == VgCt_Function);
+      cu.data.function.fn ( cu.data.function.arg );
+   }
+   my_assert(cu.type == VgCt_Longjmp);
+   my_assert(ub == NULL || ub == cu.data.longjmp.ub);
+   longjmp(cu.data.longjmp.ub->jb, 1);
+   /* NOTREACHED */
+}
+
+
+__attribute ((regparm (1)))
+__attribute ((__noreturn__))
+void __pthread_unwind_next (ThreadUnwindBuf *ub)
+{
+   __pthread_unwind(NULL);
+   /* NOTREACHED */
+}
+
+
 /* ---------------------------------------------------
    THREADs
    ------------------------------------------------ */
@@ -957,8 +1175,8 @@ pthread_join (pthread_t __th, void **__thread_return)
 void pthread_exit(void *retval)
 {
    ensure_valgrind("pthread_exit");
-   /* Simple! */
-   thread_exit_wrapper(retval);
+   set_ret_val(retval);
+   __pthread_unwind(NULL);
 }
 
 
@@ -993,94 +1211,6 @@ int pthread_detach(pthread_t th)
 }
 
 
-/* ---------------------------------------------------
-   CLEANUP STACKS
-   ------------------------------------------------ */
-
-void _pthread_cleanup_push (struct _pthread_cleanup_buffer *__buffer,
-                            void (*__routine) (void *),
-                            void *__arg)
-{
-   int          res;
-   CleanupEntry cu;
-   ensure_valgrind("_pthread_cleanup_push");
-   cu.fn  = __routine;
-   cu.arg = __arg;
-   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
-                           VG_USERREQ__CLEANUP_PUSH,
-                           &cu, 0, 0, 0);
-   my_assert(res == 0);
-}
-
-
-void _pthread_cleanup_push_defer (struct _pthread_cleanup_buffer *__buffer,
-                                  void (*__routine) (void *),
-                                  void *__arg)
-{
-   /* As _pthread_cleanup_push, but first save the thread's original
-      cancellation type in __buffer and set it to Deferred. */
-   int orig_ctype;
-   ensure_valgrind("_pthread_cleanup_push_defer");
-   /* Set to Deferred, and put the old cancellation type in res. */
-   my_assert(-1 != PTHREAD_CANCEL_DEFERRED);
-   my_assert(-1 != PTHREAD_CANCEL_ASYNCHRONOUS);
-   my_assert(sizeof(struct _pthread_cleanup_buffer) >= sizeof(int));
-   VALGRIND_MAGIC_SEQUENCE(orig_ctype, (-1) /* default */,
-                           VG_USERREQ__SET_CANCELTYPE,
-                           PTHREAD_CANCEL_DEFERRED, 0, 0, 0);   
-   my_assert(orig_ctype != -1);
-   *((int*)(__buffer)) = orig_ctype;
-   /* Now push the cleanup. */
-   _pthread_cleanup_push(NULL, __routine, __arg);
-}
-
-
-void _pthread_cleanup_pop (struct _pthread_cleanup_buffer *__buffer,
-                           int __execute)
-{
-   int          res;
-   CleanupEntry cu;
-   ensure_valgrind("_pthread_cleanup_push");
-   cu.fn = cu.arg = NULL; /* paranoia */
-   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
-                           VG_USERREQ__CLEANUP_POP,
-                           &cu, 0, 0, 0);
-   if (res == 0) {
-      /* pop succeeded */
-     if (__execute) {
-        cu.fn ( cu.arg );
-     }
-     return;
-   }   
-   if (res == -1) {
-      /* stack underflow */
-      return;
-   }
-   barf("_pthread_cleanup_pop");
-}
-
-
-void _pthread_cleanup_pop_restore (struct _pthread_cleanup_buffer *__buffer,
-                                   int __execute)
-{
-   int orig_ctype, fake_ctype;
-   /* As _pthread_cleanup_pop, but after popping/running the handler,
-      restore the thread's original cancellation type from the first
-      word of __buffer. */
-   _pthread_cleanup_pop(NULL, __execute);
-   orig_ctype = *((int*)(__buffer));
-   my_assert(orig_ctype == PTHREAD_CANCEL_DEFERRED
-          || orig_ctype == PTHREAD_CANCEL_ASYNCHRONOUS);
-   my_assert(-1 != PTHREAD_CANCEL_DEFERRED);
-   my_assert(-1 != PTHREAD_CANCEL_ASYNCHRONOUS);
-   my_assert(sizeof(struct _pthread_cleanup_buffer) >= sizeof(int));
-   VALGRIND_MAGIC_SEQUENCE(fake_ctype, (-1) /* default */,
-                           VG_USERREQ__SET_CANCELTYPE,
-                           orig_ctype, 0, 0, 0); 
-   my_assert(fake_ctype == PTHREAD_CANCEL_DEFERRED);
-}
-
-
 /* ---------------------------------------------------
    MUTEX ATTRIBUTES
    ------------------------------------------------ */
@@ -1406,7 +1536,7 @@ int pthread_cancel(pthread_t thread)
    ensure_valgrind("pthread_cancel");
    VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
                            VG_USERREQ__SET_CANCELPEND,
-                           thread, &thread_exit_wrapper, 0, 0);
+                           thread, &pthread_exit, 0, 0);
    my_assert(res != -1);
    return res;
 }
@@ -1835,6 +1965,7 @@ enum __libc_tsd_key_t { _LIBC_TSD_KEY_MALLOC = 0,
 
 typedef
    struct {
+     void               *ret_val;
      int                *errno_ptr;
      int                *h_errno_ptr;
      struct __res_state *res_state_ptr;
@@ -1852,6 +1983,13 @@ static ThreadSpecificState thread_specific_state[VG_N_THREADS];
 static int             global_init_done    = 0;
 static pthread_mutex_t global_init_done_mx = PTHREAD_MUTEX_INITIALIZER;
 
+static
+void cleanup_root(void *arg)
+{
+   thread_exit_wrapper(get_ret_val());
+   /* NOTREACHED */
+}
+
 static void
 init_global_thread_specific_state ( void )
 {
@@ -1882,6 +2020,9 @@ init_global_thread_specific_state ( void )
    /* Signify init done. */
    global_init_done = 1;
 
+   /* Install a cleanup routine to handle the root thread exiting */
+   _pthread_cleanup_push(NULL, cleanup_root, NULL);
+
    /* Unlock and return. */
    res = __pthread_mutex_unlock(&global_init_done_mx);
    if (res != 0) barf("init_global_thread_specific_state: unlock");
@@ -1893,6 +2034,9 @@ init_thread_specific_state ( void )
    int tid = pthread_self();
    int i;
 
+   /* No return value yet */
+   thread_specific_state[tid].ret_val = NULL;
+
    /* Initialise the errno and resolver state pointers. */
    thread_specific_state[tid].errno_ptr = NULL;
    thread_specific_state[tid].h_errno_ptr = NULL;
@@ -1911,6 +2055,26 @@ init_thread_specific_state ( void )
 #  endif
 }
 
+static void
+set_ret_val ( void* ret_val )
+{
+   int tid;
+   VALGRIND_MAGIC_SEQUENCE(tid, 1 /* default */,
+                           VG_USERREQ__PTHREAD_GET_THREADID,
+                           0, 0, 0, 0);
+   thread_specific_state[tid].ret_val = ret_val;
+}
+
+static void *
+get_ret_val ( void )
+{
+   int tid;
+   VALGRIND_MAGIC_SEQUENCE(tid, 1 /* default */,
+                           VG_USERREQ__PTHREAD_GET_THREADID,
+                           0, 0, 0, 0);
+   return thread_specific_state[tid].ret_val;
+}
+
 int* __errno_location ( void )
 {
    int tid;
index 53a967009dc7e6e17e320027d2f1772d3a528c73..2ef96f9b6a00e436b9fc91f34816e0c48f7f7e60 100644 (file)
@@ -1472,9 +1472,23 @@ void do__cleanup_push ( ThreadId tid, CleanupEntry* cu )
    vg_assert(VG_(is_valid_tid)(tid));
    sp = VG_(threads)[tid].custack_used;
    if (VG_(clo_trace_sched)) {
-      VG_(sprintf)(msg_buf, 
-         "cleanup_push (fn %p, arg %p) -> slot %d", 
-         cu->fn, cu->arg, sp);
+      switch (cu->type) {
+         case VgCt_Function:
+            VG_(sprintf)(msg_buf, 
+               "cleanup_push (fn %p, arg %p) -> slot %d", 
+               cu->data.function.fn, cu->data.function.arg, sp);
+            break;
+         case VgCt_Longjmp:
+            VG_(sprintf)(msg_buf,
+               "cleanup_push (ub %p) -> slot %d",
+               cu->data.longjmp.ub, sp);
+            break;
+         default:
+            VG_(sprintf)(msg_buf,
+               "cleanup_push (unknown type) -> slot %d",
+               sp);
+            break;
+      }
       print_sched_event(tid, msg_buf);
    }
    vg_assert(sp >= 0 && sp <= VG_N_CLEANUPSTACK);
@@ -2654,15 +2668,16 @@ void do__get_key_destr_and_spec ( ThreadId tid,
    VG_TRACK( pre_mem_write, Vg_CorePThread, tid, "get_key_destr_and_spec: cu",
                             (Addr)cu, sizeof(CleanupEntry) );
 
-   cu->fn = vg_thread_keys[key].destructor;
+   cu->type = VgCt_Function;
+   cu->data.function.fn = vg_thread_keys[key].destructor;
    if (VG_(threads)[tid].specifics_ptr == NULL) {
-      cu->arg = NULL;
+      cu->data.function.arg = NULL;
    } else {
       VG_TRACK( pre_mem_read, Vg_CorePThread, tid,
                 "get_key_destr_and_spec: key",
                 (Addr)(&VG_(threads)[tid].specifics_ptr[key]), 
                 sizeof(void*) );
-      cu->arg = VG_(threads)[tid].specifics_ptr[key];
+      cu->data.function.arg = VG_(threads)[tid].specifics_ptr[key];
    }
 
    VG_TRACK( post_mem_write, (Addr)cu, sizeof(CleanupEntry) );