From: Dirk Mueller Date: Sat, 3 Jan 2004 14:49:18 +0000 (+0000) Subject: adding FreeBSD port made by Doug Rabson, thanks! X-Git-Tag: svn/VALGRIND_2_1_1~136 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f62c6dfb1852ca5d7c6f1eccfc2f435406fde6a8;p=thirdparty%2Fvalgrind.git adding FreeBSD port made by Doug Rabson, thanks! 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 --- diff --git a/coregrind/arch/x86-freebsd/vg_libpthread.c b/coregrind/arch/x86-freebsd/vg_libpthread.c new file mode 100644 index 0000000000..d23e662610 --- /dev/null +++ b/coregrind/arch/x86-freebsd/vg_libpthread.c @@ -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 +#include +#undef __USE_UNIX98 + +#include +#include +#include +#include +#include +#include +#include + +#include + +# 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 + +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 +#include +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 + +/* 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 index 0000000000..5a636e4542 --- /dev/null +++ b/coregrind/arch/x86-freebsd/vg_syscall.S @@ -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 ---## +##--------------------------------------------------------------------##