]> rtime.felk.cvut.cz Git - l4.git/blobdiff - l4/pkg/valgrind/src/valgrind-3.6.0-svn/helgrind/hg_main.c
update
[l4.git] / l4 / pkg / valgrind / src / valgrind-3.6.0-svn / helgrind / hg_main.c
index 1be4f1382b37e854eb9cf592965e31c4d81f2022..bbda2c033ea5c979aecd947a692c21cf12a77e45 100644 (file)
@@ -52,6 +52,8 @@
 #include "pub_tool_debuginfo.h" // VG_(find_seginfo), VG_(seginfo_soname)
 #include "pub_tool_redir.h"     // sonames for the dynamic linkers
 #include "pub_tool_vki.h"       // VKI_PAGE_SIZE
+#include "pub_tool_libcproc.h"  // VG_(atfork)
+#include "pub_tool_aspacemgr.h" // VG_(am_is_valid_for_client)
 
 #include "hg_basics.h"
 #include "hg_wordset.h"
    worthwhile performance benefits over -O.
 */
 
-// FIXME catch sync signals (SEGV, basically) and unlock BHL,
-// if held.  Otherwise a LOCK-prefixed insn which segfaults 
-// gets Helgrind into a total muddle as the BHL will not be
-// released after the insn.
-
 // FIXME what is supposed to happen to locks in memory which
 // is relocated as a result of client realloc?
 
 
 /* ------------ Debug/trace options ------------ */
 
-// this is:
-// shadow_mem_make_NoAccess: 29156 SMs, 1728 scanned
-// happens_before_wrk: 1000
-// ev__post_thread_join: 3360 SMs, 29 scanned, 252 re-Excls
-#define SHOW_EXPENSIVE_STUFF 0
-
 // 0 for silent, 1 for some stuff, 2 for lots of stuff
 #define SHOW_EVENTS 0
 
@@ -130,7 +121,9 @@ static void all__sanity_check ( Char* who ); /* fwds */
 /* Admin linked list of Threads */
 static Thread* admin_threads = NULL;
 
-/* Admin linked list of Locks */
+/* Admin double linked list of Locks */
+/* We need a double linked list to properly and efficiently
+   handle del_LockN. */
 static Lock* admin_locks = NULL;
 
 /* Mapping table for core ThreadIds to Thread* */
@@ -139,17 +132,10 @@ static Thread** map_threads = NULL; /* Array[VG_N_THREADS] of Thread* */
 /* Mapping table for lock guest addresses to Lock* */
 static WordFM* map_locks = NULL; /* WordFM LockAddr Lock* */
 
-/* The word-set universes for thread sets and lock sets. */
-static WordSetU* univ_tsets = NULL; /* sets of Thread* */
+/* The word-set universes for lock sets. */
 static WordSetU* univ_lsets = NULL; /* sets of Lock* */
 static WordSetU* univ_laog  = NULL; /* sets of Lock*, for LAOG */
 
-/* never changed; we only care about its address.  Is treated as if it
-   was a standard userspace lock.  Also we have a Lock* describing it
-   so it can participate in lock sets in the usual way. */
-static Int   __bus_lock = 0;
-static Lock* __bus_lock_Lock = NULL;
-
 
 /*----------------------------------------------------------------*/
 /*--- Simple helpers for the data structures                   ---*/
@@ -180,10 +166,17 @@ static Thread* mk_Thread ( Thr* hbthr ) {
 }
 
 // Make a new lock which is unlocked (hence ownerless)
+// and insert the new lock in admin_locks double linked list.
 static Lock* mk_LockN ( LockKind kind, Addr guestaddr ) {
    static ULong unique = 0;
    Lock* lock             = HG_(zalloc)( "hg.mk_Lock.1", sizeof(Lock) );
-   lock->admin            = admin_locks;
+   /* begin: add to double linked list */
+   if (admin_locks)
+      admin_locks->admin_prev = lock;
+   lock->admin_next       = admin_locks;
+   lock->admin_prev       = NULL;
+   admin_locks            = lock;
+   /* end: add */
    lock->unique           = unique++;
    lock->magic            = LockN_MAGIC;
    lock->appeared_at      = NULL;
@@ -194,12 +187,11 @@ static Lock* mk_LockN ( LockKind kind, Addr guestaddr ) {
    lock->heldW            = False;
    lock->heldBy           = NULL;
    tl_assert(HG_(is_sane_LockN)(lock));
-   admin_locks            = lock;
    return lock;
 }
 
 /* Release storage for a Lock.  Also release storage in .heldBy, if
-   any. */
+   any. Removes from admin_locks double linked list. */
 static void del_LockN ( Lock* lk ) 
 {
    tl_assert(HG_(is_sane_LockN)(lk));
@@ -207,6 +199,20 @@ static void del_LockN ( Lock* lk )
    libhb_so_dealloc(lk->hbso);
    if (lk->heldBy)
       VG_(deleteBag)( lk->heldBy );
+   /* begin: del lock from double linked list */
+   if (lk == admin_locks) {
+      tl_assert(lk->admin_prev == NULL);
+      if (lk->admin_next)
+         lk->admin_next->admin_prev = NULL;
+      admin_locks = lk->admin_next;
+   }
+   else {
+      tl_assert(lk->admin_prev != NULL);
+      lk->admin_prev->admin_next = lk->admin_next;
+      if (lk->admin_next)
+         lk->admin_next->admin_prev = lk->admin_prev;
+   }
+   /* end: del */
    VG_(memset)(lk, 0xAA, sizeof(*lk));
    HG_(free)(lk);
 }
@@ -360,8 +366,6 @@ static void remove_Lock_from_locksets_of_all_owning_Threads( Lock* lk )
 /*--- Print out the primary data structures                    ---*/
 /*----------------------------------------------------------------*/
 
-//static WordSetID del_BHL ( WordSetID lockset ); /* fwds */
-
 #define PP_THREADS      (1<<1)
 #define PP_LOCKS        (1<<2)
 #define PP_ALL (PP_THREADS | PP_LOCKS)
@@ -444,8 +448,9 @@ static void pp_Lock ( Int d, Lock* lk )
 {
    space(d+0); VG_(printf)("Lock %p (ga %#lx) {\n", lk, lk->guestaddr);
    if (sHOW_ADMIN) {
-      space(d+3); VG_(printf)("admin  %p\n",   lk->admin);
-      space(d+3); VG_(printf)("magic  0x%x\n", (UInt)lk->magic);
+      space(d+3); VG_(printf)("admin_n  %p\n",   lk->admin_next);
+      space(d+3); VG_(printf)("admin_p  %p\n",   lk->admin_prev);
+      space(d+3); VG_(printf)("magic    0x%x\n", (UInt)lk->magic);
    }
    space(d+3); VG_(printf)("unique %llu\n", lk->unique);
    space(d+3); VG_(printf)("kind   %s\n", show_LockKind(lk->kind));
@@ -469,11 +474,11 @@ static void pp_admin_locks ( Int d )
 {
    Int   i, n;
    Lock* lk;
-   for (n = 0, lk = admin_locks;  lk;  n++, lk = lk->admin) {
+   for (n = 0, lk = admin_locks;  lk;  n++, lk = lk->admin_next) {
       /* nothing */
    }
    space(d); VG_(printf)("admin_locks (%d records) {\n", n);
-   for (i = 0, lk = admin_locks;  lk;  i++, lk = lk->admin) {
+   for (i = 0, lk = admin_locks;  lk;  i++, lk = lk->admin_next) {
       if (0) {
          space(n); 
          VG_(printf)("admin_locks record %d of %d:\n", i, n);
@@ -549,24 +554,17 @@ static void initialise_data_structures ( Thr* hbthr_root )
                            NULL/*unboxed Word cmp*/);
    tl_assert(map_locks != NULL);
 
-   __bus_lock_Lock = mk_LockN( LK_nonRec, (Addr)&__bus_lock );
-   tl_assert(HG_(is_sane_LockN)(__bus_lock_Lock));
-   VG_(addToFM)( map_locks, (Word)&__bus_lock, (Word)__bus_lock_Lock );
-
-   tl_assert(univ_tsets == NULL);
-   univ_tsets = HG_(newWordSetU)( HG_(zalloc), "hg.ids.3", HG_(free),
-                                  8/*cacheSize*/ );
-   tl_assert(univ_tsets != NULL);
-
    tl_assert(univ_lsets == NULL);
    univ_lsets = HG_(newWordSetU)( HG_(zalloc), "hg.ids.4", HG_(free),
                                   8/*cacheSize*/ );
    tl_assert(univ_lsets != NULL);
 
    tl_assert(univ_laog == NULL);
-   univ_laog = HG_(newWordSetU)( HG_(zalloc), "hg.ids.5 (univ_laog)",
-                                 HG_(free), 24/*cacheSize*/ );
-   tl_assert(univ_laog != NULL);
+   if (HG_(clo_track_lockorders)) {
+      univ_laog = HG_(newWordSetU)( HG_(zalloc), "hg.ids.5 (univ_laog)",
+                                    HG_(free), 24/*cacheSize*/ );
+      tl_assert(univ_laog != NULL);
+   }
 
    /* Set up entries for the root thread */
    // FIXME: this assumes that the first real ThreadId is 1
@@ -575,8 +573,8 @@ static void initialise_data_structures ( Thr* hbthr_root )
    thr = mk_Thread(hbthr_root);
    thr->coretid = 1; /* FIXME: hardwires an assumption about the
                         identity of the root thread. */
-   tl_assert( libhb_get_Thr_opaque(hbthr_root) == NULL );
-   libhb_set_Thr_opaque(hbthr_root, thr);
+   tl_assert( libhb_get_Thr_hgthread(hbthr_root) == NULL );
+   libhb_set_Thr_hgthread(hbthr_root, thr);
 
    /* and bind it in the thread-map table. */
    tl_assert(HG_(is_sane_ThreadId)(thr->coretid));
@@ -586,10 +584,6 @@ static void initialise_data_structures ( Thr* hbthr_root )
 
    tl_assert(VG_INVALID_THREADID == 0);
 
-   /* Mark the new bus lock correctly (to stop the sanity checks
-      complaining) */
-   tl_assert( sizeof(__bus_lock) == 4 );
-
    all__sanity_check("initialise_data_structures");
 }
 
@@ -839,7 +833,7 @@ static void locks__sanity_check ( Char* who )
    Lock*     lk;
    Int       i;
    // # entries in admin_locks == # entries in map_locks
-   for (i = 0, lk = admin_locks;  lk;  i++, lk = lk->admin)
+   for (i = 0, lk = admin_locks;  lk;  i++, lk = lk->admin_next)
       ;
    if (i != VG_(sizeFM)(map_locks)) BAD("1");
    // for each entry (gla, lk) in map_locks
@@ -851,7 +845,7 @@ static void locks__sanity_check ( Char* who )
    }
    VG_(doneIterFM)( map_locks );
    // scan through admin_locks ...
-   for (lk = admin_locks; lk; lk = lk->admin) {
+   for (lk = admin_locks; lk; lk = lk->admin_next) {
       // lock is sane.  Quite comprehensive, also checks that
       // referenced (holder) threads are sane.
       if (!HG_(is_sane_LockN)(lk)) BAD("3");
@@ -899,7 +893,8 @@ static void all_except_Locks__sanity_check ( Char* who ) {
    stats__sanity_checks++;
    if (0) VG_(printf)("all_except_Locks__sanity_check(%s)\n", who);
    threads__sanity_check(who);
-   laog__sanity_check(who);
+   if (HG_(clo_track_lockorders))
+      laog__sanity_check(who);
 }
 static void all__sanity_check ( Char* who ) {
    all_except_Locks__sanity_check(who);
@@ -907,136 +902,6 @@ static void all__sanity_check ( Char* who ) {
 }
 
 
-/*----------------------------------------------------------------*/
-/*--- the core memory state machine (msm__* functions)         ---*/
-/*----------------------------------------------------------------*/
-
-//static WordSetID add_BHL ( WordSetID lockset ) {
-//   return HG_(addToWS)( univ_lsets, lockset, (Word)__bus_lock_Lock );
-//}
-//static WordSetID del_BHL ( WordSetID lockset ) {
-//   return HG_(delFromWS)( univ_lsets, lockset, (Word)__bus_lock_Lock );
-//}
-
-
-///* Last-lock-lossage records.  This mechanism exists to help explain
-//   to programmers why we are complaining about a race.  The idea is to
-//   monitor all lockset transitions.  When a previously nonempty
-//   lockset becomes empty, the lock(s) that just disappeared (the
-//   "lossage") are the locks that have consistently protected the
-//   location (ga_of_access) in question for the longest time.  Most of
-//   the time the lossage-set is a single lock.  Because the
-//   lossage-lock is the one that has survived longest, there is there
-//   is a good chance that it is indeed the lock that the programmer
-//   intended to use to protect the location.
-//
-//   Note that we cannot in general just look at the lossage set when we
-//   see a transition to ShM(...,empty-set), because a transition to an
-//   empty lockset can happen arbitrarily far before the point where we
-//   want to report an error.  This is in the case where there are many
-//   transitions ShR -> ShR, all with an empty lockset, and only later
-//   is there a transition to ShM.  So what we want to do is note the
-//   lossage lock at the point where a ShR -> ShR transition empties out
-//   the lockset, so we can present it later if there should be a
-//   transition to ShM.
-//
-//   So this function finds such transitions.  For each, it associates
-//   in ga_to_lastlock, the guest address and the lossage lock.  In fact
-//   we do not record the Lock* directly as that may disappear later,
-//   but instead the ExeContext inside the Lock which says where it was
-//   initialised or first locked.  ExeContexts are permanent so keeping
-//   them indefinitely is safe.
-//
-//   A boring detail: the hardware bus lock is not interesting in this
-//   respect, so we first remove that from the pre/post locksets.
-//*/
-//
-//static UWord stats__ga_LL_adds = 0;
-//
-//static WordFM* ga_to_lastlock = NULL; /* GuestAddr -> ExeContext* */
-//
-//static 
-//void record_last_lock_lossage ( Addr ga_of_access,
-//                                WordSetID lset_old, WordSetID lset_new )
-//{
-//   Lock* lk;
-//   Int   card_old, card_new;
-//
-//   tl_assert(lset_old != lset_new);
-//
-//   if (0) VG_(printf)("XX1: %d (card %ld) -> %d (card %ld) %#lx\n",
-//                      (Int)lset_old, 
-//                      HG_(cardinalityWS)(univ_lsets,lset_old),
-//                      (Int)lset_new, 
-//                      HG_(cardinalityWS)(univ_lsets,lset_new),
-//                      ga_of_access );
-//
-//   /* This is slow, but at least it's simple.  The bus hardware lock
-//      just confuses the logic, so remove it from the locksets we're
-//      considering before doing anything else. */
-//   lset_new = del_BHL( lset_new );
-//
-//   if (!HG_(isEmptyWS)( univ_lsets, lset_new )) {
-//      /* The post-transition lock set is not empty.  So we are not
-//         interested.  We're only interested in spotting transitions
-//         that make locksets become empty. */
-//      return;
-//   }
-//
-//   /* lset_new is now empty */
-//   card_new = HG_(cardinalityWS)( univ_lsets, lset_new );
-//   tl_assert(card_new == 0);
-//
-//   lset_old = del_BHL( lset_old );
-//   card_old = HG_(cardinalityWS)( univ_lsets, lset_old );
-//
-//   if (0) VG_(printf)(" X2: %d (card %d) -> %d (card %d)\n",
-//                      (Int)lset_old, card_old, (Int)lset_new, card_new );
-//
-//   if (card_old == 0) {
-//      /* The old lockset was also empty.  Not interesting. */
-//      return;
-//   }
-//
-//   tl_assert(card_old > 0);
-//   tl_assert(!HG_(isEmptyWS)( univ_lsets, lset_old ));
-//
-//   /* Now we know we've got a transition from a nonempty lockset to an
-//      empty one.  So lset_old must be the set of locks lost.  Record
-//      some details.  If there is more than one element in the lossage
-//      set, just choose one arbitrarily -- not the best, but at least
-//      it's simple. */
-//
-//   lk = (Lock*)HG_(anyElementOfWS)( univ_lsets, lset_old );
-//   if (0) VG_(printf)("lossage %ld %p\n",
-//                      HG_(cardinalityWS)( univ_lsets, lset_old), lk );
-//   if (lk->appeared_at) {
-//      if (ga_to_lastlock == NULL)
-//         ga_to_lastlock = VG_(newFM)( HG_(zalloc), "hg.rlll.1", HG_(free), NULL );
-//      VG_(addToFM)( ga_to_lastlock, ga_of_access, (Word)lk->appeared_at );
-//      stats__ga_LL_adds++;
-//   }
-//}
-//
-///* This queries the table (ga_to_lastlock) made by
-//   record_last_lock_lossage, when constructing error messages.  It
-//   attempts to find the ExeContext of the allocation or initialisation
-//   point for the lossage lock associated with 'ga'. */
-//
-//static ExeContext* maybe_get_lastlock_initpoint ( Addr ga ) 
-//{
-//   ExeContext* ec_hint = NULL;
-//   if (ga_to_lastlock != NULL 
-//       && VG_(lookupFM)(ga_to_lastlock, 
-//                        NULL, (Word*)&ec_hint, ga)) {
-//      tl_assert(ec_hint != NULL);
-//      return ec_hint;
-//   } else {
-//      return NULL;
-//   }
-//}
-
-
 /*----------------------------------------------------------------*/
 /*--- Shadow value and address range handlers                  ---*/
 /*----------------------------------------------------------------*/
@@ -1077,11 +942,20 @@ static void shadow_mem_make_New ( Thread* thr, Addr a, SizeT len )
    libhb_srange_new( thr->hbthr, a, len );
 }
 
-static void shadow_mem_make_NoAccess ( Thread* thr, Addr aIN, SizeT len )
+static void shadow_mem_make_NoAccess_NoFX ( Thread* thr, Addr aIN, SizeT len )
 {
    if (0 && len > 500)
-      VG_(printf)("make NoAccess ( %#lx, %ld )\n", aIN, len );
-   libhb_srange_noaccess( thr->hbthr, aIN, len );
+      VG_(printf)("make NoAccess_NoFX ( %#lx, %ld )\n", aIN, len );
+   // has no effect (NoFX)
+   libhb_srange_noaccess_NoFX( thr->hbthr, aIN, len );
+}
+
+static void shadow_mem_make_NoAccess_AHAE ( Thread* thr, Addr aIN, SizeT len )
+{
+   if (0 && len > 500)
+      VG_(printf)("make NoAccess_AHAE ( %#lx, %ld )\n", aIN, len );
+   // Actually Has An Effect (AHAE)
+   libhb_srange_noaccess_AHAE( thr->hbthr, aIN, len );
 }
 
 static void shadow_mem_make_Untracked ( Thread* thr, Addr aIN, SizeT len )
@@ -1205,9 +1079,11 @@ void evhH__post_thread_w_acquires_lock ( Thread* thr,
    goto noerror;
 
   noerror:
-   /* check lock order acquisition graph, and update.  This has to
-      happen before the lock is added to the thread's locksetA/W. */
-   laog__pre_thread_acquires_lock( thr, lk );
+   if (HG_(clo_track_lockorders)) {
+      /* check lock order acquisition graph, and update.  This has to
+         happen before the lock is added to the thread's locksetA/W. */
+      laog__pre_thread_acquires_lock( thr, lk );
+   }
    /* update the thread's held-locks set */
    thr->locksetA = HG_(addToWS)( univ_lsets, thr->locksetA, (Word)lk );
    thr->locksetW = HG_(addToWS)( univ_lsets, thr->locksetW, (Word)lk );
@@ -1278,9 +1154,11 @@ void evhH__post_thread_r_acquires_lock ( Thread* thr,
    goto noerror;
 
   noerror:
-   /* check lock order acquisition graph, and update.  This has to
-      happen before the lock is added to the thread's locksetA/W. */
-   laog__pre_thread_acquires_lock( thr, lk );
+   if (HG_(clo_track_lockorders)) {
+      /* check lock order acquisition graph, and update.  This has to
+         happen before the lock is added to the thread's locksetA/W. */
+      laog__pre_thread_acquires_lock( thr, lk );
+   }
    /* update the thread's held-locks set */
    thr->locksetA = HG_(addToWS)( univ_lsets, thr->locksetA, (Word)lk );
    /* but don't update thr->locksetW, since lk is only rd-held */
@@ -1534,31 +1412,49 @@ void evh__new_mem_w_perms ( Addr a, SizeT len,
 static
 void evh__set_perms ( Addr a, SizeT len,
                       Bool rr, Bool ww, Bool xx ) {
+   // This handles mprotect requests.  If the memory is being put
+   // into no-R no-W state, paint it as NoAccess, for the reasons
+   // documented at evh__die_mem_munmap().
    if (SHOW_EVENTS >= 1)
-      VG_(printf)("evh__set_perms(%p, %lu, %d,%d,%d)\n",
+      VG_(printf)("evh__set_perms(%p, %lu, r=%d w=%d x=%d)\n",
                   (void*)a, len, (Int)rr, (Int)ww, (Int)xx );
    /* Hmm.  What should we do here, that actually makes any sense?
       Let's say: if neither readable nor writable, then declare it
       NoAccess, else leave it alone. */
    if (!(rr || ww))
-      shadow_mem_make_NoAccess( get_current_Thread(), a, len );
+      shadow_mem_make_NoAccess_AHAE( get_current_Thread(), a, len );
    if (len >= SCE_BIGRANGE_T && (HG_(clo_sanity_flags) & SCE_BIGRANGE))
       all__sanity_check("evh__set_perms-post");
 }
 
 static
 void evh__die_mem ( Addr a, SizeT len ) {
-   // urr, libhb ignores this.
+   // Urr, libhb ignores this.
    if (SHOW_EVENTS >= 2)
       VG_(printf)("evh__die_mem(%p, %lu)\n", (void*)a, len );
-   shadow_mem_make_NoAccess( get_current_Thread(), a, len );
+   shadow_mem_make_NoAccess_NoFX( get_current_Thread(), a, len );
    if (len >= SCE_BIGRANGE_T && (HG_(clo_sanity_flags) & SCE_BIGRANGE))
       all__sanity_check("evh__die_mem-post");
 }
 
+static
+void evh__die_mem_munmap ( Addr a, SizeT len ) {
+   // It's important that libhb doesn't ignore this.  If, as is likely,
+   // the client is subject to address space layout randomization,
+   // then unmapped areas may never get remapped over, even in long
+   // runs.  If we just ignore them we wind up with large resource
+   // (VTS) leaks in libhb.  So force them to NoAccess, so that all
+   // VTS references in the affected area are dropped.  Marking memory
+   // as NoAccess is expensive, but we assume that munmap is sufficiently
+   // rare that the space gains of doing this are worth the costs.
+   if (SHOW_EVENTS >= 2)
+      VG_(printf)("evh__die_mem_munmap(%p, %lu)\n", (void*)a, len );
+   shadow_mem_make_NoAccess_AHAE( get_current_Thread(), a, len );
+}
+
 static
 void evh__untrack_mem ( Addr a, SizeT len ) {
-   // whereas it doesn't ignore this
+   // Libhb doesn't ignore this.
    if (SHOW_EVENTS >= 2)
       VG_(printf)("evh__untrack_mem(%p, %lu)\n", (void*)a, len );
    shadow_mem_make_Untracked( get_current_Thread(), a, len );
@@ -1600,15 +1496,15 @@ void evh__pre_thread_ll_create ( ThreadId parent, ThreadId child )
 
       hbthr_p = thr_p->hbthr;
       tl_assert(hbthr_p != NULL);
-      tl_assert( libhb_get_Thr_opaque(hbthr_p) == thr_p );
+      tl_assert( libhb_get_Thr_hgthread(hbthr_p) == thr_p );
 
       hbthr_c = libhb_create ( hbthr_p );
 
       /* Create a new thread record for the child. */
       /* a Thread for the new thread ... */
       thr_c = mk_Thread( hbthr_c );
-      tl_assert( libhb_get_Thr_opaque(hbthr_c) == NULL );
-      libhb_set_Thr_opaque(hbthr_c, thr_c);
+      tl_assert( libhb_get_Thr_hgthread(hbthr_c) == NULL );
+      libhb_set_Thr_hgthread(hbthr_c, thr_c);
 
       /* and bind it in the thread-map table */
       map_threads[child] = thr_c;
@@ -1679,6 +1575,8 @@ void evh__pre_thread_ll_exit ( ThreadId quit_tid )
       - tell libhb the thread is gone
       - clear the map_threads entry, in order that the Valgrind core
         can re-use it. */
+   /* Cleanup actions (next 5 lines) copied in evh__atfork_child; keep
+      in sync. */
    tl_assert(thr_q->hbthr);
    libhb_async_exit(thr_q->hbthr);
    tl_assert(thr_q->coretid == quit_tid);
@@ -1689,6 +1587,35 @@ void evh__pre_thread_ll_exit ( ThreadId quit_tid )
       all__sanity_check("evh__pre_thread_ll_exit-post");
 }
 
+/* This is called immediately after fork, for the child only.  'tid'
+   is the only surviving thread (as per POSIX rules on fork() in
+   threaded programs), so we have to clean up map_threads to remove
+   entries for any other threads. */
+static
+void evh__atfork_child ( ThreadId tid )
+{
+   UInt    i;
+   Thread* thr;
+   /* Slot 0 should never be used. */
+   thr = map_threads_maybe_lookup( 0/*INVALID*/ );
+   tl_assert(!thr);
+   /* Clean up all other slots except 'tid'. */
+   for (i = 1; i < VG_N_THREADS; i++) {
+      if (i == tid)
+         continue;
+      thr = map_threads_maybe_lookup(i);
+      if (!thr)
+         continue;
+      /* Cleanup actions (next 5 lines) copied from end of
+         evh__pre_thread_ll_exit; keep in sync. */
+      tl_assert(thr->hbthr);
+      libhb_async_exit(thr->hbthr);
+      tl_assert(thr->coretid == i);
+      thr->coretid = VG_INVALID_THREADID;
+      map_threads_delete(i);
+   }
+}
+
 
 static
 void evh__HG_PTHREAD_JOIN_POST ( ThreadId stay_tid, Thread* quit_thr )
@@ -1714,8 +1641,8 @@ void evh__HG_PTHREAD_JOIN_POST ( ThreadId stay_tid, Thread* quit_thr )
    hbthr_s = thr_s->hbthr;
    hbthr_q = thr_q->hbthr;
    tl_assert(hbthr_s != hbthr_q);
-   tl_assert( libhb_get_Thr_opaque(hbthr_s) == thr_s );
-   tl_assert( libhb_get_Thr_opaque(hbthr_q) == thr_q );
+   tl_assert( libhb_get_Thr_hgthread(hbthr_s) == thr_s );
+   tl_assert( libhb_get_Thr_hgthread(hbthr_q) == thr_q );
 
    /* Allocate a temporary synchronisation object and use it to send
       an imaginary message from the quitter to the stayer, the purpose
@@ -1765,7 +1692,12 @@ void evh__pre_mem_read_asciiz ( CorePart part, ThreadId tid,
    if (SHOW_EVENTS >= 1)
       VG_(printf)("evh__pre_mem_asciiz(ctid=%d, \"%s\", %p)\n", 
                   (Int)tid, s, (void*)a );
-   // FIXME: think of a less ugly hack
+   // Don't segfault if the string starts in an obviously stupid
+   // place.  Actually we should check the whole string, not just
+   // the start address, but that's too much trouble.  At least
+   // checking the first byte is better than nothing.  See #255009.
+   if (!VG_(am_is_valid_for_client) (a, 1, VKI_PROT_READ))
+      return;
    len = VG_(strlen)( (Char*) a );
    shadow_mem_cread_range( map_threads_lookup(tid), a, len+1 );
    if (len >= SCE_BIGRANGE_T && (HG_(clo_sanity_flags) & SCE_BIGRANGE))
@@ -1800,9 +1732,20 @@ void evh__new_mem_heap ( Addr a, SizeT len, Bool is_inited ) {
 
 static
 void evh__die_mem_heap ( Addr a, SizeT len ) {
+   Thread* thr;
    if (SHOW_EVENTS >= 1)
       VG_(printf)("evh__die_mem_heap(%p, %lu)\n", (void*)a, len );
-   shadow_mem_make_NoAccess( get_current_Thread(), a, len );
+   thr = get_current_Thread();
+   tl_assert(thr);
+   if (HG_(clo_free_is_write)) {
+      /* Treat frees as if the memory was written immediately prior to
+         the free.  This shakes out more races, specifically, cases
+         where memory is referenced by one thread, and freed by
+         another, and there's no observable synchronisation event to
+         guarantee that the reference happens before the free. */
+      shadow_mem_cwrite_range(thr, a, len);
+   }
+   shadow_mem_make_NoAccess_NoFX( thr, a, len );
    if (len >= SCE_BIGRANGE_T && (HG_(clo_sanity_flags) & SCE_BIGRANGE))
       all__sanity_check("evh__pre_mem_read-post");
 }
@@ -1938,8 +1881,9 @@ void evh__HG_PTHREAD_MUTEX_DESTROY_PRE( ThreadId tid, void* mutex )
       }
       tl_assert( !lk->heldBy );
       tl_assert( HG_(is_sane_LockN)(lk) );
-
-      laog__handle_one_lock_deletion(lk);
+      
+      if (HG_(clo_track_lockorders))
+         laog__handle_one_lock_deletion(lk);
       map_locks_delete( lk->guestaddr );
       del_LockN( lk );
    }
@@ -2413,8 +2357,9 @@ void evh__HG_PTHREAD_RWLOCK_DESTROY_PRE( ThreadId tid, void* rwl )
       }
       tl_assert( !lk->heldBy );
       tl_assert( HG_(is_sane_LockN)(lk) );
-
-      laog__handle_one_lock_deletion(lk);
+      
+      if (HG_(clo_track_lockorders))
+         laog__handle_one_lock_deletion(lk);
       map_locks_delete( lk->guestaddr );
       del_LockN( lk );
    }
@@ -3114,17 +3059,16 @@ static SO* map_usertag_to_SO_lookup_or_alloc ( UWord usertag ) {
    }
 }
 
-// If it's ever needed (XXX check before use)
-//static void map_usertag_to_SO_delete ( UWord usertag ) {
-//   UWord keyW, valW;
-//   map_usertag_to_SO_INIT();
-//   if (VG_(delFromFM)( map_usertag_to_SO, &keyW, &valW, usertag )) {
-//      SO* so = (SO*)valW;
-//      tl_assert(keyW == usertag);
-//      tl_assert(so);
-//      libhb_so_dealloc(so);
-//   }
-//}
+static void map_usertag_to_SO_delete ( UWord usertag ) {
+   UWord keyW, valW;
+   map_usertag_to_SO_INIT();
+   if (VG_(delFromFM)( map_usertag_to_SO, &keyW, &valW, usertag )) {
+      SO* so = (SO*)valW;
+      tl_assert(keyW == usertag);
+      tl_assert(so);
+      libhb_so_dealloc(so);
+   }
+}
 
 
 static
@@ -3133,9 +3077,12 @@ void evh__HG_USERSO_SEND_PRE ( ThreadId tid, UWord usertag )
    /* TID is just about to notionally sent a message on a notional
       abstract synchronisation object whose identity is given by
       USERTAG.  Bind USERTAG to a real SO if it is not already so
-      bound, and do a 'strong send' on the SO.  This is later used by
+      bound, and do a 'weak send' on the SO.  This joins the vector
+      clocks from this thread into any vector clocks already present
+      in the SO.  The resulting SO vector clocks are later used by
       other thread(s) which successfully 'receive' from the SO,
-      thereby acquiring a dependency on this signalling event. */
+      thereby acquiring a dependency on all the events that have
+      previously signalled on this SO. */
    Thread* thr;
    SO*     so;
 
@@ -3149,7 +3096,7 @@ void evh__HG_USERSO_SEND_PRE ( ThreadId tid, UWord usertag )
    so = map_usertag_to_SO_lookup_or_alloc( usertag );
    tl_assert(so);
 
-   libhb_so_send( thr->hbthr, so, True/*strong_send*/ );
+   libhb_so_send( thr->hbthr, so, False/*!strong_send*/ );
 }
 
 static
@@ -3180,6 +3127,21 @@ void evh__HG_USERSO_RECV_POST ( ThreadId tid, UWord usertag )
    libhb_so_recv( thr->hbthr, so, True/*strong_recv*/ );
 }
 
+static
+void evh__HG_USERSO_FORGET_ALL ( ThreadId tid, UWord usertag )
+{
+   /* TID declares that any happens-before edges notionally stored in
+      USERTAG can be deleted.  If (as would normally be the case) a
+      SO is associated with USERTAG, then the assocation is removed
+      and all resources associated with SO are freed.  Importantly,
+      that frees up any VTSs stored in SO. */
+   if (SHOW_EVENTS >= 1)
+      VG_(printf)("evh__HG_USERSO_FORGET_ALL(ctid=%d, usertag=%#lx)\n", 
+                  (Int)tid, usertag );
+
+   map_usertag_to_SO_delete( usertag );
+}
+
 
 /*--------------------------------------------------------------*/
 /*--- Lock acquisition order monitoring                      ---*/
@@ -3253,6 +3215,7 @@ static void laog__init ( void )
 {
    tl_assert(!laog);
    tl_assert(!laog_exposition);
+   tl_assert(HG_(clo_track_lockorders));
 
    laog = VG_(newFM)( HG_(zalloc), "hg.laog__init.1", 
                       HG_(free), NULL/*unboxedcmp*/ );
@@ -3429,8 +3392,6 @@ static void laog__sanity_check ( Char* who ) {
    UWord* ws_words;
    Lock* me;
    LAOGLinks* links;
-   if (UNLIKELY(!laog || !laog_exposition))
-      laog__init();
    VG_(initIterFM)( laog );
    me = NULL;
    links = NULL;
@@ -3542,9 +3503,6 @@ static void laog__pre_thread_acquires_lock (
    if (HG_(elemWS)( univ_lsets, thr->locksetA, (Word)lk ))
       return;
 
-   if (UNLIKELY(!laog || !laog_exposition))
-      laog__init();
-
    /* First, the check.  Complain if there is any path in laog from lk
       to any of the locks already held by thr, since if any such path
       existed, it would mean that previously lk was acquired before
@@ -3616,9 +3574,6 @@ static void laog__handle_one_lock_deletion ( Lock* lk )
    Word preds_size, succs_size, i, j;
    UWord *preds_words, *succs_words;
 
-   if (UNLIKELY(!laog || !laog_exposition))
-      laog__init();
-
    preds = laog__preds( lk );
    succs = laog__succs( lk );
 
@@ -3650,8 +3605,6 @@ static void laog__handle_one_lock_deletion ( Lock* lk )
 //   Word   i, ws_size;
 //   UWord* ws_words;
 //
-//   if (UNLIKELY(!laog || !laog_exposition))
-//      laog__init();
 //
 //   HG_(getPayloadWS)( &ws_words, &ws_size, univ_lsets, locksToDelete );
 //   for (i = 0; i < ws_size; i++)
@@ -4604,6 +4557,11 @@ Bool hg_handle_client_request ( ThreadId tid, UWord* args, UWord* ret)
          evh__HG_USERSO_RECV_POST( tid, args[1] );
          break;
 
+      case _VG_USERREQ__HG_USERSO_FORGET_ALL:
+         /* UWord arbitrary-SO-tag */
+         evh__HG_USERSO_FORGET_ALL( tid, args[1] );
+         break;
+
       default:
          /* Unhandled Helgrind client request! */
          tl_assert2(0, "unhandled Helgrind client request 0x%lx",
@@ -4660,6 +4618,8 @@ static Bool hg_process_cmd_line_option ( Char* arg )
       if (0) VG_(printf)("XXX sanity flags: 0x%lx\n", HG_(clo_sanity_flags));
    }
 
+   else if VG_BOOL_CLO(arg, "--free-is-write",
+                            HG_(clo_free_is_write)) {}
    else 
       return VG_(replacement_malloc_process_cmd_line_option)(arg);
 
@@ -4669,6 +4629,7 @@ static Bool hg_process_cmd_line_option ( Char* arg )
 static void hg_print_usage ( void )
 {
    VG_(printf)(
+"    --free-is-write=no|yes    treat heap frees as writes [no]\n"
 "    --track-lockorders=no|yes show lock ordering errors? [yes]\n"
 "    --history-level=none|approx|full [full]\n"
 "       full:   show both stack traces for a data race (can be very slow)\n"
@@ -4694,10 +4655,6 @@ static void hg_print_debug_usage ( void )
    VG_(printf)("       000001   at thread create/join events\n");
 }
 
-static void hg_post_clo_init ( void )
-{
-}
-
 static void hg_fini ( Int exitcode )
 {
    if (VG_(clo_verbosity) == 1 && !VG_(clo_xml)) {
@@ -4722,12 +4679,12 @@ static void hg_fini ( Int exitcode )
    if (VG_(clo_stats)) {
 
       if (1) {
-         VG_(printf)("\n");
-         HG_(ppWSUstats)( univ_tsets, "univ_tsets" );
          VG_(printf)("\n");
          HG_(ppWSUstats)( univ_lsets, "univ_lsets" );
-         VG_(printf)("\n");
-         HG_(ppWSUstats)( univ_laog,  "univ_laog" );
+         if (HG_(clo_track_lockorders)) {
+            VG_(printf)("\n");
+            HG_(ppWSUstats)( univ_laog,  "univ_laog" );
+         }
       }
 
       //zz       VG_(printf)("\n");
@@ -4745,10 +4702,10 @@ static void hg_fini ( Int exitcode )
       VG_(printf)("\n");
       VG_(printf)("        locksets: %'8d unique lock sets\n",
                   (Int)HG_(cardinalityWSU)( univ_lsets ));
-      VG_(printf)("      threadsets: %'8d unique thread sets\n",
-                  (Int)HG_(cardinalityWSU)( univ_tsets ));
-      VG_(printf)("       univ_laog: %'8d unique lock sets\n",
-                  (Int)HG_(cardinalityWSU)( univ_laog ));
+      if (HG_(clo_track_lockorders)) {
+         VG_(printf)("       univ_laog: %'8d unique lock sets\n",
+                     (Int)HG_(cardinalityWSU)( univ_laog ));
+      }
 
       //VG_(printf)("L(ast)L(ock) map: %'8lu inserts (%d map size)\n",
       //            stats__ga_LL_adds,
@@ -4761,10 +4718,13 @@ static void hg_fini ( Int exitcode )
       VG_(printf)("string table map: %'8llu queries (%llu map size)\n",
                   HG_(stats__string_table_queries),
                   HG_(stats__string_table_get_map_size)() );
-      VG_(printf)("            LAOG: %'8d map size\n",
-                  (Int)(laog ? VG_(sizeFM)( laog ) : 0));
-      VG_(printf)(" LAOG exposition: %'8d map size\n",
-                  (Int)(laog_exposition ? VG_(sizeFM)( laog_exposition ) : 0));
+      if (HG_(clo_track_lockorders)) {
+         VG_(printf)("            LAOG: %'8d map size\n",
+                     (Int)(laog ? VG_(sizeFM)( laog ) : 0));
+         VG_(printf)(" LAOG exposition: %'8d map size\n",
+                     (Int)(laog_exposition ? VG_(sizeFM)( laog_exposition ) : 0));
+      }
+         
       VG_(printf)("           locks: %'8lu acquires, "
                   "%'lu releases\n",
                   stats__lockN_acquires,
@@ -4786,7 +4746,7 @@ void for_libhb__get_stacktrace ( Thr* hbt, Addr* frames, UWord nRequest )
    ThreadId    tid;
    UWord       nActual;
    tl_assert(hbt);
-   thr = libhb_get_Thr_opaque( hbt );
+   thr = libhb_get_Thr_hgthread( hbt );
    tl_assert(thr);
    tid = map_threads_maybe_reverse_lookup_SLOW(thr);
    nActual = (UWord)VG_(get_StackTrace)( tid, frames, (UInt)nRequest,
@@ -4803,7 +4763,7 @@ ExeContext* for_libhb__get_EC ( Thr* hbt )
    ThreadId    tid;
    ExeContext* ec;
    tl_assert(hbt);
-   thr = libhb_get_Thr_opaque( hbt );
+   thr = libhb_get_Thr_hgthread( hbt );
    tl_assert(thr);
    tid = map_threads_maybe_reverse_lookup_SLOW(thr);
    /* this will assert if tid is invalid */
@@ -4812,17 +4772,31 @@ ExeContext* for_libhb__get_EC ( Thr* hbt )
 }
 
 
-static void hg_pre_clo_init ( void )
+static void hg_post_clo_init ( void )
 {
    Thr* hbthr_root;
 
+   /////////////////////////////////////////////
+   hbthr_root = libhb_init( for_libhb__get_stacktrace, 
+                            for_libhb__get_EC );
+   /////////////////////////////////////////////
+
+
+   if (HG_(clo_track_lockorders))
+      laog__init();
+
+   initialise_data_structures(hbthr_root);
+}
+
+static void hg_pre_clo_init ( void )
+{
    VG_(details_name)            ("Helgrind");
    VG_(details_version)         (NULL);
    VG_(details_description)     ("a thread error detector");
    VG_(details_copyright_author)(
       "Copyright (C) 2007-2010, and GNU GPL'd, by OpenWorks LLP et al.");
    VG_(details_bug_reports_to)  (VG_BUGS_TO);
-   VG_(details_avg_translation_sizeB) ( 200 );
+   VG_(details_avg_translation_sizeB) ( 320 );
 
    VG_(basic_tool_funcs)          (hg_post_clo_init,
                                    hg_instrument,
@@ -4881,8 +4855,8 @@ static void hg_pre_clo_init ( void )
    VG_(track_change_mem_mprotect) ( evh__set_perms );
 
    VG_(track_die_mem_stack_signal)( evh__die_mem );
-   VG_(track_die_mem_brk)         ( evh__die_mem );
-   VG_(track_die_mem_munmap)      ( evh__die_mem );
+   VG_(track_die_mem_brk)         ( evh__die_mem_munmap );
+   VG_(track_die_mem_munmap)      ( evh__die_mem_munmap );
    VG_(track_die_mem_stack)       ( evh__die_mem );
 
    // FIXME: what is this for?
@@ -4901,13 +4875,6 @@ static void hg_pre_clo_init ( void )
    VG_(track_start_client_code)( evh__start_client_code );
    VG_(track_stop_client_code)( evh__stop_client_code );
 
-   /////////////////////////////////////////////
-   hbthr_root = libhb_init( for_libhb__get_stacktrace, 
-                            for_libhb__get_EC );
-   /////////////////////////////////////////////
-
-   initialise_data_structures(hbthr_root);
-
    /* Ensure that requirements for "dodgy C-as-C++ style inheritance"
       as described in comments at the top of pub_tool_hashtable.h, are
       met.  Blargh. */
@@ -4916,6 +4883,8 @@ static void hg_pre_clo_init ( void )
    hg_mallocmeta_table
       = VG_(HT_construct)( "hg_malloc_metadata_table" );
 
+   // add a callback to clean up on (threaded) fork.
+   VG_(atfork)(NULL/*pre*/, NULL/*parent*/, evh__atfork_child/*child*/);
 }
 
 VG_DETERMINE_INTERFACE_VERSION(hg_pre_clo_init)