]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
adding FreeBSD port made by Doug Rabson, thanks!
authorDirk Mueller <dmuell@gmx.net>
Sat, 3 Jan 2004 14:49:18 +0000 (14:49 +0000)
committerDirk Mueller <dmuell@gmx.net>
Sat, 3 Jan 2004 14:49:18 +0000 (14:49 +0000)
Currently not compiled, since the autoconf magic is still missing.
Will fill bits in when I have time.

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

coregrind/arch/x86-freebsd/vg_libpthread.c [new file with mode: 0644]
coregrind/arch/x86-freebsd/vg_syscall.S [new file with mode: 0644]

diff --git a/coregrind/arch/x86-freebsd/vg_libpthread.c b/coregrind/arch/x86-freebsd/vg_libpthread.c
new file mode 100644 (file)
index 0000000..d23e662
--- /dev/null
@@ -0,0 +1,2790 @@
+
+/*--------------------------------------------------------------------*/
+/*--- A replacement for the standard libpthread.so.                ---*/
+/*---                                              vg_libpthread.c ---*/
+/*--------------------------------------------------------------------*/
+
+/*
+   This file is part of Valgrind, an extensible x86 protected-mode
+   emulator for monitoring program execution on x86-Unixes.
+
+   Copyright (C) 2000-2003 Julian Seward
+      jseward@acm.org
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307, USA.
+
+   The GNU General Public License is contained in the file COPYING.
+*/
+
+/* ALL THIS CODE RUNS ON THE SIMULATED CPU.
+
+   This is a replacement for the standard libpthread.so.  It is loaded
+   as part of the client's image (if required) and directs pthread
+   calls through to Valgrind's request mechanism.
+
+   A couple of caveats.
+
+   1.  Since it's a binary-compatible replacement for an existing library,
+       we must take care to used exactly the same data layouts, etc, as
+       the standard pthread.so does.
+
+   2.  Since this runs as part of the client, there are no specific
+       restrictions on what headers etc we can include, so long as
+       this libpthread.so does not end up having dependencies on .so's
+       which the real one doesn't.
+
+   Later ... it appears we cannot call file-related stuff in libc here,
+   perhaps fair enough.  Be careful what you call from here.  Even exit()
+   doesn't work (gives infinite recursion and then stack overflow); hence
+   myexit().  Also fprintf doesn't seem safe.
+*/
+
+#include "valgrind.h"    /* For the request-passing mechanism */
+#include "vg_include.h"  /* For the VG_USERREQ__* constants */
+
+#define __USE_UNIX98
+#include <sys/types.h>
+#include <pthread.h>
+#undef __USE_UNIX98
+
+#include <unistd.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/poll.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <stdlib.h>
+
+# define strong_alias(name, aliasname) \
+  extern __typeof (name) aliasname __attribute__ ((alias (#name)));
+
+# define weak_alias(name, aliasname) \
+  extern __typeof (name) aliasname __attribute__ ((weak, alias (#name)));
+
+
+/* ---------------------------------------------------------------------
+   Forwardses.
+   ------------------------------------------------------------------ */
+
+#define WEAK   __attribute__((weak))
+
+static
+__inline__
+int is_kerror ( int res )
+{
+   if (res >= -4095 && res <= -1)
+      return 1;
+   else
+      return 0;
+}
+
+
+#ifdef GLIBC_2_3
+   /* kludge by JRS (not from glibc) ... */
+   typedef void* __locale_t;
+
+   /* Copied from locale/locale.h in glibc-2.2.93 sources */
+   /* This value can be passed to `uselocale' and may be returned by
+      it.  Passing this value to any other function has undefined
+      behavior.  */
+#  define LC_GLOBAL_LOCALE       ((__locale_t) -1L)
+   extern __locale_t __uselocale ( __locale_t );
+#endif
+
+static
+void init_libc_tsd_keys ( void );
+
+
+/* ---------------------------------------------------------------------
+   Helpers.  We have to be pretty self-sufficient.
+   ------------------------------------------------------------------ */
+
+/* Number of times any given error message is printed. */
+#define N_MOANS 3
+
+/* Extract from Valgrind the value of VG_(clo_trace_pthread_level).
+   Returns 0 (none) if not running on Valgrind. */
+static
+int get_pt_trace_level ( void )
+{
+   int res;
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__GET_PTHREAD_TRACE_LEVEL,
+                           0, 0, 0, 0);
+   return res;
+}
+
+/* Don't do anything if we're not under Valgrind */
+static __inline__
+void ensure_valgrind ( char* caller )
+{
+   if (!RUNNING_ON_VALGRIND) {
+      const char msg[] = "Warning: this libpthread.so should only be run with Valgrind\n";
+      VG_(do_syscall)(__NR_write, 2, msg, sizeof(msg)-1);
+      VG_(do_syscall)(__NR_exit, 1);
+   }
+}
+
+/* While we're at it ... hook our own startup function into this
+   game. */
+
+static
+__attribute__((noreturn))
+void barf ( const char* str )
+{
+   char buf[1000];
+   strcpy(buf, "\nvalgrind's libpthread.so: ");
+   strcat(buf, str);
+   strcat(buf, "\nPlease report this bug at: ");
+   strcat(buf, VG_BUGS_TO);
+   strcat(buf, "\n\n");
+   VALGRIND_INTERNAL_PRINTF(buf);
+   _exit(1);
+   /* We have to persuade gcc into believing this doesn't return. */
+   while (1) { };
+}
+
+
+static void cat_n_send ( char* s1, char* s2, char* s3 )
+{
+   char  buf[1000];
+   if (get_pt_trace_level() >= 0) {
+      snprintf(buf, sizeof(buf), "%s%s%s", s1, s2, s3);
+      buf[sizeof(buf)-1] = '\0';
+      VALGRIND_INTERNAL_PRINTF(buf);
+   }
+}
+
+static void oh_dear ( char* fn, char* aux, char* s )
+{
+   cat_n_send    ( "warning: Valgrind's ", fn, s );
+   if (NULL != aux)
+      cat_n_send ( "         ", aux, "" );
+   cat_n_send    ( "         your program may misbehave as a result", "", "" );
+}
+
+static void ignored ( char* fn, char* aux )
+{
+   oh_dear ( fn, aux, " does nothing" );
+}
+
+static void kludged ( char* fn, char* aux )
+{
+   oh_dear ( fn, aux, " is incomplete" );
+}
+
+
+__attribute__((noreturn))
+void vgPlain_unimp ( char* fn )
+{
+   cat_n_send ( "valgrind's libpthread.so: UNIMPLEMENTED FUNCTION: ", fn, "" );
+   barf("unimplemented function");
+}
+
+
+static
+void my_assert_fail ( const Char* expr, const Char* file, Int line, const Char* fn )
+{
+   char buf[1000];
+   static Bool entered = False;
+   if (entered)
+      _exit(2);
+   entered = True;
+   sprintf(buf, "\n%s: %s:%d (%s): Assertion `%s' failed.\n",
+                "valgrind", file, line, fn, expr );
+   cat_n_send ( "", buf, "" );
+   sprintf(buf, "Please report this bug at: %s\n\n", VG_BUGS_TO);
+   cat_n_send ( "", buf, "" );
+   _exit(1);
+}
+
+#define MY__STRING(__str)  #__str
+
+#define my_assert(expr)                                               \
+  ((void) ((expr) ? 0 :                                                      \
+          (my_assert_fail  (MY__STRING(expr),                        \
+                             __FILE__, __LINE__,                     \
+                              __PRETTY_FUNCTION__), 0)))
+
+static
+void my_free ( void* ptr )
+{
+#if 0
+   int res;
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__FREE, ptr, 0, 0, 0);
+   my_assert(res == 0);
+#else
+   free(ptr);
+#endif
+}
+
+
+static
+void* my_malloc ( int nbytes )
+{
+   void* res;
+#if 0
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__MALLOC, nbytes, 0, 0, 0);
+#else
+   res = malloc(nbytes);
+#endif
+   my_assert(res != (void*)0);
+   return res;
+}
+
+
+
+/* ---------------------------------------------------------------------
+   Pass pthread_ calls to Valgrind's request mechanism.
+   ------------------------------------------------------------------ */
+
+
+/* ---------------------------------------------------
+   Ummm ..
+   ------------------------------------------------ */
+
+static
+void pthread_error ( const char* msg )
+{
+   int res;
+   VALGRIND_MAGIC_SEQUENCE(res, 0,
+                           VG_USERREQ__PTHREAD_ERROR,
+                           msg, 0, 0, 0);
+}
+
+
+/* ---------------------------------------------------
+   Here so it can be inlined without complaint.
+   ------------------------------------------------ */
+
+__inline__
+pthread_t pthread_self(void)
+{
+   int tid;
+   ensure_valgrind("pthread_self");
+   VALGRIND_MAGIC_SEQUENCE(tid, 0 /* default */,
+                           VG_USERREQ__PTHREAD_GET_THREADID,
+                           0, 0, 0, 0);
+   if (tid < 1 || tid >= VG_N_THREADS)
+      barf("pthread_self: invalid ThreadId");
+   return (pthread_t) tid;
+}
+
+
+/* ---------------------------------------------------
+   THREAD ATTRIBUTES
+   ------------------------------------------------ */
+
+int pthread_attr_init(pthread_attr_t *attrp)
+{
+   struct pthread_attr *attr = my_malloc(sizeof(struct pthread_attr));
+   *attrp = attr;
+   /* Just initialise the fields which we might look at. */
+   attr->__detachstate = PTHREAD_CREATE_JOINABLE;
+   /* Linuxthreads sets this field to the value __getpagesize(), so I
+      guess the following is OK. */
+   attr->__guardsize = VKI_BYTES_PER_PAGE;   return 0;
+}
+
+int pthread_attr_setdetachstate(pthread_attr_t *attrp, int detachstate)
+{
+   struct pthread_attr *attr = *attrp;
+   if (detachstate != PTHREAD_CREATE_JOINABLE
+       && detachstate != PTHREAD_CREATE_DETACHED) {
+      pthread_error("pthread_attr_setdetachstate: "
+                    "detachstate is invalid");
+      return EINVAL;
+   }
+   attr->__detachstate = detachstate;
+   return 0;
+}
+
+int pthread_attr_getdetachstate(const pthread_attr_t *attrp, int *detachstate)
+{
+   struct pthread_attr *attr = *attrp;
+   *detachstate = attr->__detachstate;
+   return 0;
+}
+
+int pthread_attr_setinheritsched(pthread_attr_t *attrp, int inherit)
+{
+   static int moans = N_MOANS;
+   if (moans-- > 0)
+      ignored("pthread_attr_setinheritsched", NULL);
+   return 0;
+}
+
+WEAK
+int pthread_attr_setstacksize (pthread_attr_t *__attrp,
+                               size_t __stacksize)
+{
+   size_t limit;
+   char buf[1024];
+   ensure_valgrind("pthread_attr_setstacksize");
+   limit = VG_PTHREAD_STACK_SIZE - VG_AR_CLIENT_STACKBASE_REDZONE_SZB
+                                 - 1000; /* paranoia */
+   if (__stacksize < limit)
+      return 0;
+   snprintf(buf, sizeof(buf), "pthread_attr_setstacksize: "
+            "requested size %d >= VG_PTHREAD_STACK_SIZE\n   "
+            "edit vg_include.h and rebuild.", __stacksize);
+   buf[sizeof(buf)-1] = '\0'; /* Make sure it is zero terminated */
+   barf(buf);
+}
+
+
+/* This is completely bogus. */
+int  pthread_attr_getschedparam(const  pthread_attr_t  *attrp,
+                                struct sched_param *param)
+{
+   static int moans = N_MOANS;
+   if (moans-- > 0)
+      kludged("pthread_attr_getschedparam", NULL);
+#  ifdef HAVE_SCHED_PRIORITY
+   if (param) param->sched_priority = 0; /* who knows */
+#  else
+   if (param) param->__sched_priority = 0; /* who knows */
+#  endif
+   return 0;
+}
+
+int  pthread_attr_setschedparam(pthread_attr_t  *attrp,
+                                const  struct sched_param *param)
+{
+   static int moans = N_MOANS;
+   if (moans-- > 0)
+      ignored("pthread_attr_setschedparam", "(scheduling not changeable)");
+   return 0;
+}
+
+int pthread_attr_destroy(pthread_attr_t *attrp)
+{
+   static int moans = N_MOANS;
+   if (moans-- > 0)
+      ignored("pthread_attr_destroy", NULL);
+   if (*attrp) {
+      my_free(*attrp);
+      *attrp = 0;
+   }
+   return 0;
+}
+
+/* These are no-ops, as with LinuxThreads. */
+int pthread_attr_setscope ( pthread_attr_t *attrp, int scope )
+{
+   ensure_valgrind("pthread_attr_setscope");
+   if (scope == PTHREAD_SCOPE_SYSTEM)
+      return 0;
+   pthread_error("pthread_attr_setscope: "
+                 "invalid or unsupported scope");
+   if (scope == PTHREAD_SCOPE_PROCESS)
+      return ENOTSUP;
+   return EINVAL;
+}
+
+int pthread_attr_getscope ( const pthread_attr_t *attrp, int *scope )
+{
+   ensure_valgrind("pthread_attr_setscope");
+   if (scope)
+      *scope = PTHREAD_SCOPE_SYSTEM;
+   return 0;
+}
+
+/* Bogus ... */
+WEAK
+int pthread_attr_getstackaddr ( const pthread_attr_t * attr,
+                                void ** stackaddr )
+{
+   ensure_valgrind("pthread_attr_getstackaddr");
+   kludged("pthread_attr_getstackaddr", "(it always sets stackaddr to zero)");
+   if (stackaddr)
+      *stackaddr = NULL;
+   return 0;
+}
+
+/* Not bogus (!) */
+WEAK
+int pthread_attr_getstacksize ( const pthread_attr_t * _attr,
+                                size_t * __stacksize )
+{
+   size_t limit;
+   ensure_valgrind("pthread_attr_getstacksize");
+   limit = VG_PTHREAD_STACK_SIZE - VG_AR_CLIENT_STACKBASE_REDZONE_SZB
+                                 - 1000; /* paranoia */
+   if (__stacksize)
+      *__stacksize = limit;
+   return 0;
+}
+
+int pthread_attr_setschedpolicy(pthread_attr_t *attrp, int policy)
+{
+  struct pthread_attr *attr = *attrp;
+  if (policy != SCHED_OTHER && policy != SCHED_FIFO && policy != SCHED_RR)
+    return EINVAL;
+  attr->__schedpolicy = policy;
+  return 0;
+}
+
+int pthread_attr_getschedpolicy(const pthread_attr_t *attrp, int *policy)
+{
+  struct pthread_attr *attr = *attrp;
+  *policy = attr->__schedpolicy;
+  return 0;
+}
+
+
+/* This is completely bogus.  We reject all attempts to change it from
+   VKI_BYTES_PER_PAGE.  I don't have a clue what it's for so it seems
+   safest to be paranoid. */
+WEAK
+int pthread_attr_setguardsize(pthread_attr_t *attrp, size_t guardsize)
+{
+   static int moans = N_MOANS;
+
+   if (guardsize == VKI_BYTES_PER_PAGE)
+      return 0;
+
+   if (moans-- > 0)
+       ignored("pthread_attr_setguardsize",
+               "(it ignores any guardsize != 4096)");
+
+   return 0;
+}
+
+/* A straight copy of the LinuxThreads code. */
+WEAK
+int pthread_attr_getguardsize(const pthread_attr_t *attrp, size_t *guardsize)
+{
+   struct pthread_attr *attr = *attrp;
+   *guardsize = attr->__guardsize;
+   return 0;
+}
+
+/* Again, like LinuxThreads. */
+
+static int concurrency_current_level = 0;
+
+WEAK
+int pthread_setconcurrency(int new_level)
+{
+   if (new_level < 0)
+      return EINVAL;
+   else {
+      concurrency_current_level = new_level;
+      return 0;
+   }
+}
+
+WEAK
+int pthread_getconcurrency(void)
+{
+   return concurrency_current_level;
+}
+
+
+
+/* ---------------------------------------------------
+   Helper functions for running a thread
+   and for clearing up afterwards.
+   ------------------------------------------------ */
+
+/* All exiting threads eventually pass through here, bearing the
+   return value, or PTHREAD_CANCELED, in ret_val. */
+static
+__attribute__((noreturn))
+void thread_exit_wrapper ( void* ret_val )
+{
+   int           detached, res;
+   CleanupEntry  cu;
+   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++) {
+      VALGRIND_MAGIC_SEQUENCE(res, (-2) /* default */,
+                              VG_USERREQ__GET_KEY_D_AND_S,
+                              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 */ );
+         continue;
+      }
+      my_assert(res == -1);
+   }
+
+   /* Free up my specifics space, if any. */
+   VALGRIND_MAGIC_SEQUENCE(specifics_ptr, 3 /* default */,
+                           VG_USERREQ__PTHREAD_GETSPECIFIC_PTR,
+                           pthread_self(), 0, 0, 0);
+   my_assert(specifics_ptr != (void**)3);
+   my_assert(specifics_ptr != (void**)1); /* 1 means invalid thread */
+   if (specifics_ptr != NULL)
+      my_free(specifics_ptr);
+
+   /* Decide on my final disposition. */
+   VALGRIND_MAGIC_SEQUENCE(detached, (-1) /* default */,
+                           VG_USERREQ__SET_OR_GET_DETACH,
+                           2 /* get */, pthread_self(), 0, 0);
+   my_assert(detached == 0 || detached == 1);
+
+   if (detached) {
+      /* Detached; I just quit right now. */
+      VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                              VG_USERREQ__QUIT, 0, 0, 0, 0);
+   } else {
+      /* Not detached; so I wait for a joiner. */
+      VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                              VG_USERREQ__WAIT_JOINER, ret_val, 0, 0, 0);
+   }
+   /* NOTREACHED */
+   barf("thread_exit_wrapper: still alive?!");
+}
+
+
+/* This function is a wrapper function for running a thread.  It runs
+   the root function specified in pthread_create, and then, should the
+   root function return a value, it arranges to run the thread's
+   cleanup handlers and exit correctly. */
+
+/* Struct used to convey info from pthread_create to thread_wrapper.
+   Must be careful not to pass to the child thread any pointers to
+   objects which might be on the parent's stack.  */
+typedef
+   struct {
+      int   attr__detachstate;
+      void* (*root_fn) ( void* );
+      void* arg;
+   }
+   NewThreadInfo;
+
+
+/* This is passed to the VG_USERREQ__APPLY_IN_NEW_THREAD and so must
+   not return.  Note that this runs in the new thread, not the
+   parent. */
+static
+__attribute__((noreturn))
+void thread_wrapper ( NewThreadInfo* info )
+{
+   int   attr__detachstate;
+   void* (*root_fn) ( void* );
+   void* arg;
+   void* ret_val;
+
+   attr__detachstate = info->attr__detachstate;
+   root_fn           = info->root_fn;
+   arg               = info->arg;
+
+   /* Free up the arg block that pthread_create malloced. */
+   my_free(info);
+
+   /* Minimally observe the attributes supplied. */
+   if (attr__detachstate != PTHREAD_CREATE_DETACHED
+       && attr__detachstate != PTHREAD_CREATE_JOINABLE)
+      pthread_error("thread_wrapper: invalid attr->__detachstate");
+   if (attr__detachstate == PTHREAD_CREATE_DETACHED)
+      pthread_detach(pthread_self());
+
+#  ifdef GLIBC_2_3
+   /* Set this thread's locale to the global (default) locale.  A hack
+      in support of glibc-2.3.  This does the biz for the all new
+      threads; the root thread is done with a horrible hack in
+      init_libc_tsd_keys() below.
+   */
+   __uselocale(LC_GLOBAL_LOCALE);
+#  endif
+
+   /* 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);
+   thread_exit_wrapper(ret_val);
+   /* NOTREACHED */
+}
+
+
+/* ---------------------------------------------------
+   THREADs
+   ------------------------------------------------ */
+
+static void __valgrind_pthread_yield ( void )
+{
+   int res;
+   ensure_valgrind("pthread_yield");
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__PTHREAD_YIELD, 0, 0, 0, 0);
+}
+
+WEAK
+void pthread_yield ( void )
+{
+   __valgrind_pthread_yield();
+}
+
+
+int pthread_equal(pthread_t thread1, pthread_t thread2)
+{
+   return thread1 == thread2 ? 1 : 0;
+}
+
+
+/* Bundle up the args into a malloc'd block and create a new thread
+   consisting of thread_wrapper() applied to said malloc'd block. */
+int
+pthread_create (pthread_t *__restrict __thredd,
+                __const pthread_attr_t *__restrict __attr,
+                void *(*__start_routine) (void *),
+                void *__restrict __arg)
+{
+   int            tid_child;
+   NewThreadInfo* info;
+
+   ensure_valgrind("pthread_create");
+
+   /* make sure the tsd keys, and hence locale info, are initialised
+      before we get into complications making new threads. */
+   init_libc_tsd_keys();
+
+   /* Allocate space for the arg block.  thread_wrapper will free
+      it. */
+   info = my_malloc(sizeof(NewThreadInfo));
+   my_assert(info != NULL);
+
+   if (__attr)
+      info->attr__detachstate = (*__attr)->__detachstate;
+   else
+      info->attr__detachstate = PTHREAD_CREATE_JOINABLE;
+
+   info->root_fn = __start_routine;
+   info->arg     = __arg;
+   VALGRIND_MAGIC_SEQUENCE(tid_child, VG_INVALID_THREADID /* default */,
+                           VG_USERREQ__APPLY_IN_NEW_THREAD,
+                           &thread_wrapper, info, 0, 0);
+   my_assert(tid_child != VG_INVALID_THREADID);
+
+   if (__thredd)
+      *__thredd = (pthread_t) tid_child;
+   return 0; /* success */
+}
+
+
+int
+pthread_join (pthread_t __th, void **__thread_return)
+{
+   int res;
+   ensure_valgrind("pthread_join");
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__PTHREAD_JOIN,
+                           __th, __thread_return, 0, 0);
+   return res;
+}
+
+
+void pthread_exit(void *retval)
+{
+   ensure_valgrind("pthread_exit");
+   /* Simple! */
+   thread_exit_wrapper(retval);
+}
+
+
+int pthread_detach(pthread_t th)
+{
+   int res;
+   ensure_valgrind("pthread_detach");
+   /* First we enquire as to the current detach state. */
+   VALGRIND_MAGIC_SEQUENCE(res, (-2) /* default */,
+                           VG_USERREQ__SET_OR_GET_DETACH,
+                           2 /* get */, th, 0, 0);
+   if (res == -1) {
+      /* not found */
+      pthread_error("pthread_detach: "
+                    "invalid target thread");
+      return ESRCH;
+   }
+   if (res == 1) {
+      /* already detached */
+      pthread_error("pthread_detach: "
+                    "target thread is already detached");
+      return EINVAL;
+   }
+   if (res == 0) {
+      VALGRIND_MAGIC_SEQUENCE(res, (-2) /* default */,
+                              VG_USERREQ__SET_OR_GET_DETACH,
+                              1 /* set */, th, 0, 0);
+      my_assert(res == 0);
+      return 0;
+   }
+   barf("pthread_detach");
+}
+
+
+/* ---------------------------------------------------
+   CLEANUP STACKS
+   ------------------------------------------------ */
+
+void pthread_cleanup_push (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_pop (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");
+}
+
+
+
+/* ---------------------------------------------------
+   MUTEX ATTRIBUTES
+   ------------------------------------------------ */
+
+int __pthread_mutexattr_init(pthread_mutexattr_t *attrp)
+{
+   struct pthread_mutex_attr *attr = my_malloc(sizeof(struct pthread_mutex_attr));
+   *attrp = attr;
+   attr->__mutexkind = PTHREAD_MUTEX_ERRORCHECK;
+   return 0;
+}
+
+int __pthread_mutexattr_settype(pthread_mutexattr_t *attrp, int type)
+{
+   struct pthread_mutex_attr *attr = *attrp;
+   switch (type) {
+      case PTHREAD_MUTEX_RECURSIVE:
+      case PTHREAD_MUTEX_ERRORCHECK:
+      case PTHREAD_MUTEX_NORMAL:
+         attr->__mutexkind = type;
+         return 0;
+      default:
+         pthread_error("pthread_mutexattr_settype: "
+                       "invalid type");
+         return EINVAL;
+   }
+}
+
+int __pthread_mutexattr_destroy(pthread_mutexattr_t *attrp)
+{
+   if (*attrp) {
+      my_free(*attrp);
+      *attrp = 0;
+   }
+   return 0;
+}
+
+int __pthread_mutexattr_setpshared ( pthread_mutexattr_t* attr, int pshared)
+{
+  if (pshared != PTHREAD_PROCESS_PRIVATE && pshared != PTHREAD_PROCESS_SHARED)
+    return EINVAL;
+
+  /* For now it is not possible to shared a conditional variable.  */
+  if (pshared != PTHREAD_PROCESS_PRIVATE)
+    return ENOSYS;
+
+  return 0;
+}
+
+
+/* ---------------------------------------------------
+   MUTEXes
+   ------------------------------------------------ */
+
+int __pthread_mutex_init(pthread_mutex_t *mutexp,
+                         const  pthread_mutexattr_t *mutexattrp)
+{
+   struct pthread_mutex *mutex = my_malloc(sizeof(struct pthread_mutex));
+   *mutexp = mutex;
+   mutex->__m_count = 0;
+   mutex->__m_owner = VG_INVALID_THREADID;
+   mutex->__m_kind  = PTHREAD_MUTEX_ERRORCHECK;
+   if (mutexattrp)
+      mutex->__m_kind = (*mutexattrp)->__mutexkind;
+   return 0;
+}
+
+
+int __pthread_mutex_lock(pthread_mutex_t *mutexp)
+{
+   struct pthread_mutex *mutex = *mutexp;
+   int res;
+
+   if (*mutexp == PTHREAD_MUTEX_INITIALIZER) {
+      __pthread_mutex_init(mutexp, 0);
+      mutex = *mutexp;
+   }
+
+   if (RUNNING_ON_VALGRIND) {
+      VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                              VG_USERREQ__PTHREAD_MUTEX_LOCK,
+                              mutexp, 0, 0, 0);
+      return res;
+   } else {
+      /* Play at locking */
+      if (0)
+        kludged("prehistoric lock", NULL);
+      mutex->__m_owner = 1;
+      mutex->__m_count = 1;
+      mutex->__m_kind |= VG_PTHREAD_PREHISTORY;
+      return 0; /* success */
+   }
+}
+
+
+int __pthread_mutex_trylock(pthread_mutex_t *mutexp)
+{
+   struct pthread_mutex *mutex = *mutexp;
+   int res;
+
+   if (RUNNING_ON_VALGRIND) {
+      VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                              VG_USERREQ__PTHREAD_MUTEX_TRYLOCK,
+                              mutexp, 0, 0, 0);
+      return res;
+   } else {
+      /* Play at locking */
+      if (0)
+        kludged("prehistoric trylock", NULL);
+      mutex->__m_owner = 1;
+      mutex->__m_count = 1;
+      mutex->__m_kind |= VG_PTHREAD_PREHISTORY;
+      return 0; /* success */
+   }
+}
+
+
+int __pthread_mutex_unlock(pthread_mutex_t *mutexp)
+{
+   struct pthread_mutex *mutex = *mutexp;
+   int res;
+
+   if (RUNNING_ON_VALGRIND) {
+      VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                              VG_USERREQ__PTHREAD_MUTEX_UNLOCK,
+                              mutexp, 0, 0, 0);
+      return res;
+   } else {
+      /* Play at locking */
+      if (0)
+        kludged("prehistoric unlock", NULL);
+      mutex->__m_owner = 0;
+      mutex->__m_count = 0;
+      mutex->__m_kind &= ~VG_PTHREAD_PREHISTORY;
+      return 0; /* success */
+   }
+}
+
+
+int __pthread_mutex_destroy(pthread_mutex_t *mutexp)
+{
+   struct pthread_mutex *mutex = *mutexp;
+
+   /* Valgrind doesn't hold any resources on behalf of the mutex, so no
+      need to involve it. */
+   if (mutex->__m_count > 0) {
+      /* Oh, the horror.  glibc's internal use of pthreads "knows"
+        that destroying a lock does an implicit unlock.  Make it
+        explicit. */
+      __pthread_mutex_unlock(mutexp);
+      pthread_error("pthread_mutex_destroy: "
+                   "mutex is still in use");
+      return EBUSY;
+   }
+   mutex->__m_count = 0;
+   mutex->__m_owner = VG_INVALID_THREADID;
+   mutex->__m_kind  = PTHREAD_MUTEX_ERRORCHECK;
+   my_free(mutex);
+   *mutexp = 0;
+   return 0;
+}
+
+
+/* ---------------------------------------------------
+   CONDITION VARIABLES
+   ------------------------------------------------ */
+
+/* LinuxThreads supports no attributes for conditions.  Hence ... */
+
+int pthread_condattr_init(pthread_condattr_t *attrp)
+{
+   return 0;
+}
+
+int pthread_condattr_destroy(pthread_condattr_t *attrp)
+{
+   return 0;
+}
+
+int pthread_cond_init( pthread_cond_t *condp,
+                       const pthread_condattr_t *cond_attr)
+{
+   struct pthread_cond *cond = my_malloc(sizeof(struct pthread_cond));
+   *condp = cond;
+   cond->__c_waiting = VG_INVALID_THREADID;
+   return 0;
+}
+
+int pthread_cond_destroy(pthread_cond_t *condp)
+{
+   /* should check that no threads are waiting on this CV */
+   static int moans = N_MOANS;
+   if (moans-- > 0)
+      kludged("pthread_cond_destroy",
+              "(it doesn't check if the cond is waited on)" );
+   my_free(*condp);
+   *condp = 0;
+   return 0;
+}
+
+/* ---------------------------------------------------
+   SCHEDULING
+   ------------------------------------------------ */
+
+/* This is completely bogus. */
+int   pthread_getschedparam(pthread_t  target_thread,
+                            int  *policy,
+                            struct sched_param *param)
+{
+   static int moans = N_MOANS;
+   if (moans-- > 0)
+      kludged("pthread_getschedparam", NULL);
+   if (policy) *policy = SCHED_OTHER;
+#  ifdef HAVE_SCHED_PRIORITY
+   if (param) param->sched_priority = 0; /* who knows */
+#  else
+   if (param) param->__sched_priority = 0; /* who knows */
+#  endif
+   return 0;
+}
+
+int pthread_setschedparam(pthread_t target_thread,
+                          int policy,
+                          const struct sched_param *param)
+{
+   static int moans = N_MOANS;
+   if (moans-- > 0)
+      ignored("pthread_setschedparam", "(scheduling not changeable)");
+   return 0;
+}
+
+int pthread_cond_wait(pthread_cond_t *condp, pthread_mutex_t *mutexp)
+{
+   int res;
+   ensure_valgrind("pthread_cond_wait");
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__PTHREAD_COND_WAIT,
+                          condp, mutexp, 0, 0);
+   return res;
+}
+
+int pthread_cond_timedwait ( pthread_cond_t *condp,
+                             pthread_mutex_t *mutexp,
+                             const struct  timespec *abstime )
+{
+   int res;
+   unsigned int ms_now, ms_end;
+   struct  timeval timeval_now;
+   unsigned long long int ull_ms_now_after_1970;
+   unsigned long long int ull_ms_end_after_1970;
+
+   ensure_valgrind("pthread_cond_timedwait");
+   VALGRIND_MAGIC_SEQUENCE(ms_now, 0xFFFFFFFF /* default */,
+                           VG_USERREQ__READ_MILLISECOND_TIMER,
+                           0, 0, 0, 0);
+   my_assert(ms_now != 0xFFFFFFFF);
+   res = gettimeofday(&timeval_now, NULL);
+   my_assert(res == 0);
+
+   ull_ms_now_after_1970
+      = 1000ULL * ((unsigned long long int)(timeval_now.tv_sec))
+        + ((unsigned long long int)(timeval_now.tv_usec / 1000000));
+   ull_ms_end_after_1970
+      = 1000ULL * ((unsigned long long int)(abstime->tv_sec))
+        + ((unsigned long long int)(abstime->tv_nsec / 1000000));
+   if (ull_ms_end_after_1970 < ull_ms_now_after_1970)
+      ull_ms_end_after_1970 = ull_ms_now_after_1970;
+   ms_end
+      = ms_now + (unsigned int)(ull_ms_end_after_1970 - ull_ms_now_after_1970);
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__PTHREAD_COND_TIMEDWAIT,
+                          condp, mutexp, ms_end, 0);
+   return res;
+}
+
+
+int pthread_cond_signal(pthread_cond_t *cond)
+{
+   int res;
+   ensure_valgrind("pthread_cond_signal");
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__PTHREAD_COND_SIGNAL,
+                          cond, 0, 0, 0);
+   return res;
+}
+
+int pthread_cond_broadcast(pthread_cond_t *cond)
+{
+   int res;
+   ensure_valgrind("pthread_cond_broadcast");
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__PTHREAD_COND_BROADCAST,
+                          cond, 0, 0, 0);
+   return res;
+}
+
+
+/* ---------------------------------------------------
+   CANCELLATION
+   ------------------------------------------------ */
+
+int pthread_setcancelstate(int state, int *oldstate)
+{
+   int res;
+   ensure_valgrind("pthread_setcancelstate");
+   if (state != PTHREAD_CANCEL_ENABLE
+       && state != PTHREAD_CANCEL_DISABLE) {
+      pthread_error("pthread_setcancelstate: "
+                    "invalid state");
+      return EINVAL;
+   }
+   my_assert(-1 != PTHREAD_CANCEL_ENABLE);
+   my_assert(-1 != PTHREAD_CANCEL_DISABLE);
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__SET_CANCELSTATE,
+                           state, 0, 0, 0);
+   my_assert(res != -1);
+   if (oldstate)
+      *oldstate = res;
+   return 0;
+}
+
+int pthread_setcanceltype(int type, int *oldtype)
+{
+   int res;
+   ensure_valgrind("pthread_setcanceltype");
+   if (type != PTHREAD_CANCEL_DEFERRED
+       && type != PTHREAD_CANCEL_ASYNCHRONOUS) {
+      pthread_error("pthread_setcanceltype: "
+                    "invalid type");
+      return EINVAL;
+   }
+   my_assert(-1 != PTHREAD_CANCEL_DEFERRED);
+   my_assert(-1 != PTHREAD_CANCEL_ASYNCHRONOUS);
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__SET_CANCELTYPE,
+                           type, 0, 0, 0);
+   my_assert(res != -1);
+   if (oldtype)
+      *oldtype = res;
+   return 0;
+}
+
+int pthread_cancel(pthread_t thread)
+{
+   int res;
+   ensure_valgrind("pthread_cancel");
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__SET_CANCELPEND,
+                           thread, &thread_exit_wrapper, 0, 0);
+   my_assert(res != -1);
+   return res;
+}
+
+static
+void __my_pthread_testcancel(void)
+{
+   int res;
+   ensure_valgrind("__my_pthread_testcancel");
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__TESTCANCEL,
+                           0, 0, 0, 0);
+   my_assert(res == 0);
+}
+
+void pthread_testcancel ( void )
+{
+   __my_pthread_testcancel();
+}
+
+
+/* Not really sure what this is for.  I suspect for doing the POSIX
+   requirements for fork() and exec().  We do this internally anyway
+   whenever those syscalls are observed, so this could be superfluous,
+   but hey ...
+*/
+void __pthread_kill_other_threads_np ( void )
+{
+   int res;
+   ensure_valgrind("__pthread_kill_other_threads_np");
+   VALGRIND_MAGIC_SEQUENCE(res, (-1) /* default */,
+                           VG_USERREQ__NUKE_OTHER_THREADS,
+                           0, 0, 0, 0);
+   my_assert(res == 0);
+}
+
+
+/* ---------------------------------------------------
+   SIGNALS
+   ------------------------------------------------ */
+
+#include <signal.h>
+
+int pthread_sigmask(int how, const sigset_t *newmask,
+                             sigset_t *oldmask)
+{
+   int res;
+
+   /* A bit subtle, because the scheduler expects newmask and oldmask
+      to be vki_sigset_t* rather than sigset_t*, and the two are
+      different.  Fortunately the first 64 bits of a sigset_t are
+      exactly a vki_sigset_t, so we just pass the pointers through
+      unmodified.  Haaaack!
+
+      Also mash the how value so that the SIG_ constants from glibc
+      constants to VKI_ constants, so that the former do not have to
+      be included into vg_scheduler.c. */
+
+   ensure_valgrind("pthread_sigmask");
+
+   switch (how) {
+      case SIG_SETMASK: how = VKI_SIG_SETMASK; break;
+      case SIG_BLOCK:   how = VKI_SIG_BLOCK; break;
+      case SIG_UNBLOCK: how = VKI_SIG_UNBLOCK; break;
+      default: pthread_error("pthread_sigmask: invalid how");
+               return EINVAL;
+   }
+
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__PTHREAD_SIGMASK,
+                           how, newmask, oldmask, 0);
+
+   /* The scheduler tells us of any memory violations. */
+   return res == 0 ? 0 : EFAULT;
+}
+
+int sigwait ( const sigset_t* set, int* sig )
+{
+   int res;
+   siginfo_t si;
+
+   __my_pthread_testcancel();
+
+   si.si_signo = 0;
+   res = sigtimedwait(set, &si, NULL);
+   *sig = si.si_signo;
+
+   return 0;                   /* always returns 0 */
+}
+
+
+int pthread_kill(pthread_t thread, int signo)
+{
+   int res;
+   ensure_valgrind("pthread_kill");
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__PTHREAD_KILL,
+                           thread, signo, 0, 0);
+   return res;
+}
+
+
+/* Copied verbatim from Linuxthreads */
+/* Redefine raise() to send signal to calling thread only,
+   as per POSIX 1003.1c */
+int raise (int sig)
+{
+  int retcode = pthread_kill(pthread_self(), sig);
+  if (retcode == 0) {
+    return 0;
+  } else {
+    *(__error()) = retcode;
+    return -1;
+  }
+}
+
+
+
+/* ---------------------------------------------------
+   THREAD-SPECIFICs
+   ------------------------------------------------ */
+
+static
+int key_is_valid (pthread_key_t key)
+{
+   int res;
+   VALGRIND_MAGIC_SEQUENCE(res, 2 /* default */,
+                           VG_USERREQ__PTHREAD_KEY_VALIDATE,
+                           key, 0, 0, 0);
+   my_assert(res != 2);
+   return res;
+}
+
+
+/* Returns NULL if thread is invalid.  Otherwise, if the thread
+   already has a specifics area, return that.  Otherwise allocate it
+   one. */
+static
+void** get_or_allocate_specifics_ptr ( pthread_t thread )
+{
+   int    res, i;
+   void** specifics_ptr;
+   ensure_valgrind("get_or_allocate_specifics_ptr");
+
+   /* Returns zero if the thread has no specific_ptr.  One if thread
+      is invalid.  Otherwise, the specific_ptr value.  This is
+      allocated with my_malloc and so is aligned and cannot be
+      confused with 1 or 3. */
+   VALGRIND_MAGIC_SEQUENCE(specifics_ptr, 3 /* default */,
+                           VG_USERREQ__PTHREAD_GETSPECIFIC_PTR,
+                           thread, 0, 0, 0);
+   my_assert(specifics_ptr != (void**)3);
+
+   if (specifics_ptr == (void**)1)
+      return NULL; /* invalid thread */
+
+   if (specifics_ptr != NULL)
+      return specifics_ptr; /* already has a specifics ptr. */
+
+   /* None yet ... allocate a new one.  Should never fail. */
+   specifics_ptr = my_malloc( VG_N_THREAD_KEYS * sizeof(void*) );
+   my_assert(specifics_ptr != NULL);
+
+   VALGRIND_MAGIC_SEQUENCE(res, -1 /* default */,
+                           VG_USERREQ__PTHREAD_SETSPECIFIC_PTR,
+                           specifics_ptr, 0, 0, 0);
+   my_assert(res == 0);
+
+   /* POSIX sez: "Upon thread creation, the value NULL shall be
+      associated with all defined keys in the new thread."  This
+      allocation is in effect a delayed allocation of the specific
+      data for a thread, at its first-use.  Hence we initialise it
+      here. */
+   for (i = 0; i < VG_N_THREAD_KEYS; i++) {
+      specifics_ptr[i] = NULL;
+   }
+
+   return specifics_ptr;
+}
+
+
+int __pthread_key_create(pthread_key_t *key,
+                         void  (*destr_function)  (void *))
+{
+   void** specifics_ptr;
+   int    res, i;
+   ensure_valgrind("pthread_key_create");
+
+   /* This writes *key if successful.  It should never fail. */
+   VALGRIND_MAGIC_SEQUENCE(res, 1 /* default */,
+                           VG_USERREQ__PTHREAD_KEY_CREATE,
+                           key, destr_function, 0, 0);
+
+   if (res == 0) {
+      /* POSIX sez: "Upon key creation, the value NULL shall be
+        associated with the new key in all active threads." */
+      for (i = 0; i < VG_N_THREADS; i++) {
+        specifics_ptr = get_or_allocate_specifics_ptr((pthread_t)i);
+        /* we get NULL if i is an invalid thread. */
+        if (specifics_ptr != NULL)
+           specifics_ptr[*key] = NULL;
+      }
+   }
+
+   return res;
+}
+
+int pthread_key_delete(pthread_key_t key)
+{
+   int res;
+   ensure_valgrind("pthread_key_delete");
+   if (!key_is_valid(key))
+      return EINVAL;
+   VALGRIND_MAGIC_SEQUENCE(res, 0 /* default */,
+                           VG_USERREQ__PTHREAD_KEY_DELETE,
+                           key, 0, 0, 0);
+   my_assert(res == 0);
+   return 0;
+}
+
+int __pthread_setspecific(pthread_key_t key, const void *pointer)
+{
+   void** specifics_ptr;
+   ensure_valgrind("pthread_setspecific");
+
+   if (!key_is_valid(key))
+      return EINVAL;
+
+   specifics_ptr = get_or_allocate_specifics_ptr(pthread_self());
+   specifics_ptr[key] = (void*)pointer;
+   return 0;
+}
+
+void * __pthread_getspecific(pthread_key_t key)
+{
+   void** specifics_ptr;
+   ensure_valgrind("pthread_getspecific");
+
+   if (!key_is_valid(key))
+      return NULL;
+
+   specifics_ptr = get_or_allocate_specifics_ptr(pthread_self());
+   return specifics_ptr[key];
+}
+
+
+#ifdef GLIBC_2_3
+static
+void ** __pthread_getspecific_addr(pthread_key_t key)
+{
+   void** specifics_ptr;
+   ensure_valgrind("pthread_getspecific_addr");
+
+   if (!key_is_valid(key))
+      return NULL;
+
+   specifics_ptr = get_or_allocate_specifics_ptr(pthread_self());
+   return &(specifics_ptr[key]);
+}
+#endif
+
+
+/* ---------------------------------------------------
+   ONCEry
+   ------------------------------------------------ */
+
+/* This protects reads and writes of the once_control variable
+   supplied.  It is never held whilst any particular initialiser is
+   running. */
+static pthread_mutex_t once_masterlock = PTHREAD_MUTEX_INITIALIZER;
+
+int __pthread_once ( pthread_once_t *once_control,
+                     void (*init_routine) (void) )
+{
+   int res;
+   int done;
+
+#  define TAKE_LOCK                                   \
+      res = __pthread_mutex_lock(&once_masterlock);   \
+      my_assert(res == 0);
+
+#  define RELEASE_LOCK                                \
+      res = __pthread_mutex_unlock(&once_masterlock); \
+      my_assert(res == 0);
+
+   void cleanup(void *v) {
+      TAKE_LOCK;
+      once_control->state = PTHREAD_NEEDS_INIT;
+      RELEASE_LOCK;
+   }
+
+   ensure_valgrind("pthread_once");
+
+   /* Grab the lock transiently, so we can safely see what state this
+      once_control is in. */
+
+   TAKE_LOCK;
+
+   switch (once_control->state) {
+
+      case PTHREAD_NEEDS_INIT:
+        /* Not started.  Change state to indicate running, drop the
+           lock and run.  */
+         once_control->state = 2; /* XXX */
+        pthread_cleanup_push(cleanup, NULL);
+        RELEASE_LOCK;
+         init_routine();
+         /* re-take the lock, and set state to indicate done. */
+        TAKE_LOCK;
+        pthread_cleanup_pop(False);
+         once_control->state = PTHREAD_DONE_INIT;
+        RELEASE_LOCK;
+        break;
+
+      case 2:
+        /* This is the tricky case.  The initialiser is running in
+            some other thread, but we have to delay this thread till
+            the other one completes.  So we sort-of busy wait.  In
+            fact it makes sense to yield now, because what we want to
+            happen is for the thread running the initialiser to
+            complete ASAP. */
+        RELEASE_LOCK;
+         done = 0;
+         while (1) {
+            /* Let others run for a while. */
+           __valgrind_pthread_yield();
+           /* Grab the lock and see if we're done waiting. */
+           TAKE_LOCK;
+            if (once_control->state == PTHREAD_DONE_INIT)
+               done = 1;
+           RELEASE_LOCK;
+           if (done)
+               break;
+        }
+        break;
+
+      case PTHREAD_DONE_INIT:
+      default:
+        /* Easy.  It's already done.  Just drop the lock. */
+         RELEASE_LOCK;
+        break;
+   }
+
+   return 0;
+
+#  undef TAKE_LOCK
+#  undef RELEASE_LOCK
+}
+
+#undef P_ONCE_NOT_DONE
+#undef P_ONCE_RUNNING
+#undef P_ONCE_COMPLETED
+
+
+/* ---------------------------------------------------
+   MISC
+   ------------------------------------------------ */
+
+static pthread_mutex_t pthread_atfork_lock
+   = PTHREAD_MUTEX_INITIALIZER;
+
+int __pthread_atfork ( void (*prepare)(void),
+                       void (*parent)(void),
+                       void (*child)(void) )
+{
+   int n, res;
+   ForkHandlerEntry entry;
+
+   ensure_valgrind("pthread_atfork");
+   __pthread_mutex_lock(&pthread_atfork_lock);
+
+   /* Fetch old counter */
+   VALGRIND_MAGIC_SEQUENCE(n, -2 /* default */,
+                           VG_USERREQ__GET_FHSTACK_USED,
+                           0, 0, 0, 0);
+   my_assert(n >= 0 && n < VG_N_FORKHANDLERSTACK);
+   if (n == VG_N_FORKHANDLERSTACK-1)
+      barf("pthread_atfork: VG_N_FORKHANDLERSTACK is too low; "
+           "increase and recompile");
+
+   /* Add entry */
+   entry.prepare = *prepare;
+   entry.parent  = *parent;
+   entry.child   = *child;
+   VALGRIND_MAGIC_SEQUENCE(res, -2 /* default */,
+                           VG_USERREQ__SET_FHSTACK_ENTRY,
+                           n, &entry, 0, 0);
+   my_assert(res == 0);
+
+   /* Bump counter */
+   VALGRIND_MAGIC_SEQUENCE(res, -2 /* default */,
+                           VG_USERREQ__SET_FHSTACK_USED,
+                           n+1, 0, 0, 0);
+   my_assert(res == 0);
+
+   __pthread_mutex_unlock(&pthread_atfork_lock);
+   return 0;
+}
+
+
+#ifdef GLIBC_2_3
+/* This seems to be a hook which appeared in glibc-2.3.2. */
+int __register_atfork ( void (*prepare)(void),
+                        void (*parent)(void),
+                        void (*child)(void) )
+{
+   return __pthread_atfork(prepare,parent,child);
+}
+#endif
+
+WEAK
+void __pthread_initialize ( void )
+{
+   ensure_valgrind("__pthread_initialize");
+}
+
+
+/* ---------------------------------------------------
+   LIBRARY-PRIVATE THREAD SPECIFIC STATE
+   ------------------------------------------------ */
+
+#include <netinet/in.h>
+#include <resolv.h>
+static int thread_specific_errno[VG_N_THREADS];
+static int thread_specific_h_errno[VG_N_THREADS];
+static struct __res_state
+           thread_specific_res_state[VG_N_THREADS];
+
+#undef errno
+extern int errno;
+int* __errno_location ( void )
+{
+   int tid;
+   int *ret;
+
+   ensure_valgrind("__errno_location");
+   VALGRIND_MAGIC_SEQUENCE(tid, 1 /* default */,
+                           VG_USERREQ__PTHREAD_GET_THREADID,
+                           0, 0, 0, 0);
+   /* 'cos I'm paranoid ... */
+   if (tid < 1 || tid >= VG_N_THREADS)
+      barf("__errno_location: invalid ThreadId");
+   if (tid == 1)
+      ret = &errno;
+   else
+      ret = &thread_specific_errno[tid];
+
+   return ret;
+}
+
+#undef h_errno
+extern int h_errno;
+int* __h_errno_location ( void )
+{
+   int tid;
+   /* ensure_valgrind("__h_errno_location"); */
+   VALGRIND_MAGIC_SEQUENCE(tid, 1 /* default */,
+                           VG_USERREQ__PTHREAD_GET_THREADID,
+                           0, 0, 0, 0);
+   /* 'cos I'm paranoid ... */
+   if (tid < 1 || tid >= VG_N_THREADS)
+      barf("__h_errno_location: invalid ThreadId");
+   if (tid == 1)
+      return &h_errno;
+   return & thread_specific_h_errno[tid];
+}
+
+
+#undef _res
+extern struct __res_state _res;
+struct __res_state* __res_state ( void )
+{
+   int tid;
+   /* ensure_valgrind("__res_state"); */
+   VALGRIND_MAGIC_SEQUENCE(tid, 1 /* default */,
+                           VG_USERREQ__PTHREAD_GET_THREADID,
+                           0, 0, 0, 0);
+   /* 'cos I'm paranoid ... */
+   if (tid < 1 || tid >= VG_N_THREADS)
+      barf("__res_state: invalid ThreadId");
+   if (tid == 1)
+      return & _res;
+   return & thread_specific_res_state[tid];
+}
+
+
+/* ---------------------------------------------------
+   LIBC-PRIVATE SPECIFIC DATA
+   ------------------------------------------------ */
+
+/* Relies on assumption that initial private data is NULL.  This
+   should be fixed somehow. */
+
+/* The allowable keys (indices) (all 3 of them).
+   From sysdeps/pthread/bits/libc-tsd.h
+*/
+/* as per glibc anoncvs HEAD of 20021001. */
+enum __libc_tsd_key_t { _LIBC_TSD_KEY_MALLOC = 0,
+                        _LIBC_TSD_KEY_DL_ERROR,
+                        _LIBC_TSD_KEY_RPC_VARS,
+                        _LIBC_TSD_KEY_LOCALE,
+                        _LIBC_TSD_KEY_CTYPE_B,
+                        _LIBC_TSD_KEY_CTYPE_TOLOWER,
+                        _LIBC_TSD_KEY_CTYPE_TOUPPER,
+                        _LIBC_TSD_KEY_N };
+
+/* Auto-initialising subsystem.  libc_specifics_inited is set
+   after initialisation.  libc_specifics_inited_mx guards it. */
+static int             libc_specifics_inited    = 0;
+static pthread_mutex_t libc_specifics_inited_mx = PTHREAD_MUTEX_INITIALIZER;
+
+
+/* These are the keys we must initialise the first time. */
+static pthread_key_t libc_specifics_keys[_LIBC_TSD_KEY_N];
+
+
+/* Initialise the keys, if they are not already initialised. */
+static
+void init_libc_tsd_keys ( void )
+{
+   int res, i;
+   pthread_key_t k;
+
+   /* Don't fall into deadlock if we get called again whilst we still
+      hold the lock, via the __uselocale() call herein. */
+   if (libc_specifics_inited != 0)
+      return;
+
+   /* Take the lock. */
+   res = __pthread_mutex_lock(&libc_specifics_inited_mx);
+   if (res != 0) barf("init_libc_tsd_keys: lock");
+
+   /* Now test again, to be sure there is no mistake. */
+   if (libc_specifics_inited != 0) {
+      res = __pthread_mutex_unlock(&libc_specifics_inited_mx);
+      if (res != 0) barf("init_libc_tsd_keys: unlock(1)");
+      return;
+   }
+
+   /* Actually do the initialisation. */
+   /* printf("INIT libc specifics\n"); */
+   for (i = 0; i < _LIBC_TSD_KEY_N; i++) {
+      res = __pthread_key_create(&k, NULL);
+      if (res != 0) barf("init_libc_tsd_keys: create");
+      libc_specifics_keys[i] = k;
+   }
+
+   /* Signify init done. */
+   libc_specifics_inited = 1;
+
+#  ifdef GLIBC_2_3
+   /* Set the initialising thread's locale to the global (default)
+      locale.  A hack in support of glibc-2.3.  This does the biz for
+      the root thread.  For all other threads we run this in
+      thread_wrapper(), which does the real work of
+      pthread_create(). */
+   /* assert that we are the root thread.  I don't know if this is
+      really a valid assertion to make; if it breaks I'll reconsider
+      it. */
+   my_assert(pthread_self() == 1);
+   __uselocale(LC_GLOBAL_LOCALE);
+#  endif
+
+   /* Unlock and return. */
+   res = __pthread_mutex_unlock(&libc_specifics_inited_mx);
+   if (res != 0) barf("init_libc_tsd_keys: unlock");
+}
+
+
+static int
+libc_internal_tsd_set ( enum __libc_tsd_key_t key,
+                        const void * pointer )
+{
+   int res;
+   /* printf("SET SET SET key %d ptr %p\n", key, pointer); */
+   if (key < _LIBC_TSD_KEY_MALLOC || key >= _LIBC_TSD_KEY_N)
+      barf("libc_internal_tsd_set: invalid key");
+   init_libc_tsd_keys();
+   res = __pthread_setspecific(libc_specifics_keys[key], pointer);
+   if (res != 0) barf("libc_internal_tsd_set: setspecific failed");
+   return 0;
+}
+
+static void *
+libc_internal_tsd_get ( enum __libc_tsd_key_t key )
+{
+   void* v;
+   /* printf("GET GET GET key %d\n", key); */
+   if (key < _LIBC_TSD_KEY_MALLOC || key >= _LIBC_TSD_KEY_N)
+      barf("libc_internal_tsd_get: invalid key");
+   init_libc_tsd_keys();
+   v = __pthread_getspecific(libc_specifics_keys[key]);
+   /* if (v == NULL) barf("libc_internal_tsd_set: getspecific failed"); */
+   return v;
+}
+
+
+int (*__libc_internal_tsd_set)
+    (enum __libc_tsd_key_t key, const void * pointer)
+   = libc_internal_tsd_set;
+
+void* (*__libc_internal_tsd_get)
+      (enum __libc_tsd_key_t key)
+   = libc_internal_tsd_get;
+
+
+#ifdef GLIBC_2_3
+/* This one was first spotted be me in the glibc-2.2.93 sources. */
+static void**
+libc_internal_tsd_address ( enum __libc_tsd_key_t key )
+{
+   void** v;
+   /* printf("ADDR ADDR ADDR key %d\n", key); */
+   if (key < _LIBC_TSD_KEY_MALLOC || key >= _LIBC_TSD_KEY_N)
+      barf("libc_internal_tsd_address: invalid key");
+   init_libc_tsd_keys();
+   v = __pthread_getspecific_addr(libc_specifics_keys[key]);
+   return v;
+}
+
+void ** (*__libc_internal_tsd_address)
+        (enum __libc_tsd_key_t key)
+   = libc_internal_tsd_address;
+#endif
+
+
+/* ---------------------------------------------------------------------
+   These are here (I think) because they are deemed cancellation
+   points by POSIX.  For the moment we'll simply pass the call along
+   to the corresponding thread-unaware (?) libc routine.
+   ------------------------------------------------------------------ */
+
+#ifdef GLIBC_2_1
+extern
+int __sigaction
+             (int signum,
+              const struct sigaction *act,
+              struct  sigaction *oldact);
+#else
+extern
+int _sigaction
+             (int signum,
+              const struct sigaction *act,
+              struct  sigaction *oldact);
+#endif
+int sigaction(int signum,
+              const struct sigaction *act,
+              struct  sigaction *oldact)
+{
+   __my_pthread_testcancel();
+#  ifdef GLIBC_2_1
+   return __sigaction(signum, act, oldact);
+#  else
+   return _sigaction(signum, act, oldact);
+#  endif
+}
+
+extern
+int _accept(int fd, struct sockaddr *addr, socklen_t *len);
+
+WEAK int __accept(int fd, struct sockaddr *addr, socklen_t *len)
+{
+   __my_pthread_testcancel();
+   return _accept(fd, addr, len);
+}
+strong_alias(__accept, accept);
+
+extern
+int  _connect(int  sockfd,
+                    const  struct  sockaddr  *serv_addr,
+                    socklen_t addrlen);
+WEAK
+int  connect(int  sockfd,
+             const  struct  sockaddr  *serv_addr,
+             socklen_t addrlen)
+{
+   __my_pthread_testcancel();
+   return _connect(sockfd, serv_addr, addrlen);
+}
+
+
+extern
+int _fcntl(int fd, int cmd, long arg);
+WEAK
+int fcntl(int fd, int cmd, long arg)
+{
+   __my_pthread_testcancel();
+   return _fcntl(fd, cmd, arg);
+}
+
+
+extern
+ssize_t _write(int fd, const void *buf, size_t count);
+WEAK
+ssize_t write(int fd, const void *buf, size_t count)
+{
+   __my_pthread_testcancel();
+   return _write(fd, buf, count);
+}
+
+
+extern
+ssize_t _read(int fd, void *buf, size_t count);
+WEAK
+ssize_t read(int fd, void *buf, size_t count)
+{
+   __my_pthread_testcancel();
+   return _read(fd, buf, count);
+}
+
+extern
+int _open(const char *pathname, int flags, mode_t mode);
+/* WEAK */
+int open(const char *pathname, int flags, mode_t mode)
+{
+   return _open(pathname, flags, mode);
+}
+
+extern
+int _close(int fd);
+WEAK
+int close(int fd)
+{
+   __my_pthread_testcancel();
+   return _close(fd);
+}
+
+
+extern
+pid_t _waitpid(pid_t pid, int *status, int options);
+WEAK
+pid_t waitpid(pid_t pid, int *status, int options)
+{
+   __my_pthread_testcancel();
+   return _waitpid(pid, status, options);
+}
+
+
+extern
+int _nanosleep(const struct timespec *req, struct timespec *rem);
+WEAK
+int __nanosleep(const struct timespec *req, struct timespec *rem)
+{
+   __my_pthread_testcancel();
+   return _nanosleep(req, rem);
+}
+
+extern
+int _pause(void);
+WEAK
+int __pause(void)
+{
+   __my_pthread_testcancel();
+   return _pause();
+}
+
+
+extern
+int _fsync(int fd);
+WEAK
+int fsync(int fd)
+{
+   __my_pthread_testcancel();
+   return _fsync(fd);
+}
+
+extern
+int _sendmsg(int s, const struct msghdr *msg, int flags);
+WEAK
+int sendmsg(int s, const struct msghdr *msg, int flags)
+{
+   __my_pthread_testcancel();
+   return _sendmsg(s, msg, flags);
+}
+
+
+extern
+int _recvmsg(int s, struct msghdr *msg, int flags);
+WEAK
+int recvmsg(int s, struct msghdr *msg, int flags)
+{
+   __my_pthread_testcancel();
+   return _recvmsg(s, msg, flags);
+}
+
+
+extern
+int _recvfrom(int s, void *buf, size_t len, int flags,
+                    struct sockaddr *from, socklen_t *fromlen);
+WEAK
+int recvfrom(int s, void *buf, size_t len, int flags,
+             struct sockaddr *from, socklen_t *fromlen)
+{
+   __my_pthread_testcancel();
+   return _recvfrom(s, buf, len, flags, from, fromlen);
+}
+
+
+extern
+int _sendto(int s, const void *msg, size_t len, int flags,
+                  const struct sockaddr *to, socklen_t tolen);
+WEAK
+int sendto(int s, const void *msg, size_t len, int flags,
+           const struct sockaddr *to, socklen_t tolen)
+{
+   __my_pthread_testcancel();
+   return _sendto(s, msg, len, flags, to, tolen);
+}
+
+
+extern
+int _system(const char* str);
+WEAK
+int system(const char* str)
+{
+   __my_pthread_testcancel();
+   return _system(str);
+}
+
+
+extern
+pid_t _wait(int *status);
+WEAK
+pid_t wait(int *status)
+{
+   __my_pthread_testcancel();
+   return _wait(status);
+}
+
+
+extern
+int _msync(const void *start, size_t length, int flags);
+WEAK
+int msync(const void *start, size_t length, int flags)
+{
+   __my_pthread_testcancel();
+   return _msync(start, length, flags);
+}
+
+strong_alias(close, __close)
+strong_alias(fcntl, __fcntl)
+strong_alias(open, __open)
+strong_alias(read, __read)
+strong_alias(wait, __wait)
+strong_alias(write, __write)
+strong_alias(connect, __connect)
+strong_alias(send, __send)
+
+weak_alias(__nanosleep, nanosleep)
+weak_alias(__pause, pause)
+
+
+extern
+void _longjmp(jmp_buf env, int val) __attribute((noreturn));
+/* not weak: WEAK */
+void longjmp(jmp_buf env, int val)
+{
+   _longjmp(env, val);
+}
+
+
+/*--- fork and its helper ---*/
+
+static
+void run_fork_handlers ( int what )
+{
+   ForkHandlerEntry entry;
+   int n_h, n_handlers, i, res;
+
+   my_assert(what == 0 || what == 1 || what == 2);
+
+   /* Fetch old counter */
+   VALGRIND_MAGIC_SEQUENCE(n_handlers, -2 /* default */,
+                           VG_USERREQ__GET_FHSTACK_USED,
+                           0, 0, 0, 0);
+   my_assert(n_handlers >= 0 && n_handlers < VG_N_FORKHANDLERSTACK);
+
+   /* Prepare handlers (what == 0) are called in opposite order of
+      calls to pthread_atfork.  Parent and child handlers are called
+      in the same order as calls to pthread_atfork. */
+   if (what == 0)
+      n_h = n_handlers - 1;
+   else
+      n_h = 0;
+
+   for (i = 0; i < n_handlers; i++) {
+      VALGRIND_MAGIC_SEQUENCE(res, -2 /* default */,
+                              VG_USERREQ__GET_FHSTACK_ENTRY,
+                              n_h, &entry, 0, 0);
+      my_assert(res == 0);
+      switch (what) {
+         case 0:  if (entry.prepare) entry.prepare();
+                  n_h--; break;
+         case 1:  if (entry.parent) entry.parent();
+                  n_h++; break;
+         case 2:  if (entry.child) entry.child();
+                  n_h++; break;
+         default: barf("run_fork_handlers: invalid what");
+      }
+   }
+
+   if (what != 0 /* prepare */) {
+      /* Empty out the stack. */
+      VALGRIND_MAGIC_SEQUENCE(res, -2 /* default */,
+                              VG_USERREQ__SET_FHSTACK_USED,
+                              0, 0, 0, 0);
+      my_assert(res == 0);
+   }
+}
+
+extern
+pid_t _fork(void);
+pid_t __fork(void)
+{
+   pid_t pid;
+   __my_pthread_testcancel();
+   __pthread_mutex_lock(&pthread_atfork_lock);
+
+   run_fork_handlers(0 /* prepare */);
+   pid = _fork();
+   if (pid == 0) {
+      /* I am the child */
+      run_fork_handlers(2 /* child */);
+      __pthread_mutex_unlock(&pthread_atfork_lock);
+      __pthread_mutex_init(&pthread_atfork_lock, NULL);
+   } else {
+      /* I am the parent */
+      run_fork_handlers(1 /* parent */);
+      __pthread_mutex_unlock(&pthread_atfork_lock);
+   }
+   return pid;
+}
+
+
+pid_t __vfork(void)
+{
+   return __fork();
+}
+
+
+
+/* ---------------------------------------------------------------------
+   Hacky implementation of semaphores.
+   ------------------------------------------------------------------ */
+
+#include <semaphore.h>
+
+/* This is a terrible way to do the remapping.  Plan is to import an
+   AVL tree at some point. */
+
+typedef
+   struct {
+      pthread_mutex_t se_mx;
+      pthread_cond_t se_cv;
+      int count;
+   }
+   vg_sem_t;
+
+static pthread_mutex_t se_remap_mx = PTHREAD_MUTEX_INITIALIZER;
+
+static int      se_remap_used = 0;
+static sem_t*   se_remap_orig[VG_N_SEMAPHORES];
+static vg_sem_t se_remap_new[VG_N_SEMAPHORES];
+
+static vg_sem_t* se_remap ( sem_t* orig )
+{
+   int res, i;
+   res = __pthread_mutex_lock(&se_remap_mx);
+   my_assert(res == 0);
+
+   for (i = 0; i < se_remap_used; i++) {
+      if (se_remap_orig[i] == orig)
+         break;
+   }
+   if (i == se_remap_used) {
+      if (se_remap_used == VG_N_SEMAPHORES) {
+         res = pthread_mutex_unlock(&se_remap_mx);
+         my_assert(res == 0);
+         barf("VG_N_SEMAPHORES is too low.  Increase and recompile.");
+      }
+      se_remap_used++;
+      se_remap_orig[i] = orig;
+      /* printf("allocated semaphore %d\n", i); */
+   }
+   res = __pthread_mutex_unlock(&se_remap_mx);
+   my_assert(res == 0);
+   return &se_remap_new[i];
+}
+
+
+int sem_init(sem_t *sem, int pshared, unsigned int value)
+{
+   int       res;
+   vg_sem_t* vg_sem;
+   ensure_valgrind("sem_init");
+   if (pshared != 0) {
+      pthread_error("sem_init: unsupported pshared value");
+      *(__errno_location()) = ENOSYS;
+      return -1;
+   }
+   vg_sem = se_remap(sem);
+   res = pthread_mutex_init(&vg_sem->se_mx, NULL);
+   my_assert(res == 0);
+   res = pthread_cond_init(&vg_sem->se_cv, NULL);
+   my_assert(res == 0);
+   vg_sem->count = value;
+   return 0;
+}
+
+
+int sem_wait ( sem_t* sem )
+{
+   int       res;
+   vg_sem_t* vg_sem;
+   ensure_valgrind("sem_wait");
+   vg_sem = se_remap(sem);
+   res = __pthread_mutex_lock(&vg_sem->se_mx);
+   my_assert(res == 0);
+   while (vg_sem->count == 0) {
+      res = pthread_cond_wait(&vg_sem->se_cv, &vg_sem->se_mx);
+      my_assert(res == 0);
+   }
+   vg_sem->count--;
+   res = __pthread_mutex_unlock(&vg_sem->se_mx);
+   my_assert(res == 0);
+   return 0;
+}
+
+int sem_post ( sem_t* sem )
+{
+   int       res;
+   vg_sem_t* vg_sem;
+   ensure_valgrind("sem_post");
+   vg_sem = se_remap(sem);
+   res = __pthread_mutex_lock(&vg_sem->se_mx);
+   my_assert(res == 0);
+   if (vg_sem->count == 0) {
+      vg_sem->count++;
+      res = pthread_cond_broadcast(&vg_sem->se_cv);
+      my_assert(res == 0);
+   } else {
+      vg_sem->count++;
+   }
+   res = __pthread_mutex_unlock(&vg_sem->se_mx);
+   my_assert(res == 0);
+   return 0;
+}
+
+
+int sem_trywait ( sem_t* sem )
+{
+   int       ret, res;
+   vg_sem_t* vg_sem;
+   ensure_valgrind("sem_trywait");
+   vg_sem = se_remap(sem);
+   res = __pthread_mutex_lock(&vg_sem->se_mx);
+   my_assert(res == 0);
+   if (vg_sem->count > 0) {
+      vg_sem->count--;
+      ret = 0;
+   } else {
+      ret = -1;
+      *(__errno_location()) = EAGAIN;
+   }
+   res = __pthread_mutex_unlock(&vg_sem->se_mx);
+   my_assert(res == 0);
+   return ret;
+}
+
+
+int sem_getvalue(sem_t* sem, int * sval)
+{
+   vg_sem_t* vg_sem;
+   ensure_valgrind("sem_trywait");
+   vg_sem = se_remap(sem);
+   *sval = vg_sem->count;
+   return 0;
+}
+
+
+int sem_destroy(sem_t * sem)
+{
+   kludged("sem_destroy", "(it always succeeds, even if semaphore waited on)");
+   /* if someone waiting on this semaphore, errno = EBUSY, return -1 */
+   return 0;
+}
+
+
+int sem_timedwait(sem_t* sem, const struct timespec *abstime)
+{
+   int       res;
+   vg_sem_t* vg_sem;
+   ensure_valgrind("sem_timedwait");
+   vg_sem = se_remap(sem);
+   res = __pthread_mutex_lock(&vg_sem->se_mx);
+   my_assert(res == 0);
+   while ( vg_sem->count == 0 && res != ETIMEDOUT ) {
+      res = pthread_cond_timedwait(&vg_sem->se_cv, &vg_sem->se_mx, abstime);
+   }
+   if ( vg_sem->count > 0 ) {
+      vg_sem->count--;
+      res = __pthread_mutex_unlock(&vg_sem->se_mx);
+      my_assert(res == 0 );
+      return 0;
+   } else {
+      res = __pthread_mutex_unlock(&vg_sem->se_mx);
+      my_assert(res == 0 );
+      *(__errno_location()) = ETIMEDOUT;
+      return -1;
+   }
+}
+
+
+/* ---------------------------------------------------------------------
+   Reader-writer locks.
+   ------------------------------------------------------------------ */
+
+typedef
+   struct {
+      int             initted;  /* != 0 --> in use; sanity check only */
+      int             prefer_w; /* != 0 --> prefer writer */
+      int             nwait_r;  /* # of waiting readers */
+      int             nwait_w;  /* # of waiting writers */
+      pthread_cond_t  cv_r;     /* for signalling readers */
+      pthread_cond_t  cv_w;     /* for signalling writers */
+      pthread_mutex_t mx;
+      int             status;
+      /* allowed range for status: >= -1.  -1 means 1 writer currently
+         active, >= 0 means N readers currently active. */
+   }
+   vg_rwlock_t;
+
+
+static pthread_mutex_t rw_remap_mx = PTHREAD_MUTEX_INITIALIZER;
+
+static int                 rw_remap_used = 0;
+static pthread_rwlock_t    rw_remap_orig[VG_N_RWLOCKS];
+static vg_rwlock_t         rw_remap_new[VG_N_RWLOCKS];
+
+
+static
+void init_vg_rwlock ( vg_rwlock_t* vg_rwl )
+{
+   int res = 0;
+   vg_rwl->initted = 1;
+   vg_rwl->prefer_w = 1;
+   vg_rwl->nwait_r = 0;
+   vg_rwl->nwait_w = 0;
+   vg_rwl->status = 0;
+   res = pthread_mutex_init(&vg_rwl->mx, NULL);
+   res |= pthread_cond_init(&vg_rwl->cv_r, NULL);
+   res |= pthread_cond_init(&vg_rwl->cv_w, NULL);
+   my_assert(res == 0);
+}
+
+
+/* Take the address of a LinuxThreads rwlock_t and return the shadow
+   address of our version.  Further, if the LinuxThreads version
+   appears to have been statically initialised, do the same to the one
+   we allocate here.  The pthread_rwlock_t.__rw_readers field is set
+   to zero by PTHREAD_RWLOCK_INITIALIZER, so we take zero as meaning
+   uninitialised and non-zero meaning initialised.
+*/
+static vg_rwlock_t* rw_remap ( pthread_rwlock_t orig )
+{
+   int          res, i;
+   vg_rwlock_t* vg_rwl;
+   res = __pthread_mutex_lock(&rw_remap_mx);
+   my_assert(res == 0);
+
+   for (i = 0; i < rw_remap_used; i++) {
+      if (rw_remap_orig[i] == orig)
+         break;
+   }
+   if (i == rw_remap_used) {
+      if (rw_remap_used == VG_N_RWLOCKS) {
+         res = __pthread_mutex_unlock(&rw_remap_mx);
+         my_assert(res == 0);
+         barf("VG_N_RWLOCKS is too low.  Increase and recompile.");
+      }
+      rw_remap_used++;
+      rw_remap_orig[i] = orig;
+      rw_remap_new[i].initted = 0;
+      if (0) printf("allocated rwlock %d\n", i);
+   }
+   res = __pthread_mutex_unlock(&rw_remap_mx);
+   my_assert(res == 0);
+   vg_rwl = &rw_remap_new[i];
+
+   /* Initialise the shadow, if required. */
+   if (orig->__rw_readers == 0) {
+      orig->__rw_readers = 1;
+      init_vg_rwlock(vg_rwl);
+      vg_rwl->prefer_w = 0;
+   }
+
+   return vg_rwl;
+}
+
+
+int pthread_rwlock_init ( pthread_rwlock_t* origp,
+                          const pthread_rwlockattr_t* attrp )
+{
+   struct pthread_rwlock *orig = my_malloc(sizeof(struct pthread_rwlock));
+   *origp = orig;
+   vg_rwlock_t* rwl;
+   if (0) printf ("pthread_rwlock_init\n");
+   /* Force the remapper to initialise the shadow. */
+   orig->__rw_readers = 0;
+   /* Install the lock preference; the remapper needs to know it. */
+   orig->__rw_kind = 0;
+   if (attrp)
+      orig->__rw_kind = (*attrp)->__lockkind;
+   rwl = rw_remap ( orig );
+   return 0;
+}
+
+
+static
+void pthread_rwlock_rdlock_CANCEL_HDLR ( void* rwl_v )
+{
+   vg_rwlock_t* rwl = (vg_rwlock_t*)rwl_v;
+   rwl->nwait_r--;
+   pthread_mutex_unlock (&rwl->mx);
+}
+
+
+int pthread_rwlock_rdlock ( pthread_rwlock_t* origp )
+{
+   int res;
+   vg_rwlock_t* rwl;
+   if (0) printf ("pthread_rwlock_rdlock\n");
+   rwl = rw_remap ( *origp );
+   res = __pthread_mutex_lock(&rwl->mx);
+   my_assert(res == 0);
+   if (!rwl->initted) {
+      res = __pthread_mutex_unlock(&rwl->mx);
+      my_assert(res == 0);
+      return EINVAL;
+   }
+   if (rwl->status < 0) {
+      my_assert(rwl->status == -1);
+      rwl->nwait_r++;
+      pthread_cleanup_push( pthread_rwlock_rdlock_CANCEL_HDLR, rwl );
+      while (1) {
+         if (rwl->status == 0) break;
+         res = pthread_cond_wait(&rwl->cv_r, &rwl->mx);
+         my_assert(res == 0);
+      }
+      pthread_cleanup_pop(0);
+      rwl->nwait_r--;
+   }
+   my_assert(rwl->status >= 0);
+   rwl->status++;
+   res = __pthread_mutex_unlock(&rwl->mx);
+   my_assert(res == 0);
+   return 0;
+}
+
+
+int pthread_rwlock_tryrdlock ( pthread_rwlock_t* origp )
+{
+   int res;
+   vg_rwlock_t* rwl;
+   if (0) printf ("pthread_rwlock_tryrdlock\n");
+   rwl = rw_remap ( *origp );
+   res = __pthread_mutex_lock(&rwl->mx);
+   my_assert(res == 0);
+   if (!rwl->initted) {
+      res = __pthread_mutex_unlock(&rwl->mx);
+      my_assert(res == 0);
+      return EINVAL;
+   }
+   if (rwl->status == -1) {
+      /* Writer active; we have to give up. */
+      res = __pthread_mutex_unlock(&rwl->mx);
+      my_assert(res == 0);
+      return EBUSY;
+   }
+   /* Success */
+   my_assert(rwl->status >= 0);
+   rwl->status++;
+   res = __pthread_mutex_unlock(&rwl->mx);
+   my_assert(res == 0);
+   return 0;
+}
+
+
+static
+void pthread_rwlock_wrlock_CANCEL_HDLR ( void* rwl_v )
+{
+   vg_rwlock_t* rwl = (vg_rwlock_t*)rwl_v;
+   rwl->nwait_w--;
+   pthread_mutex_unlock (&rwl->mx);
+}
+
+
+int pthread_rwlock_wrlock ( pthread_rwlock_t* origp )
+{
+   int res;
+   vg_rwlock_t* rwl;
+   if (0) printf ("pthread_rwlock_wrlock\n");
+   rwl = rw_remap ( *origp );
+   res = __pthread_mutex_lock(&rwl->mx);
+   my_assert(res == 0);
+   if (!rwl->initted) {
+      res = __pthread_mutex_unlock(&rwl->mx);
+      my_assert(res == 0);
+      return EINVAL;
+   }
+   if (rwl->status != 0) {
+      rwl->nwait_w++;
+      pthread_cleanup_push( pthread_rwlock_wrlock_CANCEL_HDLR, rwl );
+      while (1) {
+         if (rwl->status == 0) break;
+         res = pthread_cond_wait(&rwl->cv_w, &rwl->mx);
+         my_assert(res == 0);
+      }
+      pthread_cleanup_pop(0);
+      rwl->nwait_w--;
+   }
+   my_assert(rwl->status == 0);
+   rwl->status = -1;
+   res = __pthread_mutex_unlock(&rwl->mx);
+   my_assert(res == 0);
+   return 0;
+}
+
+
+int pthread_rwlock_trywrlock ( pthread_rwlock_t* origp )
+{
+   int res;
+   vg_rwlock_t* rwl;
+   if (0) printf ("pthread_wrlock_trywrlock\n");
+   rwl = rw_remap ( *origp );
+   res = __pthread_mutex_lock(&rwl->mx);
+   my_assert(res == 0);
+   if (!rwl->initted) {
+      res = __pthread_mutex_unlock(&rwl->mx);
+      my_assert(res == 0);
+      return EINVAL;
+   }
+   if (rwl->status != 0) {
+      /* Reader(s) or a writer active; we have to give up. */
+      res = __pthread_mutex_unlock(&rwl->mx);
+      my_assert(res == 0);
+      return EBUSY;
+   }
+   /* Success */
+   my_assert(rwl->status == 0);
+   rwl->status = -1;
+   res = __pthread_mutex_unlock(&rwl->mx);
+   my_assert(res == 0);
+   return 0;
+}
+
+
+int pthread_rwlock_unlock ( pthread_rwlock_t* origp )
+{
+   int res;
+   vg_rwlock_t* rwl;
+   if (0) printf ("pthread_rwlock_unlock\n");
+   rwl = rw_remap ( *origp );
+   res = __pthread_mutex_lock(&rwl->mx);
+   my_assert(res == 0);
+   if (!rwl->initted) {
+      res = __pthread_mutex_unlock(&rwl->mx);
+      my_assert(res == 0);
+      return EINVAL;
+   }
+   if (rwl->status == 0) {
+      res = __pthread_mutex_unlock(&rwl->mx);
+      my_assert(res == 0);
+      return EPERM;
+   }
+   my_assert(rwl->status != 0);
+   if (rwl->status == -1) {
+     rwl->status = 0;
+   } else {
+     my_assert(rwl->status > 0);
+     rwl->status--;
+   }
+
+   my_assert(rwl->status >= 0);
+
+   if (rwl->prefer_w) {
+
+      /* Favour waiting writers, if any. */
+      if (rwl->nwait_w > 0) {
+         /* Writer(s) are waiting. */
+         if (rwl->status == 0) {
+            /* We can let a writer in. */
+            res = pthread_cond_signal(&rwl->cv_w);
+            my_assert(res == 0);
+         } else {
+            /* There are still readers active.  Do nothing; eventually
+               they will disappear, at which point a writer will be
+               admitted. */
+         }
+      }
+      else
+      /* No waiting writers. */
+      if (rwl->nwait_r > 0) {
+         /* Let in a waiting reader. */
+         res = pthread_cond_signal(&rwl->cv_r);
+         my_assert(res == 0);
+      }
+
+   } else {
+
+      /* Favour waiting readers, if any. */
+      if (rwl->nwait_r > 0) {
+         /* Reader(s) are waiting; let one in. */
+         res = pthread_cond_signal(&rwl->cv_r);
+         my_assert(res == 0);
+      }
+      else
+      /* No waiting readers. */
+      if (rwl->nwait_w > 0 && rwl->status == 0) {
+         /* We have waiting writers and no active readers; let a
+            writer in. */
+         res = pthread_cond_signal(&rwl->cv_w);
+         my_assert(res == 0);
+      }
+   }
+
+   res = __pthread_mutex_unlock(&rwl->mx);
+   my_assert(res == 0);
+   return 0;
+}
+
+
+int pthread_rwlock_destroy ( pthread_rwlock_t *origp )
+{
+   int res;
+   vg_rwlock_t* rwl;
+   if (0) printf ("pthread_rwlock_destroy\n");
+   rwl = rw_remap ( *origp );
+   res = __pthread_mutex_lock(&rwl->mx);
+   my_assert(res == 0);
+   if (!rwl->initted) {
+      res = __pthread_mutex_unlock(&rwl->mx);
+      my_assert(res == 0);
+      return EINVAL;
+   }
+   if (rwl->status != 0 || rwl->nwait_r > 0 || rwl->nwait_w > 0) {
+      res = __pthread_mutex_unlock(&rwl->mx);
+      my_assert(res == 0);
+      return EBUSY;
+   }
+   rwl->initted = 0;
+   res = __pthread_mutex_unlock(&rwl->mx);
+   my_assert(res == 0);
+   my_free(*origp);
+   *origp = 0;
+   return 0;
+}
+
+
+/* Copied directly from LinuxThreads. */
+int
+pthread_rwlockattr_init (pthread_rwlockattr_t *attrp)
+{
+  struct pthread_rwlockattr *attr = my_malloc(sizeof(struct pthread_rwlockattr));
+  *attrp = attr;
+  attr->__lockkind = 0;
+  attr->__pshared = PTHREAD_PROCESS_PRIVATE;
+
+  return 0;
+}
+
+/* Copied directly from LinuxThreads. */
+int
+pthread_rwlockattr_destroy (pthread_rwlockattr_t *attrp)
+{
+   if (*attrp) {
+      my_free(*attrp);
+      *attrp = 0;
+   }
+   return 0;
+}
+
+/* Copied directly from LinuxThreads. */
+int
+pthread_rwlockattr_setpshared (pthread_rwlockattr_t *attrp, int pshared)
+{
+  if (pshared != PTHREAD_PROCESS_PRIVATE && pshared != PTHREAD_PROCESS_SHARED)
+    return EINVAL;
+
+  /* For now it is not possible to shared a conditional variable.  */
+  if (pshared != PTHREAD_PROCESS_PRIVATE)
+    return ENOSYS;
+
+  (*attrp)->__pshared = pshared;
+
+  return 0;
+}
+
+
+
+/* ---------------------------------------------------------------------
+   Manage the allocation and use of RT signals.  The Valgrind core
+   uses one.  glibc needs us to implement this to make RT signals
+   work; things just seem to crash if we don't.
+   ------------------------------------------------------------------ */
+int _current_sigrtmin (void)
+{
+   int res;
+
+   VALGRIND_MAGIC_SEQUENCE(res, 0,
+                          VG_USERREQ__GET_SIGRT_MIN,
+                          0, 0, 0, 0);
+
+   return res;
+}
+
+int _current_sigrtmax (void)
+{
+   int res;
+
+   VALGRIND_MAGIC_SEQUENCE(res, 0,
+                          VG_USERREQ__GET_SIGRT_MAX,
+                          0, 0, 0, 0);
+
+   return res;
+}
+
+int _allocate_rtsig (int high)
+{
+   int res;
+
+   VALGRIND_MAGIC_SEQUENCE(res, 0,
+                          VG_USERREQ__ALLOC_RTSIG,
+                          high, 0, 0, 0);
+
+   return res;
+}
+
+/* ---------------------------------------------------------------------
+   B'stard.
+   ------------------------------------------------------------------ */
+strong_alias(__pthread_mutex_lock, pthread_mutex_lock)
+strong_alias(__pthread_mutex_trylock, pthread_mutex_trylock)
+strong_alias(__pthread_mutex_unlock, pthread_mutex_unlock)
+strong_alias(__pthread_mutexattr_init, pthread_mutexattr_init)
+  weak_alias(__pthread_mutexattr_settype, pthread_mutexattr_settype)
+  weak_alias(__pthread_mutexattr_setpshared, pthread_mutexattr_setpshared)
+strong_alias(__pthread_mutex_init, pthread_mutex_init)
+strong_alias(__pthread_mutexattr_destroy, pthread_mutexattr_destroy)
+strong_alias(__pthread_mutex_destroy, pthread_mutex_destroy)
+strong_alias(__pthread_once, pthread_once)
+strong_alias(__pthread_atfork, pthread_atfork)
+strong_alias(__pthread_key_create, pthread_key_create)
+strong_alias(__pthread_getspecific, pthread_getspecific)
+strong_alias(__pthread_setspecific, pthread_setspecific)
+
+#ifndef GLIBC_2_1
+strong_alias(sigaction, __sigaction)
+#endif
+
+weak_alias(__fork, fork)
+weak_alias(__vfork, vfork)
+weak_alias (__pthread_kill_other_threads_np, pthread_kill_other_threads_np)
+
+/*--------------------------------------------------*/
+
+weak_alias(pthread_rwlock_rdlock, __pthread_rwlock_rdlock)
+weak_alias(pthread_rwlock_unlock, __pthread_rwlock_unlock)
+weak_alias(pthread_rwlock_wrlock, __pthread_rwlock_wrlock)
+
+weak_alias(pthread_rwlock_destroy, __pthread_rwlock_destroy)
+weak_alias(pthread_rwlock_init, __pthread_rwlock_init)
+weak_alias(pthread_rwlock_tryrdlock, __pthread_rwlock_tryrdlock)
+weak_alias(pthread_rwlock_trywrlock, __pthread_rwlock_trywrlock)
+
+
+
+/* This doesn't seem to be needed to simulate libpthread.so's external
+   interface, but many people complain about its absence. */
+
+strong_alias(__pthread_mutexattr_settype, __pthread_mutexattr_setkind_np)
+weak_alias(__pthread_mutexattr_setkind_np, pthread_mutexattr_setkind_np)
+
+/* POSIX spinlocks, taken from glibc linuxthreads/sysdeps/i386 */
+
+int pthread_spin_init(pthread_spinlock_t *lockp, int pshared)
+{
+   struct pthread_spinlock *lock = my_malloc(sizeof(struct pthread_spinlock));
+   *lockp = lock;
+
+   /* We can ignore the `pshared' parameter.  Since we are busy-waiting
+      all processes which can access the memory location `lock' points
+      to can use the spinlock.  */
+   lock->__lock = 1;
+   return 0;
+}
+
+int pthread_spin_lock(pthread_spinlock_t *lockp)
+{
+   asm volatile
+      ("\n"
+       "1:\n\t"
+       "lock; decl %0\n\t"
+       "js 2f\n\t"
+       ".section .text.spinlock,\"ax\"\n"
+       "2:\n\t"
+       "cmpl $0,%0\n\t"
+       "rep; nop\n\t"
+       "jle 2b\n\t"
+       "jmp 1b\n\t"
+       ".previous"
+       : "=m" (**lockp));
+   return 0;
+}
+
+int pthread_spin_unlock(pthread_spinlock_t *lockp)
+{
+  asm volatile
+    ("movl $1,%0"
+     : "=m" (**lockp));
+  return 0;
+}
+
+int pthread_spin_destroy(pthread_spinlock_t *lockp)
+{
+  my_free(*lockp);
+  *lockp = 0;
+  return 0;
+}
+
+int pthread_spin_trylock(pthread_spinlock_t *lockp)
+{
+  int oldval;
+
+  asm volatile
+    ("xchgl %0,%1"
+     : "=r" (oldval), "=m" (**lockp)
+     : "0" (0));
+  return oldval > 0 ? 0 : EBUSY;
+}
+
+/*--------------------------------------------------------------------*/
+/*--- end                                          vg_libpthread.c ---*/
+/*--------------------------------------------------------------------*/
+
diff --git a/coregrind/arch/x86-freebsd/vg_syscall.S b/coregrind/arch/x86-freebsd/vg_syscall.S
new file mode 100644 (file)
index 0000000..5a636e4
--- /dev/null
@@ -0,0 +1,195 @@
+
+##--------------------------------------------------------------------##
+##--- Support for doing system calls.                              ---##
+##---                                                 vg_syscall.S ---##
+##--------------------------------------------------------------------##
+
+/*
+  This file is part of Valgrind, an extensible x86 protected-mode
+  emulator for monitoring program execution on x86-Unixes.
+
+  Copyright (C) 2000-2003 Julian Seward 
+     jseward@acm.org
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 2 of the
+  License, or (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+  02111-1307, USA.
+
+  The GNU General Public License is contained in the file COPYING.
+*/
+
+#include "vg_constants.h"
+#include "vg_unistd.h"
+
+.globl VG_(do_syscall)
+
+/*
+       Perform a Linux syscall with int 0x80
+       
+       Syscall args are passed on the stack
+       Int VG_(do_syscall)(Int syscall_no, ...)
+
+       This has no effect on the virtual machine; the expectation is
+       that the syscall mechanism makes no useful changes to any
+       register except %eax, which is returned.
+ */
+VG_(do_syscall):
+       pop     %ecx
+       pop     %eax
+       push    %ecx
+       int     $0x80
+       push    %ecx
+       jae     1f
+       movl    $-1,%eax
+1:
+       ret
+
+/*
+       Perform a FreeBSD syscall with int 0x80, returning error flag
+
+       Syscall args are passed on the stack
+       Int VG_(do_syscall_err)(Int syscall_no, UInt edx, UInt* eflags, ...)
+
+       This has no effect on the virtual machine; the expectation is
+       that the syscall mechanism makes no useful changes to any
+       register except %eax and %edx, which are returned.
+ */
+.globl VG_(do_syscall_err)
+VG_(do_syscall_err):
+       movl    12(%esp),%ecx
+       andb    $254,(%ecx)
+       movl    (%esp),%eax
+       movl    %eax,12(%esp)
+       movl    4(%esp),%eax
+       movl    8(%esp),%edx
+       addl    $12,%esp
+       int     $0x80
+       jae     1f
+       orb     $1,(%ecx)
+1:
+       movl    (%esp),%ecx
+       push    %ecx
+       push    %ecx
+       push    %ecx
+       ret
+
+/*
+       Perform a clone system call.  clone is strange because it has
+       fork()-like return-twice semantics, so it needs special
+       handling here.
+
+       int VG_(clone)(int (*fn)(void *), void *child_stack, int flags, void *arg, 
+                      0                  4                  8          12
+                      pid_t *child_tid, pid_t *parent_tid)
+                      16                20
+
+ */
+.globl VG_(clone)
+VG_(clone):
+       ret
+
+/*
+ *                8      12          16         20
+ * rfork_thread(flags, stack_addr, start_fnc, start_arg);
+ *
+ * flags:              Flags to rfork system call.  See rfork(2).
+ * stack_addr:         Top of stack for thread.
+ * start_fnc:          Address of thread function to call in child.
+ * start_arg:          Argument to pass to the thread function in child.
+ */
+
+.globl VG_(rfork_thread)
+VG_(rfork_thread):
+       pushl   %ebp
+       movl    %esp, %ebp
+       pushl   %esi
+
+       /*
+        * Push thread info onto the new thread's stack
+        */
+       movl    12(%ebp), %esi  # get stack addr
+
+       subl    $4, %esi
+       movl    20(%ebp), %eax  # get start argument
+       movl    %eax, (%esi)
+
+       subl    $4, %esi
+       movl    16(%ebp), %eax  # get start thread address
+       movl    %eax, (%esi)
+
+       /*
+        * Prepare and execute the thread creation syscall
+        */
+       pushl   8(%ebp)
+       pushl   $0
+       movl    $__NR_rfork, %eax
+       int     $0x80
+       jb      2f
+
+       /*
+        * Check to see if we are in the parent or child
+        */
+       cmpl    $0, %edx
+       jnz     1f
+       addl    $8, %esp
+       popl    %esi
+       movl    %ebp, %esp
+       popl    %ebp
+       ret
+       .p2align 2
+
+       /*
+        * If we are in the child (new thread), then
+        * set-up the call to the internal subroutine.  If it
+        * returns, then call __exit.
+        */
+1:
+       movl    %esi,%esp
+       popl    %eax
+       call    *%eax
+       addl    $4, %esp
+
+       /*
+        * Exit system call
+        */
+       pushl   %eax
+       pushl   $0
+       movl    $__NR_exit, %eax
+       int     $0x80
+
+       /*
+        * Branch here if the thread creation fails:
+        */
+2:
+       movl    $-1,%eax
+       ret
+       
+.globl VG_(pipe)
+VG_(pipe):
+       mov     $__NR_pipe,%eax
+       int     $0x80
+       jb      1f
+       movl    4(%esp),%ecx
+       movl    %eax,(%ecx)
+       movl    %edx,4(%ecx)
+       movl    $0,%eax
+       ret
+1:
+       negl    %eax
+       ret
+
+
+##--------------------------------------------------------------------##
+##--- end                                             vg_syscall.S ---##
+##--------------------------------------------------------------------##