| |
| /*--------------------------------------------------------------------*/ |
| /*--- pthread intercepts for thread checking. ---*/ |
| /*--- hg_intercepts.c ---*/ |
| /*--------------------------------------------------------------------*/ |
| |
| /* |
| This file is part of Helgrind, a Valgrind tool for detecting errors |
| in threaded programs. |
| |
| Copyright (C) 2007-2015 OpenWorks LLP |
| info@open-works.co.uk |
| |
| 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. |
| |
| Neither the names of the U.S. Department of Energy nor the |
| University of California nor the names of its contributors may be |
| used to endorse or promote products derived from this software |
| without prior written permission. |
| */ |
| |
| /* RUNS ON SIMULATED CPU |
| Interceptors for pthread_* functions, so that tc_main can see |
| significant thread events. |
| |
| Important: when adding a function wrapper to this file, remember to |
| add a test case to tc20_verifywrap.c. A common cause of failure is |
| for wrappers to not engage on different distros, and |
| tc20_verifywrap essentially checks that each wrapper is really |
| doing something. |
| */ |
| |
| // DDD: for Darwin, need to have non-"@*"-suffixed versions for all pthread |
| // functions that currently have them. |
| // Note also, in the comments and code below, all Darwin symbols start |
| // with a leading underscore, which is not shown either in the comments |
| // nor in the redirect specs. |
| |
| |
| #include "pub_tool_basics.h" |
| #include "pub_tool_redir.h" |
| #include "pub_tool_clreq.h" |
| #include "helgrind.h" |
| #include "config.h" |
| |
| |
| #if defined(VGO_solaris) |
| /* See porting comments in drd/drd_pthread_intercepts.c |
| However when a POSIX threads API function (for example pthread_cond_init) |
| is built upon the Solaris one (cond_init), intercept only the bottom one. |
| Helgrind does not contain generic synchronization nesting like DRD |
| and double intercept confuses it. */ |
| #include <synch.h> |
| #include <thread.h> |
| #endif /* VGO_solaris */ |
| |
| |
| #define TRACE_PTH_FNS 0 |
| #define TRACE_QT4_FNS 0 |
| #define TRACE_GNAT_FNS 0 |
| |
| |
| /*----------------------------------------------------------------*/ |
| /*--- ---*/ |
| /*----------------------------------------------------------------*/ |
| |
| #if defined(VGO_solaris) |
| /* On Solaris, libpthread is just a filter library on top of libc. |
| * Threading and synchronization functions in runtime linker are not |
| * intercepted. |
| */ |
| #define PTH_FUNC(ret_ty, f, args...) \ |
| ret_ty I_WRAP_SONAME_FNNAME_ZZ(VG_Z_LIBC_SONAME,f)(args); \ |
| ret_ty I_WRAP_SONAME_FNNAME_ZZ(VG_Z_LIBC_SONAME,f)(args) |
| |
| /* pthread_t is typedef'd to 'unsigned int' but in DO_CREQ_* macros |
| sizeof(Word) is expected. */ |
| #define CREQ_PTHREAD_T Word |
| #define SEM_ERROR ret |
| #else |
| #define PTH_FUNC(ret_ty, f, args...) \ |
| ret_ty I_WRAP_SONAME_FNNAME_ZZ(VG_Z_LIBPTHREAD_SONAME,f)(args); \ |
| ret_ty I_WRAP_SONAME_FNNAME_ZZ(VG_Z_LIBPTHREAD_SONAME,f)(args) |
| #define CREQ_PTHREAD_T pthread_t |
| #define SEM_ERROR errno |
| #endif /* VGO_solaris */ |
| |
| // Do a client request. These are macros rather than a functions so |
| // as to avoid having an extra frame in stack traces. |
| |
| // NB: these duplicate definitions in helgrind.h. But here, we |
| // can have better typing (Word etc) and assertions, whereas |
| // in helgrind.h we can't. Obviously it's important the two |
| // sets of definitions are kept in sync. |
| |
| // nuke the previous definitions |
| #undef DO_CREQ_v_W |
| #undef DO_CREQ_v_WW |
| #undef DO_CREQ_W_WW |
| #undef DO_CREQ_v_WWW |
| |
| #define DO_CREQ_v_W(_creqF, _ty1F,_arg1F) \ |
| do { \ |
| Word _arg1; \ |
| assert(sizeof(_ty1F) == sizeof(Word)); \ |
| _arg1 = (Word)(_arg1F); \ |
| VALGRIND_DO_CLIENT_REQUEST_STMT((_creqF), \ |
| _arg1, 0,0,0,0); \ |
| } while (0) |
| |
| #define DO_CREQ_v_WW(_creqF, _ty1F,_arg1F, _ty2F,_arg2F) \ |
| do { \ |
| Word _arg1, _arg2; \ |
| assert(sizeof(_ty1F) == sizeof(Word)); \ |
| assert(sizeof(_ty2F) == sizeof(Word)); \ |
| _arg1 = (Word)(_arg1F); \ |
| _arg2 = (Word)(_arg2F); \ |
| VALGRIND_DO_CLIENT_REQUEST_STMT((_creqF), \ |
| _arg1,_arg2,0,0,0); \ |
| } while (0) |
| |
| #define DO_CREQ_W_WW(_resF, _creqF, _ty1F,_arg1F, \ |
| _ty2F,_arg2F) \ |
| do { \ |
| Word _res, _arg1, _arg2; \ |
| assert(sizeof(_ty1F) == sizeof(Word)); \ |
| assert(sizeof(_ty2F) == sizeof(Word)); \ |
| _arg1 = (Word)(_arg1F); \ |
| _arg2 = (Word)(_arg2F); \ |
| _res = VALGRIND_DO_CLIENT_REQUEST_EXPR(2, \ |
| (_creqF), \ |
| _arg1,_arg2,0,0,0); \ |
| _resF = _res; \ |
| } while (0) |
| |
| #define DO_CREQ_v_WWW(_creqF, _ty1F,_arg1F, \ |
| _ty2F,_arg2F, _ty3F, _arg3F) \ |
| do { \ |
| Word _arg1, _arg2, _arg3; \ |
| assert(sizeof(_ty1F) == sizeof(Word)); \ |
| assert(sizeof(_ty2F) == sizeof(Word)); \ |
| assert(sizeof(_ty3F) == sizeof(Word)); \ |
| _arg1 = (Word)(_arg1F); \ |
| _arg2 = (Word)(_arg2F); \ |
| _arg3 = (Word)(_arg3F); \ |
| VALGRIND_DO_CLIENT_REQUEST_STMT((_creqF), \ |
| _arg1,_arg2,_arg3,0,0); \ |
| } while (0) |
| |
| #define DO_CREQ_v_WWWW(_creqF, _ty1F,_arg1F, \ |
| _ty2F, _arg2F, _ty3F, _arg3F, \ |
| _ty4F, _arg4F) \ |
| do { \ |
| Word _arg1, _arg2, _arg3, _arg4; \ |
| assert(sizeof(_ty1F) == sizeof(Word)); \ |
| assert(sizeof(_ty2F) == sizeof(Word)); \ |
| assert(sizeof(_ty3F) == sizeof(Word)); \ |
| assert(sizeof(_ty4F) == sizeof(Word)); \ |
| _arg1 = (Word)(_arg1F); \ |
| _arg2 = (Word)(_arg2F); \ |
| _arg3 = (Word)(_arg3F); \ |
| _arg4 = (Word)(_arg4F); \ |
| VALGRIND_DO_CLIENT_REQUEST_STMT((_creqF), \ |
| _arg1,_arg2,_arg3,_arg4,0); \ |
| } while (0) |
| |
| #define DO_PthAPIerror(_fnnameF, _errF) \ |
| do { \ |
| const char* _fnname = (_fnnameF); \ |
| long _err = (long)(int)(_errF); \ |
| const char* _errstr = lame_strerror(_err); \ |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTH_API_ERROR, \ |
| char*,_fnname, \ |
| long,_err, char*,_errstr); \ |
| } while (0) |
| |
| |
| /* Needed for older glibcs (2.3 and older, at least) who don't |
| otherwise "know" about pthread_rwlock_anything or about |
| PTHREAD_MUTEX_RECURSIVE (amongst things). */ |
| #define _GNU_SOURCE 1 |
| |
| #include <stdio.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <pthread.h> |
| |
| /* A standalone memcmp. */ |
| __attribute__((noinline)) |
| static int my_memcmp ( const void* ptr1, const void* ptr2, size_t size) |
| { |
| const unsigned char* uchar_ptr1 = (const unsigned char*) ptr1; |
| const unsigned char* uchar_ptr2 = (const unsigned char*) ptr2; |
| size_t i; |
| for (i = 0; i < size; ++i) { |
| if (uchar_ptr1[i] != uchar_ptr2[i]) |
| return (uchar_ptr1[i] < uchar_ptr2[i]) ? -1 : 1; |
| } |
| return 0; |
| } |
| |
| /* A lame version of strerror which doesn't use the real libc |
| strerror_r, since using the latter just generates endless more |
| threading errors (glibc goes off and does tons of crap w.r.t. |
| locales etc) */ |
| static const HChar* lame_strerror ( long err ) |
| { |
| switch (err) { |
| case EPERM: return "EPERM: Operation not permitted"; |
| case ENOENT: return "ENOENT: No such file or directory"; |
| case ESRCH: return "ESRCH: No such process"; |
| case EINTR: return "EINTR: Interrupted system call"; |
| case EBADF: return "EBADF: Bad file number"; |
| case EAGAIN: return "EAGAIN: Try again"; |
| case ENOMEM: return "ENOMEM: Out of memory"; |
| case EACCES: return "EACCES: Permission denied"; |
| case EFAULT: return "EFAULT: Bad address"; |
| case EEXIST: return "EEXIST: File exists"; |
| case EINVAL: return "EINVAL: Invalid argument"; |
| case EMFILE: return "EMFILE: Too many open files"; |
| case ENOSYS: return "ENOSYS: Function not implemented"; |
| case EOVERFLOW: return "EOVERFLOW: Value too large " |
| "for defined data type"; |
| case EBUSY: return "EBUSY: Device or resource busy"; |
| case ETIMEDOUT: return "ETIMEDOUT: Connection timed out"; |
| case EDEADLK: return "EDEADLK: Resource deadlock would occur"; |
| case EOPNOTSUPP: return "EOPNOTSUPP: Operation not supported on " |
| "transport endpoint"; /* honest, guv */ |
| case ETIME: return "ETIME: Timer expired"; |
| default: return "hg_intercepts.c: lame_strerror(): " |
| "unhandled case -- please fix me!"; |
| } |
| } |
| |
| #if defined(VGO_solaris) |
| /* |
| * Solaris provides higher throughput, parallelism and scalability than other |
| * operating systems, at the cost of more fine-grained locking activity. |
| * This means for example that when a thread is created under Linux, just one |
| * big lock in glibc is used for all thread setup. Solaris libc uses several |
| * fine-grained locks and the creator thread resumes its activities as soon |
| * as possible, leaving for example stack and TLS setup activities to the |
| * created thread. |
| * |
| * This situation confuses Helgrind as it assumes there is some false ordering |
| * in place between creator and created thread; and therefore many types of |
| * race conditions in the application would not be reported. To prevent such |
| * false ordering, command line option --ignore-thread-creation is set to |
| * 'yes' by default on Solaris. All activity (loads, stores, client requests) |
| * is therefore ignored during: |
| * - pthread_create() call in the creator thread [libc.so] |
| * - thread creation phase (stack and TLS setup) in the created thread [libc.so] |
| * |
| * As explained in the comments for _ti_bind_guard(), whenever the runtime |
| * linker has to perform any activity (such as resolving a symbol), it protects |
| * its data structures by calling into rt_bind_guard() which in turn invokes |
| * _ti_bind_guard() in libc. Pointers to _ti_bind_guard() and _ti_bind_clear() |
| * are passed from libc to runtime linker in _ld_libc() call during libc_init(). |
| * All activity is also ignored during: |
| * - runtime dynamic linker work between rt_bind_guard() and rt_bind_clear() |
| * calls [ld.so] |
| * |
| * This also means that Helgrind does not report race conditions in libc (when |
| * --ignore-thread-creation=yes) and runtime linker itself (unconditionally) |
| * during these ignored sequences. |
| */ |
| |
| #include "pub_tool_libcassert.h" |
| #include "pub_tool_vki.h" |
| |
| /* |
| * Original function pointers for _ti_bind_guard() and _ti_bind_clear() |
| * from libc. They are intercepted in function wrapper of _ld_libc(). |
| */ |
| typedef int (*hg_rtld_guard_fn)(int flags); |
| static hg_rtld_guard_fn hg_rtld_bind_guard = NULL; |
| static hg_rtld_guard_fn hg_rtld_bind_clear = NULL; |
| |
| static void hg_init(void) __attribute__((constructor)); |
| static void hg_init(void) |
| { |
| if ((hg_rtld_bind_guard == NULL) || (hg_rtld_bind_clear == NULL)) { |
| fprintf(stderr, |
| "Bind guard functions for the runtime linker (ld.so.1) were not intercepted.\n" |
| "This means the interface between libc and runtime linker changed\n" |
| "and Helgrind needs to be ported properly. Giving up.\n"); |
| tl_assert(0); |
| } |
| } |
| |
| /* |
| * Intercepts for _ti_bind_guard() and _ti_bind_clear() functions from libc. |
| * These are intercepted during _ld_libc() call by identifying CI_BIND_GUARD |
| * and CI_BIND_CLEAR, to provide resilience against function renaming. |
| */ |
| static int _ti_bind_guard_intercept_WRK(int flags) |
| { |
| VALGRIND_DO_CLIENT_REQUEST_STMT(_VG_USERREQ__HG_RTLD_BIND_GUARD, |
| flags, 0, 0, 0, 0); |
| return hg_rtld_bind_guard(flags); |
| } |
| |
| static int _ti_bind_clear_intercept_WRK(int flags) |
| { |
| int ret = hg_rtld_bind_clear(flags); |
| VALGRIND_DO_CLIENT_REQUEST_STMT(_VG_USERREQ__HG_RTLD_BIND_CLEAR, |
| flags, 0, 0, 0, 0); |
| return ret; |
| } |
| |
| /* |
| * Wrapped _ld_libc() from the runtime linker ld.so.1. |
| */ |
| void I_WRAP_SONAME_FNNAME_ZZ(VG_Z_LD_SO_1, ZuldZulibc)(vki_Lc_interface *ptr); |
| void I_WRAP_SONAME_FNNAME_ZZ(VG_Z_LD_SO_1, ZuldZulibc)(vki_Lc_interface *ptr) |
| { |
| OrigFn fn; |
| int tag; |
| |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| vki_Lc_interface *funcs = ptr; |
| for (tag = funcs->ci_tag; tag != 0; tag = (++funcs)->ci_tag) { |
| switch (tag) { |
| case VKI_CI_BIND_GUARD: |
| if (funcs->vki_ci_un.ci_func != _ti_bind_guard_intercept_WRK) { |
| hg_rtld_bind_guard = funcs->vki_ci_un.ci_func; |
| funcs->vki_ci_un.ci_func = _ti_bind_guard_intercept_WRK; |
| } |
| break; |
| case VKI_CI_BIND_CLEAR: |
| if (funcs->vki_ci_un.ci_func != _ti_bind_clear_intercept_WRK) { |
| hg_rtld_bind_clear = funcs->vki_ci_un.ci_func; |
| funcs->vki_ci_un.ci_func = _ti_bind_clear_intercept_WRK; |
| } |
| break; |
| } |
| } |
| |
| CALL_FN_v_W(fn, ptr); |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| /*----------------------------------------------------------------*/ |
| /*--- pthread_create, pthread_join, pthread_exit ---*/ |
| /*----------------------------------------------------------------*/ |
| |
| static void* mythread_wrapper ( void* xargsV ) |
| { |
| volatile Word* xargs = (volatile Word*) xargsV; |
| void*(*fn)(void*) = (void*(*)(void*))xargs[0]; |
| void* arg = (void*)xargs[1]; |
| pthread_t me = pthread_self(); |
| /* Tell the tool what my pthread_t is. */ |
| DO_CREQ_v_W(_VG_USERREQ__HG_SET_MY_PTHREAD_T, CREQ_PTHREAD_T, me); |
| /* allow the parent to proceed. We can't let it proceed until |
| we're ready because (1) we need to make sure it doesn't exit and |
| hence deallocate xargs[] while we still need it, and (2) we |
| don't want either parent nor child to proceed until the tool has |
| been notified of the child's pthread_t. |
| |
| Note that parent and child access args[] without a lock, |
| effectively using args[2] as a spinlock in order to get the |
| parent to wait until the child passes this point. The parent |
| disables checking on xargs[] before creating the child and |
| re-enables it once the child goes past this point, so the user |
| never sees the race. The previous approach (suppressing the |
| resulting error) was flawed, because it could leave shadow |
| memory for args[] in a state in which subsequent use of it by |
| the parent would report further races. */ |
| xargs[2] = 0; |
| /* Now we can no longer safely use xargs[]. */ |
| return (void*) fn( (void*)arg ); |
| } |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_create@GLIBC_2.0 |
| // glibc: pthread_create@@GLIBC_2.1 |
| // glibc: pthread_create@@GLIBC_2.2.5 |
| // darwin: pthread_create |
| // darwin: pthread_create_suspended_np (trapped) |
| // |
| /* ensure this has its own frame, so as to make it more distinguishable |
| in suppressions */ |
| __attribute__((noinline)) |
| static int pthread_create_WRK(pthread_t *thread, const pthread_attr_t *attr, |
| void *(*start) (void *), void *arg) |
| { |
| int ret; |
| OrigFn fn; |
| volatile Word xargs[3]; |
| |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_create wrapper"); fflush(stderr); |
| } |
| xargs[0] = (Word)start; |
| xargs[1] = (Word)arg; |
| xargs[2] = 1; /* serves as a spinlock -- sigh */ |
| /* Disable checking on the spinlock and the two words used to |
| convey args to the child. Basically we need to make it appear |
| as if the child never accessed this area, since merely |
| suppressing the resulting races does not address the issue that |
| that piece of the parent's stack winds up in the "wrong" state |
| and therefore may give rise to mysterious races when the parent |
| comes to re-use this piece of stack in some other frame. */ |
| VALGRIND_HG_DISABLE_CHECKING(&xargs, sizeof(xargs)); |
| |
| VALGRIND_DO_CLIENT_REQUEST_STMT(_VG_USERREQ__HG_PTHREAD_CREATE_BEGIN, |
| 0, 0, 0, 0, 0); |
| CALL_FN_W_WWWW(ret, fn, thread,attr,mythread_wrapper,&xargs[0]); |
| VALGRIND_DO_CLIENT_REQUEST_STMT(_VG_USERREQ__HG_PTHREAD_CREATE_END, |
| 0, 0, 0, 0, 0); |
| |
| if (ret == 0) { |
| /* we have to wait for the child to notify the tool of its |
| pthread_t before continuing */ |
| while (xargs[2] != 0) { |
| /* Do nothing. We need to spin until the child writes to |
| xargs[2]. However, that can lead to starvation in the |
| child and very long delays (eg, tc19_shadowmem on |
| ppc64-linux Fedora Core 6). So yield the cpu if we can, |
| to let the child run at the earliest available |
| opportunity. */ |
| sched_yield(); |
| } |
| } else { |
| DO_PthAPIerror( "pthread_create", ret ); |
| } |
| |
| /* Reenable checking on the area previously used to communicate |
| with the child. */ |
| VALGRIND_HG_ENABLE_CHECKING(&xargs, sizeof(xargs)); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: pth_create -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZucreateZAZa, // pthread_create@* |
| pthread_t *thread, const pthread_attr_t *attr, |
| void *(*start) (void *), void *arg) { |
| return pthread_create_WRK(thread, attr, start, arg); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZucreate, // pthread_create |
| pthread_t *thread, const pthread_attr_t *attr, |
| void *(*start) (void *), void *arg) { |
| return pthread_create_WRK(thread, attr, start, arg); |
| } |
| PTH_FUNC(int, pthreadZucreateZuZa, // pthread_create_* |
| pthread_t *thread, const pthread_attr_t *attr, |
| void *(*start) (void *), void *arg) { |
| // trap anything else |
| assert(0); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, pthreadZucreate, // pthread_create |
| pthread_t *thread, const pthread_attr_t *attr, |
| void *(*start) (void *), void *arg) { |
| return pthread_create_WRK(thread, attr, start, arg); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| #if defined(VGO_solaris) |
| /* Solaris also provides thr_create() in addition to pthread_create(). |
| * Both pthread_create(3C) and thr_create(3C) are based on private |
| * _thrp_create(). |
| */ |
| __attribute__((noinline)) |
| static int thr_create_WRK(void *stk, size_t stksize, void *(*start)(void *), |
| void *arg, long flags, thread_t *new_thread) |
| { |
| int ret; |
| OrigFn fn; |
| volatile Word xargs[3]; |
| |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< thr_create wrapper"); fflush(stderr); |
| } |
| xargs[0] = (Word)start; |
| xargs[1] = (Word)arg; |
| xargs[2] = 1; /* serves as a spinlock -- sigh */ |
| /* See comments in pthread_create_WRK() */ |
| VALGRIND_HG_DISABLE_CHECKING(&xargs, sizeof(xargs)); |
| |
| VALGRIND_DO_CLIENT_REQUEST_STMT(_VG_USERREQ__HG_PTHREAD_CREATE_BEGIN, |
| 0, 0, 0, 0, 0); |
| CALL_FN_W_6W(ret, fn, stk, stksize, mythread_wrapper, start, flags, |
| new_thread); |
| VALGRIND_DO_CLIENT_REQUEST_STMT(_VG_USERREQ__HG_PTHREAD_CREATE_END, |
| 0, 0, 0, 0, 0); |
| |
| if (ret == 0) { |
| while (xargs[2] != 0) { |
| /* See comments in pthread_create_WRK(). */ |
| sched_yield(); |
| } |
| } else { |
| DO_PthAPIerror("thr_create", ret); |
| } |
| |
| VALGRIND_HG_ENABLE_CHECKING(&xargs, sizeof(xargs)); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: thr_create -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| PTH_FUNC(int, thrZucreate, // thr_create |
| void *stk, size_t stksize, void *(*start)(void *), |
| void *arg, long flags, thread_t *new_thread) { |
| return thr_create_WRK(stk, stksize, start, arg, flags, new_thread); |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_join |
| // darwin: pthread_join |
| // darwin: pthread_join$NOCANCEL$UNIX2003 |
| // darwin pthread_join$UNIX2003 |
| __attribute__((noinline)) |
| static int pthread_join_WRK(pthread_t thread, void** value_pointer) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_join wrapper"); fflush(stderr); |
| } |
| |
| CALL_FN_W_WW(ret, fn, thread,value_pointer); |
| |
| /* At least with NPTL as the thread library, this is safe because |
| it is guaranteed (by NPTL) that the joiner will completely gone |
| before pthread_join (the original) returns. See email below.*/ |
| if (ret == 0 /*success*/) { |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_JOIN_POST, CREQ_PTHREAD_T, thread); |
| } else { |
| DO_PthAPIerror( "pthread_join", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: pth_join -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZujoin, // pthread_join |
| pthread_t thread, void** value_pointer) { |
| return pthread_join_WRK(thread, value_pointer); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZujoinZa, // pthread_join* |
| pthread_t thread, void** value_pointer) { |
| return pthread_join_WRK(thread, value_pointer); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, pthreadZujoin, // pthread_join |
| pthread_t thread, void** value_pointer) { |
| return pthread_join_WRK(thread, value_pointer); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| /* Behaviour of pthread_join on NPTL: |
| |
| Me: |
| I have a question re the NPTL pthread_join implementation. |
| |
| Suppose I am the thread 'stayer'. |
| |
| If I call pthread_join(quitter), is it guaranteed that the |
| thread 'quitter' has really exited before pthread_join returns? |
| |
| IOW, is it guaranteed that 'quitter' will not execute any further |
| instructions after pthread_join returns? |
| |
| I believe this is true based on the following analysis of |
| glibc-2.5 sources. However am not 100% sure and would appreciate |
| confirmation. |
| |
| 'quitter' will be running start_thread() in nptl/pthread_create.c |
| |
| The last action of start_thread() is to exit via |
| __exit_thread_inline(0), which simply does sys_exit |
| (nptl/pthread_create.c:403) |
| |
| 'stayer' meanwhile is waiting for lll_wait_tid (pd->tid) |
| (call at nptl/pthread_join.c:89) |
| |
| As per comment at nptl/sysdeps/unix/sysv/linux/i386/lowlevellock.h:536, |
| lll_wait_tid will not return until kernel notifies via futex |
| wakeup that 'quitter' has terminated. |
| |
| Hence pthread_join cannot return until 'quitter' really has |
| completely disappeared. |
| |
| Drepper: |
| > As per comment at nptl/sysdeps/unix/sysv/linux/i386/lowlevellock.h:536, |
| > lll_wait_tid will not return until kernel notifies via futex |
| > wakeup that 'quitter' has terminated. |
| That's the key. The kernel resets the TID field after the thread is |
| done. No way the joiner can return before the thread is gone. |
| */ |
| |
| #if defined(VGO_solaris) |
| /* Solaris also provides thr_join() in addition to pthread_join(). |
| * Both pthread_join(3C) and thr_join(3C) are based on private _thrp_join(). |
| * |
| * :TODO: No functionality is currently provided for joinee == 0 and departed. |
| * This would require another client request, of course. |
| */ |
| __attribute__((noinline)) |
| static int thr_join_WRK(thread_t joinee, thread_t *departed, void **thread_return) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< thr_join wrapper"); fflush(stderr); |
| } |
| |
| CALL_FN_W_WWW(ret, fn, joinee, departed, thread_return); |
| |
| if (ret == 0 /*success*/) { |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_JOIN_POST, CREQ_PTHREAD_T, joinee); |
| } else { |
| DO_PthAPIerror("thr_join", ret); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: thr_join -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| PTH_FUNC(int, thrZujoin, // thr_join |
| thread_t joinee, thread_t *departed, void **thread_return) { |
| return thr_join_WRK(joinee, departed, thread_return); |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| //----------------------------------------------------------- |
| // Ada gcc gnat runtime: |
| // The gnat gcc Ada runtime does not use pthread_join. Instead, it uses |
| // a combination of other pthread primitives to ensure a child thread |
| // is gone. This combination is somewhat functionally equivalent to a |
| // pthread_join. |
| // We wrap two hook procedures called by the gnat gcc Ada runtime |
| // that allows helgrind to understand the semantic of Ada task dependencies |
| // and termination. |
| |
| // System.Tasking.Debug.Master_Hook is called by a task Dependent to |
| // indicate that its master is identified by master+master_level. |
| void I_WRAP_SONAME_FNNAME_ZU |
| (Za, |
| system__tasking__debug__master_hook) |
| (void *dependent, void *master, int master_level); |
| void I_WRAP_SONAME_FNNAME_ZU |
| (Za, |
| system__tasking__debug__master_hook) |
| (void *dependent, void *master, int master_level) |
| { |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_GNAT_FNS) { |
| fprintf(stderr, "<< GNAT master_hook wrapper " |
| "dependent %p master %p master_level %d\n", |
| dependent, master, master_level); fflush(stderr); |
| } |
| |
| // We call the wrapped function, even if it is a null body. |
| CALL_FN_v_WWW(fn, dependent, master, master_level); |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_GNAT_MASTER_HOOK, |
| void*,dependent, void*,master, |
| Word, (Word)master_level); |
| |
| if (TRACE_GNAT_FNS) { |
| fprintf(stderr, " :: GNAT master_hook >>\n"); |
| } |
| } |
| |
| // System.Tasking.Debug.Master_Completed_Hook is called by a task to |
| // indicate that it has completed a master. |
| // This indicates that all its Dependent tasks (that identified themselves |
| // with the Master_Hook call) are terminated. Helgrind can consider |
| // at this point that the equivalent of a 'pthread_join' has been done |
| // between self_id and all dependent tasks at master_level. |
| void I_WRAP_SONAME_FNNAME_ZU |
| (Za, |
| system__tasking__debug__master_completed_hook) |
| (void *self_id, int master_level); |
| void I_WRAP_SONAME_FNNAME_ZU |
| (Za, |
| system__tasking__debug__master_completed_hook) |
| (void *self_id, int master_level) |
| { |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_GNAT_FNS) { |
| fprintf(stderr, "<< GNAT master_completed_hook wrapper " |
| "self_id %p master_level %d\n", |
| self_id, master_level); fflush(stderr); |
| } |
| |
| // We call the wrapped function, even if it is a null body. |
| CALL_FN_v_WW(fn, self_id, master_level); |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_GNAT_MASTER_COMPLETED_HOOK, |
| void*,self_id, Word,(Word)master_level); |
| |
| if (TRACE_GNAT_FNS) { |
| fprintf(stderr, " :: GNAT master_completed_hook >>\n"); |
| } |
| } |
| |
| /*----------------------------------------------------------------*/ |
| /*--- pthread_mutex_t functions ---*/ |
| /*----------------------------------------------------------------*/ |
| |
| /* Handled: pthread_mutex_init pthread_mutex_destroy |
| pthread_mutex_lock |
| pthread_mutex_trylock pthread_mutex_timedlock |
| pthread_mutex_unlock |
| */ |
| |
| //----------------------------------------------------------- |
| #if !defined(VGO_solaris) |
| // glibc: pthread_mutex_init |
| // darwin: pthread_mutex_init |
| PTH_FUNC(int, pthreadZumutexZuinit, // pthread_mutex_init |
| pthread_mutex_t *mutex, |
| pthread_mutexattr_t* attr) |
| { |
| int ret; |
| long mbRec; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_mxinit %p", mutex); fflush(stderr); |
| } |
| |
| mbRec = 0; |
| if (attr) { |
| int ty, zzz; |
| zzz = pthread_mutexattr_gettype(attr, &ty); |
| if (zzz == 0 && ty == PTHREAD_MUTEX_RECURSIVE) |
| mbRec = 1; |
| } |
| |
| CALL_FN_W_WW(ret, fn, mutex,attr); |
| |
| if (ret == 0 /*success*/) { |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_INIT_POST, |
| pthread_mutex_t*,mutex, long,mbRec); |
| } else { |
| DO_PthAPIerror( "pthread_mutex_init", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: mxinit -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| |
| #else /* VGO_solaris */ |
| |
| // Solaris: mutex_init (pthread_mutex_init calls here) |
| PTH_FUNC(int, mutexZuinit, // mutex_init |
| mutex_t *mutex, int type, void *arg) |
| { |
| int ret; |
| long mbRec; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< mxinit %p", mutex); fflush(stderr); |
| } |
| |
| mbRec = ((type & LOCK_RECURSIVE) != 0) ? 1 : 0; |
| |
| CALL_FN_W_WWW(ret, fn, mutex, type, arg); |
| |
| if (ret == 0 /*success*/) { |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_INIT_POST, |
| mutex_t *, mutex, long, mbRec); |
| } else { |
| DO_PthAPIerror("mutex_init", ret); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: mxinit -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_mutex_destroy |
| // darwin: pthread_mutex_destroy |
| // Solaris: mutex_destroy (pthread_mutex_destroy is a weak alias) |
| __attribute__((noinline)) |
| static int mutex_destroy_WRK(pthread_mutex_t *mutex) |
| { |
| int ret; |
| unsigned long mutex_is_init; |
| OrigFn fn; |
| |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_mxdestroy %p", mutex); fflush(stderr); |
| } |
| |
| if (mutex != NULL) { |
| static const pthread_mutex_t mutex_init = PTHREAD_MUTEX_INITIALIZER; |
| mutex_is_init = my_memcmp(mutex, &mutex_init, sizeof(*mutex)) == 0; |
| } else { |
| mutex_is_init = 0; |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_DESTROY_PRE, |
| pthread_mutex_t*, mutex, unsigned long, mutex_is_init); |
| |
| CALL_FN_W_W(ret, fn, mutex); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_mutex_destroy", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: mxdestroy -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| |
| #if defined(VGO_linux) || defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZumutexZudestroy, // pthread_mutex_destroy |
| pthread_mutex_t *mutex) { |
| return mutex_destroy_WRK(mutex); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, mutexZudestroy, // mutex_destroy |
| pthread_mutex_t *mutex) { |
| return mutex_destroy_WRK(mutex); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_mutex_lock |
| // darwin: pthread_mutex_lock |
| // Solaris: mutex_lock (pthread_mutex_lock is a weak alias) |
| __attribute__((noinline)) |
| static int mutex_lock_WRK(pthread_mutex_t *mutex) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_mxlock %p", mutex); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_PRE, |
| pthread_mutex_t*,mutex, long,0/*!isTryLock*/); |
| |
| CALL_FN_W_W(ret, fn, mutex); |
| |
| /* There's a hole here: libpthread now knows the lock is locked, |
| but the tool doesn't, so some other thread could run and detect |
| that the lock has been acquired by someone (this thread). Does |
| this matter? Not sure, but I don't think so. */ |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_POST, |
| pthread_mutex_t *, mutex, long, (ret == 0) ? True : False); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_mutex_lock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: mxlock -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| |
| #if defined(VGO_linux) || defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZumutexZulock, // pthread_mutex_lock |
| pthread_mutex_t *mutex) { |
| return mutex_lock_WRK(mutex); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, mutexZulock, // mutex_lock |
| pthread_mutex_t *mutex) { |
| return mutex_lock_WRK(mutex); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| #if defined(VGO_solaris) |
| /* Internal to libc. Mutex is usually initialized only implicitly, |
| * by zeroing mutex_t structure. |
| */ |
| __attribute__((noinline)) |
| PTH_FUNC(void, lmutexZulock, // lmutex_lock |
| mutex_t *mutex) |
| { |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< lmxlock %p", mutex); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_PRE, |
| mutex_t *, mutex, long, 0 /*!isTryLock*/); |
| CALL_FN_v_W(fn, mutex); |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_POST, |
| mutex_t *, mutex, long, True); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: lmxlock >>\n"); |
| } |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_mutex_trylock |
| // darwin: pthread_mutex_trylock |
| // Solaris: mutex_trylock (pthread_mutex_trylock is a weak alias) |
| // |
| // pthread_mutex_trylock. The handling needed here is very similar |
| // to that for pthread_mutex_lock, except that we need to tell |
| // the pre-lock creq that this is a trylock-style operation, and |
| // therefore not to complain if the lock is nonrecursive and |
| // already locked by this thread -- because then it'll just fail |
| // immediately with EBUSY. |
| __attribute__((noinline)) |
| static int mutex_trylock_WRK(pthread_mutex_t *mutex) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_mxtrylock %p", mutex); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_PRE, |
| pthread_mutex_t*,mutex, long,1/*isTryLock*/); |
| |
| CALL_FN_W_W(ret, fn, mutex); |
| |
| /* There's a hole here: libpthread now knows the lock is locked, |
| but the tool doesn't, so some other thread could run and detect |
| that the lock has been acquired by someone (this thread). Does |
| this matter? Not sure, but I don't think so. */ |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_POST, |
| pthread_mutex_t *, mutex, long, (ret == 0) ? True : False); |
| |
| if (ret != 0) { |
| if (ret != EBUSY) |
| DO_PthAPIerror( "pthread_mutex_trylock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: mxtrylock -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| |
| #if defined(VGO_linux) || defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZumutexZutrylock, // pthread_mutex_trylock |
| pthread_mutex_t *mutex) { |
| return mutex_trylock_WRK(mutex); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, mutexZutrylock, // mutex_trylock |
| pthread_mutex_t *mutex) { |
| return mutex_trylock_WRK(mutex); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_mutex_timedlock |
| // darwin: (doesn't appear to exist) |
| // Solaris: pthread_mutex_timedlock |
| // |
| // pthread_mutex_timedlock. Identical logic to pthread_mutex_trylock. |
| __attribute__((noinline)) |
| static int mutex_timedlock_WRK(pthread_mutex_t *mutex, |
| void *timeout) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_mxtimedlock %p %p", mutex, timeout); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_PRE, |
| pthread_mutex_t*,mutex, long,1/*isTryLock-ish*/); |
| |
| CALL_FN_W_WW(ret, fn, mutex,timeout); |
| |
| /* There's a hole here: libpthread now knows the lock is locked, |
| but the tool doesn't, so some other thread could run and detect |
| that the lock has been acquired by someone (this thread). Does |
| this matter? Not sure, but I don't think so. */ |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_POST, |
| pthread_mutex_t *, mutex, long, (ret == 0) ? True : False); |
| |
| if (ret != 0) { |
| if (ret != ETIMEDOUT) |
| DO_PthAPIerror( "pthread_mutex_timedlock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: mxtimedlock -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| |
| PTH_FUNC(int, pthreadZumutexZutimedlock, // pthread_mutex_timedlock |
| pthread_mutex_t *mutex, |
| void *timeout) { |
| return mutex_timedlock_WRK(mutex, timeout); |
| } |
| #if defined(VGO_solaris) |
| PTH_FUNC(int, pthreadZumutexZureltimedlock, // pthread_mutex_reltimedlock |
| pthread_mutex_t *mutex, |
| void *timeout) { |
| return mutex_timedlock_WRK(mutex, timeout); |
| } |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_mutex_unlock |
| // darwin: pthread_mutex_unlock |
| // Solaris: mutex_unlock (pthread_mutex_unlock is a weak alias) |
| __attribute__((noinline)) |
| static int mutex_unlock_WRK(pthread_mutex_t *mutex) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_mxunlk %p", mutex); fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_MUTEX_UNLOCK_PRE, |
| pthread_mutex_t*,mutex); |
| |
| CALL_FN_W_W(ret, fn, mutex); |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_MUTEX_UNLOCK_POST, |
| pthread_mutex_t*,mutex); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_mutex_unlock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " mxunlk -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| |
| #if defined(VGO_linux) || defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZumutexZuunlock, // pthread_mutex_unlock |
| pthread_mutex_t *mutex) { |
| return mutex_unlock_WRK(mutex); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, mutexZuunlock, // mutex_unlock |
| pthread_mutex_t *mutex) { |
| return mutex_unlock_WRK(mutex); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| #if defined(VGO_solaris) |
| /* Internal to libc. */ |
| __attribute__((noinline)) |
| PTH_FUNC(void, lmutexZuunlock, // lmutex_unlock |
| mutex_t *mutex) |
| { |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< lmxunlk %p", mutex); fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_MUTEX_UNLOCK_PRE, |
| mutex_t *, mutex); |
| CALL_FN_v_W(fn, mutex); |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_MUTEX_UNLOCK_POST, |
| mutex_t*, mutex); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " lmxunlk >>\n"); |
| } |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| /*----------------------------------------------------------------*/ |
| /*--- pthread_cond_t functions ---*/ |
| /*----------------------------------------------------------------*/ |
| |
| /* Handled: pthread_cond_wait pthread_cond_timedwait |
| pthread_cond_signal pthread_cond_broadcast |
| pthread_cond_init |
| pthread_cond_destroy |
| */ |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_cond_wait@GLIBC_2.2.5 |
| // glibc: pthread_cond_wait@@GLIBC_2.3.2 |
| // darwin: pthread_cond_wait |
| // darwin: pthread_cond_wait$NOCANCEL$UNIX2003 |
| // darwin: pthread_cond_wait$UNIX2003 |
| // Solaris: cond_wait (pthread_cond_wait is built atop of cond_wait) |
| // |
| __attribute__((noinline)) |
| static int pthread_cond_wait_WRK(pthread_cond_t* cond, |
| pthread_mutex_t* mutex) |
| { |
| int ret; |
| OrigFn fn; |
| unsigned long mutex_is_valid; |
| |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_cond_wait %p %p", cond, mutex); |
| fflush(stderr); |
| } |
| |
| /* Tell the tool a cond-wait is about to happen, so it can check |
| for bogus argument values. In return it tells us whether it |
| thinks the mutex is valid or not. */ |
| DO_CREQ_W_WW(mutex_is_valid, |
| _VG_USERREQ__HG_PTHREAD_COND_WAIT_PRE, |
| pthread_cond_t*,cond, pthread_mutex_t*,mutex); |
| assert(mutex_is_valid == 1 || mutex_is_valid == 0); |
| |
| /* Tell the tool we're about to drop the mutex. This reflects the |
| fact that in a cond_wait, we show up holding the mutex, and the |
| call atomically drops the mutex and waits for the cv to be |
| signalled. */ |
| if (mutex_is_valid) { |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_MUTEX_UNLOCK_PRE, |
| pthread_mutex_t*,mutex); |
| } |
| |
| CALL_FN_W_WW(ret, fn, cond,mutex); |
| |
| /* this conditional look stupid, but compare w/ same logic for |
| pthread_cond_timedwait below */ |
| if (mutex_is_valid) { |
| /* and now we have the mutex again if (ret == 0) */ |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_POST, |
| pthread_mutex_t *, mutex, long, (ret == 0) ? True : False); |
| } |
| |
| DO_CREQ_v_WWWW(_VG_USERREQ__HG_PTHREAD_COND_WAIT_POST, |
| pthread_cond_t*,cond, pthread_mutex_t*,mutex, long,0, |
| long, (ret == 0 && mutex_is_valid) ? True : False); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_cond_wait", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " cowait -> %d >>\n", ret); |
| } |
| |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZucondZuwaitZAZa, // pthread_cond_wait@* |
| pthread_cond_t* cond, pthread_mutex_t* mutex) { |
| return pthread_cond_wait_WRK(cond, mutex); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZucondZuwaitZa, // pthread_cond_wait* |
| pthread_cond_t* cond, pthread_mutex_t* mutex) { |
| return pthread_cond_wait_WRK(cond, mutex); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, condZuwait, // cond_wait |
| pthread_cond_t *cond, pthread_mutex_t *mutex) { |
| return pthread_cond_wait_WRK(cond, mutex); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_cond_timedwait@@GLIBC_2.3.2 |
| // glibc: pthread_cond_timedwait@GLIBC_2.2.5 |
| // glibc: pthread_cond_timedwait@GLIBC_2.0 |
| // darwin: pthread_cond_timedwait |
| // darwin: pthread_cond_timedwait$NOCANCEL$UNIX2003 |
| // darwin: pthread_cond_timedwait$UNIX2003 |
| // darwin: pthread_cond_timedwait_relative_np (trapped) |
| // Solaris: cond_timedwait (pthread_cond_timedwait is built on cond_timedwait) |
| // Solaris: cond_reltimedwait (pthread_cond_reltimedwait_np is built on this) |
| // |
| __attribute__((noinline)) |
| static int pthread_cond_timedwait_WRK(pthread_cond_t* cond, |
| pthread_mutex_t* mutex, |
| struct timespec* abstime, |
| int timeout_error) |
| { |
| int ret; |
| OrigFn fn; |
| unsigned long mutex_is_valid; |
| Bool abstime_is_valid; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_cond_timedwait %p %p %p", |
| cond, mutex, abstime); |
| fflush(stderr); |
| } |
| |
| /* Tell the tool a cond-wait is about to happen, so it can check |
| for bogus argument values. In return it tells us whether it |
| thinks the mutex is valid or not. */ |
| DO_CREQ_W_WW(mutex_is_valid, |
| _VG_USERREQ__HG_PTHREAD_COND_WAIT_PRE, |
| pthread_cond_t*,cond, pthread_mutex_t*,mutex); |
| assert(mutex_is_valid == 1 || mutex_is_valid == 0); |
| |
| abstime_is_valid = abstime->tv_nsec >= 0 && abstime->tv_nsec < 1000000000; |
| |
| /* Tell the tool we're about to drop the mutex. This reflects the |
| fact that in a cond_wait, we show up holding the mutex, and the |
| call atomically drops the mutex and waits for the cv to be |
| signalled. */ |
| if (mutex_is_valid && abstime_is_valid) { |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_MUTEX_UNLOCK_PRE, |
| pthread_mutex_t*,mutex); |
| } |
| |
| CALL_FN_W_WWW(ret, fn, cond,mutex,abstime); |
| |
| if (mutex_is_valid && !abstime_is_valid && ret != EINVAL) { |
| DO_PthAPIerror("Bug in libpthread: pthread_cond_timedwait " |
| "invalid abstime did not cause" |
| " EINVAL", ret); |
| } |
| |
| if (mutex_is_valid && abstime_is_valid) { |
| /* and now we have the mutex again if (ret == 0 || ret == timeout) */ |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_POST, |
| pthread_mutex_t *, mutex, |
| long, (ret == 0 || ret == timeout_error) ? True : False); |
| } |
| |
| DO_CREQ_v_WWWW(_VG_USERREQ__HG_PTHREAD_COND_WAIT_POST, |
| pthread_cond_t*,cond, pthread_mutex_t*,mutex, |
| long,ret == timeout_error, |
| long, (ret == 0 || ret == timeout_error) && mutex_is_valid |
| ? True : False); |
| |
| if (ret != 0 && ret != timeout_error) { |
| DO_PthAPIerror( "pthread_cond_timedwait", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " cotimedwait -> %d >>\n", ret); |
| } |
| |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZucondZutimedwaitZAZa, // pthread_cond_timedwait@* |
| pthread_cond_t* cond, pthread_mutex_t* mutex, |
| struct timespec* abstime) { |
| return pthread_cond_timedwait_WRK(cond, mutex, abstime, ETIMEDOUT); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZucondZutimedwait, // pthread_cond_timedwait |
| pthread_cond_t* cond, pthread_mutex_t* mutex, |
| struct timespec* abstime) { |
| return pthread_cond_timedwait_WRK(cond, mutex, abstime, ETIMEDOUT); |
| } |
| PTH_FUNC(int, pthreadZucondZutimedwaitZDZa, // pthread_cond_timedwait$* |
| pthread_cond_t* cond, pthread_mutex_t* mutex, |
| struct timespec* abstime) { |
| return pthread_cond_timedwait_WRK(cond, mutex, abstime, ETIMEDOUT); |
| } |
| PTH_FUNC(int, pthreadZucondZutimedwaitZuZa, // pthread_cond_timedwait_* |
| pthread_cond_t* cond, pthread_mutex_t* mutex, |
| struct timespec* abstime) { |
| assert(0); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, condZutimedwait, // cond_timedwait |
| pthread_cond_t *cond, pthread_mutex_t *mutex, |
| struct timespec *abstime) { |
| return pthread_cond_timedwait_WRK(cond, mutex, abstime, ETIME); |
| } |
| PTH_FUNC(int, condZureltimedwait, // cond_reltimedwait |
| pthread_cond_t *cond, pthread_mutex_t *mutex, |
| struct timespec *reltime) { |
| return pthread_cond_timedwait_WRK(cond, mutex, reltime, ETIME); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_cond_signal@GLIBC_2.0 |
| // glibc: pthread_cond_signal@GLIBC_2.2.5 |
| // glibc: pthread_cond_signal@@GLIBC_2.3.2 |
| // darwin: pthread_cond_signal |
| // darwin: pthread_cond_signal_thread_np (don't intercept this) |
| // Solaris: cond_signal (pthread_cond_signal is a weak alias) |
| // |
| __attribute__((noinline)) |
| static int pthread_cond_signal_WRK(pthread_cond_t* cond) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_cond_signal %p", cond); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_COND_SIGNAL_PRE, |
| pthread_cond_t*,cond); |
| |
| CALL_FN_W_W(ret, fn, cond); |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_COND_SIGNAL_POST, |
| pthread_cond_t*,cond); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_cond_signal", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " cosig -> %d >>\n", ret); |
| } |
| |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZucondZusignalZAZa, // pthread_cond_signal@* |
| pthread_cond_t* cond) { |
| return pthread_cond_signal_WRK(cond); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZucondZusignal, // pthread_cond_signal |
| pthread_cond_t* cond) { |
| return pthread_cond_signal_WRK(cond); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, condZusignal, // cond_signal |
| pthread_cond_t *cond) { |
| return pthread_cond_signal_WRK(cond); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_cond_broadcast@GLIBC_2.0 |
| // glibc: pthread_cond_broadcast@GLIBC_2.2.5 |
| // glibc: pthread_cond_broadcast@@GLIBC_2.3.2 |
| // darwin: pthread_cond_broadcast |
| // Solaris: cond_broadcast (pthread_cond_broadcast is a weak alias) |
| // |
| // Note, this is pretty much identical, from a dependency-graph |
| // point of view, with cond_signal, so the code is duplicated. |
| // Maybe it should be commoned up. |
| // |
| __attribute__((noinline)) |
| static int pthread_cond_broadcast_WRK(pthread_cond_t* cond) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_cond_broadcast %p", cond); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_COND_BROADCAST_PRE, |
| pthread_cond_t*,cond); |
| |
| CALL_FN_W_W(ret, fn, cond); |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_COND_BROADCAST_POST, |
| pthread_cond_t*,cond); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_cond_broadcast", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " cobro -> %d >>\n", ret); |
| } |
| |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZucondZubroadcastZAZa, // pthread_cond_broadcast@* |
| pthread_cond_t* cond) { |
| return pthread_cond_broadcast_WRK(cond); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZucondZubroadcast, // pthread_cond_broadcast |
| pthread_cond_t* cond) { |
| return pthread_cond_broadcast_WRK(cond); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, condZubroadcast, // cond_broadcast |
| pthread_cond_t *cond) { |
| return pthread_cond_broadcast_WRK(cond); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| // glibc: pthread_cond_init@GLIBC_2.0 |
| // glibc: pthread_cond_init@GLIBC_2.2.5 |
| // glibc: pthread_cond_init@@GLIBC_2.3.2 |
| // darwin: pthread_cond_init |
| // Solaris: cond_init (pthread_cond_init is built atop on this function) |
| // Easy way out: Handling of attr could have been messier. |
| // It turns out that pthread_cond_init under linux ignores |
| // all information in cond_attr, so do we. |
| // FIXME: MacOS X? |
| #if !defined(VGO_solaris) |
| __attribute__((noinline)) |
| static int pthread_cond_init_WRK(pthread_cond_t* cond, pthread_condattr_t *cond_attr) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_cond_init %p", cond); |
| fflush(stderr); |
| } |
| |
| CALL_FN_W_WW(ret, fn, cond, cond_attr); |
| |
| if (ret == 0) { |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_COND_INIT_POST, |
| pthread_cond_t*,cond, pthread_condattr_t*, cond_attr); |
| } else { |
| DO_PthAPIerror( "pthread_cond_init", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " coinit -> %d >>\n", ret); |
| } |
| |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZucondZuinitZAZa, // pthread_cond_init@* |
| pthread_cond_t* cond, pthread_condattr_t* cond_attr) { |
| return pthread_cond_init_WRK(cond, cond_attr); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZucondZuinit, // pthread_cond_init |
| pthread_cond_t* cond, pthread_condattr_t * cond_attr) { |
| return pthread_cond_init_WRK(cond, cond_attr); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| #else /* VGO_solaris */ |
| __attribute__((noinline)) |
| PTH_FUNC(int, condZuinit, // cond_init |
| cond_t *cond, int type, void *arg) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< cond_init %p", cond); fflush(stderr); |
| } |
| |
| CALL_FN_W_WWW(ret, fn, cond, type, arg); |
| |
| if (ret == 0) { |
| /* Luckily evh__HG_PTHREAD_COND_INIT_POST() ignores cond_attr. |
| See also comment for pthread_cond_init_WRK(). */ |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_COND_INIT_POST, |
| cond_t *, cond, void *, NULL); |
| } else { |
| DO_PthAPIerror("cond_init", ret); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " cond_init -> %d >>\n", ret); |
| } |
| |
| return ret; |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_cond_destroy@@GLIBC_2.3.2 |
| // glibc: pthread_cond_destroy@GLIBC_2.2.5 |
| // glibc: pthread_cond_destroy@GLIBC_2.0 |
| // darwin: pthread_cond_destroy |
| // Solaris: cond_destroy (pthread_cond_destroy is a weak alias) |
| // |
| __attribute__((noinline)) |
| static int pthread_cond_destroy_WRK(pthread_cond_t* cond) |
| { |
| int ret; |
| unsigned long cond_is_init; |
| OrigFn fn; |
| |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_cond_destroy %p", cond); |
| fflush(stderr); |
| } |
| |
| if (cond != NULL) { |
| const pthread_cond_t cond_init = PTHREAD_COND_INITIALIZER; |
| cond_is_init = my_memcmp(cond, &cond_init, sizeof(*cond)) == 0; |
| } else { |
| cond_is_init = 0; |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_COND_DESTROY_PRE, |
| pthread_cond_t*, cond, unsigned long, cond_is_init); |
| |
| CALL_FN_W_W(ret, fn, cond); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_cond_destroy", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " codestr -> %d >>\n", ret); |
| } |
| |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZucondZudestroyZAZa, // pthread_cond_destroy@* |
| pthread_cond_t* cond) { |
| return pthread_cond_destroy_WRK(cond); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZucondZudestroy, // pthread_cond_destroy |
| pthread_cond_t* cond) { |
| return pthread_cond_destroy_WRK(cond); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, condZudestroy, // cond_destroy |
| pthread_cond_t *cond) { |
| return pthread_cond_destroy_WRK(cond); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| /*----------------------------------------------------------------*/ |
| /*--- pthread_barrier_t functions ---*/ |
| /*----------------------------------------------------------------*/ |
| |
| #if defined(HAVE_PTHREAD_BARRIER_INIT) |
| |
| /* Handled: pthread_barrier_init |
| pthread_barrier_wait |
| pthread_barrier_destroy |
| |
| Unhandled: pthread_barrierattr_destroy |
| pthread_barrierattr_getpshared |
| pthread_barrierattr_init |
| pthread_barrierattr_setpshared |
| -- are these important? |
| */ |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_barrier_init |
| // darwin: (doesn't appear to exist) |
| // Solaris: pthread_barrier_init |
| PTH_FUNC(int, pthreadZubarrierZuinit, // pthread_barrier_init |
| pthread_barrier_t* bar, |
| pthread_barrierattr_t* attr, unsigned long count) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_barrier_init %p %p %lu", |
| bar, attr, count); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_BARRIER_INIT_PRE, |
| pthread_barrier_t*, bar, |
| unsigned long, count, |
| unsigned long, 0/*!resizable*/); |
| |
| CALL_FN_W_WWW(ret, fn, bar,attr,count); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_barrier_init", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " pthread_barrier_init -> %d >>\n", ret); |
| } |
| |
| return ret; |
| } |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_barrier_wait |
| // darwin: (doesn't appear to exist) |
| // Solaris: pthread_barrier_wait |
| PTH_FUNC(int, pthreadZubarrierZuwait, // pthread_barrier_wait |
| pthread_barrier_t* bar) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_barrier_wait %p", bar); |
| fflush(stderr); |
| } |
| |
| /* That this works correctly, and doesn't screw up when a thread |
| leaving the barrier races round to the front and re-enters while |
| other threads are still leaving it, is quite subtle. See |
| comments in the handler for PTHREAD_BARRIER_WAIT_PRE in |
| hg_main.c. */ |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_BARRIER_WAIT_PRE, |
| pthread_barrier_t*,bar); |
| |
| CALL_FN_W_W(ret, fn, bar); |
| |
| if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) { |
| DO_PthAPIerror( "pthread_barrier_wait", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " pthread_barrier_wait -> %d >>\n", ret); |
| } |
| |
| return ret; |
| } |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_barrier_destroy |
| // darwin: (doesn't appear to exist) |
| // Solaris: pthread_barrier_destroy |
| PTH_FUNC(int, pthreadZubarrierZudestroy, // pthread_barrier_destroy |
| pthread_barrier_t* bar) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_barrier_destroy %p", bar); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_BARRIER_DESTROY_PRE, |
| pthread_barrier_t*,bar); |
| |
| CALL_FN_W_W(ret, fn, bar); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_barrier_destroy", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " pthread_barrier_destroy -> %d >>\n", ret); |
| } |
| |
| return ret; |
| } |
| |
| #endif // defined(HAVE_PTHREAD_BARRIER_INIT) |
| |
| |
| /*----------------------------------------------------------------*/ |
| /*--- pthread_spinlock_t functions ---*/ |
| /*----------------------------------------------------------------*/ |
| |
| #if defined(HAVE_PTHREAD_SPIN_LOCK) \ |
| && !defined(DISABLE_PTHREAD_SPINLOCK_INTERCEPT) |
| |
| /* Handled: pthread_spin_init pthread_spin_destroy |
| pthread_spin_lock pthread_spin_trylock |
| pthread_spin_unlock |
| |
| Unhandled: |
| */ |
| |
| /* This is a nasty kludge, in that glibc "knows" that initialising a |
| spin lock unlocks it, and pthread_spin_{init,unlock} are names for |
| the same function. Hence we have to have a wrapper which does both |
| things, without knowing which the user intended to happen. |
| Solaris has distinct functions for init/unlock but client requests |
| are immutable in helgrind.h so follow the glibc lead. */ |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_spin_init |
| // glibc: pthread_spin_unlock |
| // darwin: (doesn't appear to exist) |
| // Solaris: pthread_spin_init |
| // Solaris: pthread_spin_unlock |
| __attribute__((noinline)) |
| static int pthread_spin_init_or_unlock_WRK(pthread_spinlock_t* lock, |
| int pshared) { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_spin_iORu %p", lock); fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_SPIN_INIT_OR_UNLOCK_PRE, |
| pthread_spinlock_t*, lock); |
| |
| CALL_FN_W_WW(ret, fn, lock,pshared); |
| |
| if (ret == 0 /*success*/) { |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_SPIN_INIT_OR_UNLOCK_POST, |
| pthread_spinlock_t*,lock); |
| } else { |
| DO_PthAPIerror( "pthread_spinlock_{init,unlock}", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: spiniORu -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZuspinZuinit, // pthread_spin_init |
| pthread_spinlock_t* lock, int pshared) { |
| return pthread_spin_init_or_unlock_WRK(lock, pshared); |
| } |
| PTH_FUNC(int, pthreadZuspinZuunlock, // pthread_spin_unlock |
| pthread_spinlock_t* lock) { |
| /* this is never actually called */ |
| return pthread_spin_init_or_unlock_WRK(lock, 0/*pshared*/); |
| } |
| #elif defined(VGO_darwin) |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, pthreadZuspinZuinit, // pthread_spin_init |
| pthread_spinlock_t *lock, int pshared) { |
| return pthread_spin_init_or_unlock_WRK(lock, pshared); |
| } |
| PTH_FUNC(int, pthreadZuspinZuunlock, // pthread_spin_unlock |
| pthread_spinlock_t *lock) { |
| return pthread_spin_init_or_unlock_WRK(lock, 0/*pshared*/); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_spin_destroy |
| // darwin: (doesn't appear to exist) |
| // Solaris: pthread_spin_destroy |
| __attribute__((noinline)) |
| static int pthread_spin_destroy_WRK(pthread_spinlock_t *lock) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_spin_destroy %p", lock); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_SPIN_DESTROY_PRE, |
| pthread_spinlock_t*,lock); |
| |
| CALL_FN_W_W(ret, fn, lock); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_spin_destroy", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: spindestroy -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZuspinZusdestroy, // pthread_spin_destroy |
| pthread_spinlock_t *lock) { |
| return pthread_spin_destroy_WRK(lock); |
| } |
| #elif defined(VGO_darwin) |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, pthreadZuspinZusdestroy, // pthread_spin_destroy |
| pthread_spinlock_t *lock) { |
| return pthread_spin_destroy_WRK(lock); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_spin_lock |
| // darwin: (doesn't appear to exist) |
| // Solaris: pthread_spin_lock |
| __attribute__((noinline)) |
| static int pthread_spin_lock_WRK(pthread_spinlock_t *lock) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_spinlock %p", lock); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_SPIN_LOCK_PRE, |
| pthread_spinlock_t*,lock, long,0/*!isTryLock*/); |
| |
| CALL_FN_W_W(ret, fn, lock); |
| |
| /* There's a hole here: libpthread now knows the lock is locked, |
| but the tool doesn't, so some other thread could run and detect |
| that the lock has been acquired by someone (this thread). Does |
| this matter? Not sure, but I don't think so. */ |
| |
| if (ret == 0 /*success*/) { |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_SPIN_LOCK_POST, |
| pthread_spinlock_t*,lock); |
| } else { |
| DO_PthAPIerror( "pthread_spin_lock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: spinlock -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZuspinZulock, // pthread_spin_lock |
| pthread_spinlock_t *lock) { |
| return pthread_spin_lock_WRK(lock); |
| } |
| #elif defined(VGO_darwin) |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, pthreadZuspinZulock, // pthread_spin_lock |
| pthread_spinlock_t *lock) { |
| return pthread_spin_lock_WRK(lock); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_spin_trylock |
| // darwin: (doesn't appear to exist) |
| // Solaris: pthread_spin_trylock |
| __attribute__((noinline)) |
| static int pthread_spin_trylock_WRK(pthread_spinlock_t *lock) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_spin_trylock %p", lock); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_SPIN_LOCK_PRE, |
| pthread_spinlock_t*,lock, long,1/*isTryLock*/); |
| |
| CALL_FN_W_W(ret, fn, lock); |
| |
| /* There's a hole here: libpthread now knows the lock is locked, |
| but the tool doesn't, so some other thread could run and detect |
| that the lock has been acquired by someone (this thread). Does |
| this matter? Not sure, but I don't think so. */ |
| |
| if (ret == 0 /*success*/) { |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_SPIN_LOCK_POST, |
| pthread_spinlock_t*,lock); |
| } else { |
| if (ret != EBUSY) |
| DO_PthAPIerror( "pthread_spin_trylock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: spin_trylock -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZuspinZutrylock, // pthread_spin_trylock |
| pthread_spinlock_t *lock) { |
| return pthread_spin_trylock_WRK(lock); |
| } |
| #elif defined(VGO_darwin) |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, pthreadZuspinZutrylock, // pthread_spin_trylock |
| pthread_spinlock_t *lock) { |
| return pthread_spin_trylock_WRK(lock); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| #endif // defined(HAVE_PTHREAD_SPIN_LOCK) |
| |
| |
| /*----------------------------------------------------------------*/ |
| /*--- pthread_rwlock_t functions ---*/ |
| /*----------------------------------------------------------------*/ |
| |
| /* Android's pthread.h doesn't say anything about rwlocks, hence these |
| functions have to be conditionally compiled. */ |
| #if defined(HAVE_PTHREAD_RWLOCK_T) |
| |
| /* Handled: pthread_rwlock_init pthread_rwlock_destroy |
| pthread_rwlock_rdlock |
| pthread_rwlock_wrlock |
| pthread_rwlock_unlock |
| pthread_rwlock_tryrdlock |
| pthread_rwlock_trywrlock |
| |
| Unhandled: pthread_rwlock_timedrdlock |
| pthread_rwlock_timedwrlock |
| */ |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_rwlock_init |
| // darwin: pthread_rwlock_init |
| // darwin: pthread_rwlock_init$UNIX2003 |
| // Solaris: rwlock_init (pthread_rwlock_init is built atop of rwlock_init) |
| __attribute__((noinline)) |
| static int pthread_rwlock_init_WRK(pthread_rwlock_t *rwl, |
| pthread_rwlockattr_t* attr) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_rwl_init %p", rwl); fflush(stderr); |
| } |
| |
| CALL_FN_W_WW(ret, fn, rwl,attr); |
| |
| if (ret == 0 /*success*/) { |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_RWLOCK_INIT_POST, |
| pthread_rwlock_t*,rwl); |
| } else { |
| DO_PthAPIerror( "pthread_rwlock_init", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: rwl_init -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZurwlockZuinit, // pthread_rwlock_init |
| pthread_rwlock_t *rwl, |
| pthread_rwlockattr_t* attr) { |
| return pthread_rwlock_init_WRK(rwl, attr); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZurwlockZuinitZa, // pthread_rwlock_init* |
| pthread_rwlock_t *rwl, |
| pthread_rwlockattr_t* attr) { |
| return pthread_rwlock_init_WRK(rwl, attr); |
| } |
| #elif defined(VGO_solaris) |
| static int pthread_rwlock_init_WRK(pthread_rwlock_t *rwl, |
| pthread_rwlockattr_t* attr) |
| __attribute__((unused)); |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| #if defined(VGO_solaris) |
| PTH_FUNC(int, rwlockZuinit, // rwlock_init |
| rwlock_t *rwlock, |
| int type, |
| void *arg) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< rwl_init %p", rwlock); fflush(stderr); |
| } |
| |
| CALL_FN_W_WWW(ret, fn, rwlock, type, arg); |
| |
| if (ret == 0 /*success*/) { |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_RWLOCK_INIT_POST, |
| rwlock_t *, rwlock); |
| } else { |
| DO_PthAPIerror("rwlock_init", ret); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: rwl_init -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_rwlock_destroy |
| // darwin: pthread_rwlock_destroy |
| // darwin: pthread_rwlock_destroy$UNIX2003 |
| // Solaris: rwlock_destroy (pthread_rwlock_destroy is a weak alias) |
| // |
| __attribute__((noinline)) |
| static int pthread_rwlock_destroy_WRK(pthread_rwlock_t* rwl) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_rwl_destroy %p", rwl); fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_RWLOCK_DESTROY_PRE, |
| pthread_rwlock_t*,rwl); |
| |
| CALL_FN_W_W(ret, fn, rwl); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_rwlock_destroy", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: rwl_destroy -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZurwlockZudestroy, // pthread_rwlock_destroy |
| pthread_rwlock_t *rwl) { |
| return pthread_rwlock_destroy_WRK(rwl); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZurwlockZudestroyZa, // pthread_rwlock_destroy* |
| pthread_rwlock_t *rwl) { |
| return pthread_rwlock_destroy_WRK(rwl); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, rwlockZudestroy, // rwlock_destroy |
| pthread_rwlock_t *rwl) { |
| return pthread_rwlock_destroy_WRK(rwl); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_rwlock_wrlock |
| // darwin: pthread_rwlock_wrlock |
| // darwin: pthread_rwlock_wrlock$UNIX2003 |
| // Solaris: rw_wrlock (pthread_rwlock_wrlock is a weak alias) |
| // |
| __attribute__((noinline)) |
| static int pthread_rwlock_wrlock_WRK(pthread_rwlock_t* rwlock) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_rwl_wlk %p", rwlock); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_PRE, |
| pthread_rwlock_t*,rwlock, |
| long,1/*isW*/, long,0/*!isTryLock*/); |
| |
| CALL_FN_W_W(ret, fn, rwlock); |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, |
| pthread_rwlock_t*,rwlock, long,1/*isW*/, |
| long, (ret == 0) ? True : False); |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_rwlock_wrlock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: rwl_wlk -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZurwlockZuwrlock, // pthread_rwlock_wrlock |
| pthread_rwlock_t* rwlock) { |
| return pthread_rwlock_wrlock_WRK(rwlock); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZurwlockZuwrlockZa, // pthread_rwlock_wrlock* |
| pthread_rwlock_t* rwlock) { |
| return pthread_rwlock_wrlock_WRK(rwlock); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, rwZuwrlock, // rw_wrlock |
| pthread_rwlock_t *rwlock) { |
| return pthread_rwlock_wrlock_WRK(rwlock); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| #if defined(VGO_solaris) |
| /* Internal to libc. */ |
| PTH_FUNC(void, lrwZuwrlock, // lrw_wrlock |
| rwlock_t *rwlock) |
| { |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< lrw_wlk %p", rwlock); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_PRE, |
| pthread_rwlock_t *, rwlock, |
| long, 1/*isW*/, long, 0/*!isTryLock*/); |
| |
| CALL_FN_v_W(fn, rwlock); |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, |
| pthread_rwlock_t *, rwlock, long, 1/*isW*/, long, True); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: lrw_wlk >>\n"); |
| } |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_rwlock_rdlock |
| // darwin: pthread_rwlock_rdlock |
| // darwin: pthread_rwlock_rdlock$UNIX2003 |
| // Solaris: rw_rdlock (pthread_rwlock_rdlock is a weak alias) |
| // |
| __attribute__((noinline)) |
| static int pthread_rwlock_rdlock_WRK(pthread_rwlock_t* rwlock) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_rwl_rlk %p", rwlock); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_PRE, |
| pthread_rwlock_t*,rwlock, |
| long,0/*!isW*/, long,0/*!isTryLock*/); |
| |
| CALL_FN_W_W(ret, fn, rwlock); |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, |
| pthread_rwlock_t*,rwlock, long,0/*!isW*/, |
| long, (ret == 0) ? True : False); |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_rwlock_rdlock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: rwl_rlk -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZurwlockZurdlock, // pthread_rwlock_rdlock |
| pthread_rwlock_t* rwlock) { |
| return pthread_rwlock_rdlock_WRK(rwlock); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZurwlockZurdlockZa, // pthread_rwlock_rdlock* |
| pthread_rwlock_t* rwlock) { |
| return pthread_rwlock_rdlock_WRK(rwlock); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, rwZurdlock, // rw_rdlock |
| pthread_rwlock_t *rwlock) { |
| return pthread_rwlock_rdlock_WRK(rwlock); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| #if defined(VGO_solaris) |
| /* Internal to libc. */ |
| PTH_FUNC(void, lrwZurdlock, // lrw_rdlock |
| rwlock_t *rwlock) |
| { |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< lrw_rlk %p", rwlock); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_PRE, |
| pthread_rwlock_t *, rwlock, |
| long, 0/*!isW*/, long, 0/*!isTryLock*/); |
| |
| CALL_FN_v_W(fn, rwlock); |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, |
| pthread_rwlock_t *, rwlock, long, 0/*!isW*/, long, True); |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: lrw_rlk ->>\n"); |
| } |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_rwlock_trywrlock |
| // darwin: pthread_rwlock_trywrlock |
| // darwin: pthread_rwlock_trywrlock$UNIX2003 |
| // Solaris: rw_trywrlock (pthread_rwlock_trywrlock is a weak alias) |
| // |
| __attribute__((noinline)) |
| static int pthread_rwlock_trywrlock_WRK(pthread_rwlock_t* rwlock) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_rwl_trywlk %p", rwlock); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_PRE, |
| pthread_rwlock_t*,rwlock, |
| long,1/*isW*/, long,1/*isTryLock*/); |
| |
| CALL_FN_W_W(ret, fn, rwlock); |
| |
| /* There's a hole here: libpthread now knows the lock is locked, |
| but the tool doesn't, so some other thread could run and detect |
| that the lock has been acquired by someone (this thread). Does |
| this matter? Not sure, but I don't think so. */ |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, |
| pthread_rwlock_t*,rwlock, long,1/*isW*/, |
| long, (ret == 0) ? True : False); |
| if (ret != 0) { |
| if (ret != EBUSY) |
| DO_PthAPIerror( "pthread_rwlock_trywrlock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: rwl_trywlk -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZurwlockZutrywrlock, // pthread_rwlock_trywrlock |
| pthread_rwlock_t* rwlock) { |
| return pthread_rwlock_trywrlock_WRK(rwlock); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZurwlockZutrywrlockZa, // pthread_rwlock_trywrlock* |
| pthread_rwlock_t* rwlock) { |
| return pthread_rwlock_trywrlock_WRK(rwlock); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, rwZutrywrlock, // rw_trywrlock |
| pthread_rwlock_t *rwlock) { |
| return pthread_rwlock_trywrlock_WRK(rwlock); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_rwlock_tryrdlock |
| // darwin: pthread_rwlock_tryrdlock |
| // darwin: pthread_rwlock_tryrdlock$UNIX2003 |
| // Solaris: rw_tryrdlock (pthread_rwlock_tryrdlock is a weak alias) |
| // |
| __attribute__((noinline)) |
| static int pthread_rwlock_tryrdlock_WRK(pthread_rwlock_t* rwlock) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_rwl_tryrlk %p", rwlock); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_PRE, |
| pthread_rwlock_t*,rwlock, |
| long,0/*!isW*/, long,1/*isTryLock*/); |
| |
| CALL_FN_W_W(ret, fn, rwlock); |
| |
| /* There's a hole here: libpthread now knows the lock is locked, |
| but the tool doesn't, so some other thread could run and detect |
| that the lock has been acquired by someone (this thread). Does |
| this matter? Not sure, but I don't think so. */ |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, |
| pthread_rwlock_t*,rwlock, long,0/*!isW*/, |
| long, (ret == 0) ? True : False); |
| |
| if (ret != 0) { |
| if (ret != EBUSY) |
| DO_PthAPIerror( "pthread_rwlock_tryrdlock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: rwl_tryrlk -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZurwlockZutryrdlock, // pthread_rwlock_tryrdlock |
| pthread_rwlock_t* rwlock) { |
| return pthread_rwlock_tryrdlock_WRK(rwlock); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZurwlockZutryrdlockZa, // pthread_rwlock_tryrdlock* |
| pthread_rwlock_t* rwlock) { |
| return pthread_rwlock_tryrdlock_WRK(rwlock); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, rwZutryrdlock, // rw_tryrdlock |
| pthread_rwlock_t *rwlock) { |
| return pthread_rwlock_tryrdlock_WRK(rwlock); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: Unhandled |
| // darwin: Unhandled |
| // Solaris: pthread_rwlock_timedrdlock |
| // Solaris: pthread_rwlock_reltimedrdlock_np |
| // |
| __attribute__((noinline)) __attribute__((unused)) |
| static int pthread_rwlock_timedrdlock_WRK(pthread_rwlock_t *rwlock, |
| const struct timespec *timeout) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_rwl_timedrdl %p", rwlock); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_PRE, |
| pthread_rwlock_t *, rwlock, |
| long, 0/*isW*/, long, 0/*isTryLock*/); |
| |
| CALL_FN_W_WW(ret, fn, rwlock, timeout); |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, |
| pthread_rwlock_t *, rwlock, long, 0/*isW*/, |
| long, (ret == 0) ? True : False); |
| if (ret != 0) { |
| DO_PthAPIerror("pthread_rwlock_timedrdlock", ret); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: rwl_timedrdl -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| #elif defined(VGO_darwin) |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, pthreadZurwlockZutimedrdlock, // pthread_rwlock_timedrdlock |
| pthread_rwlock_t *rwlock, |
| const struct timespec *timeout) { |
| return pthread_rwlock_timedrdlock_WRK(rwlock, timeout); |
| } |
| PTH_FUNC(int, pthreadZurwlockZureltimedrdlockZunp, // pthread_rwlock_timedrdlock_np |
| pthread_rwlock_t *rwlock, |
| const struct timespec *timeout) { |
| return pthread_rwlock_timedrdlock_WRK(rwlock, timeout); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: Unhandled |
| // darwin: Unhandled |
| // Solaris: pthread_rwlock_timedwrlock |
| // Solaris: pthread_rwlock_reltimedwrlock_np |
| // |
| __attribute__((noinline)) __attribute__((unused)) |
| static int pthread_rwlock_timedwrlock_WRK(pthread_rwlock_t *rwlock, |
| const struct timespec *timeout) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_rwl_timedwrl %p", rwlock); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_PRE, |
| pthread_rwlock_t *, rwlock, |
| long, 1/*isW*/, long, 0/*isTryLock*/); |
| |
| CALL_FN_W_WW(ret, fn, rwlock, timeout); |
| |
| DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, |
| pthread_rwlock_t *, rwlock, long, 1/*isW*/, |
| long, (ret == 0) ? True : False); |
| if (ret != 0) { |
| DO_PthAPIerror("pthread_rwlock_timedwrlock", ret); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: rwl_timedwrl -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| #elif defined(VGO_darwin) |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, pthreadZurwlockZutimedwrlock, // pthread_rwlock_timedwrlock |
| pthread_rwlock_t *rwlock, |
| const struct timespec *timeout) { |
| return pthread_rwlock_timedwrlock_WRK(rwlock, timeout); |
| } |
| PTH_FUNC(int, pthreadZurwlockZureltimedwrlockZunp, // pthread_rwlock_timedwrlock_np |
| pthread_rwlock_t *rwlock, |
| const struct timespec *timeout) { |
| return pthread_rwlock_timedwrlock_WRK(rwlock, timeout); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: pthread_rwlock_unlock |
| // darwin: pthread_rwlock_unlock |
| // darwin: pthread_rwlock_unlock$UNIX2003 |
| // Solaris: rw_unlock (pthread_rwlock_unlock is a weak alias) |
| __attribute__((noinline)) |
| static int pthread_rwlock_unlock_WRK(pthread_rwlock_t* rwlock) |
| { |
| int ret; |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, "<< pthread_rwl_unlk %p", rwlock); fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_RWLOCK_UNLOCK_PRE, |
| pthread_rwlock_t*,rwlock); |
| |
| CALL_FN_W_W(ret, fn, rwlock); |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_RWLOCK_UNLOCK_POST, |
| pthread_rwlock_t*,rwlock); |
| if (ret != 0) { |
| DO_PthAPIerror( "pthread_rwlock_unlock", ret ); |
| } |
| |
| if (TRACE_PTH_FNS) { |
| fprintf(stderr, " :: rwl_unlk -> %d >>\n", ret); |
| } |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, pthreadZurwlockZuunlock, // pthread_rwlock_unlock |
| pthread_rwlock_t* rwlock) { |
| return pthread_rwlock_unlock_WRK(rwlock); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, pthreadZurwlockZuunlockZa, // pthread_rwlock_unlock* |
| pthread_rwlock_t* rwlock) { |
| return pthread_rwlock_unlock_WRK(rwlock); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, rwZuunlock, // rw_unlock |
| pthread_rwlock_t *rwlock) { |
| return pthread_rwlock_unlock_WRK(rwlock); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| #endif /* defined(HAVE_PTHREAD_RWLOCK_T) */ |
| |
| |
| /*----------------------------------------------------------------*/ |
| /*--- POSIX semaphores ---*/ |
| /*----------------------------------------------------------------*/ |
| |
| #include <semaphore.h> |
| #include <fcntl.h> /* O_CREAT */ |
| |
| #define TRACE_SEM_FNS 0 |
| |
| /* Handled: |
| int sem_init(sem_t *sem, int pshared, unsigned value); |
| int sem_destroy(sem_t *sem); |
| int sem_wait(sem_t *sem); |
| int sem_post(sem_t *sem); |
| sem_t* sem_open(const char *name, int oflag, |
| ... [mode_t mode, unsigned value]); |
| [complete with its idiotic semantics] |
| int sem_close(sem_t* sem); |
| |
| Unhandled: |
| int sem_trywait(sem_t *sem); |
| int sem_timedwait(sem_t *restrict sem, |
| const struct timespec *restrict abs_timeout); |
| */ |
| |
| //----------------------------------------------------------- |
| // glibc: sem_init@@GLIBC_2.2.5 |
| // glibc: sem_init@@GLIBC_2.1 |
| // glibc: sem_init@GLIBC_2.0 |
| // darwin: sem_init |
| // Solaris: sema_init (sem_init is built on top of sem_init) |
| // |
| #if !defined(VGO_solaris) |
| __attribute__((noinline)) |
| static int sem_init_WRK(sem_t* sem, int pshared, unsigned long value) |
| { |
| OrigFn fn; |
| int ret; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, "<< sem_init(%p,%d,%lu) ", sem,pshared,value); |
| fflush(stderr); |
| } |
| |
| CALL_FN_W_WWW(ret, fn, sem,pshared,value); |
| |
| if (ret == 0) { |
| DO_CREQ_v_WW(_VG_USERREQ__HG_POSIX_SEM_INIT_POST, |
| sem_t*, sem, unsigned long, value); |
| } else { |
| DO_PthAPIerror( "sem_init", errno ); |
| } |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, " sem_init -> %d >>\n", ret); |
| fflush(stderr); |
| } |
| |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, semZuinitZAZa, // sem_init@* |
| sem_t* sem, int pshared, unsigned long value) { |
| return sem_init_WRK(sem, pshared, value); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, semZuinit, // sem_init |
| sem_t* sem, int pshared, unsigned long value) { |
| return sem_init_WRK(sem, pshared, value); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| #else /* VGO_solaris */ |
| PTH_FUNC(int, semaZuinit, // sema_init |
| sema_t *sem, |
| unsigned int value, |
| int type, |
| void *arg) |
| { |
| OrigFn fn; |
| int ret; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, "<< sema_init(%p, %d, %u) ", sem, type, value); |
| fflush(stderr); |
| } |
| |
| CALL_FN_W_WWWW(ret, fn, sem, value, type, arg); |
| |
| if (ret == 0) { |
| DO_CREQ_v_WW(_VG_USERREQ__HG_POSIX_SEM_INIT_POST, |
| sema_t *, sem, Word, value); |
| } else { |
| DO_PthAPIerror("sema_init", ret); |
| } |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, " sema_init -> %d >>\n", ret); |
| fflush(stderr); |
| } |
| |
| return ret; |
| } |
| #endif /* VGO_solaris */ |
| |
| |
| //----------------------------------------------------------- |
| // glibc: sem_destroy@GLIBC_2.0 |
| // glibc: sem_destroy@@GLIBC_2.1 |
| // glibc: sem_destroy@@GLIBC_2.2.5 |
| // darwin: sem_destroy |
| // Solaris: sema_destroy (sem_destroy is built on top of sema_destroy) |
| __attribute__((noinline)) |
| static int sem_destroy_WRK(sem_t* sem) |
| { |
| OrigFn fn; |
| int ret; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, "<< sem_destroy(%p) ", sem); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_POSIX_SEM_DESTROY_PRE, sem_t*, sem); |
| |
| CALL_FN_W_W(ret, fn, sem); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "sem_destroy", SEM_ERROR ); |
| } |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, " sem_destroy -> %d >>\n", ret); |
| fflush(stderr); |
| } |
| |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, semZudestroyZAZa, // sem_destroy* |
| sem_t* sem) { |
| return sem_destroy_WRK(sem); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, semZudestroy, // sem_destroy |
| sem_t* sem) { |
| return sem_destroy_WRK(sem); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, semaZudestroy, // sema_destroy |
| sem_t *sem) { |
| return sem_destroy_WRK(sem); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: sem_wait |
| // glibc: sem_wait@GLIBC_2.0 |
| // glibc: sem_wait@@GLIBC_2.1 |
| // darwin: sem_wait |
| // darwin: sem_wait$NOCANCEL$UNIX2003 |
| // darwin: sem_wait$UNIX2003 |
| // Solaris: sema_wait (sem_wait is built on top of sema_wait) |
| // |
| /* wait: decrement semaphore - acquire lockage */ |
| __attribute__((noinline)) |
| static int sem_wait_WRK(sem_t* sem) |
| { |
| OrigFn fn; |
| int ret; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, "<< sem_wait(%p) ", sem); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_POSIX_SEM_WAIT_PRE, sem_t*,sem); |
| |
| CALL_FN_W_W(ret, fn, sem); |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_POSIX_SEM_WAIT_POST, sem_t*,sem, |
| long, (ret == 0) ? True : False); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "sem_wait", SEM_ERROR ); |
| } |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, " sem_wait -> %d >>\n", ret); |
| fflush(stderr); |
| } |
| |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, semZuwait, sem_t* sem) { /* sem_wait */ |
| return sem_wait_WRK(sem); |
| } |
| PTH_FUNC(int, semZuwaitZAZa, sem_t* sem) { /* sem_wait@* */ |
| return sem_wait_WRK(sem); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, semZuwait, sem_t* sem) { /* sem_wait */ |
| return sem_wait_WRK(sem); |
| } |
| PTH_FUNC(int, semZuwaitZDZa, sem_t* sem) { /* sem_wait$* */ |
| return sem_wait_WRK(sem); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, semaZuwait, sem_t *sem) { /* sema_wait */ |
| return sem_wait_WRK(sem); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: sem_post |
| // glibc: sem_post@GLIBC_2.0 |
| // glibc: sem_post@@GLIBC_2.1 |
| // darwin: sem_post |
| // Solaris: sema_post (sem_post is built on top of sema_post) |
| // |
| /* post: increment semaphore - release lockage */ |
| __attribute__((noinline)) |
| static int sem_post_WRK(sem_t* sem) |
| { |
| OrigFn fn; |
| int ret; |
| |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, "<< sem_post(%p) ", sem); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_POSIX_SEM_POST_PRE, sem_t*,sem); |
| |
| CALL_FN_W_W(ret, fn, sem); |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_POSIX_SEM_POST_POST, sem_t*,sem); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "sem_post", SEM_ERROR ); |
| } |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, " sem_post -> %d >>\n", ret); |
| fflush(stderr); |
| } |
| |
| return ret; |
| } |
| #if defined(VGO_linux) |
| PTH_FUNC(int, semZupost, sem_t* sem) { /* sem_post */ |
| return sem_post_WRK(sem); |
| } |
| PTH_FUNC(int, semZupostZAZa, sem_t* sem) { /* sem_post@* */ |
| return sem_post_WRK(sem); |
| } |
| #elif defined(VGO_darwin) |
| PTH_FUNC(int, semZupost, sem_t* sem) { /* sem_post */ |
| return sem_post_WRK(sem); |
| } |
| #elif defined(VGO_solaris) |
| PTH_FUNC(int, semaZupost, sem_t *sem) { /* sema_post */ |
| return sem_post_WRK(sem); |
| } |
| #else |
| # error "Unsupported OS" |
| #endif |
| |
| |
| //----------------------------------------------------------- |
| // glibc: sem_open |
| // darwin: sem_open |
| // Solaris: sem_open |
| // |
| PTH_FUNC(sem_t*, semZuopen, |
| const char* name, long oflag, |
| long mode, unsigned long value) |
| { |
| /* A copy of sem_init_WRK (more or less). Is this correct? */ |
| OrigFn fn; |
| sem_t* ret; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, "<< sem_open(\"%s\",%ld,%lx,%lu) ", |
| name,oflag,mode,value); |
| fflush(stderr); |
| } |
| |
| CALL_FN_W_WWWW(ret, fn, name,oflag,mode,value); |
| |
| if (ret != SEM_FAILED && (oflag & O_CREAT)) { |
| DO_CREQ_v_WW(_VG_USERREQ__HG_POSIX_SEM_INIT_POST, |
| sem_t*, ret, unsigned long, value); |
| } |
| if (ret == SEM_FAILED) { |
| DO_PthAPIerror( "sem_open", errno ); |
| } |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, " sem_open -> %p >>\n", ret); |
| fflush(stderr); |
| } |
| |
| return ret; |
| } |
| |
| |
| //----------------------------------------------------------- |
| // glibc: sem_close |
| // darwin: sem_close |
| // Solaris: sem_close |
| PTH_FUNC(int, sem_close, sem_t* sem) |
| { |
| OrigFn fn; |
| int ret; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, "<< sem_close(%p) ", sem); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_POSIX_SEM_DESTROY_PRE, sem_t*, sem); |
| |
| CALL_FN_W_W(ret, fn, sem); |
| |
| if (ret != 0) { |
| DO_PthAPIerror( "sem_close", errno ); |
| } |
| |
| if (TRACE_SEM_FNS) { |
| fprintf(stderr, " close -> %d >>\n", ret); |
| fflush(stderr); |
| } |
| |
| return ret; |
| } |
| |
| |
| /*----------------------------------------------------------------*/ |
| /*--- Qt 4 threading functions (w/ GNU name mangling) ---*/ |
| /*----------------------------------------------------------------*/ |
| |
| /* Handled: |
| QMutex::lock() |
| QMutex::unlock() |
| QMutex::tryLock() |
| QMutex::tryLock(int) |
| |
| QMutex::QMutex(QMutex::RecursionMode) _ZN6QMutexC1ENS_13RecursionModeE |
| QMutex::QMutex(QMutex::RecursionMode) _ZN6QMutexC2ENS_13RecursionModeE |
| QMutex::~QMutex() _ZN6QMutexD1Ev |
| QMutex::~QMutex() _ZN6QMutexD2Ev |
| |
| Unhandled: |
| QReadWriteLock::lockForRead() |
| QReadWriteLock::lockForWrite() |
| QReadWriteLock::unlock() |
| QReadWriteLock::tryLockForRead(int) |
| QReadWriteLock::tryLockForRead() |
| QReadWriteLock::tryLockForWrite(int) |
| QReadWriteLock::tryLockForWrite() |
| |
| QWaitCondition::wait(QMutex*, unsigned long) |
| QWaitCondition::wakeAll() |
| QWaitCondition::wakeOne() |
| |
| QSemaphore::* |
| */ |
| /* More comments, 19 Nov 08, based on assessment of qt-4.5.0TP1, |
| at least on Unix: |
| |
| It's apparently only necessary to intercept QMutex, since that is |
| not implemented using pthread_mutex_t; instead Qt4 has its own |
| implementation based on atomics (to check the non-contended case) |
| and pthread_cond_wait (to wait in the contended case). |
| |
| QReadWriteLock is built on top of QMutex, counters, and a wait |
| queue. So we don't need to handle it specially once QMutex |
| handling is correct -- presumably the dependencies through QMutex |
| are sufficient to avoid any false race reports. On the other hand, |
| it is an open question whether too many dependencies are observed |
| -- in which case we may miss races (false negatives). I suspect |
| this is likely to be the case, unfortunately. |
| |
| QWaitCondition is built on pthread_cond_t, pthread_mutex_t, QMutex |
| and QReadWriteLock. Same compositional-correctness justificiation |
| and limitations as fro QReadWriteLock. |
| |
| Ditto QSemaphore (from cursory examination). |
| |
| Does it matter that only QMutex is handled directly? Open |
| question. From testing with drd/tests/qt4_* and with KDE4 apps, it |
| appears that no false errors are reported; however it is not clear |
| if this is causing false negatives. |
| |
| Another problem with Qt4 is thread exiting. Threads are created |
| with pthread_create (fine); but they detach and simply exit when |
| done. There is no use of pthread_join, and the provided |
| wait-for-a-thread-to-exit mechanism (QThread::wait, I believe) |
| relies on a system of mutexes and flags. I suspect this also |
| causes too many dependencies to appear. Consequently H sometimes |
| fails to detect races at exit in some very short-lived racy |
| programs, because it appears that a thread can exit _and_ have an |
| observed dependency edge back to the main thread (presumably) |
| before the main thread reaps the child (that is, calls |
| QThread::wait). |
| |
| This theory is supported by the observation that if all threads are |
| made to wait at a pthread_barrier_t immediately before they exit, |
| then H's detection of races in such programs becomes reliable; |
| without the barrier, it is varies from run to run, depending |
| (according to investigation) on whether aforementioned |
| exit-before-reaping behaviour happens or not. |
| |
| Finally, why is it necessary to intercept the QMutex constructors |
| and destructors? The constructors are intercepted only as a matter |
| of convenience, so H can print accurate "first observed at" |
| clauses. However, it is actually necessary to intercept the |
| destructors (as it is with pthread_mutex_destroy) in order that |
| locks get removed from LAOG when they are destroyed. |
| */ |
| |
| // soname is libQtCore.so.4 ; match against libQtCore.so* |
| #define QT4_FUNC(ret_ty, f, args...) \ |
| ret_ty I_WRAP_SONAME_FNNAME_ZU(libQtCoreZdsoZa,f)(args); \ |
| ret_ty I_WRAP_SONAME_FNNAME_ZU(libQtCoreZdsoZa,f)(args) |
| |
| // soname is libQt5Core.so.4 ; match against libQt5Core.so* |
| #define QT5_FUNC(ret_ty, f, args...) \ |
| ret_ty I_WRAP_SONAME_FNNAME_ZU(libQt5CoreZdsoZa,f)(args); \ |
| ret_ty I_WRAP_SONAME_FNNAME_ZU(libQt5CoreZdsoZa,f)(args) |
| |
| //----------------------------------------------------------- |
| // QMutex::lock() |
| __attribute__((noinline)) |
| static void QMutex_lock_WRK(void* self) |
| { |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_QT4_FNS) { |
| fprintf(stderr, "<< QMutex::lock %p", self); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_PRE, |
| void*,self, long,0/*!isTryLock*/); |
| |
| CALL_FN_v_W(fn, self); |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_POST, |
| void *, self, long, True); |
| |
| if (TRACE_QT4_FNS) { |
| fprintf(stderr, " :: Q::lock done >>\n"); |
| } |
| } |
| |
| QT4_FUNC(void, _ZN6QMutex4lockEv, void* self) { |
| QMutex_lock_WRK(self); |
| } |
| QT5_FUNC(void, _ZN6QMutex4lockEv, void* self) { |
| QMutex_lock_WRK(self); |
| } |
| |
| //----------------------------------------------------------- |
| // QMutex::unlock() |
| __attribute__((noinline)) |
| static void QMutex_unlock_WRK(void* self) |
| { |
| OrigFn fn; |
| VALGRIND_GET_ORIG_FN(fn); |
| |
| if (TRACE_QT4_FNS) { |
| fprintf(stderr, "<< QMutex::unlock %p", self); fflush(stderr); |
| } |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_MUTEX_UNLOCK_PRE, |
| void*, self); |
| |
| CALL_FN_v_W(fn, self); |
| |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_MUTEX_UNLOCK_POST, |
| void*, self); |
| |
| if (TRACE_QT4_FNS) { |
| fprintf(stderr, " Q::unlock done >>\n"); |
| } |
| } |
| |
| QT4_FUNC(void, _ZN6QMutex6unlockEv, void* self) { |
| QMutex_unlock_WRK(self); |
| } |
| QT5_FUNC(void, _ZN6QMutex6unlockEv, void* self) { |
| QMutex_unlock_WRK(self); |
| } |
| |
| //----------------------------------------------------------- |
| // bool QMutex::tryLock() |
| // using 'long' to mimic C++ 'bool' |
| __attribute__((noinline)) |
| static long QMutex_tryLock_WRK(void* self) |
| { |
| OrigFn fn; |
| long ret; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_QT4_FNS) { |
| fprintf(stderr, "<< QMutex::tryLock %p", self); fflush(stderr); |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_PRE, |
| void*,self, long,1/*isTryLock*/); |
| |
| CALL_FN_W_W(ret, fn, self); |
| |
| // assumes that only the low 8 bits of the 'bool' are significant |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_POST, |
| void *, self, long, (ret & 0xFF) ? True : False); |
| |
| if (TRACE_QT4_FNS) { |
| fprintf(stderr, " :: Q::tryLock -> %lu >>\n", ret); |
| } |
| |
| return ret; |
| } |
| |
| QT4_FUNC(long, _ZN6QMutex7tryLockEv, void* self) { |
| return QMutex_tryLock_WRK(self); |
| } |
| QT5_FUNC(long, _ZN6QMutex7tryLockEv, void* self) { |
| return QMutex_tryLock_WRK(self); |
| } |
| |
| //----------------------------------------------------------- |
| // bool QMutex::tryLock(int) |
| // using 'long' to mimic C++ 'bool' |
| __attribute__((noinline)) |
| static long QMutex_tryLock_int_WRK(void* self, long arg2) |
| { |
| OrigFn fn; |
| long ret; |
| VALGRIND_GET_ORIG_FN(fn); |
| if (TRACE_QT4_FNS) { |
| fprintf(stderr, "<< QMutex::tryLock(int) %p %d", self, (int)arg2); |
| fflush(stderr); |
| } |
| |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_PRE, |
| void*,self, long,1/*isTryLock*/); |
| |
| CALL_FN_W_WW(ret, fn, self,arg2); |
| |
| // assumes that only the low 8 bits of the 'bool' are significant |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_LOCK_POST, |
| void *, self, long, (ret & 0xFF) ? True : False); |
| |
| if (TRACE_QT4_FNS) { |
| fprintf(stderr, " :: Q::tryLock(int) -> %lu >>\n", ret); |
| } |
| |
| return ret; |
| } |
| |
| QT4_FUNC(long, _ZN6QMutex7tryLockEi, void* self, long arg2) { |
| return QMutex_tryLock_int_WRK(self, arg2); |
| } |
| QT5_FUNC(long, _ZN6QMutex7tryLockEi, void* self, long arg2) { |
| return QMutex_tryLock_int_WRK(self, arg2); |
| } |
| |
| //----------------------------------------------------------- |
| // It's not really very clear what the args are here. But from |
| // a bit of dataflow analysis of the generated machine code of |
| // the original function, it appears this takes two args, and |
| // returns nothing. Nevertheless preserve return value just in |
| // case. A bit of debug printing indicates that the first arg |
| // is that of the mutex and the second is either zero or one, |
| // probably being the recursion mode, therefore. |
| // QMutex::QMutex(QMutex::RecursionMode) ("C1ENS" variant) |
| __attribute__((noinline)) |
| static void* QMutex_constructor_WRK(void* mutex, long recmode) |
| { |
| OrigFn fn; |
| long ret; |
| VALGRIND_GET_ORIG_FN(fn); |
| CALL_FN_W_WW(ret, fn, mutex, recmode); |
| // fprintf(stderr, "QMutex constructor 1: %p <- %p %p\n", ret, arg1, arg2); |
| DO_CREQ_v_WW(_VG_USERREQ__HG_PTHREAD_MUTEX_INIT_POST, |
| void*,mutex, long,1/*mbRec*/); |
| return (void*)ret; |
| } |
| |
| QT4_FUNC(void*, _ZN6QMutexC1ENS_13RecursionModeE, void* self, long recmode) { |
| return QMutex_constructor_WRK(self, recmode); |
| } |
| QT5_FUNC(void*, _ZN6QMutexC1ENS_13RecursionModeE, void* self, long recmode) { |
| return QMutex_constructor_WRK(self, recmode); |
| } |
| |
| //----------------------------------------------------------- |
| // QMutex::~QMutex() ("D1Ev" variant) |
| __attribute__((noinline)) |
| static void* QMutex_destructor_WRK(void* mutex) |
| { |
| OrigFn fn; |
| long ret; |
| VALGRIND_GET_ORIG_FN(fn); |
| DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_MUTEX_DESTROY_PRE, |
| void*,mutex); |
| CALL_FN_W_W(ret, fn, mutex); |
| return (void*)ret; |
| } |
| |
| QT4_FUNC(void*, _ZN6QMutexD1Ev, void* self) { |
| return QMutex_destructor_WRK(self); |
| } |
| QT5_FUNC(void*, _ZN6QMutexD1Ev, void* self) { |
| return QMutex_destructor_WRK(self); |
| } |
| |
| //----------------------------------------------------------- |
| // QMutex::QMutex(QMutex::RecursionMode) ("C2ENS" variant) |
| QT4_FUNC(void*, _ZN6QMutexC2ENS_13RecursionModeE, |
| void* mutex, |
| long recmode) |
| { |
| assert(0); |
| /*NOTREACHED*/ |
| /* Android's gcc behaves like it doesn't know that assert(0) |
| never returns. Hence: */ |
| return NULL; |
| } |
| |
| QT5_FUNC(void*, _ZN6QMutexC2ENS_13RecursionModeE, void* self, long recmode) |
| { |
| assert(0); |
| /*NOTREACHED*/ |
| return NULL; |
| } |
| |
| //----------------------------------------------------------- |
| // QMutex::~QMutex() ("D2Ev" variant) |
| QT4_FUNC(void*, _ZN6QMutexD2Ev, void* mutex) |
| { |
| assert(0); |
| /* Android's gcc behaves like it doesn't know that assert(0) |
| never returns. Hence: */ |
| return NULL; |
| } |
| |
| QT5_FUNC(void*, _ZN6QMutexD2Ev, void* self) |
| { |
| assert(0); |
| /*NOTREACHED*/ |
| return NULL; |
| } |
| |
| // QReadWriteLock is not intercepted directly. See comments |
| // above. |
| |
| //// QReadWriteLock::lockForRead() |
| //// _ZN14QReadWriteLock11lockForReadEv == QReadWriteLock::lockForRead() |
| //QT4_FUNC(void, ZuZZN14QReadWriteLock11lockForReadEv, |
| // // _ZN14QReadWriteLock11lockForReadEv |
| // void* self) |
| //{ |
| // OrigFn fn; |
| // VALGRIND_GET_ORIG_FN(fn); |
| // if (TRACE_QT4_FNS) { |
| // fprintf(stderr, "<< QReadWriteLock::lockForRead %p", self); |
| // fflush(stderr); |
| // } |
| // |
| // DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_PRE, |
| // void*,self, |
| // long,0/*!isW*/, long,0/*!isTryLock*/); |
| // |
| // CALL_FN_v_W(fn, self); |
| // |
| // DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, |
| // void*,self, long,0/*!isW*/, long, True); |
| // |
| // if (TRACE_QT4_FNS) { |
| // fprintf(stderr, " :: Q::lockForRead :: done >>\n"); |
| // } |
| //} |
| // |
| //// QReadWriteLock::lockForWrite() |
| //// _ZN14QReadWriteLock12lockForWriteEv == QReadWriteLock::lockForWrite() |
| //QT4_FUNC(void, ZuZZN14QReadWriteLock12lockForWriteEv, |
| // // _ZN14QReadWriteLock12lockForWriteEv |
| // void* self) |
| //{ |
| // OrigFn fn; |
| // VALGRIND_GET_ORIG_FN(fn); |
| // if (TRACE_QT4_FNS) { |
| // fprintf(stderr, "<< QReadWriteLock::lockForWrite %p", self); |
| // fflush(stderr); |
| // } |
| // |
| // DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_PRE, |
| // void*,self, |
| // long,1/*isW*/, long,0/*!isTryLock*/); |
| // |
| // CALL_FN_v_W(fn, self); |
| // |
| // DO_CREQ_v_WWW(_VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, |
| // void*,self, long,1/*isW*/, long, True); |
| // |
| // if (TRACE_QT4_FNS) { |
| // fprintf(stderr, " :: Q::lockForWrite :: done >>\n"); |
| // } |
| //} |
| // |
| //// QReadWriteLock::unlock() |
| //// _ZN14QReadWriteLock6unlockEv == QReadWriteLock::unlock() |
| //QT4_FUNC(void, ZuZZN14QReadWriteLock6unlockEv, |
| // // _ZN14QReadWriteLock6unlockEv |
| // void* self) |
| //{ |
| // OrigFn fn; |
| // VALGRIND_GET_ORIG_FN(fn); |
| // if (TRACE_QT4_FNS) { |
| // fprintf(stderr, "<< QReadWriteLock::unlock %p", self); |
| // fflush(stderr); |
| // } |
| // |
| // DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_RWLOCK_UNLOCK_PRE, |
| // void*,self); |
| // |
| // CALL_FN_v_W(fn, self); |
| // |
| // DO_CREQ_v_W(_VG_USERREQ__HG_PTHREAD_RWLOCK_UNLOCK_POST, |
| // void*,self); |
| // |
| // if (TRACE_QT4_FNS) { |
| // fprintf(stderr, " :: Q::unlock :: done >>\n"); |
| // } |
| //} |
| |
| |
| /*----------------------------------------------------------------*/ |
| /*--- Replacements for basic string functions, that don't ---*/ |
| /*--- overrun the input arrays. ---*/ |
| /*----------------------------------------------------------------*/ |
| |
| #include "../shared/vg_replace_strmem.c" |
| |
| /*--------------------------------------------------------------------*/ |
| /*--- end hg_intercepts.c ---*/ |
| /*--------------------------------------------------------------------*/ |