mirror of
https://github.com/Zenithsiz/ftmemsim-valgrind.git
synced 2026-02-03 10:05:29 +00:00
605 lines
16 KiB
C
605 lines
16 KiB
C
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- Pthreads library modelling. m_pthreadmodel.c ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|
|
/*
|
|
This file is part of Valgrind, a dynamic binary instrumentation
|
|
framework.
|
|
|
|
Copyright (C) 2005 Jeremy Fitzhardinge
|
|
jeremy@goop.org
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
02111-1307, USA.
|
|
|
|
The GNU General Public License is contained in the file COPYING.
|
|
*/
|
|
|
|
/*
|
|
This file wraps the client's use of libpthread functions and calls
|
|
on vg_threadmodel.c to model the state of the the client's threads.
|
|
The intent is to 1) look for problem's in the client's use of the
|
|
pthread API, and 2) tell tools which care about thread events (eg,
|
|
helgrind).
|
|
|
|
This file is intended to be implementation-independent. It assumes
|
|
that the client is using the same pthread.h as the one we include
|
|
here, but makes minimal assumptions about the actual structures
|
|
defined and so on (ie, the exact nature of pthread_t).
|
|
|
|
(For now we assume there's a 1:1 relationship between pthread_t's
|
|
and Valgrind-visible threads; N:M implementations will need further
|
|
work.)
|
|
|
|
The model is based on the pthread standard rather than any
|
|
particular implementation, in order to encourage portable use of
|
|
libpthread. On the other hand, we will probably need to implement
|
|
particular implementation extensions if they're widely used.
|
|
|
|
One tricky problem we need to solve is the mapping between
|
|
pthread_t identifiers and internal thread identifiers.
|
|
*/
|
|
|
|
#include "pub_core_basics.h"
|
|
|
|
#if 0
|
|
|
|
#define __USE_GNU
|
|
#define __USE_UNIX98
|
|
#include <pthread.h>
|
|
|
|
static const Bool debug = False;
|
|
|
|
static Bool check_wrappings(void);
|
|
|
|
#define ENTER(x) \
|
|
do { \
|
|
if (VG_(clo_trace_pthreads)) \
|
|
VG_(message)(Vg_DebugMsg, ">>> %d entering %s", \
|
|
VG_(get_running_tid)(), #x); \
|
|
} while(0)
|
|
|
|
static const Char *pp_retval(enum return_type rt, Word retval)
|
|
{
|
|
static Char buf[50];
|
|
|
|
switch(rt) {
|
|
case RT_RETURN:
|
|
VG_(sprintf)(buf, "return %d 0x%x", retval, retval);
|
|
return buf;
|
|
|
|
case RT_LONGJMP:
|
|
return "LONGJMPed out";
|
|
|
|
case RT_EXIT:
|
|
return "thread exit";
|
|
}
|
|
return "??";
|
|
}
|
|
|
|
#define LEAVE(x, rt, retval) \
|
|
do { \
|
|
if (VG_(clo_trace_pthreads)) \
|
|
VG_(message)(Vg_DebugMsg, "<<< %d leaving %s -> %s", \
|
|
VG_(get_running_tid)(), #x, pp_retval(rt, retval)); \
|
|
} while(0)
|
|
|
|
struct pthread_map
|
|
{
|
|
pthread_t id;
|
|
|
|
ThreadId tid;
|
|
};
|
|
|
|
static Int pthread_cmp(const void *v1, const void *v2)
|
|
{
|
|
const pthread_t *a = (const pthread_t *)v1;
|
|
const pthread_t *b = (const pthread_t *)v2;
|
|
|
|
return VG_(memcmp)(a, b, sizeof(*a));
|
|
}
|
|
|
|
static SkipList sk_pthread_map = VG_SKIPLIST_INIT(struct pthread_map, id, pthread_cmp,
|
|
NULL, VG_AR_CORE);
|
|
|
|
/* Find a ThreadId for a particular pthread_t; block until it becomes available */
|
|
static ThreadId get_pthread_mapping(pthread_t id)
|
|
{
|
|
/* Nasty little spin loop; revise if this turns out to be a
|
|
problem. This should only spin for as long as it takes for the
|
|
child thread to register the pthread_t. */
|
|
for(;;) {
|
|
struct pthread_map *m = VG_(SkipList_Find_Exact)(&sk_pthread_map, &id);
|
|
|
|
if (m && m->tid != VG_INVALID_THREADID)
|
|
return m->tid;
|
|
|
|
//VG_(printf)("find %x -> %p\n", id, m);
|
|
VG_(vg_yield)();
|
|
}
|
|
}
|
|
|
|
/* Create a mapping between a ThreadId and a pthread_t */
|
|
static void pthread_id_mapping(ThreadId tid, Addr idp, UInt idsz)
|
|
{
|
|
pthread_t id = *(pthread_t *)idp;
|
|
struct pthread_map *m = VG_(SkipList_Find_Exact)(&sk_pthread_map, &id);
|
|
|
|
if (debug)
|
|
VG_(printf)("Thread %d maps to %p\n", tid, id);
|
|
|
|
if (m == NULL) {
|
|
m = VG_(SkipNode_Alloc)(&sk_pthread_map);
|
|
m->id = id;
|
|
m->tid = tid;
|
|
VG_(SkipList_Insert)(&sk_pthread_map, m);
|
|
} else {
|
|
if (m->tid != VG_INVALID_THREADID && m->tid != tid)
|
|
VG_(message)(Vg_UserMsg, "Thread %d is creating duplicate mapping for pthread identifier %x; previously mapped to %d\n",
|
|
tid, (UInt)id, m->tid);
|
|
m->tid = tid;
|
|
}
|
|
}
|
|
|
|
static void check_thread_exists(ThreadId tid)
|
|
{
|
|
if (!VG_(tm_thread_exists)(tid)) {
|
|
if (debug)
|
|
VG_(printf)("creating thread %d\n", tid);
|
|
VG_(tm_thread_create)(VG_INVALID_THREADID, tid, False);
|
|
}
|
|
}
|
|
|
|
static Addr startfunc_wrapper = 0;
|
|
|
|
void VG_(pthread_startfunc_wrapper)(Addr wrapper)
|
|
{
|
|
startfunc_wrapper = wrapper;
|
|
}
|
|
|
|
struct pthread_create_nonce {
|
|
Bool detached;
|
|
pthread_t *threadid;
|
|
};
|
|
|
|
static void *before_pthread_create(va_list va)
|
|
{
|
|
pthread_t *threadp = va_arg(va, pthread_t *);
|
|
const pthread_attr_t *attr = va_arg(va, const pthread_attr_t *);
|
|
void *(*start)(void *) = va_arg(va, void *(*)(void *));
|
|
void *arg = va_arg(va, void *);
|
|
struct pthread_create_nonce *n;
|
|
struct vg_pthread_newthread_data *data;
|
|
ThreadState *tst;
|
|
|
|
if (!check_wrappings())
|
|
return NULL;
|
|
|
|
ENTER(pthread_create);
|
|
|
|
/* Data is in the client heap and is freed by the client in the
|
|
startfunc_wrapper. */
|
|
vg_assert(startfunc_wrapper != 0);
|
|
|
|
tst = VG_(get_ThreadState)(VG_(get_running_tid)());
|
|
|
|
// XXX: why use TL_(malloc)() here? What characteristics does this
|
|
// allocation require?
|
|
// [Possible: When using a tool that replaces malloc(), we want to call
|
|
// the replacement version. Otherwise, we want to use VG_(cli_malloc)().
|
|
// So we go via the default version of TL_(malloc)() in vg_default?]
|
|
tl_assert2(0, "read the comment in the code about this...");
|
|
|
|
// XXX: These three lines are going to have to change. They relied on
|
|
// TL_(malloc) being a weak symbol, and it just doesn't fit with the
|
|
// VG_(tdict) approach that we've switched to. The right way to do this
|
|
// will be to provide a function in the core that checks if
|
|
// VG_(tdict).malloc_malloc has been set; if so, it should
|
|
// call it, if not, it should call VG_(cli_malloc)().
|
|
// VG_(tl_malloc_called_deliberately) = True;
|
|
// data = TL_(malloc)(sizeof(*data));
|
|
// VG_(tl_malloc_called_deliberately) = False;
|
|
|
|
VG_TRACK(pre_mem_write, Vg_CorePThread, tst->tid, "new thread data",
|
|
(Addr)data, sizeof(*data));
|
|
data->startfunc = start;
|
|
data->arg = arg;
|
|
VG_TRACK(post_mem_write, (Addr)data, sizeof(*data));
|
|
|
|
/* Substitute arguments
|
|
XXX hack: need an API to do this. */
|
|
((Word *)tst->arch.m_esp)[3] = startfunc_wrapper;
|
|
((Word *)tst->arch.m_esp)[4] = (Word)data;
|
|
|
|
if (debug)
|
|
VG_(printf)("starting thread at wrapper %p\n", startfunc_wrapper);
|
|
|
|
n = VG_(arena_malloc)(VG_AR_CORE, sizeof(*n));
|
|
n->detached = attr && !!attr->__detachstate;
|
|
n->threadid = threadp;
|
|
|
|
return n;
|
|
}
|
|
|
|
static void after_pthread_create(void *nonce, enum return_type rt, Word retval)
|
|
{
|
|
struct pthread_create_nonce *n = (struct pthread_create_nonce *)nonce;
|
|
ThreadId tid = VG_(get_running_tid)();
|
|
|
|
if (n == NULL)
|
|
return;
|
|
|
|
if (rt == RT_RETURN && retval == 0) {
|
|
if (!VG_(tm_thread_exists)(tid))
|
|
VG_(tm_thread_create)(tid, get_pthread_mapping(*n->threadid),
|
|
n->detached);
|
|
else {
|
|
if (n->detached)
|
|
VG_(tm_thread_detach)(tid);
|
|
/* XXX set creator tid as well? */
|
|
}
|
|
}
|
|
|
|
VG_(arena_free)(VG_AR_CORE, n);
|
|
|
|
LEAVE(pthread_create, rt, retval);
|
|
}
|
|
|
|
static void *before_pthread_join(va_list va)
|
|
{
|
|
pthread_t pt_joinee = va_arg(va, pthread_t);
|
|
ThreadId joinee;
|
|
|
|
if (!check_wrappings())
|
|
return NULL;
|
|
|
|
ENTER(pthread_join);
|
|
|
|
joinee = get_pthread_mapping(pt_joinee);
|
|
|
|
VG_(tm_thread_join)(VG_(get_running_tid)(), joinee);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void after_pthread_join(void *v, enum return_type rt, Word retval)
|
|
{
|
|
/* nothing to be done? */
|
|
if (!check_wrappings())
|
|
return;
|
|
|
|
LEAVE(pthread_join, rt, retval);
|
|
}
|
|
|
|
struct pthread_detach_data {
|
|
pthread_t id;
|
|
};
|
|
|
|
static void *before_pthread_detach(va_list va)
|
|
{
|
|
pthread_t id = va_arg(va, pthread_t);
|
|
struct pthread_detach_data *data;
|
|
|
|
if (!check_wrappings())
|
|
return NULL;
|
|
|
|
ENTER(pthread_detach);
|
|
|
|
data = VG_(arena_malloc)(VG_AR_CORE, sizeof(*data));
|
|
data->id = id;
|
|
|
|
return data;
|
|
}
|
|
|
|
static void after_pthread_detach(void *nonce, enum return_type rt, Word retval)
|
|
{
|
|
struct pthread_detach_data *data = (struct pthread_detach_data *)nonce;
|
|
ThreadId tid;
|
|
|
|
if (data == NULL)
|
|
return;
|
|
|
|
tid = get_pthread_mapping(data->id);
|
|
|
|
VG_(arena_free)(VG_AR_CORE, data);
|
|
|
|
if (rt == RT_RETURN && retval == 0)
|
|
VG_(tm_thread_detach)(tid);
|
|
|
|
LEAVE(pthread_detach, rt, retval);
|
|
}
|
|
|
|
|
|
|
|
static void *before_pthread_self(va_list va)
|
|
{
|
|
/* If pthread_t is a structure, then this might be passed a pointer
|
|
to the return value. On Linux/glibc, it's a simple scalar, so it is
|
|
returned normally. */
|
|
if (!check_wrappings())
|
|
return NULL;
|
|
|
|
ENTER(pthread_self);
|
|
|
|
check_thread_exists(VG_(get_running_tid)());
|
|
return NULL;
|
|
}
|
|
|
|
static void after_pthread_self(void *nonce, enum return_type rt, Word retval)
|
|
{
|
|
pthread_t ret = (pthread_t)retval;
|
|
|
|
if (!check_wrappings())
|
|
return;
|
|
|
|
pthread_id_mapping(VG_(get_running_tid)(), (Addr)&ret, sizeof(ret));
|
|
|
|
LEAVE(pthread_self, rt, retval);
|
|
}
|
|
|
|
|
|
/* If a mutex hasn't been initialized, check it against all the static
|
|
initializers to see if it appears to have been statically
|
|
initialized. */
|
|
static void check_mutex_init(ThreadId tid, pthread_mutex_t *mx)
|
|
{
|
|
static const pthread_mutex_t initializers[] = {
|
|
PTHREAD_MUTEX_INITIALIZER,
|
|
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
|
|
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP,
|
|
PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP,
|
|
};
|
|
Int i;
|
|
|
|
if (VG_(tm_mutex_exists)((Addr)mx))
|
|
return;
|
|
|
|
VG_TRACK(pre_mem_read, Vg_CorePThread, tid, "pthread_mutex_t", (Addr)mx, sizeof(*mx));
|
|
|
|
for(i = 0; i < sizeof(initializers)/sizeof(*initializers); i++)
|
|
if (VG_(memcmp)(&initializers[i], mx, sizeof(*mx)) == 0) {
|
|
VG_(tm_mutex_init)(tid, (Addr)mx);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void *before_pthread_mutex_init(va_list va)
|
|
{
|
|
pthread_mutex_t *mx = va_arg(va, pthread_mutex_t *);
|
|
const pthread_mutexattr_t *attr = va_arg(va, const pthread_mutexattr_t *);
|
|
|
|
if (!check_wrappings())
|
|
return NULL;
|
|
|
|
ENTER(pthread_mutex_init);
|
|
|
|
/* XXX look for recursive mutex */
|
|
/* XXX look for non-process scope */
|
|
(void)attr;
|
|
|
|
return mx;
|
|
}
|
|
|
|
static void after_pthread_mutex_init(void *nonce, enum return_type rt, Word retval)
|
|
{
|
|
if (!check_wrappings())
|
|
return;
|
|
|
|
if (rt == RT_RETURN && retval == 0)
|
|
VG_(tm_mutex_init)(VG_(get_running_tid)(), (Addr)nonce);
|
|
|
|
LEAVE(pthread_mutex_init, rt, retval);
|
|
}
|
|
|
|
static void *before_pthread_mutex_destroy(va_list va)
|
|
{
|
|
pthread_mutex_t *mx = va_arg(va, pthread_mutex_t *);
|
|
|
|
if (!check_wrappings())
|
|
return NULL;
|
|
|
|
ENTER(pthread_mutex_destroy);
|
|
|
|
VG_(tm_mutex_destroy)(VG_(get_running_tid)(), (Addr)mx);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void after_pthread_mutex_destroy(void *nonce, enum return_type rt, Word retval)
|
|
{
|
|
if (!check_wrappings())
|
|
return;
|
|
|
|
LEAVE(pthread_mutex_destroy, rt, retval);
|
|
}
|
|
|
|
static void *before_pthread_mutex_lock(va_list va)
|
|
{
|
|
pthread_mutex_t *mx = va_arg(va, pthread_mutex_t *);
|
|
|
|
if (!check_wrappings())
|
|
return NULL;
|
|
|
|
ENTER(pthread_mutex_lock);
|
|
|
|
if (debug)
|
|
VG_(printf)("%d locking %p\n", VG_(get_running_tid)(), mx);
|
|
check_thread_exists(VG_(get_running_tid)());
|
|
check_mutex_init(VG_(get_running_tid)(), mx); /* mutex might be statically initialized */
|
|
VG_(tm_mutex_trylock)(VG_(get_running_tid)(), (Addr)mx);
|
|
|
|
return mx;
|
|
}
|
|
|
|
static void after_pthread_mutex_lock(void *nonce, enum return_type rt, Word retval)
|
|
{
|
|
if (!check_wrappings())
|
|
return;
|
|
|
|
if (rt == RT_RETURN && retval == 0)
|
|
VG_(tm_mutex_acquire)(VG_(get_running_tid)(), (Addr)nonce);
|
|
else {
|
|
if (debug)
|
|
VG_(printf)("after mutex_lock failed: rt=%d ret=%d\n", rt, retval);
|
|
VG_(tm_mutex_giveup)(VG_(get_running_tid)(), (Addr)nonce);
|
|
}
|
|
|
|
LEAVE(pthread_mutex_lock, rt, retval);
|
|
}
|
|
|
|
static void *before_pthread_mutex_trylock(va_list va)
|
|
{
|
|
pthread_mutex_t *mx = va_arg(va, pthread_mutex_t *);
|
|
|
|
if (!check_wrappings())
|
|
return NULL;
|
|
|
|
ENTER(pthread_mutex_trylock);
|
|
|
|
if (debug)
|
|
VG_(printf)("%d trylocking %p\n", VG_(get_running_tid)(), mx);
|
|
check_thread_exists(VG_(get_running_tid)());
|
|
check_mutex_init(VG_(get_running_tid)(), mx); /* mutex might be statically initialized */
|
|
VG_(tm_mutex_trylock)(VG_(get_running_tid)(), (Addr)mx);
|
|
|
|
return mx;
|
|
}
|
|
|
|
static void after_pthread_mutex_trylock(void *nonce, enum return_type rt, Word retval)
|
|
{
|
|
if (nonce == NULL)
|
|
return;
|
|
|
|
if (rt == RT_RETURN && retval == 0)
|
|
VG_(tm_mutex_acquire)(VG_(get_running_tid)(), (Addr)nonce);
|
|
else {
|
|
if (debug)
|
|
VG_(printf)("after mutex_trylock failed: rt=%d ret=%d\n", rt, retval);
|
|
VG_(tm_mutex_giveup)(VG_(get_running_tid)(), (Addr)nonce);
|
|
}
|
|
|
|
LEAVE(pthread_mutex_trylock, rt, retval);
|
|
}
|
|
|
|
static void *before_pthread_mutex_unlock(va_list va)
|
|
{
|
|
pthread_mutex_t *mx = va_arg(va, pthread_mutex_t *);
|
|
|
|
if (!check_wrappings())
|
|
return NULL;
|
|
|
|
ENTER(pthread_mutex_unlock);
|
|
|
|
VG_(tm_mutex_tryunlock)(VG_(get_running_tid)(), (Addr)mx);
|
|
|
|
return mx;
|
|
}
|
|
|
|
static void after_pthread_mutex_unlock(void *nonce, enum return_type rt, Word retval)
|
|
{
|
|
if (nonce == NULL)
|
|
return;
|
|
|
|
if (rt == RT_RETURN && retval == 0)
|
|
VG_(tm_mutex_unlock)(VG_(get_running_tid)(), (Addr)nonce); /* complete unlock */
|
|
else
|
|
VG_(tm_mutex_acquire)(VG_(get_running_tid)(), (Addr)nonce); /* re-acquire */
|
|
|
|
LEAVE(pthread_mutex_unlock, rt, retval);
|
|
}
|
|
|
|
|
|
static struct pt_wraps {
|
|
const Char *name;
|
|
FuncWrapper wrapper;
|
|
const CodeRedirect *redir;
|
|
} wraps[] = {
|
|
#define WRAP(func, extra) { #func extra, { before_##func, after_##func } }
|
|
WRAP(pthread_create, "@@GLIBC_2.1"), /* XXX TODO: 2.0 ABI (?) */
|
|
WRAP(pthread_join, ""),
|
|
WRAP(pthread_detach, ""),
|
|
|
|
WRAP(pthread_self, ""),
|
|
|
|
WRAP(pthread_mutex_init, ""),
|
|
WRAP(pthread_mutex_destroy, ""),
|
|
WRAP(pthread_mutex_lock, ""),
|
|
WRAP(pthread_mutex_trylock, ""),
|
|
WRAP(pthread_mutex_unlock, ""),
|
|
#undef WRAP
|
|
};
|
|
|
|
/* Check to see if all the wrappers are resolved */
|
|
static Bool check_wrappings()
|
|
{
|
|
Int i;
|
|
static Bool ok = True;
|
|
static Bool checked = False;
|
|
|
|
if (checked)
|
|
return ok;
|
|
|
|
for(i = 0; i < sizeof(wraps)/sizeof(*wraps); i++) {
|
|
if (!VG_(is_resolved)(wraps[i].redir)) {
|
|
VG_(message)(Vg_DebugMsg, "Pthread wrapper for \"%s\" is not resolved",
|
|
wraps[i].name);
|
|
ok = False;
|
|
}
|
|
}
|
|
|
|
if (startfunc_wrapper == 0) {
|
|
VG_(message)(Vg_DebugMsg, "Pthread wrapper for thread start function is not resolved");
|
|
ok = False;
|
|
}
|
|
|
|
if (!ok)
|
|
VG_(message)(Vg_DebugMsg, "Missing intercepts; model disabled");
|
|
|
|
checked = True;
|
|
return ok;
|
|
}
|
|
|
|
/*
|
|
Set up all the wrappers for interesting functions.
|
|
*/
|
|
void VG_(pthread_init)()
|
|
{
|
|
Int i;
|
|
|
|
for(i = 0; i < sizeof(wraps)/sizeof(*wraps); i++) {
|
|
//VG_(printf)("adding pthread wrapper for %s\n", wraps[i].name);
|
|
wraps[i].redir = VG_(add_wrapper)("soname:libpthread.so.0",
|
|
wraps[i].name, &wraps[i].wrapper);
|
|
}
|
|
VG_(tm_init)();
|
|
VG_(tm_thread_create)(VG_INVALID_THREADID, VG_(master_tid), True);
|
|
}
|
|
|
|
#else /* !0 */
|
|
/* Stubs for now */
|
|
//:: void VG_(pthread_init)()
|
|
//:: {
|
|
//:: }
|
|
//::
|
|
//:: void VG_(pthread_startfunc_wrapper)(Addr wrapper)
|
|
//:: {
|
|
//:: }
|
|
#endif /* 0 */
|
|
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- end ---*/
|
|
/*--------------------------------------------------------------------*/
|