blob: a3516cfbec2535204956cc3f17c1def67661b383 [file] [log] [blame]
/*--------------------------------------------------------------------*/
/*--- Helgrind: checking for data races in threaded programs. ---*/
/*--- hg_main.c ---*/
/*--------------------------------------------------------------------*/
/*
This file is part of Helgrind, a Valgrind tool for detecting
data races in threaded programs.
Copyright (C) 2002-2004 Nicholas Nethercote
njn25@cam.ac.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.
*/
#include "vg_skin.h"
#include "helgrind.h"
static UInt n_eraser_warnings = 0;
static UInt n_lockorder_warnings = 0;
/*------------------------------------------------------------*/
/*--- Debug guff ---*/
/*------------------------------------------------------------*/
#define DEBUG_LOCK_TABLE 0 /* Print lock table at end */
#define DEBUG_MAKE_ACCESSES 0 /* Print make_access() calls */
#define DEBUG_LOCKS 0 /* Print lock()/unlock() calls and locksets */
#define DEBUG_NEW_LOCKSETS 0 /* Print new locksets when created */
#define DEBUG_ACCESSES 0 /* Print reads, writes */
#define DEBUG_MEM_LOCKSET_CHANGES 0
/* Print when an address's lockset
changes; only useful with
DEBUG_ACCESSES */
#define SLOW_ASSERTS 0 /* do expensive asserts */
#define DEBUG_VIRGIN_READS 0 /* Dump around address on VIRGIN reads */
#if SLOW_ASSERTS
#define SK_ASSERT(x) sk_assert(x)
#else
#define SK_ASSERT(x)
#endif
/* heavyweight LockSet sanity checking:
0 == never
1 == after important ops
2 == As 1 and also after pthread_mutex_* ops (excessively slow)
*/
#define LOCKSET_SANITY 0
/* Rotate an unsigned quantity left */
#define ROTL(x, n) (((x) << (n)) | ((x) >> ((sizeof(x)*8)-(n))))
/* round a up to the next multiple of N. N must be a power of 2 */
#define ROUNDUP(a, N) ((a + N - 1) & ~(N-1))
/* Round a down to the next multiple of N. N must be a power of 2 */
#define ROUNDDN(a, N) ((a) & ~(N-1))
/*------------------------------------------------------------*/
/*--- Command line options ---*/
/*------------------------------------------------------------*/
static enum {
EC_None,
EC_Some,
EC_All
} clo_execontext = EC_None;
static Bool clo_priv_stacks = False;
/*------------------------------------------------------------*/
/*--- Crude profiling machinery. ---*/
/*------------------------------------------------------------*/
// PPP: work out if I want this
#define PROF_EVENT(x)
#if 0
#ifdef VG_PROFILE_MEMORY
#define N_PROF_EVENTS 150
static UInt event_ctr[N_PROF_EVENTS];
void VGE_(done_prof_mem) ( void )
{
Int i;
for (i = 0; i < N_PROF_EVENTS; i++) {
if ((i % 10) == 0)
VG_(printf)("\n");
if (event_ctr[i] > 0)
VG_(printf)( "prof mem event %2d: %d\n", i, event_ctr[i] );
}
VG_(printf)("\n");
}
#define PROF_EVENT(ev) \
do { sk_assert((ev) >= 0 && (ev) < N_PROF_EVENTS); \
event_ctr[ev]++; \
} while (False);
#else
//static void init_prof_mem ( void ) { }
// void VG_(done_prof_mem) ( void ) { }
#define PROF_EVENT(ev) /* */
#endif /* VG_PROFILE_MEMORY */
/* Event index. If just the name of the fn is given, this means the
number of calls to the fn. Otherwise it is the specified event.
[PPP: snip event numbers...]
*/
#endif /* 0 */
/*------------------------------------------------------------*/
/*--- Data defns. ---*/
/*------------------------------------------------------------*/
typedef
struct _HG_Chunk {
struct _HG_Chunk* next;
Addr data; /* ptr to actual block */
Int size; /* size requested */
ExeContext* where; /* where it was allocated */
ThreadId tid; /* allocating thread */
}
HG_Chunk;
typedef enum
{ Vge_VirginInit, Vge_NonVirginInit, Vge_SegmentInit, Vge_Error }
VgeInitStatus;
/* Should add up to 32 to fit in one word */
#define OTHER_BITS 30
#define STATE_BITS 2
#define ESEC_MAP_WORDS 16384 /* Words per secondary map */
/* This is for indicating that a memory block has been initialised but not
* really directly by a particular thread... (eg. text/data initialised
* automatically at startup).
* Must be different to virgin_word.other */
#define TID_INDICATING_NONVIRGIN 1
/* Magic packed TLS used for error suppression; if word state is Excl
and tid is this, then it means all access are OK without changing
state and without raising any more errors */
#define TLSP_INDICATING_ALL ((1 << OTHER_BITS) - 1)
/* Number of entries must fit in STATE_BITS bits */
typedef enum { Vge_Virgin, Vge_Excl, Vge_Shar, Vge_SharMod } pth_state;
static inline const Char *pp_state(pth_state st)
{
const Char *ret;
switch(st) {
case Vge_Virgin: ret = "virgin"; break;
case Vge_Excl: ret = "exclusive"; break;
case Vge_Shar: ret = "shared RO"; break;
case Vge_SharMod: ret = "shared RW"; break;
default: ret = "???";
}
return ret;
}
typedef
struct {
/* gcc arranges this bitfield with state in the 2LSB and other
in the 30MSB, which is what we want */
UInt state:STATE_BITS;
UInt other:OTHER_BITS;
} shadow_word;
#define SW(st, other) ((shadow_word) { st, other })
typedef
struct {
shadow_word swords[ESEC_MAP_WORDS];
}
ESecMap;
static ESecMap* primary_map[ 65536 ];
static ESecMap distinguished_secondary_map;
static const shadow_word virgin_sword = SW(Vge_Virgin, 0);
static const shadow_word error_sword = SW(Vge_Excl, TLSP_INDICATING_ALL);
#define VGE_IS_DISTINGUISHED_SM(smap) \
((smap) == &distinguished_secondary_map)
#define ENSURE_MAPPABLE(addr,caller) \
do { \
if (VGE_IS_DISTINGUISHED_SM(primary_map[(addr) >> 16])) { \
primary_map[(addr) >> 16] = alloc_secondary_map(caller); \
/*VG_(printf)("new 2map because of %p\n", addr);*/ \
} \
} while(0)
/* Parallel map which contains execution contexts when words last
changed state (if required) */
typedef struct EC_EIP {
union u_ec_eip {
Addr eip;
ExeContext *ec;
} uu_ec_eip;
UInt state:STATE_BITS;
UInt tls:OTHER_BITS; /* packed TLS */
} EC_EIP;
#define NULL_EC_EIP ((EC_EIP){ { 0 }, 0, 0})
#define EIP(eip, prev, tls) ((EC_EIP) { (union u_ec_eip)(eip), (prev).state, packTLS(tls) })
#define EC(ec, prev, tls) ((EC_EIP) { (union u_ec_eip)(ec), (prev).state, packTLS(tls) })
static inline UInt packEC(ExeContext *ec)
{
SK_ASSERT(((UInt)ec & ((1 << STATE_BITS)-1)) == 0);
return ((UInt)ec) >> STATE_BITS;
}
static inline ExeContext *unpackEC(UInt i)
{
return (ExeContext *)(i << STATE_BITS);
}
/* Lose 2 LSB of eip */
static inline UInt packEIP(Addr eip)
{
return ((UInt)eip) >> STATE_BITS;
}
static inline Addr unpackEIP(UInt i)
{
return (Addr)(i << STATE_BITS);
}
typedef struct {
EC_EIP execontext[ESEC_MAP_WORDS];
} ExeContextMap;
static ExeContextMap** execontext_map;
static inline void setExeContext(Addr a, EC_EIP ec)
{
UInt idx = (a >> 16) & 0xffff;
UInt off = (a >> 2) & 0x3fff;
if (execontext_map[idx] == NULL) {
execontext_map[idx] = VG_(malloc)(sizeof(ExeContextMap));
VG_(memset)(execontext_map[idx], 0, sizeof(ExeContextMap));
}
execontext_map[idx]->execontext[off] = ec;
}
static inline EC_EIP getExeContext(Addr a)
{
UInt idx = (a >> 16) & 0xffff;
UInt off = (a >> 2) & 0x3fff;
EC_EIP ec = NULL_EC_EIP;
if (execontext_map[idx] != NULL)
ec = execontext_map[idx]->execontext[off];
return ec;
}
/*------------------------------------------------------------*/
/*--- Thread lifetime segments ---*/
/*------------------------------------------------------------*/
/*
* This mechanism deals with the common case of a parent thread
* creating a structure for a child thread, and then passing ownership
* of the structure to that thread. It similarly copes with a child
* thread passing information back to another thread waiting to join
* on it.
*
* Each thread's lifetime can be partitioned into segments. Those
* segments are arranged to form an interference graph which indicates
* whether two thread lifetime segments can possibly be concurrent.
* If not, then memory with is exclusively accessed by one TLS can be
* passed on to another TLS without an error occurring, and without
* moving it from Excl state.
*
* At present this only considers thread creation and join as
* synchronisation events for creating new lifetime segments, but
* others may be possible (like mutex operations).
*/
typedef struct _ThreadLifeSeg ThreadLifeSeg;
struct _ThreadLifeSeg {
ThreadId tid;
ThreadLifeSeg *prior[2]; /* Previous lifetime segments */
UInt refcount; /* Number of memory locations pointing here */
UInt mark; /* mark used for graph traversal */
ThreadLifeSeg *next; /* list of all TLS */
};
static ThreadLifeSeg *all_tls;
static UInt tls_since_gc;
#define TLS_SINCE_GC 10000
/* current mark used for TLS graph traversal */
static UInt tlsmark;
static ThreadLifeSeg *thread_seg[VG_N_THREADS];
static void tls_gc(void)
{
/* XXX later. Walk through all TLSs and look for ones with 0
refcount and remove them from the structure and free them.
Could probably get rid of ThreadLifeSeg.refcount and simply use
mark-sweep from the shadow table. */
VG_(printf)("WRITEME: TLS GC\n");
}
static void newTLS(ThreadId tid)
{
static const Bool debug = False;
ThreadLifeSeg *tls;
/* Initial NULL */
if (thread_seg[tid] == NULL) {
tls = VG_(malloc)(sizeof(*tls));
tls->tid = tid;
tls->prior[0] = tls->prior[1] = NULL;
tls->refcount = 0;
tls->mark = tlsmark-1;
tls->next = all_tls;
all_tls = tls;
tls_since_gc++;
thread_seg[tid] = tls;
return;
}
/* Previous TLS was unused, so just recycle */
if (thread_seg[tid]->refcount == 0) {
if (debug)
VG_(printf)("newTLS; recycling TLS %p for tid %u\n",
thread_seg[tid], tid);
return;
}
/* Use existing TLS for this tid as a prior for new TLS */
tls = VG_(malloc)(sizeof(*tls));
tls->tid = tid;
tls->prior[0] = thread_seg[tid];
tls->prior[1] = NULL;
tls->refcount = 0;
tls->mark = tlsmark-1;
tls->next = all_tls;
all_tls = tls;
if (++tls_since_gc > TLS_SINCE_GC) {
tls_gc();
tls_since_gc = 0;
}
if (debug)
VG_(printf)("newTLS: made new TLS %p for tid %u (prior %p(%u))\n",
tls, tid, tls->prior[0], tls->prior[0]->tid);
thread_seg[tid] = tls;
}
/* clear out a TLS for a thread that's died */
static void clearTLS(ThreadId tid)
{
newTLS(tid);
thread_seg[tid]->prior[0] = NULL;
thread_seg[tid]->prior[1] = NULL;
}
static void addPriorTLS(ThreadId tid, ThreadId prior)
{
static const Bool debug = False;
ThreadLifeSeg *tls = thread_seg[tid];
if (debug)
VG_(printf)("making TLS %p(%u) prior to TLS %p(%u)\n",
thread_seg[prior], prior, tls, tid);
sk_assert(thread_seg[tid] != NULL);
sk_assert(thread_seg[prior] != NULL);
if (tls->prior[0] == NULL)
tls->prior[0] = thread_seg[prior];
else {
sk_assert(tls->prior[1] == NULL);
tls->prior[1] = thread_seg[prior];
}
}
/* Return True if prior is definitely not concurrent with tls */
static Bool tlsIsDisjoint(const ThreadLifeSeg *tls,
const ThreadLifeSeg *prior)
{
Bool isPrior(const ThreadLifeSeg *t) {
if (t == NULL || t->mark == tlsmark)
return False;
if (t == prior)
return True;
((ThreadLifeSeg *)t)->mark = tlsmark;
return isPrior(t->prior[0]) || isPrior(t->prior[1]);
}
tlsmark++; /* new traversal mark */
return isPrior(tls);
}
static inline UInt packTLS(ThreadLifeSeg *tls)
{
SK_ASSERT(((UInt)tls & ((1 << STATE_BITS)-1)) == 0);
return ((UInt)tls) >> STATE_BITS;
}
static inline ThreadLifeSeg *unpackTLS(UInt i)
{
return (ThreadLifeSeg *)(i << STATE_BITS);
}
/*------------------------------------------------------------*/
/*--- Low-level support for memory tracking. ---*/
/*------------------------------------------------------------*/
/*
All reads and writes are recorded in the memory map, which
records the state of all memory in the process. The memory map is
organised like that for normal Valgrind, except each that everything
is done at word-level instead of byte-level, and each word has only
one word of shadow (instead of 36 bits).
As for normal Valgrind there is a distinguished secondary map. But we're
working at word-granularity, so it has 16k word entries instead of 64k byte
entries. Lookup is done as follows:
bits 31..16: primary map lookup
bits 15.. 2: secondary map lookup
bits 1.. 0: ignored
*/
/*------------------------------------------------------------*/
/*--- Basic bitmap management, reading and writing. ---*/
/*------------------------------------------------------------*/
/* Allocate and initialise a secondary map, marking all words as virgin. */
/* Just a value that isn't a real pointer */
#define SEC_MAP_ACCESS (shadow_word*)0x99
static
ESecMap* alloc_secondary_map ( __attribute__ ((unused)) Char* caller )
{
ESecMap* map;
UInt i;
//PROF_EVENT(10); PPP
/* It just happens that a SecMap occupies exactly 18 pages --
although this isn't important, so the following assert is
spurious. (SSS: not true for ESecMaps -- they're 16 pages) */
sk_assert(0 == (sizeof(ESecMap) % VKI_BYTES_PER_PAGE));
map = (ESecMap *)VG_(shadow_alloc)(sizeof(ESecMap));
for (i = 0; i < ESEC_MAP_WORDS; i++)
map->swords[i] = virgin_sword;
return map;
}
/* Set a word. The byte give by 'a' could be anywhere in the word -- the whole
* word gets set. */
static /* __inline__ */
void set_sword ( Addr a, shadow_word sword )
{
ESecMap* sm;
shadow_word *oldsw;
//PROF_EVENT(23); PPP
ENSURE_MAPPABLE(a, "VGE_(set_sword)");
/* Use bits 31..16 for primary, 15..2 for secondary lookup */
sm = primary_map[a >> 16];
sk_assert(sm != &distinguished_secondary_map);
oldsw = &sm->swords[(a & 0xFFFC) >> 2];
if (oldsw->state == Vge_Excl && oldsw->other != TLSP_INDICATING_ALL) {
ThreadLifeSeg *tls = unpackTLS(oldsw->other);
tls->refcount--;
}
if (sword.state == Vge_Excl && sword.other != TLSP_INDICATING_ALL) {
ThreadLifeSeg *tls = unpackTLS(sword.other);
tls->refcount++;
}
sm->swords[(a & 0xFFFC) >> 2] = sword;
if (VGE_IS_DISTINGUISHED_SM(sm)) {
VG_(printf)("wrote to distinguished 2ndary map! 0x%x\n", a);
// XXX: may be legit, but I want to know when it happens --njn
VG_(skin_panic)("wrote to distinguished 2ndary map!");
}
}
static __inline__
shadow_word* get_sword_addr ( Addr a )
{
/* Use bits 31..16 for primary, 15..2 for secondary lookup */
ESecMap* sm = primary_map[a >> 16];
UInt sm_off = (a & 0xFFFC) >> 2;
if (VGE_IS_DISTINGUISHED_SM(sm)) {
VG_(printf)("accessed distinguished 2ndary map! 0x%x\n", a);
// XXX: may be legit, but I want to know when it happens --njn
//VG_(skin_panic)("accessed distinguished 2ndary map!");
return SEC_MAP_ACCESS;
}
//PROF_EVENT(21); PPP
return & (sm->swords[sm_off]);
}
// SSS: rename these so they're not so similar to memcheck, unless it's
// appropriate of course
static __inline__
void init_virgin_sword(Addr a)
{
if (clo_execontext != EC_None)
setExeContext(a, NULL_EC_EIP);
set_sword(a, virgin_sword);
}
static __inline__
void init_error_sword(Addr a)
{
set_sword(a, error_sword);
}
static __inline__
void init_nonvirgin_sword(Addr a)
{
shadow_word sword;
ThreadId tid = VG_(get_current_or_recent_tid)();
ThreadLifeSeg *tls;
sk_assert(tid != VG_INVALID_THREADID);
tls = thread_seg[tid];
sword = SW(Vge_Excl, packTLS(tls));
set_sword(a, sword);
}
/* In this case, we treat it for Eraser's sake like virgin (it hasn't
* been inited by a particular thread, it's just done automatically upon
* startup), but we mark its .state specially so it doesn't look like an
* uninited read. */
static __inline__
void init_magically_inited_sword(Addr a)
{
shadow_word sword;
sk_assert(VG_INVALID_THREADID == VG_(get_current_tid)());
sword = SW(Vge_Virgin, TID_INDICATING_NONVIRGIN);
set_sword(a, virgin_sword);
}
/*------------------------------------------------------------*/
/*--- Implementation of lock sets. ---*/
/*------------------------------------------------------------*/
typedef struct _Mutex Mutex; /* forward decl */
typedef struct _LockSet LockSet;
typedef enum MutexState {
MxUnknown, /* don't know */
MxUnlocked, /* unlocked */
MxLocked, /* locked */
MxDead /* destroyed */
} MutexState;
struct _Mutex {
Addr mutexp;
Mutex *next;
MutexState state; /* mutex state */
ThreadId tid; /* owner */
ExeContext *location; /* where the last change happened */
const LockSet *lockdep; /* set of locks we depend on */
UInt mark; /* mark for graph traversal */
};
static inline Int mutex_cmp(const Mutex *a, const Mutex *b)
{
return a->mutexp - b->mutexp;
}
struct _LockSet {
Int setsize; /* number of members */
UInt hash; /* hash code */
LockSet *next; /* next in hash chain */
const Mutex *mutex[0]; /* locks */
};
static const LockSet *emptyset;
/* Each one is an index into the lockset table. */
static const LockSet *thread_locks[VG_N_THREADS];
#define LOCKSET_HASH_SZ 1021
static LockSet *lockset_hash[LOCKSET_HASH_SZ];
/* Pack and unpack a LockSet pointer into shadow_word.other */
static inline UInt packLockSet(const LockSet *p)
{
UInt id;
SK_ASSERT(((UInt)p & ((1 << STATE_BITS)-1)) == 0);
id = ((UInt)p) >> STATE_BITS;
return id;
}
static inline const LockSet *unpackLockSet(UInt id)
{
return (LockSet *)(id << STATE_BITS);
}
static
void pp_LockSet(const LockSet* p)
{
Int i;
VG_(printf)("{ ");
for(i = 0; i < p->setsize; i++) {
const Mutex *mx = p->mutex[i];
VG_(printf)("%p%(y ", mx->mutexp, mx->mutexp);
}
VG_(printf)("}\n");
}
static void print_LockSet(const Char *s, const LockSet *ls)
{
VG_(printf)("%s: ", s);
pp_LockSet(ls);
}
/* Compute the hash of a LockSet */
static UInt hash_LockSet_w_wo(const LockSet *ls,
const Mutex *with,
const Mutex *without)
{
Int i;
UInt hash = ls->setsize + (with != NULL) - (without != NULL);
sk_assert(with == NULL || with != without);
for(i = 0; with != NULL || i < ls->setsize; i++) {
const Mutex *mx = i >= ls->setsize ? NULL : ls->mutex[i];
if (without && mutex_cmp(without, mx) == 0)
continue;
if (with && (mx == NULL || mutex_cmp(with, mx) < 0)) {
mx = with;
with = NULL;
i--;
}
hash = ROTL(hash, 17);
hash ^= (UInt)mx->mutexp;
}
return hash % LOCKSET_HASH_SZ;
}
static inline UInt hash_LockSet_with(const LockSet *ls, const Mutex *with)
{
UInt hash = hash_LockSet_w_wo(ls, with, NULL);
if (0)
VG_(printf)("hash_with %p+%p -> %d\n", ls, with->mutexp, hash);
return hash;
}
static inline UInt hash_LockSet_without(const LockSet *ls, const Mutex *without)
{
UInt hash = hash_LockSet_w_wo(ls, NULL, without);
if (0)
VG_(printf)("hash_with %p-%p -> %d\n", ls, without->mutexp, hash);
return hash;
}
static inline UInt hash_LockSet(const LockSet *ls)
{
UInt hash = hash_LockSet_w_wo(ls, NULL, NULL);
if (0)
VG_(printf)("hash %p -> %d\n", ls, hash);
return hash;
}
static
Bool structural_eq_LockSet(const LockSet* a, const LockSet* b)
{
Int i;
if (a == b)
return True;
if (a->setsize != b->setsize)
return False;
for(i = 0; i < a->setsize; i++) {
if (mutex_cmp(a->mutex[i], b->mutex[i]) != 0)
return False;
}
return True;
}
/* Tricky: equivalent to (compare(insert(missing_elem, a), b)), but
* doesn't do the insertion. Returns True if they match.
*/
static Bool
weird_LockSet_equals(const LockSet* a, const LockSet* b,
const Mutex *missing_mutex)
{
static const Bool debug = False;
Int ia, ib;
/* Idea is to try and match each element of b against either an
element of a, or missing_mutex. */
if (debug) {
print_LockSet("weird_LockSet_equals a", a);
print_LockSet(" b", b);
VG_(printf)( " missing: %p%(y\n",
missing_mutex->mutexp, missing_mutex->mutexp);
}
if ((a->setsize + 1) != b->setsize) {
if (debug)
VG_(printf)(" fastpath length mismatch -> 0\n");
return False;
}
/* There are three phases to this compare:
1 the section from the start of a up to missing_mutex
2 missing mutex itself
3 the section after missing_mutex to the end of a
*/
ia = 0;
ib = 0;
/* 1: up to missing_mutex */
for(; ia < a->setsize && mutex_cmp(a->mutex[ia], missing_mutex) < 0; ia++, ib++) {
if (debug) {
print_LockSet(" 1:a", a);
print_LockSet(" 1:b", b);
}
if (ib == b->setsize || mutex_cmp(a->mutex[ia], b->mutex[ib]) != 0)
return False;
}
/* 2: missing_mutex itself */
if (debug) {
VG_(printf)( " 2:missing: %p%(y\n",
missing_mutex->mutexp, missing_mutex->mutexp);
print_LockSet(" 2: b", b);
}
sk_assert(ia == a->setsize || mutex_cmp(a->mutex[ia], missing_mutex) >= 0);
if (ib == b->setsize || mutex_cmp(missing_mutex, b->mutex[ib]) != 0)
return False;
ib++;
/* 3: after missing_mutex to end */
for(; ia < a->setsize && ib < b->setsize; ia++, ib++) {
if (debug) {
print_LockSet(" 3:a", a);
print_LockSet(" 3:b", b);
}
if (mutex_cmp(a->mutex[ia], b->mutex[ib]) != 0)
return False;
}
if (debug)
VG_(printf)(" ia=%d ib=%d --> %d\n", ia, ib, ia == a->setsize && ib == b->setsize);
return ia == a->setsize && ib == b->setsize;
}
static const LockSet *lookup_LockSet(const LockSet *set)
{
UInt bucket = set->hash;
LockSet *ret;
for(ret = lockset_hash[bucket]; ret != NULL; ret = ret->next)
if (set == ret || structural_eq_LockSet(set, ret))
return ret;
return NULL;
}
static const LockSet *lookup_LockSet_with(const LockSet *set, Mutex *mutex)
{
UInt bucket = hash_LockSet_with(set, mutex);
const LockSet *ret;
for(ret = lockset_hash[bucket]; ret != NULL; ret = ret->next)
if (weird_LockSet_equals(set, ret, mutex))
return ret;
return NULL;
}
static const LockSet *lookup_LockSet_without(const LockSet *set, Mutex *mutex)
{
UInt bucket = hash_LockSet_without(set, mutex);
const LockSet *ret;
for(ret = lockset_hash[bucket]; ret != NULL; ret = ret->next)
if (weird_LockSet_equals(ret, set, mutex))
return ret;
return NULL;
}
static void insert_LockSet(LockSet *set)
{
UInt hash = hash_LockSet(set);
set->hash = hash;
sk_assert(lookup_LockSet(set) == NULL);
set->next = lockset_hash[hash];
lockset_hash[hash] = set;
}
static inline
LockSet *alloc_LockSet(UInt setsize)
{
LockSet *ret = VG_(malloc)(sizeof(*ret) + sizeof(Mutex *) * setsize);
ret->setsize = setsize;
return ret;
}
static inline
void free_LockSet(LockSet *p)
{
/* assert: not present in hash */
VG_(free)(p);
}
static
void pp_all_LockSets ( void )
{
Int i;
Int sets, buckets;
sets = buckets = 0;
for (i = 0; i < LOCKSET_HASH_SZ; i++) {
const LockSet *ls = lockset_hash[i];
Bool first = True;
for(; ls != NULL; ls = ls->next) {
if (first) {
buckets++;
VG_(printf)("[%4d] = ", i);
} else
VG_(printf)(" ");
sets++;
first = False;
pp_LockSet(ls);
}
}
VG_(printf)("%d distinct LockSets in %d buckets\n", sets, buckets);
}
static inline Bool isempty(const LockSet *ls)
{
return ls == NULL || ls->setsize == 0;
}
static Bool ismember(const LockSet *ls, const Mutex *mx)
{
Int i;
/* XXX use binary search */
for(i = 0; i < ls->setsize; i++)
if (mutex_cmp(mx, ls->mutex[i]) == 0)
return True;
return False;
}
/* Check invariants:
- all locksets are unique
- each set is an array in strictly increasing order of mutex addr
*/
static
void sanity_check_locksets ( const Char* caller )
{
Int i;
const Char *badness;
LockSet *ls;
for(i = 0; i < LOCKSET_HASH_SZ; i++) {
for(ls = lockset_hash[i]; ls != NULL; ls = ls->next) {
const Mutex *prev;
Int j;
if (hash_LockSet(ls) != ls->hash) {
badness = "mismatched hash";
goto bad;
}
if (ls->hash != (UInt)i) {
badness = "wrong bucket";
goto bad;
}
if (lookup_LockSet(ls) != ls) {
badness = "non-unique set";
goto bad;
}
prev = ls->mutex[0];
for(j = 1; j < ls->setsize; j++) {
if (mutex_cmp(prev, ls->mutex[j]) >= 0) {
badness = "mutexes out of order";
goto bad;
}
}
}
}
return;
bad:
VG_(printf)("sanity_check_locksets: "
"i = %d, ls=%p badness = %s, caller = %s\n",
i, ls, badness, caller);
pp_all_LockSets();
VG_(skin_panic)("sanity_check_locksets");
}
static
LockSet *add_LockSet(const LockSet *ls, const Mutex *mx)
{
static const Bool debug = False;
LockSet *ret = NULL;
Int i, j;
if (debug || DEBUG_MEM_LOCKSET_CHANGES) {
VG_(printf)("add-IN mutex %p%(y\n", mx->mutexp, mx->mutexp);
print_LockSet("add-IN", ls);
}
if (debug || LOCKSET_SANITY)
sanity_check_locksets("add-IN");
sk_assert(!ismember(ls, mx));
ret = alloc_LockSet(ls->setsize+1);
for(i = j = 0; i < ls->setsize; i++) {
if (debug)
VG_(printf)("i=%d j=%d ls->mutex[i]=%p mx=%p\n",
i, j, ls->mutex[i]->mutexp, mx ? mx->mutexp : 0);
if (mx && mutex_cmp(mx, ls->mutex[i]) < 0) {
ret->mutex[j++] = mx;
mx = NULL;
}
ret->mutex[j++] = ls->mutex[i];
}
/* not added in loop - must be after */
if (mx)
ret->mutex[j++] = mx;
sk_assert(j == ret->setsize);
if (debug || LOCKSET_SANITY) {
print_LockSet("add-OUT", ret);
sanity_check_locksets("add-OUT");
}
return ret;
}
/* Builds ls with mx removed. mx should actually be in ls!
(a checked assertion). Resulting set should not already
exist in the table (unchecked).
*/
static
LockSet *remove_LockSet ( const LockSet *ls, const Mutex *mx )
{
static const Bool debug = False;
LockSet *ret = NULL;
Int i, j;
if (debug || DEBUG_MEM_LOCKSET_CHANGES) {
print_LockSet("remove-IN", ls);
}
if (debug || LOCKSET_SANITY)
sanity_check_locksets("remove-IN");
sk_assert(ismember(ls, mx));
ret = alloc_LockSet(ls->setsize-1);
for(i = j = 0; i < ls->setsize; i++) {
if (mutex_cmp(ls->mutex[i], mx) == 0)
continue;
ret->mutex[j++] = ls->mutex[i];
}
sk_assert(j == ret->setsize);
if (debug || LOCKSET_SANITY) {
print_LockSet("remove-OUT", ret);
sanity_check_locksets("remove-OUT");
}
return ret;
}
/* Builds the intersection, and then unbuilds it if it's already in the table.
*/
static const LockSet *_intersect(const LockSet *a, const LockSet *b)
{
static const Bool debug = False;
Int iret;
Int ia, ib;
Int size;
LockSet *ret;
const LockSet *found;
if (debug || LOCKSET_SANITY)
sanity_check_locksets("intersect-IN");
if (debug || DEBUG_MEM_LOCKSET_CHANGES) {
print_LockSet("intersect a", a);
print_LockSet("intersect b", b);
}
/* count the size of the new set */
size = 0;
ia = ib = 0;
for(size = ia = ib = 0; ia < a->setsize && ib < b->setsize; ) {
if (mutex_cmp(a->mutex[ia], b->mutex[ib]) == 0) {
size++;
ia++;
ib++;
} else if (mutex_cmp(a->mutex[ia], b->mutex[ib]) < 0) {
ia++;
} else {
sk_assert(mutex_cmp(a->mutex[ia], b->mutex[ib]) > 0);
ib++;
}
}
/* Build the intersection of the two sets */
ret = alloc_LockSet(size);
for (iret = ia = ib = 0; ia < a->setsize && ib < b->setsize; ) {
if (mutex_cmp(a->mutex[ia], b->mutex[ib]) == 0) {
sk_assert(iret < ret->setsize);
ret->mutex[iret++] = a->mutex[ia];
ia++;
ib++;
} else if (mutex_cmp(a->mutex[ia], b->mutex[ib]) < 0) {
ia++;
} else {
sk_assert(mutex_cmp(a->mutex[ia], b->mutex[ib]) > 0);
ib++;
}
}
ret->hash = hash_LockSet(ret);
/* Now search for it in the table, adding it if not seen before */
found = lookup_LockSet(ret);
if (found != NULL) {
free_LockSet(ret);
} else {
insert_LockSet(ret);
found = ret;
}
if (debug || LOCKSET_SANITY) {
print_LockSet("intersect-OUT", found);
sanity_check_locksets("intersect-OUT");
}
return found;
}
/* inline the fastpath */
static inline const LockSet *intersect(const LockSet *a, const LockSet *b)
{
static const Bool debug = False;
/* Fast case -- when the two are the same */
if (a == b) {
if (debug || DEBUG_MEM_LOCKSET_CHANGES) {
print_LockSet("intersect-same fastpath", a);
}
return a;
}
if (isempty(a) || isempty(b)) {
if (debug)
VG_(printf)("intersect empty fastpath\n");
return emptyset;
}
return _intersect(a, b);
}
static const LockSet *ls_union(const LockSet *a, const LockSet *b)
{
static const Bool debug = False;
Int iret;
Int ia, ib;
Int size;
LockSet *ret;
const LockSet *found;
if (debug || LOCKSET_SANITY)
sanity_check_locksets("union-IN");
/* Fast case -- when the two are the same */
if (a == b) {
if (debug || DEBUG_MEM_LOCKSET_CHANGES) {
print_LockSet("union-same fastpath", a);
}
return a;
}
if (isempty(a)) {
if (debug)
print_LockSet("union a=empty b", b);
return b;
}
if (isempty(b)) {
if (debug)
print_LockSet("union b=empty a", a);
return a;
}
if (debug || DEBUG_MEM_LOCKSET_CHANGES) {
print_LockSet("union a", a);
print_LockSet("union b", b);
}
/* count the size of the new set */
for(size = ia = ib = 0; (ia < a->setsize) || (ib < b->setsize); ) {
Int cmp;
if ((ia < a->setsize) && (ib < b->setsize))
cmp = mutex_cmp(a->mutex[ia], b->mutex[ib]);
else if (ia == a->setsize)
cmp = 1;
else
cmp = -1;
if (cmp == 0) {
size++;
ia++;
ib++;
} else if (cmp < 0) {
size++;
ia++;
} else {
sk_assert(cmp > 0);
size++;
ib++;
}
}
/* Build the intersection of the two sets */
ret = alloc_LockSet(size);
for (iret = ia = ib = 0; (ia < a->setsize) || (ib < b->setsize); ) {
Int cmp;
sk_assert(iret < ret->setsize);
if ((ia < a->setsize) && (ib < b->setsize))
cmp = mutex_cmp(a->mutex[ia], b->mutex[ib]);
else if (ia == a->setsize)
cmp = 1;
else
cmp = -1;
if (cmp == 0) {
ret->mutex[iret++] = a->mutex[ia];
ia++;
ib++;
} else if (cmp < 0) {
ret->mutex[iret++] = a->mutex[ia];
ia++;
} else {
sk_assert(cmp > 0);
ret->mutex[iret++] = b->mutex[ib];
ib++;
}
}
sk_assert(iret == ret->setsize);
ret->hash = hash_LockSet(ret);
/* Now search for it in the table, adding it if not seen before */
found = lookup_LockSet(ret);
if (found != NULL) {
if (debug)
print_LockSet("union found existing set", found);
free_LockSet(ret);
} else {
if (debug)
print_LockSet("union inserting new set", ret);
insert_LockSet(ret);
found = ret;
}
if (debug || LOCKSET_SANITY) {
print_LockSet("union-OUT", found);
sanity_check_locksets("union-OUT");
}
return found;
}
/*------------------------------------------------------------*/
/*--- Implementation of mutex structure. ---*/
/*------------------------------------------------------------*/
static UInt graph_mark; /* current mark we're using for graph traversal */
static void record_mutex_error(ThreadId tid, Mutex *mutex,
Char *str, ExeContext *ec);
static void record_lockgraph_error(ThreadId tid, Mutex *mutex,
const LockSet *lockset_holding,
const LockSet *lockset_prev);
static void set_mutex_state(Mutex *mutex, MutexState state, ThreadId tid);
#define M_MUTEX_HASHSZ 1021
static Mutex *mutex_hash[M_MUTEX_HASHSZ];
static UInt total_mutexes;
static const Char *pp_MutexState(MutexState st)
{
switch(st) {
case MxLocked: return "Locked";
case MxUnlocked: return "Unlocked";
case MxDead: return "Dead";
case MxUnknown: return "Unknown";
}
return "???";
}
static void pp_all_mutexes()
{
Int i;
Int locks, buckets;
locks = buckets = 0;
for(i = 0; i < M_MUTEX_HASHSZ; i++) {
Mutex *mx;
Bool first = True;
for(mx = mutex_hash[i]; mx != NULL; mx = mx->next) {
if (first) {
buckets++;
VG_(printf)("[%4d] = ", i);
} else
VG_(printf)(" ");
locks++;
first = False;
VG_(printf)("%p [%8s] -> %p%(y\n",
mx, pp_MutexState(mx->state), mx->mutexp, mx->mutexp);
}
}
VG_(printf)("%d locks in %d buckets (%d allocated)\n",
locks, buckets, total_mutexes);
}
/* find or create a Mutex for a program's mutex use */
static Mutex *get_mutex(Addr mutexp)
{
UInt bucket = ((UInt)mutexp) % M_MUTEX_HASHSZ;
Mutex *mp;
for(mp = mutex_hash[bucket]; mp != NULL; mp = mp->next)
if (mp->mutexp == mutexp)
return mp;
total_mutexes++;
mp = VG_(malloc)(sizeof(*mp));
mp->mutexp = mutexp;
mp->next = mutex_hash[bucket];
mutex_hash[bucket] = mp;
mp->state = MxUnknown;
mp->tid = VG_INVALID_THREADID;
mp->location = NULL;
mp->lockdep = emptyset;
mp->mark = graph_mark - 1;
return mp;
}
/* Find all mutexes in a range of memory, and call the callback.
Remove the mutex from the hash if the callback returns True (mutex
structure itself is not freed, because it may be pointed to by a
LockSet. */
static void find_mutex_range(Addr start, Addr end, Bool (*action)(Mutex *))
{
UInt first = start % M_MUTEX_HASHSZ;
UInt last = (end+1) % M_MUTEX_HASHSZ;
UInt i;
/* Single pass over the hash table, looking for likely hashes */
for(i = first; i != last; ) {
Mutex *mx;
Mutex **prev = &mutex_hash[i];
for(mx = mutex_hash[i]; mx != NULL; prev = &mx->next, mx = mx->next) {
if (mx->mutexp >= start && mx->mutexp < end && (*action)(mx))
*prev = mx->next;
}
if (++i == M_MUTEX_HASHSZ)
i = 0;
}
}
#define MARK_LOOP (graph_mark+0)
#define MARK_DONE (graph_mark+1)
static Bool check_cycle(const Mutex *start, const LockSet* lockset)
{
Bool check_cycle_inner(const Mutex *mutex, const LockSet *ls)
{
static const Bool debug = False;
Int i;
if (mutex->mark == MARK_LOOP)
return True; /* found cycle */
if (mutex->mark == MARK_DONE)
return False; /* been here before, its OK */
((Mutex*)mutex)->mark = MARK_LOOP;
if (debug)
VG_(printf)("mark=%d visiting %p%(y mutex->lockset=%d\n",
graph_mark, mutex->mutexp, mutex->mutexp, mutex->lockdep);
for(i = 0; i < ls->setsize; i++) {
const Mutex *mx = ls->mutex[i];
if (debug)
VG_(printf)(" %y ls=%p (ls->mutex=%p%(y)\n",
mutex->mutexp, ls,
mx->mutexp, mx->mutexp);
if (check_cycle_inner(mx, mx->lockdep))
return True;
}
((Mutex*)mutex)->mark = MARK_DONE;
return False;
}
graph_mark += 2; /* clear all marks */
return check_cycle_inner(start, lockset);
}
/* test to see if a mutex state change would be problematic; this
makes no changes to the mutex state. This should be called before
the locking thread has actually blocked. */
static void test_mutex_state(Mutex *mutex, MutexState state, ThreadId tid)
{
static const Bool debug = False;
if (mutex->state == MxDead) {
Char *str;
switch(state) {
case MxLocked: str = "lock dead mutex"; break;
case MxUnlocked: str = "unlock dead mutex"; break;
default: str = "operate on dead mutex"; break;
}
/* can't do anything legal to a destroyed mutex */
record_mutex_error(tid, mutex, str, mutex->location);
return;
}
switch(state) {
case MxLocked:
sk_assert(!check_cycle(mutex, mutex->lockdep));
if (debug)
print_LockSet("thread holding", thread_locks[tid]);
if (check_cycle(mutex, thread_locks[tid]))
record_lockgraph_error(tid, mutex, thread_locks[tid], mutex->lockdep);
else {
mutex->lockdep = ls_union(mutex->lockdep, thread_locks[tid]);
if (debug) {
VG_(printf)("giving mutex %p%(y lockdep = %p ",
mutex->mutexp, mutex->mutexp, mutex->lockdep);
print_LockSet("lockdep", mutex->lockdep);
}
}
break;
case MxUnlocked:
if (debug)
print_LockSet("thread holding", thread_locks[tid]);
if (mutex->state != MxLocked) {
record_mutex_error(tid, mutex,
"unlock non-locked mutex", mutex->location);
}
if (mutex->tid != tid) {
record_mutex_error(tid, mutex,
"unlock someone else's mutex", mutex->location);
}
break;
case MxDead:
break;
default:
break;
}
}
/* Update a mutex state. Expects most error testing and reporting to
have happened in test_mutex_state(). The assumption is that no
client code is run by thread tid between test and set, either
because it is blocked or test and set are called together
atomically.
Setting state to MxDead is the exception, since that can happen as
a result of any thread freeing memory; in this case set_mutex_state
does all the error reporting as well.
*/
static void set_mutex_state(Mutex *mutex, MutexState state, ThreadId tid)
{
static const Bool debug = False;
if (debug)
VG_(printf)("\ntid %d changing mutex (%p)->%p%(y state %s -> %s\n",
tid, mutex, mutex->mutexp, mutex->mutexp,
pp_MutexState(mutex->state), pp_MutexState(state));
if (mutex->state == MxDead) {
/* can't do anything legal to a destroyed mutex */
return;
}
switch(state) {
case MxLocked:
if (mutex->state == MxLocked) {
if (mutex->tid != tid)
record_mutex_error(tid, mutex, "take lock held by someone else",
mutex->location);
else
record_mutex_error(tid, mutex, "take lock we already hold",
mutex->location);
VG_(skin_panic)("core should have checked this\n");
break;
}
sk_assert(!check_cycle(mutex, mutex->lockdep));
mutex->tid = tid;
break;
case MxUnlocked:
if (debug)
print_LockSet("thread holding", thread_locks[tid]);
if (mutex->state != MxLocked || mutex->tid != tid)
break;
mutex->tid = VG_INVALID_THREADID;
break;
case MxDead:
if (mutex->state == MxLocked) {
/* forcably remove offending lock from thread's lockset */
sk_assert(ismember(thread_locks[mutex->tid], mutex));
thread_locks[mutex->tid] = remove_LockSet(thread_locks[mutex->tid], mutex);
mutex->tid = VG_INVALID_THREADID;
record_mutex_error(tid, mutex,
"free locked mutex", mutex->location);
}
break;
default:
break;
}
mutex->location = VG_(get_ExeContext)(tid);
mutex->state = state;
}
/*------------------------------------------------------------*/
/*--- Setting and checking permissions. ---*/
/*------------------------------------------------------------*/
static
void set_address_range_state ( Addr a, UInt len /* in bytes */,
VgeInitStatus status )
{
Addr end;
/* only clean up dead mutexes */
Bool cleanmx(Mutex *mx) {
return mx->state == MxDead;
}
# if DEBUG_MAKE_ACCESSES
VG_(printf)("make_access: 0x%x, %u, status=%u\n", a, len, status);
# endif
//PROF_EVENT(30); PPP
if (len == 0)
return;
if (len > 100 * 1000 * 1000)
VG_(message)(Vg_UserMsg,
"Warning: set address range state: large range %d",
len);
VGP_PUSHCC(VgpSARP);
/* Remove mutexes in recycled memory range from hash */
find_mutex_range(a, a+len, cleanmx);
/* Memory block may not be aligned or a whole word multiple. In neat cases,
* we have to init len/4 words (len is in bytes). In nasty cases, it's
* len/4+1 words. This works out which it is by aligning the block and
* seeing if the end byte is in the same word as it is for the unaligned
* block; if not, it's the awkward case. */
end = ROUNDUP(a + len, 4);
a = ROUNDDN(a, 4);
/* Do it ... */
switch (status) {
case Vge_VirginInit:
for ( ; a < end; a += 4) {
//PROF_EVENT(31); PPP
init_virgin_sword(a);
}
break;
case Vge_NonVirginInit:
for ( ; a < end; a += 4) {
//PROF_EVENT(31); PPP
init_nonvirgin_sword(a);
}
break;
case Vge_SegmentInit:
for ( ; a < end; a += 4) {
//PROF_EVENT(31); PPP
init_magically_inited_sword(a);
}
break;
case Vge_Error:
for ( ; a < end; a += 4) {
//PROF_EVENT(31); PPP
init_error_sword(a);
}
break;
default:
VG_(printf)("init_status = %u\n", status);
VG_(skin_panic)("Unexpected Vge_InitStatus");
}
/* Check that zero page and highest page have not been written to
-- this could happen with buggy syscall wrappers. Today
(2001-04-26) had precisely such a problem with
__NR_setitimer. */
sk_assert(SK_(cheap_sanity_check)());
VGP_POPCC(VgpSARP);
}
static void make_segment_readable ( Addr a, UInt len )
{
//PROF_EVENT(??); PPP
set_address_range_state ( a, len, Vge_SegmentInit );
}
static void make_writable ( Addr a, UInt len )
{
//PROF_EVENT(36); PPP
set_address_range_state( a, len, Vge_VirginInit );
}
static void make_readable ( Addr a, UInt len )
{
//PROF_EVENT(37); PPP
set_address_range_state( a, len, Vge_VirginInit );
}
/* Block-copy states (needed for implementing realloc()). */
static void copy_address_range_state(Addr src, Addr dst, UInt len)
{
UInt i;
//PROF_EVENT(40); PPP
for (i = 0; i < len; i += 4) {
shadow_word sword = *(get_sword_addr ( src+i ));
//PROF_EVENT(41); PPP
set_sword ( dst+i, sword );
}
}
// SSS: put these somewhere better
static void eraser_mem_read (Addr a, UInt data_size, ThreadId tid);
static void eraser_mem_write(Addr a, UInt data_size, ThreadId tid);
#define REGPARM(x) __attribute__((regparm (x)))
static void eraser_mem_help_read_1(Addr a) REGPARM(1);
static void eraser_mem_help_read_2(Addr a) REGPARM(1);
static void eraser_mem_help_read_4(Addr a) REGPARM(1);
static void eraser_mem_help_read_N(Addr a, UInt size) REGPARM(2);
static void eraser_mem_help_write_1(Addr a, UInt val) REGPARM(2);
static void eraser_mem_help_write_2(Addr a, UInt val) REGPARM(2);
static void eraser_mem_help_write_4(Addr a, UInt val) REGPARM(2);
static void eraser_mem_help_write_N(Addr a, UInt size) REGPARM(2);
static void bus_lock(void);
static void bus_unlock(void);
static
void eraser_pre_mem_read(CorePart part, ThreadId tid,
Char* s, UInt base, UInt size )
{
if (tid > 50) { VG_(printf)("pid = %d, s = `%s`, part = %d\n", tid, s, part); VG_(skin_panic)("a");}
eraser_mem_read(base, size, tid);
}
static
void eraser_pre_mem_read_asciiz(CorePart part, ThreadId tid,
Char* s, UInt base )
{
eraser_mem_read(base, VG_(strlen)((Char*)base), tid);
}
static
void eraser_pre_mem_write(CorePart part, ThreadId tid,
Char* s, UInt base, UInt size )
{
eraser_mem_write(base, size, tid);
}
static
void eraser_new_mem_startup( Addr a, UInt len, Bool rr, Bool ww, Bool xx )
{
/* Ignore the permissions, just make it readable. Seems to work... */
make_segment_readable(a, len);
}
static
void eraser_new_mem_heap ( Addr a, UInt len, Bool is_inited )
{
if (is_inited) {
make_readable(a, len);
} else {
make_writable(a, len);
}
}
static
void eraser_set_perms (Addr a, UInt len,
Bool rr, Bool ww, Bool xx)
{
if (rr) make_readable(a, len);
else if (ww) make_writable(a, len);
/* else do nothing */
}
static
void eraser_new_mem_stack_private(Addr a, UInt len)
{
set_address_range_state(a, len, Vge_NonVirginInit);
}
static
void eraser_new_mem_stack(Addr a, UInt len)
{
set_address_range_state(a, len, Vge_VirginInit);
}
/*--------------------------------------------------------------*/
/*--- Initialise the memory audit system on program startup. ---*/
/*--------------------------------------------------------------*/
static
void init_shadow_memory(void)
{
Int i;
for (i = 0; i < ESEC_MAP_WORDS; i++)
distinguished_secondary_map.swords[i] = virgin_sword;
/* These entries gradually get overwritten as the used address
space expands. */
for (i = 0; i < 65536; i++)
primary_map[i] = &distinguished_secondary_map;
}
/*------------------------------------------------------------*/
/*--- malloc() et al replacements ---*/
/*------------------------------------------------------------*/
static VgHashTable hg_malloc_list = NULL;
#define N_FREED_CHUNKS 2
static Int freechunkptr = 0;
static HG_Chunk *freechunks[N_FREED_CHUNKS];
/* Use a small redzone (paranoia) */
UInt VG_(vg_malloc_redzone_szB) = 4;
/* Allocate a user-chunk of size bytes. Also allocate its shadow
block, make the shadow block point at the user block. Put the
shadow chunk on the appropriate list, and set all memory
protections correctly. */
static void add_HG_Chunk ( ThreadId tid, Addr p, UInt size )
{
HG_Chunk* hc;
hc = VG_(malloc)(sizeof(HG_Chunk));
hc->data = p;
hc->size = size;
hc->where = VG_(get_ExeContext)(tid);
hc->tid = tid;
VG_(HT_add_node)( hg_malloc_list, (VgHashNode*)hc );
}
/* Allocate memory and note change in memory available */
static __inline__
void* alloc_and_new_mem ( Int size, UInt alignment, Bool is_zeroed )
{
Addr p;
if (size < 0) return NULL;
p = (Addr)VG_(cli_malloc)(alignment, size);
if (is_zeroed) VG_(memset)((void*)p, 0, size);
add_HG_Chunk ( VG_(get_current_or_recent_tid)(), p, size );
eraser_new_mem_heap( p, size, is_zeroed );
return (void*)p;
}
void* SK_(malloc) ( Int n )
{
return alloc_and_new_mem ( n, VG_(clo_alignment), /*is_zeroed*/False );
}
void* SK_(__builtin_new) ( Int n )
{
return alloc_and_new_mem ( n, VG_(clo_alignment), /*is_zeroed*/False );
}
void* SK_(__builtin_vec_new) ( Int n )
{
return alloc_and_new_mem ( n, VG_(clo_alignment), /*is_zeroed*/False );
}
void* SK_(memalign) ( Int align, Int n )
{
return alloc_and_new_mem ( n, align, /*is_zeroed*/False );
}
void* SK_(calloc) ( Int nmemb, Int size )
{
return alloc_and_new_mem ( nmemb*size, VG_(clo_alignment),
/*is_zeroed*/True );
}
static
void die_and_free_mem ( ThreadId tid, HG_Chunk* hc,
HG_Chunk** prev_chunks_next_ptr )
{
Addr start = hc->data;
Addr end = start + hc->size;
Bool deadmx(Mutex *mx) {
if (mx->state != MxDead)
set_mutex_state(mx, MxDead, tid);
return False;
}
/* Remove hc from the malloclist using prev_chunks_next_ptr to
avoid repeating the hash table lookup. Can't remove until at least
after free and free_mismatch errors are done because they use
describe_addr() which looks for it in malloclist. */
*prev_chunks_next_ptr = hc->next;
/* Record where freed */
hc->where = VG_(get_ExeContext) ( tid );
/* maintain a small window so that the error reporting machinery
knows about this memory */
if (freechunks[freechunkptr] != NULL) {
/* free HG_Chunk */
HG_Chunk* sc1 = freechunks[freechunkptr];
VG_(cli_free) ( (void*)(sc1->data) );
VG_(free) ( sc1 );
}
freechunks[freechunkptr] = hc;
if (++freechunkptr == N_FREED_CHUNKS)
freechunkptr = 0;
/* mark all mutexes in range dead */
find_mutex_range(start, end, deadmx);
}
static __inline__
void handle_free ( void* p )
{
HG_Chunk* hc;
HG_Chunk** prev_chunks_next_ptr;
hc = (HG_Chunk*)VG_(HT_get_node) ( hg_malloc_list, (UInt)p,
(VgHashNode***)&prev_chunks_next_ptr );
if (hc == NULL) {
return;
}
die_and_free_mem ( VG_(get_current_or_recent_tid)(),
hc, prev_chunks_next_ptr );
}
void SK_(free) ( void* p )
{
handle_free(p);
}
void SK_(__builtin_delete) ( void* p )
{
handle_free(p);
}
void SK_(__builtin_vec_delete) ( void* p )
{
handle_free(p);
}
void* SK_(realloc) ( void* p, Int new_size )
{
HG_Chunk *hc;
HG_Chunk **prev_chunks_next_ptr;
Int i;
ThreadId tid = VG_(get_current_or_recent_tid)();
/* First try and find the block. */
hc = (HG_Chunk*)VG_(HT_get_node) ( hg_malloc_list, (UInt)p,
(VgHashNode***)&prev_chunks_next_ptr );
if (hc == NULL) {
return NULL;
}
if (hc->size == new_size) {
/* size unchanged */
hc->where = VG_(get_ExeContext)(tid);
return p;
} else if (hc->size > new_size) {
/* new size is smaller */
hc->size = new_size;
hc->where = VG_(get_ExeContext)(tid);
return p;
} else {
/* new size is bigger */
Addr p_new;
/* Get new memory */
p_new = (Addr)VG_(cli_malloc)(VG_(clo_alignment), new_size);
/* First half kept and copied, second half new */
copy_address_range_state( (Addr)p, p_new, hc->size );
eraser_new_mem_heap ( p_new+hc->size, new_size-hc->size,
/*inited*/False );
/* Copy from old to new */
for (i = 0; i < hc->size; i++)
((UChar*)p_new)[i] = ((UChar*)p)[i];
/* Free old memory */
die_and_free_mem ( tid, hc, prev_chunks_next_ptr );
/* this has to be after die_and_free_mem, otherwise the
former succeeds in shorting out the new block, not the
old, in the case when both are on the same list. */
add_HG_Chunk ( tid, p_new, new_size );
return (void*)p_new;
}
}
/*--------------------------------------------------------------*/
/*--- Machinery to support sanity checking ---*/
/*--------------------------------------------------------------*/
Bool SK_(cheap_sanity_check) ( void )
{
/* nothing useful we can rapidly check */
return True;
}
Bool SK_(expensive_sanity_check)(void)
{
Int i;
/* Make sure nobody changed the distinguished secondary. */
for (i = 0; i < ESEC_MAP_WORDS; i++)
if (distinguished_secondary_map.swords[i].other != virgin_sword.other ||
distinguished_secondary_map.swords[i].state != virgin_sword.state)
return False;
return True;
}
/*--------------------------------------------------------------*/
/*--- Instrumentation ---*/
/*--------------------------------------------------------------*/
static UInt stk_ld, nonstk_ld, stk_st, nonstk_st;
/* Create and return an instrumented version of cb_in. Free cb_in
before returning. */
UCodeBlock* SK_(instrument) ( UCodeBlock* cb_in, Addr not_used )
{
UCodeBlock* cb;
Int i;
UInstr* u_in;
Int t_size = INVALID_TEMPREG;
Int ntemps;
Bool *stackref = NULL;
Bool locked = False; /* lock prefix */
cb = VG_(setup_UCodeBlock)(cb_in);
/* stackref[] is used for super-simple value tracking to keep note
of which tempregs currently hold a value which is derived from
ESP or EBP, and is therefore likely stack-relative if used as
the address for LOAD or STORE. */
ntemps = VG_(get_num_temps)(cb);
stackref = VG_(malloc)(sizeof(*stackref) * ntemps);
VG_(memset)(stackref, 0, sizeof(*stackref) * ntemps);
for (i = 0; i < VG_(get_num_instrs)(cb_in); i++) {
u_in = VG_(get_instr)(cb_in, i);
switch (u_in->opcode) {
case NOP: case CALLM_S: case CALLM_E:
break;
case LOCK:
locked = True;
uInstr0(cb, CCALL, 0);
uCCall(cb, (Addr)bus_lock, 0, 0, False);
break;
case JMP: case INCEIP:
if (locked) {
uInstr0(cb, CCALL, 0);
uCCall(cb, (Addr)bus_unlock, 0, 0, False);
}
locked = False;
VG_(copy_UInstr)(cb, u_in);
break;
case GET:
sk_assert(u_in->tag1 == ArchReg);
sk_assert(u_in->tag2 == TempReg);
sk_assert(u_in->val2 < ntemps);
stackref[u_in->val2] = (u_in->size == 4 &&
(u_in->val1 == R_ESP || u_in->val1 == R_EBP));
VG_(copy_UInstr)(cb, u_in);
break;
case MOV:
if (u_in->size == 4 && u_in->tag1 == TempReg) {
sk_assert(u_in->tag2 == TempReg);
stackref[u_in->val2] = stackref[u_in->val1];
}
VG_(copy_UInstr)(cb, u_in);
break;
case LEA1:
case ADD: case SUB:
if (u_in->size == 4 && u_in->tag1 == TempReg) {
sk_assert(u_in->tag2 == TempReg);
stackref[u_in->val2] |= stackref[u_in->val1];
}
VG_(copy_UInstr)(cb, u_in);
break;
case LOAD: {
void (*help)(Addr);
sk_assert(1 == u_in->size || 2 == u_in->size || 4 == u_in->size);
sk_assert(u_in->tag1 == TempReg);
if (!clo_priv_stacks || !stackref[u_in->val1]) {
nonstk_ld++;
switch(u_in->size) {
case 1: help = eraser_mem_help_read_1; break;
case 2: help = eraser_mem_help_read_2; break;
case 4: help = eraser_mem_help_read_4; break;
default:
VG_(skin_panic)("bad size");
}
/* XXX all registers should be flushed to baseblock
here */
uInstr1(cb, CCALL, 0, TempReg, u_in->val1);
uCCall(cb, (Addr)help, 1, 1, False);
} else
stk_ld++;
VG_(copy_UInstr)(cb, u_in);
t_size = INVALID_TEMPREG;
break;
}
case MMX2_MemRd:
case FPU_R: {
sk_assert(1 == u_in->size || 2 == u_in->size || 4 == u_in->size ||
8 == u_in->size || 10 == u_in->size || 108 == u_in->size);
t_size = newTemp(cb);
uInstr2(cb, MOV, 4, Literal, 0, TempReg, t_size);
uLiteral(cb, (UInt)u_in->size);
/* XXX all registers should be flushed to baseblock
here */
uInstr2(cb, CCALL, 0, TempReg, u_in->val2, TempReg, t_size);
uCCall(cb, (Addr) & eraser_mem_help_read_N, 2, 2, False);
VG_(copy_UInstr)(cb, u_in);
t_size = INVALID_TEMPREG;
break;
}
case MMX2a1_MemRd: {
sk_assert(8 == u_in->size);
t_size = newTemp(cb);
uInstr2(cb, MOV, 4, Literal, 0, TempReg, t_size);
uLiteral(cb, (UInt)u_in->size);
/* XXX all registers should be flushed to baseblock
here */
uInstr2(cb, CCALL, 0, TempReg, u_in->val3, TempReg, t_size);
uCCall(cb, (Addr) & eraser_mem_help_read_N, 2, 2, False);
VG_(copy_UInstr)(cb, u_in);
t_size = INVALID_TEMPREG;
break;
}
case SSE2a_MemRd:
case SSE2a1_MemRd:
case SSE3a_MemRd:
case SSE3a1_MemRd:
case SSE3ag_MemRd_RegWr: {
Int addr = (u_in->opcode == SSE3ag_MemRd_RegWr) ? u_in->val1 : u_in->val3;
sk_assert(u_in->size == 4 || u_in->size == 8 || u_in->size == 16 || u_in->size == 512);
t_size = newTemp(cb);
uInstr2(cb, MOV, 4, Literal, 0, TempReg, t_size);
uLiteral(cb, (UInt)u_in->size);
uInstr2(cb, CCALL, 0, TempReg, addr, TempReg, t_size);
uCCall(cb, (Addr) & eraser_mem_help_read_N, 2, 2, False);
VG_(copy_UInstr)(cb, u_in);
t_size = INVALID_TEMPREG;
break;
}
case STORE: {
void (*help)(Addr, UInt);
sk_assert(1 == u_in->size || 2 == u_in->size || 4 == u_in->size);
sk_assert(u_in->tag2 == TempReg);
if (!clo_priv_stacks || !stackref[u_in->val2]) {
nonstk_st++;
switch(u_in->size) {
case 1: help = eraser_mem_help_write_1; break;
case 2: help = eraser_mem_help_write_2; break;
case 4: help = eraser_mem_help_write_4; break;
default:
VG_(skin_panic)("bad size");
}
/* XXX all registers should be flushed to baseblock
here */
uInstr2(cb, CCALL, 0, TempReg, u_in->val2, TempReg, u_in->val1);
uCCall(cb, (Addr)help, 2, 2, False);
} else
stk_st++;
VG_(copy_UInstr)(cb, u_in);
t_size = INVALID_TEMPREG;
break;
}
case MMX2_MemWr:
case FPU_W: {
sk_assert(1 == u_in->size || 2 == u_in->size || 4 == u_in->size ||
8 == u_in->size || 10 == u_in->size || 108 == u_in->size);
t_size = newTemp(cb);
uInstr2(cb, MOV, 4, Literal, 0, TempReg, t_size);
uLiteral(cb, (UInt)u_in->size);
/* XXX all registers should be flushed to baseblock
here */
uInstr2(cb, CCALL, 0, TempReg, u_in->val2, TempReg, t_size);
uCCall(cb, (Addr) & eraser_mem_help_write_N, 2, 2, False);
VG_(copy_UInstr)(cb, u_in);
t_size = INVALID_TEMPREG;
break;
}
case SSE2a_MemWr:
case SSE3a_MemWr: {
sk_assert(4 == u_in->size || 8 == u_in->size || 16 == u_in->size ||
512 == u_in->size);
t_size = newTemp(cb);
uInstr2(cb, MOV, 4, Literal, 0, TempReg, t_size);
uLiteral(cb, (UInt)u_in->size);
/* XXX all registers should be flushed to baseblock
here */
uInstr2(cb, CCALL, 0, TempReg, u_in->val3, TempReg, t_size);
uCCall(cb, (Addr) & eraser_mem_help_write_N, 2, 2, False);
VG_(copy_UInstr)(cb, u_in);
t_size = INVALID_TEMPREG;
break;
}
default:
/* conservative tromping */
if (0 && u_in->tag1 == TempReg) /* can val1 ever be dest? */
stackref[u_in->val1] = False;
if (u_in->tag2 == TempReg)
stackref[u_in->val2] = False;
if (u_in->tag3 == TempReg)
stackref[u_in->val3] = False;
VG_(copy_UInstr)(cb, u_in);
break;
}
}
VG_(free)(stackref);
VG_(free_UCodeBlock)(cb_in);
return cb;
}
/*--------------------------------------------------------------------*/
/*--- Error and suppression handling ---*/
/*--------------------------------------------------------------------*/
typedef
enum {
/* Possible data race */
EraserSupp
}
EraserSuppKind;
/* What kind of error it is. */
typedef
enum {
EraserErr, /* data-race */
MutexErr, /* mutex operations */
LockGraphErr, /* mutex order error */
}
EraserErrorKind;
/* The classification of a faulting address. */
typedef
enum { Undescribed, /* as-yet unclassified */
Stack,
Unknown, /* classification yielded nothing useful */
Mallocd,
Freed,
Segment
}
AddrKind;
/* Records info about a faulting address. */
typedef
struct {
/* ALL */
AddrKind akind;
/* Freed, Mallocd */
Int blksize;
/* Freed, Mallocd */
Int rwoffset;
/* Freed, Mallocd */
ExeContext* lastchange;
ThreadId lasttid;
/* Stack */
ThreadId stack_tid;
/* Segment */
const Char* filename;
const Char* section;
/* True if is just-below %esp -- could be a gcc bug. */
Bool maybe_gcc;
/* symbolic address description */
Char *expr;
}
AddrInfo;
/* What kind of memory access is involved in the error? */
typedef
enum { ReadAxs, WriteAxs, ExecAxs }
AxsKind;
/* Extra context for memory errors */
typedef
struct {
AxsKind axskind;
Int size;
AddrInfo addrinfo;
Bool isWrite;
shadow_word prevstate;
/* MutexErr, LockGraphErr */
Mutex *mutex;
EC_EIP lasttouched;
ThreadId lasttid;
/* LockGraphErr */
const LockSet *held_lockset;
const LockSet *prev_lockset;
}
HelgrindError;
static __inline__
void clear_AddrInfo ( AddrInfo* ai )
{
ai->akind = Unknown;
ai->blksize = 0;
ai->rwoffset = 0;
ai->lastchange = NULL;
ai->lasttid = VG_INVALID_THREADID;
ai->filename = NULL;
ai->section = "???";
ai->stack_tid = VG_INVALID_THREADID;
ai->maybe_gcc = False;
ai->expr = NULL;
}
static __inline__
void clear_HelgrindError ( HelgrindError* err_extra )
{
err_extra->axskind = ReadAxs;
err_extra->size = 0;
err_extra->mutex = NULL;
err_extra->lasttouched= NULL_EC_EIP;
err_extra->lasttid = VG_INVALID_THREADID;
err_extra->prev_lockset = 0;
err_extra->held_lockset = 0;
err_extra->prevstate = SW(Vge_Virgin, 0);
clear_AddrInfo ( &err_extra->addrinfo );
err_extra->isWrite = False;
}
/* Describe an address as best you can, for error messages,
putting the result in ai. */
static void describe_addr ( Addr a, AddrInfo* ai )
{
HG_Chunk* hc;
Int i;
/* Nested functions, yeah. Need the lexical scoping of 'a'. */
/* Closure for searching thread stacks */
Bool addr_is_in_bounds(Addr stack_min, Addr stack_max)
{
return (stack_min <= a && a <= stack_max);
}
/* Closure for searching malloc'd and free'd lists */
Bool addr_is_in_block(VgHashNode *node)
{
HG_Chunk* hc2 = (HG_Chunk*)node;
return (hc2->data <= a && a < hc2->data + hc2->size);
}
/* Search for it in segments */
{
const SegInfo *seg;
for(seg = VG_(next_seginfo)(NULL);
seg != NULL;
seg = VG_(next_seginfo)(seg)) {
Addr base = VG_(seg_start)(seg);
UInt size = VG_(seg_size)(seg);
const UChar *filename = VG_(seg_filename)(seg);
if (a >= base && a < base+size) {
ai->akind = Segment;
ai->blksize = size;
ai->rwoffset = a - base;
ai->filename = filename;
switch(VG_(seg_sect_kind)(a)) {
case Vg_SectText: ai->section = "text"; break;
case Vg_SectData: ai->section = "data"; break;
case Vg_SectBSS: ai->section = "BSS"; break;
case Vg_SectGOT: ai->section = "GOT"; break;
case Vg_SectPLT: ai->section = "PLT"; break;
case Vg_SectUnknown:
default:
ai->section = "???"; break;
}
return;
}
}
}
/* Search for a currently malloc'd block which might bracket it. */
hc = (HG_Chunk*)VG_(HT_first_match)(hg_malloc_list, addr_is_in_block);
if (NULL != hc) {
ai->akind = Mallocd;
ai->blksize = hc->size;
ai->rwoffset = (Int)a - (Int)(hc->data);
ai->lastchange = hc->where;
ai->lasttid = hc->tid;
return;
}
/* Look in recently freed memory */
for(i = 0; i < N_FREED_CHUNKS; i++) {
hc = freechunks[i];
if (hc == NULL)
continue;
if (a >= hc->data && a < hc->data + hc->size) {
ai->akind = Freed;
ai->blksize = hc->size;
ai->rwoffset = a - hc->data;
ai->lastchange = hc->where;
ai->lasttid = hc->tid;
return;
}
}
/* Clueless ... */
ai->akind = Unknown;
return;
}
/* Updates the copy with address info if necessary. */
UInt SK_(update_extra)(Error* err)
{
HelgrindError* extra;
extra = (HelgrindError*)VG_(get_error_extra)(err);
if (extra != NULL && Undescribed == extra->addrinfo.akind) {
describe_addr ( VG_(get_error_address)(err), &(extra->addrinfo) );
}
return sizeof(HelgrindError);
}
static void record_eraser_error ( ThreadId tid, Addr a, Bool is_write,
shadow_word prevstate )
{
shadow_word *sw;
HelgrindError err_extra;
n_eraser_warnings++;
clear_HelgrindError(&err_extra);
err_extra.isWrite = is_write;
err_extra.addrinfo.akind = Undescribed;
err_extra.prevstate = prevstate;
if (clo_execontext)
err_extra.lasttouched = getExeContext(a);
err_extra.addrinfo.expr = VG_(describe_addr)(tid, a);
VG_(maybe_record_error)( tid, EraserErr, a,
(is_write ? "writing" : "reading"),
&err_extra);
sw = get_sword_addr(a);
if (sw->state == Vge_Excl && sw->other != TLSP_INDICATING_ALL) {
ThreadLifeSeg *tls = unpackTLS(sw->other);
tls->refcount--;
}
set_sword(a, error_sword);
}
static void record_mutex_error(ThreadId tid, Mutex *mutex,
Char *str, ExeContext *ec)
{
HelgrindError err_extra;
clear_HelgrindError(&err_extra);
err_extra.addrinfo.akind = Undescribed;
err_extra.mutex = mutex;
err_extra.lasttouched = EC(ec, virgin_sword, thread_seg[tid]);
err_extra.lasttid = tid;
VG_(maybe_record_error)(tid, MutexErr,
(Addr)mutex->mutexp, str, &err_extra);
}
static void record_lockgraph_error(ThreadId tid, Mutex *mutex,
const LockSet *lockset_holding,
const LockSet *lockset_prev)
{
HelgrindError err_extra;
n_lockorder_warnings++;
clear_HelgrindError(&err_extra);
err_extra.addrinfo.akind = Undescribed;
err_extra.mutex = mutex;
err_extra.lasttouched = EC(mutex->location, virgin_sword, 0);
err_extra.held_lockset = lockset_holding;
err_extra.prev_lockset = lockset_prev;
VG_(maybe_record_error)(tid, LockGraphErr, mutex->mutexp, "", &err_extra);
}
Bool SK_(eq_SkinError) ( VgRes not_used, Error* e1, Error* e2 )
{
Char *e1s, *e2s;
sk_assert(VG_(get_error_kind)(e1) == VG_(get_error_kind)(e2));
switch (VG_(get_error_kind)(e1)) {
case EraserErr:
return VG_(get_error_address)(e1) == VG_(get_error_address)(e2);
case MutexErr:
return VG_(get_error_address)(e1) == VG_(get_error_address)(e2);
}
e1s = VG_(get_error_string)(e1);
e2s = VG_(get_error_string)(e2);
if (e1s != e2s) return False;
if (0 != VG_(strcmp)(e1s, e2s)) return False;
return True;
}
static void pp_AddrInfo ( Addr a, AddrInfo* ai )
{
if (ai->expr != NULL)
VG_(message)(Vg_UserMsg,
" Address %p == %s", a, ai->expr);
switch (ai->akind) {
case Stack:
VG_(message)(Vg_UserMsg,
" Address %p is on thread %d's stack",
a, ai->stack_tid);
break;
case Unknown:
if (ai->expr != NULL)
break;
/* maybe_gcc is never set to True! This is a hangover from code
in Memcheck */
if (ai->maybe_gcc) {
VG_(message)(Vg_UserMsg,
" Address %p is just below %%esp. Possibly a bug in GCC/G++",
a);
VG_(message)(Vg_UserMsg,
" v 2.96 or 3.0.X. To suppress, use: --workaround-gcc296-bugs=yes");
} else {
VG_(message)(Vg_UserMsg,
" Address %p is not stack'd, malloc'd or free'd", a);
}
break;
case Segment:
VG_(message)(Vg_UserMsg,
" Address %p is in %s section of %s",
a, ai->section, ai->filename);
break;
case Mallocd:
case Freed: {
UInt delta;
UChar* relative;
if (ai->rwoffset < 0) {
delta = (UInt)(- ai->rwoffset);
relative = "before";
} else if (ai->rwoffset >= ai->blksize) {
delta = ai->rwoffset - ai->blksize;
relative = "after";
} else {
delta = ai->rwoffset;
relative = "inside";
}
VG_(message)(Vg_UserMsg,
" Address %p is %d bytes %s a block of size %d %s by thread %d",
a, delta, relative,
ai->blksize,
ai->akind == Mallocd ? "alloc'd" : "freed",
ai->lasttid);
VG_(pp_ExeContext)(ai->lastchange);
break;
}
default:
VG_(skin_panic)("pp_AddrInfo");
}
}
static Char *lockset_str(const Char *prefix, const LockSet *lockset)
{
Char *buf, *cp;
Int i;
buf = VG_(malloc)((prefix == NULL ? 0 : VG_(strlen)(prefix)) +
lockset->setsize * 120 +
1);
cp = buf;
if (prefix)
cp += VG_(sprintf)(cp, "%s", prefix);
for(i = 0; i < lockset->setsize; i++)
cp += VG_(sprintf)(cp, "%p%(y, ", lockset->mutex[i]->mutexp,
lockset->mutex[i]->mutexp);
if (lockset->setsize)
cp[-2] = '\0';
else
*cp = '\0';
return buf;
}
void SK_(pp_SkinError) ( Error* err )
{
HelgrindError *extra = (HelgrindError *)VG_(get_error_extra)(err);
Char buf[100];
Char *msg = buf;
const LockSet *ls;
*msg = '\0';
switch(VG_(get_error_kind)(err)) {
case EraserErr: {
Addr err_addr = VG_(get_error_address)(err);
VG_(message)(Vg_UserMsg, "Possible data race %s variable at %p %(y",
VG_(get_error_string)(err), err_addr, err_addr);
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
pp_AddrInfo(err_addr, &extra->addrinfo);
switch(extra->prevstate.state) {
case Vge_Virgin:
/* shouldn't be possible to go directly from virgin -> error */
VG_(sprintf)(buf, "virgin!?");
break;
case Vge_Excl: {
ThreadLifeSeg *tls = unpackTLS(extra->prevstate.other);
sk_assert(tls != unpackTLS(TLSP_INDICATING_ALL));
VG_(sprintf)(buf, "exclusively owned by thread %u", tls->tid);
break;
}
case Vge_Shar:
case Vge_SharMod:
ls = unpackLockSet(extra->prevstate.other);
if (isempty(ls)) {
VG_(sprintf)(buf, "shared %s, no locks",
extra->prevstate.state == Vge_Shar ? "RO" : "RW");
break;
}
msg = lockset_str(extra->prevstate.state == Vge_Shar ?
"shared RO, locked by:" :
"shared RW, locked by:", ls);
break;
}
if (*msg)
VG_(message)(Vg_UserMsg, " Previous state: %s", msg);
if (clo_execontext == EC_Some
&& extra->lasttouched.uu_ec_eip.eip != 0) {
Char file[100];
UInt line;
Addr eip = extra->lasttouched.uu_ec_eip.eip;
VG_(message)(Vg_UserMsg, " Word at %p last changed state from %s by thread %u",
err_addr,
pp_state(extra->lasttouched.state),
unpackTLS(extra->lasttouched.tls)->tid);
if (VG_(get_filename_linenum)(eip, file, sizeof(file), &line)) {
VG_(message)(Vg_UserMsg, " at %p: %y (%s:%u)",
eip, eip, file, line);
} else if (VG_(get_objname)(eip, file, sizeof(file))) {
VG_(message)(Vg_UserMsg, " at %p: %y (in %s)",
eip, eip, file);
} else {
VG_(message)(Vg_UserMsg, " at %p: %y", eip, eip);
}
} else if (clo_execontext == EC_All
&& extra->lasttouched.uu_ec_eip.ec != NULL) {
VG_(message)(Vg_UserMsg, " Word at %p last changed state from %s in tid %u",
err_addr,
pp_state(extra->lasttouched.state),
unpackTLS(extra->lasttouched.tls)->tid);
VG_(pp_ExeContext)(extra->lasttouched.uu_ec_eip.ec);
}
break;
}
case MutexErr:
VG_(message)(Vg_UserMsg, "Mutex problem at %p%(y trying to %s",
VG_(get_error_address)(err),
VG_(get_error_address)(err),
VG_(get_error_string)(err));
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
if (extra->lasttouched.uu_ec_eip.ec != NULL) {
VG_(message)(Vg_UserMsg, " last touched by thread %d", extra->lasttid);
VG_(pp_ExeContext)(extra->lasttouched.uu_ec_eip.ec);
}
pp_AddrInfo(VG_(get_error_address)(err), &extra->addrinfo);
break;
case LockGraphErr: {
const LockSet *heldset = extra->held_lockset;
Addr err_addr = VG_(get_error_address)(err);
Int i;
msg = lockset_str(NULL, heldset);
VG_(message)(Vg_UserMsg, "Mutex %p%(y locked in inconsistent order",
err_addr, err_addr);
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
VG_(message)(Vg_UserMsg, " while holding locks %s", msg);
for(i = 0; i < heldset->setsize; i++) {
const Mutex *lsmx = heldset->mutex[i];
/* needs to be a recursive search+display */
if (0 && !ismember(lsmx->lockdep, extra->mutex))
continue;
VG_(message)(Vg_UserMsg, " %p%(y last locked at",
lsmx->mutexp, lsmx->mutexp);
VG_(pp_ExeContext)(lsmx->location);
VG_(free)(msg);
msg = lockset_str(NULL, lsmx->lockdep);
VG_(message)(Vg_UserMsg, " while depending on locks %s", msg);
}
break;
}
}
if (msg != buf)
VG_(free)(msg);
}
Bool SK_(recognised_suppression) ( Char* name, Supp *su )
{
if (0 == VG_(strcmp)(name, "Eraser")) {
VG_(set_supp_kind)(su, EraserSupp);
return True;
} else {
return False;
}
}
Bool SK_(read_extra_suppression_info) ( Int fd, Char* buf, Int nBuf, Supp* su )
{
/* do nothing -- no extra suppression info present. Return True to
indicate nothing bad happened. */
return True;
}
Bool SK_(error_matches_suppression)(Error* err, Supp* su)
{
sk_assert(VG_(get_supp_kind)(su) == EraserSupp);
return (VG_(get_error_kind)(err) == EraserErr);
}
extern Char* SK_(get_error_name) ( Error* err )
{
if (EraserErr == VG_(get_error_kind)(err)) {
return "Eraser";
} else {
return NULL; /* Other errors types can't be suppressed */
}
}
extern void SK_(print_extra_suppression_info) ( Error* err )
{
/* Do nothing */
}
static void eraser_pre_mutex_lock(ThreadId tid, void* void_mutex)
{
Mutex *mutex = get_mutex((Addr)void_mutex);
test_mutex_state(mutex, MxLocked, tid);
}
static void eraser_post_mutex_lock(ThreadId tid, void* void_mutex)
{
static const Bool debug = False;
Mutex *mutex = get_mutex((Addr)void_mutex);
const LockSet* ls;
set_mutex_state(mutex, MxLocked, tid);
# if DEBUG_LOCKS
VG_(printf)("lock (%u, %p)\n", tid, mutex->mutexp);
# endif
/* VG_(printf)("LOCK: held %d, new %p\n", thread_locks[tid], mutex); */
# if LOCKSET_SANITY > 1
sanity_check_locksets("eraser_post_mutex_lock-IN");
# endif
ls = lookup_LockSet_with(thread_locks[tid], mutex);
if (ls == NULL) {
LockSet *newset = add_LockSet(thread_locks[tid], mutex);
insert_LockSet(newset);
ls = newset;
}
thread_locks[tid] = ls;
if (debug || DEBUG_LOCKS)
VG_(printf)("tid %u now has lockset %p\n", tid, ls);
if (debug || LOCKSET_SANITY > 1)
sanity_check_locksets("eraser_post_mutex_lock-OUT");
}
static void eraser_post_mutex_unlock(ThreadId tid, void* void_mutex)
{
static const Bool debug = False;
Int i = 0;
Mutex *mutex = get_mutex((Addr)void_mutex);
const LockSet *ls;
test_mutex_state(mutex, MxUnlocked, tid);
set_mutex_state(mutex, MxUnlocked, tid);
if (!ismember(thread_locks[tid], mutex))
return;
if (debug || DEBUG_LOCKS)
VG_(printf)("unlock(%u, %p%(y)\n", tid, mutex->mutexp, mutex->mutexp);
if (debug || LOCKSET_SANITY > 1)
sanity_check_locksets("eraser_post_mutex_unlock-IN");
ls = lookup_LockSet_without(thread_locks[tid], mutex);
if (ls == NULL) {
LockSet *newset = remove_LockSet(thread_locks[tid], mutex);
insert_LockSet(newset);
ls = newset;
}
/* Update the thread's lock vector */
if (debug || DEBUG_LOCKS)
VG_(printf)("tid %u reverts from %p to lockset %p\n",
tid, thread_locks[tid], i);
thread_locks[tid] = ls;
if (debug || LOCKSET_SANITY > 1)
sanity_check_locksets("eraser_post_mutex_unlock-OUT");
}
/* ---------------------------------------------------------------------
Checking memory reads and writes
------------------------------------------------------------------ */
/* Behaviour on reads and writes:
*
* VIR EXCL SHAR SH_MOD
* ----------------------------------------------------------------
* rd/wr, 1st thread | - EXCL - -
* rd, new thread | - SHAR - -
* wr, new thread | - SH_MOD - -
* rd | error! - SHAR SH_MOD
* wr | EXCL - SH_MOD SH_MOD
* ----------------------------------------------------------------
*/
static inline
void dump_around_a(Addr a)
{
UInt i;
shadow_word* sword;
VG_(printf)("NEARBY:\n");
for (i = a - 12; i <= a + 12; i += 4) {
sword = get_sword_addr(i);
VG_(printf)(" %x -- tid: %u, state: %u\n", i, sword->other, sword->state);
}
}
#if DEBUG_ACCESSES
#define DEBUG_STATE(args...) \
VG_(printf)("(%u) ", size), \
VG_(printf)(args)
#else
#define DEBUG_STATE(args...)
#endif
static void eraser_mem_read_word(Addr a, ThreadId tid)
{
shadow_word* sword /* egcs-2.91.66 complains uninit */ = NULL;
shadow_word prevstate;
ThreadLifeSeg *tls;
const LockSet *ls;
Bool statechange = False;
static const void *const states[4] = {
[Vge_Virgin] &&st_virgin,
[Vge_Excl] &&st_excl,
[Vge_Shar] &&st_shar,
[Vge_SharMod] &&st_sharmod,
};
tls = thread_seg[tid];
sk_assert(tls != NULL && tls->tid == tid);
sword = get_sword_addr(a);
if (sword == SEC_MAP_ACCESS) {
VG_(printf)("read distinguished 2ndary map! 0x%x\n", a);
return;
}
prevstate = *sword;
goto *states[sword->state];
/* This looks like reading of unitialised memory, may be legit. Eg.
* calloc() zeroes its values, so untouched memory may actually be
* initialised. Leave that stuff to Valgrind. */
st_virgin:
if (TID_INDICATING_NONVIRGIN == sword->other) {
DEBUG_STATE("Read VIRGIN --> EXCL: %8x, %u\n", a, tid);
if (DEBUG_VIRGIN_READS)
dump_around_a(a);
} else {
DEBUG_STATE("Read SPECIAL --> EXCL: %8x, %u\n", a, tid);
}
statechange = True;
*sword = SW(Vge_Excl, packTLS(tls)); /* remember exclusive owner */
tls->refcount++;
goto done;
st_excl: {
ThreadLifeSeg *sw_tls = unpackTLS(sword->other);
if (tls == sw_tls) {
DEBUG_STATE("Read EXCL: %8x, %u\n", a, tid);
} else if (unpackTLS(TLSP_INDICATING_ALL) == sw_tls) {
DEBUG_STATE("Read EXCL/ERR: %8x, %u\n", a, tid);
} else if (tlsIsDisjoint(tls, sw_tls)) {
DEBUG_STATE("Read EXCL(%u) --> EXCL: %8x, %u\n", sw_tls->tid, a, tid);
statechange = True;
sword->other = packTLS(tls);
sw_tls->refcount--;
tls->refcount++;
} else {
DEBUG_STATE("Read EXCL(%u) --> SHAR: %8x, %u\n", sw_tls->tid, a, tid);
sw_tls->refcount--;
statechange = True;
*sword = SW(Vge_Shar, packLockSet(thread_locks[tid]));
if (DEBUG_MEM_LOCKSET_CHANGES)
print_LockSet("excl read locks", unpackLockSet(sword->other));
}
goto done;
}
st_shar:
DEBUG_STATE("Read SHAR: %8x, %u\n", a, tid);
sword->other = packLockSet(intersect(unpackLockSet(sword->other),
thread_locks[tid]));
statechange = sword->other != prevstate.other;
goto done;
st_sharmod:
DEBUG_STATE("Read SHAR_MOD: %8x, %u\n", a, tid);
ls = intersect(unpackLockSet(sword->other),
thread_locks[tid]);
sword->other = packLockSet(ls);
statechange = sword->other != prevstate.other;
if (isempty(ls)) {
record_eraser_error(tid, a, False /* !is_write */, prevstate);
}
goto done;
done:
if (clo_execontext != EC_None && statechange) {
EC_EIP eceip;
if (clo_execontext == EC_Some)
eceip = EIP(VG_(get_EIP)(tid), prevstate, tls);
else
eceip = EC(VG_(get_ExeContext)(tid), prevstate, tls);
setExeContext(a, eceip);
}
}
static void eraser_mem_read(Addr a, UInt size, ThreadId tid)
{
Addr end;
end = ROUNDUP(a+size, 4);
a = ROUNDDN(a, 4);
for ( ; a < end; a += 4)
eraser_mem_read_word(a, tid);
}
static void eraser_mem_write_word(Addr a, ThreadId tid)
{
ThreadLifeSeg *tls;
shadow_word* sword /* egcs-2.91.66 complains uninit */ = NULL;
shadow_word prevstate;
Bool statechange = False;
static const void *const states[4] = {
[Vge_Virgin] &&st_virgin,
[Vge_Excl] &&st_excl,
[Vge_Shar] &&st_shar,
[Vge_SharMod] &&st_sharmod,
};
tls = thread_seg[tid];
sk_assert(tls != NULL && tls->tid == tid);
sword = get_sword_addr(a);
if (sword == SEC_MAP_ACCESS) {
VG_(printf)("read distinguished 2ndary map! 0x%x\n", a);
return;
}
prevstate = *sword;
goto *states[sword->state];
st_virgin:
if (TID_INDICATING_NONVIRGIN == sword->other)
DEBUG_STATE("Write VIRGIN --> EXCL: %8x, %u\n", a, tid);
else
DEBUG_STATE("Write SPECIAL --> EXCL: %8x, %u\n", a, tid);
statechange = True;
*sword = SW(Vge_Excl, packTLS(tls));/* remember exclusive owner */
tls->refcount++;
goto done;
st_excl: {
ThreadLifeSeg *sw_tls = unpackTLS(sword->other);
if (tls == sw_tls) {
DEBUG_STATE("Write EXCL: %8x, %u\n", a, tid);
goto done;
} else if (unpackTLS(TLSP_INDICATING_ALL) == sw_tls) {
DEBUG_STATE("Write EXCL/ERR: %8x, %u\n", a, tid);
goto done;
} else if (tlsIsDisjoint(tls, sw_tls)) {
DEBUG_STATE("Write EXCL(%u) --> EXCL: %8x, %u\n", sw_tls->tid, a, tid);
sword->other = packTLS(tls);
sw_tls->refcount--;
tls->refcount++;
goto done;
} else {
DEBUG_STATE("Write EXCL(%u) --> SHAR_MOD: %8x, %u\n", sw_tls->tid, a, tid);
statechange = True;
sw_tls->refcount--;
*sword = SW(Vge_SharMod, packLockSet(thread_locks[tid]));
if(DEBUG_MEM_LOCKSET_CHANGES)
print_LockSet("excl write locks", unpackLockSet(sword->other));
goto SHARED_MODIFIED;
}
}
st_shar:
DEBUG_STATE("Write SHAR --> SHAR_MOD: %8x, %u\n", a, tid);
sword->state = Vge_SharMod;
sword->other = packLockSet(intersect(unpackLockSet(sword->other),
thread_locks[tid]));
statechange = True;
goto SHARED_MODIFIED;
st_sharmod:
DEBUG_STATE("Write SHAR_MOD: %8x, %u\n", a, tid);
sword->other = packLockSet(intersect(unpackLockSet(sword->other),
thread_locks[tid]));
statechange = sword->other != prevstate.other;
SHARED_MODIFIED:
if (isempty(unpackLockSet(sword->other))) {
record_eraser_error(tid, a, True /* is_write */, prevstate);
}
goto done;
done:
if (clo_execontext != EC_None && statechange) {
EC_EIP eceip;
if (clo_execontext == EC_Some)
eceip = EIP(VG_(get_EIP)(tid), prevstate, tls);
else
eceip = EC(VG_(get_ExeContext)(tid), prevstate, tls);
setExeContext(a, eceip);
}
}
static void eraser_mem_write(Addr a, UInt size, ThreadId tid)
{
Addr end;
end = ROUNDUP(a+size, 4);
a = ROUNDDN(a, 4);
for ( ; a < end; a += 4)
eraser_mem_write_word(a, tid);
}
#undef DEBUG_STATE
REGPARM(1) static void eraser_mem_help_read_1(Addr a)
{
eraser_mem_read(a, 1, VG_(get_current_tid)());
}
REGPARM(1) static void eraser_mem_help_read_2(Addr a)
{
eraser_mem_read(a, 2, VG_(get_current_tid)());
}
REGPARM(1) static void eraser_mem_help_read_4(Addr a)
{
eraser_mem_read(a, 4, VG_(get_current_tid)());
}
REGPARM(2) static void eraser_mem_help_read_N(Addr a, UInt size)
{
eraser_mem_read(a, size, VG_(get_current_tid)());
}
REGPARM(2) static void eraser_mem_help_write_1(Addr a, UInt val)
{
if (*(UChar *)a != val)
eraser_mem_write(a, 1, VG_(get_current_tid)());
}
REGPARM(2) static void eraser_mem_help_write_2(Addr a, UInt val)
{
if (*(UShort *)a != val)
eraser_mem_write(a, 2, VG_(get_current_tid)());
}
REGPARM(2) static void eraser_mem_help_write_4(Addr a, UInt val)
{
if (*(UInt *)a != val)
eraser_mem_write(a, 4, VG_(get_current_tid)());
}
REGPARM(2) static void eraser_mem_help_write_N(Addr a, UInt size)
{
eraser_mem_write(a, size, VG_(get_current_tid)());
}
static void hg_thread_create(ThreadId parent, ThreadId child)
{
if (0)
VG_(printf)("CREATE: %u creating %u\n", parent, child);
newTLS(child);
addPriorTLS(child, parent);
newTLS(parent);
}
static void hg_thread_join(ThreadId joiner, ThreadId joinee)
{
if (0)
VG_(printf)("JOIN: %u joining on %u\n", joiner, joinee);
newTLS(joiner);
addPriorTLS(joiner, joinee);
clearTLS(joinee);
}
static Int __BUS_HARDWARE_LOCK__;
static void bus_lock(void)
{
ThreadId tid = VG_(get_current_tid)();
eraser_pre_mutex_lock(tid, &__BUS_HARDWARE_LOCK__);
eraser_post_mutex_lock(tid, &__BUS_HARDWARE_LOCK__);
}
static void bus_unlock(void)
{
ThreadId tid = VG_(get_current_tid)();
eraser_post_mutex_unlock(tid, &__BUS_HARDWARE_LOCK__);
}
/*--------------------------------------------------------------------*/
/*--- Client requests ---*/
/*--------------------------------------------------------------------*/
Bool SK_(handle_client_request)(ThreadId tid, UInt *args, UInt *ret)
{
if (!VG_IS_SKIN_USERREQ('H','G',args[0]))
return False;
switch(args[0]) {
case VG_USERREQ__HG_CLEAN_MEMORY:
set_address_range_state(args[1], args[2], Vge_VirginInit);
*ret = 0; /* meaningless */
break;
case VG_USERREQ__HG_KNOWN_RACE:
set_address_range_state(args[1], args[2], Vge_Error);
*ret = 0; /* meaningless */
break;
default:
return False;
}
return True;
}
/*--------------------------------------------------------------------*/
/*--- Setup ---*/
/*--------------------------------------------------------------------*/
void SK_(pre_clo_init)(void)
{
Int i;
LockSet *empty;
VG_(details_name) ("Helgrind");
VG_(details_version) (NULL);
VG_(details_description) ("a data race detector");
VG_(details_copyright_author)(
"Copyright (C) 2002-2004, and GNU GPL'd, by Nicholas Nethercote.");
VG_(details_bug_reports_to) (VG_BUGS_TO);
VG_(details_avg_translation_sizeB) ( 115 );
VG_(needs_core_errors)();
VG_(needs_skin_errors)();
VG_(needs_data_syms)();
VG_(needs_client_requests)();
VG_(needs_command_line_options)();
VG_(needs_shadow_memory)();
VG_(init_new_mem_startup) (& eraser_new_mem_startup);
/* stack ones not decided until VG_(post_clo_init)() */
VG_(init_new_mem_brk) (& make_writable);
VG_(init_new_mem_mmap) (& eraser_new_mem_startup);
VG_(init_change_mem_mprotect) (& eraser_set_perms);
VG_(init_ban_mem_stack) (NULL);
VG_(init_die_mem_stack) (NULL);
VG_(init_die_mem_stack_signal) (NULL);
VG_(init_die_mem_brk) (NULL);
VG_(init_die_mem_munmap) (NULL);
VG_(init_pre_mem_read) (& eraser_pre_mem_read);
VG_(init_pre_mem_read_asciiz) (& eraser_pre_mem_read_asciiz);
VG_(init_pre_mem_write) (& eraser_pre_mem_write);
VG_(init_post_mem_write) (NULL);
VG_(init_post_thread_create) (& hg_thread_create);
VG_(init_post_thread_join) (& hg_thread_join);
VG_(init_pre_mutex_lock) (& eraser_pre_mutex_lock);
VG_(init_post_mutex_lock) (& eraser_post_mutex_lock);
VG_(init_post_mutex_unlock) (& eraser_post_mutex_unlock);
VG_(register_compact_helper)((Addr) & eraser_mem_help_read_1);
VG_(register_compact_helper)((Addr) & eraser_mem_help_read_2);
VG_(register_compact_helper)((Addr) & eraser_mem_help_read_4);
VG_(register_noncompact_helper)((Addr) & eraser_mem_help_read_N);
VG_(register_compact_helper)((Addr) & eraser_mem_help_write_1);
VG_(register_compact_helper)((Addr) & eraser_mem_help_write_2);
VG_(register_compact_helper)((Addr) & eraser_mem_help_write_4);
VG_(register_noncompact_helper)((Addr) & eraser_mem_help_write_N);
VG_(register_noncompact_helper)((Addr) & bus_lock);
VG_(register_noncompact_helper)((Addr) & bus_unlock);
for(i = 0; i < LOCKSET_HASH_SZ; i++)
lockset_hash[i] = NULL;
empty = alloc_LockSet(0);
insert_LockSet(empty);
emptyset = empty;
/* Init lock table and thread segments */
for (i = 0; i < VG_N_THREADS; i++) {
thread_locks[i] = empty;
newTLS(i);
}
init_shadow_memory();
hg_malloc_list = VG_(HT_construct)();
}
static Bool match_Bool(Char *arg, Char *argstr, Bool *ret)
{
Int len = VG_(strlen)(argstr);
if (VG_(strncmp)(arg, argstr, len) == 0) {
if (VG_(strcmp)(arg+len, "yes") == 0) {
*ret = True;
return True;
} else if (VG_(strcmp)(arg+len, "no") == 0) {
*ret = False;
return True;
} else
VG_(bad_option)(arg);
}
return False;
}
static Bool match_str(Char *arg, Char *argstr, Char **ret)
{
Int len = VG_(strlen)(argstr);
if (VG_(strncmp)(arg, argstr, len) == 0) {
*ret = VG_(strdup)(arg+len);
return True;
}
return False;
}
Bool SK_(process_cmd_line_option)(Char* arg)
{
Char *str;
if (match_str(arg, "--show-last-access=", &str)) {
Bool ok = True;
if (VG_(strcmp)(str, "no") == 0)
clo_execontext = EC_None;
else if (VG_(strcmp)(str, "some") == 0)
clo_execontext = EC_Some;
else if (VG_(strcmp)(str, "all") == 0)
clo_execontext = EC_All;
else {
ok = False;
VG_(bad_option)(arg);
}
VG_(free)(str);
if (ok)
return True;
}
if (match_Bool(arg, "--private-stacks=", &clo_priv_stacks))
return True;
return VG_(replacement_malloc_process_cmd_line_option)(arg);
}
void SK_(print_usage)(void)
{
VG_(printf)(
" --private-stacks=yes|no assume thread stacks are used privately [no]\n"
" --show-last-access=no|some|all\n"
" show location of last word access on error [no]\n"
);
VG_(replacement_malloc_print_usage)();
}
void SK_(print_debug_usage)(void)
{
VG_(replacement_malloc_print_debug_usage)();
}
void SK_(post_clo_init)(void)
{
void (*stack_tracker)(Addr a, UInt len);
if (clo_execontext) {
execontext_map = VG_(malloc)(sizeof(ExeContextMap *) * 65536);
VG_(memset)(execontext_map, 0, sizeof(ExeContextMap *) * 65536);
}
if (clo_priv_stacks)
stack_tracker = & eraser_new_mem_stack_private;
else
stack_tracker = & eraser_new_mem_stack;
VG_(init_new_mem_stack) (stack_tracker);
VG_(init_new_mem_stack_signal) (stack_tracker);
}
void SK_(fini)(Int exitcode)
{
if (DEBUG_LOCK_TABLE) {
pp_all_LockSets();
pp_all_mutexes();
}
if (LOCKSET_SANITY)
sanity_check_locksets("SK_(fini)");
if (VG_(clo_verbosity) > 0)
VG_(message)(Vg_UserMsg, "%u possible data races found; %u lock order problems",
n_eraser_warnings, n_lockorder_warnings);
if (0)
VG_(printf)("stk_ld:%u+stk_st:%u = %u nonstk_ld:%u+nonstk_st:%u = %u %u%%\n",
stk_ld, stk_st, stk_ld + stk_st,
nonstk_ld, nonstk_st, nonstk_ld + nonstk_st,
((stk_ld+stk_st)*100) / (stk_ld + stk_st + nonstk_ld + nonstk_st));
}
/* Uses a 1:1 mapping */
VG_DETERMINE_INTERFACE_VERSION(SK_(pre_clo_init), 1.0)
/*--------------------------------------------------------------------*/
/*--- end hg_main.c ---*/
/*--------------------------------------------------------------------*/