]> rtime.felk.cvut.cz Git - l4.git/blobdiff - l4/pkg/valgrind/src/valgrind-3.6.0-svn/coregrind/m_redir.c
Inital import
[l4.git] / l4 / pkg / valgrind / src / valgrind-3.6.0-svn / coregrind / m_redir.c
diff --git a/l4/pkg/valgrind/src/valgrind-3.6.0-svn/coregrind/m_redir.c b/l4/pkg/valgrind/src/valgrind-3.6.0-svn/coregrind/m_redir.c
new file mode 100644 (file)
index 0000000..22ee4aa
--- /dev/null
@@ -0,0 +1,1389 @@
+
+/*--------------------------------------------------------------------*/
+/*--- Function replacement and wrapping.                 m_redir.c ---*/
+/*--------------------------------------------------------------------*/
+
+/*
+   This file is part of Valgrind, a dynamic binary instrumentation
+   framework.
+
+   Copyright (C) 2000-2010 Julian Seward 
+      jseward@acm.org
+   Copyright (C) 2003-2010 Jeremy Fitzhardinge
+      jeremy@goop.org
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307, USA.
+
+   The GNU General Public License is contained in the file COPYING.
+*/
+
+#include "pub_core_basics.h"
+#include "pub_core_debuglog.h"
+#include "pub_core_debuginfo.h"
+#include "pub_core_libcbase.h"
+#include "pub_core_libcassert.h"
+#include "pub_core_libcprint.h"
+#include "pub_core_seqmatch.h"
+#include "pub_core_mallocfree.h"
+#include "pub_core_options.h"
+#include "pub_core_oset.h"
+#include "pub_core_redir.h"
+#include "pub_core_trampoline.h"
+#include "pub_core_transtab.h"
+#include "pub_core_tooliface.h"    // VG_(needs).malloc_replacement
+#include "pub_core_machine.h"      // VG_(fnptr_to_fnentry)
+#include "pub_core_aspacemgr.h"    // VG_(am_find_nsegment)
+#include "pub_core_xarray.h"
+#include "pub_core_clientstate.h"  // VG_(client___libc_freeres_wrapper)
+#include "pub_core_demangle.h"     // VG_(maybe_Z_demangle)
+
+#include "config.h" /* GLIBC_2_* */
+
+#if defined(VGO_l4re)
+#include "pub_l4re_consts.h"
+#include "l4re_helper.h"
+#endif
+
+/* This module is a critical part of the redirection/intercept system.
+   It keeps track of the current intercept state, cleans up the
+   translation caches when that state changes, and finally, answers
+   queries about the whether an address is currently redirected or
+   not.  It doesn't do any of the control-flow trickery needed to put
+   the redirections into practice.  That is the job of m_translate,
+   which calls here to find out which translations need to be
+   redirected.
+
+   The interface is simple.  VG_(redir_initialise) initialises and
+   loads some hardwired redirects which never disappear; this is
+   platform-specific.
+
+   The module is notified of redirection state changes by m_debuginfo.
+   That calls VG_(redir_notify_new_DebugInfo) when a new DebugInfo
+   (shared object symbol table, basically) appears.  Appearance of new
+   symbols can cause new (active) redirections to appear for two
+   reasons: the symbols in the new table may match existing
+   redirection specifications (see comments below), and because the
+   symbols in the new table may themselves supply new redirect
+   specifications which match existing symbols (or ones in the new
+   table).
+
+   Redirect specifications are really symbols with "funny" prefixes
+   (_vgrZU_ and _vgrZZ_).  These names tell m_redir that the
+   associated code should replace the standard entry point for some
+   set of functions.  The set of functions is specified by a (soname
+   pattern, function name pattern) pair which is encoded in the symbol
+   name following the prefix.  The names use a Z-encoding scheme so
+   that they may contain punctuation characters and wildcards (*).
+   The encoding scheme is described in pub_tool_redir.h and is decoded
+   by VG_(maybe_Z_demangle).
+
+   When a shared object is unloaded, this module learns of it via a
+   call to VG_(redir_notify_delete_DebugInfo).  It then removes from
+   its tables all active redirections in any way associated with that
+   object, and tidies up the translation caches accordingly.
+
+   That takes care of tracking the redirection state.  When a
+   translation is actually to be made, m_translate calls to
+   VG_(redir_do_lookup) in this module to find out if the
+   translation's address should be redirected.
+*/
+
+/*------------------------------------------------------------*/
+/*--- Semantics                                            ---*/
+/*------------------------------------------------------------*/
+
+/* The redirector holds two pieces of state:
+
+     Specs  - a set of   (soname pattern, fnname pattern) -> redir addr
+     Active - a set of   orig addr -> (bool, redir addr)
+
+   Active is the currently active set of bindings that the translator
+   consults.  Specs is the current set of specifications as harvested
+   from reading symbol tables of the currently loaded objects.
+
+   Active is a pure function of Specs and the current symbol table
+   state (maintained by m_debuginfo).  Call the latter SyminfoState.
+
+   Therefore whenever either Specs or SyminfoState changes, Active
+   must be recomputed.  [Inefficient if done naively, but this is a
+   spec].
+
+   Active is computed as follows:
+
+      Active = empty
+      for spec in Specs {
+         sopatt = spec.soname pattern
+         fnpatt = spec.fnname pattern
+         redir  = spec.redir addr
+         for so matching sopatt in SyminfoState {
+            for fn matching fnpatt in fnnames_of(so) {
+               &fn -> redir is added to Active
+            }
+         }
+      }
+
+   [as an implementation detail, when a binding (orig -> redir) is
+   deleted from Active as a result of recomputing it, then all
+   translations intersecting redir must be deleted.  However, this is
+   not part of the spec].
+
+   [Active also depends on where the aspacemgr has decided to put all
+   the pieces of code -- that affects the "orig addr" and "redir addr"
+   values.]
+
+   ---------------------
+
+   That completes the spec, apart from one difficult issue: duplicates.
+
+   Clearly we must impose the requirement that domain(Active) contains
+   no duplicates.  The difficulty is how to constrain Specs enough to
+   avoid getting into that situation.  It's easy to write specs which
+   could cause conflicting bindings in Active, eg:
+
+      (libpthread.so, pthread_mutex_lock) ->    a1
+      (libpthread.so, pthread_*)          ->    a2
+
+   for a1 != a2.  Or even hairier:
+
+      (libpthread.so, pthread_mutex_*) ->    a1
+      (libpthread.so, pthread_*_lock)  ->    a2
+
+   I can't think of any sane way of detecting when an addition to
+   Specs would generate conflicts.  However, considering we don't
+   actually want to have a system that allows this, I propose this:
+   all changes to Specs are acceptable.  But, when recomputing Active
+   following the change, if the same orig is bound to more than one
+   redir, then the first binding for orig is retained, and all the
+   rest ignored.
+
+   ===========================================================
+   ===========================================================
+   Incremental implementation:
+
+   When a new DebugInfo appears:
+   - it may be the source of new specs
+   - it may be the source of new matches for existing specs
+   Therefore:
+
+   - (new Specs x existing DebugInfos): scan all symbols in the new
+     DebugInfo to find new specs.  Each of these needs to be compared
+     against all symbols in all the existing DebugInfos to generate
+     new actives.
+     
+   - (existing Specs x new DebugInfo): scan all symbols in the
+     DebugInfo, trying to match them to any existing specs, also
+     generating new actives.
+
+   - (new Specs x new DebugInfo): scan all symbols in the new
+     DebugInfo, trying to match them against the new specs, to
+     generate new actives.
+
+   - Finally, add new new specs to the current set of specs.
+
+   When adding a new active (s,d) to the Actives:
+     lookup s in Actives
+        if already bound to d, ignore
+        if already bound to something other than d, complain loudly and ignore
+        else add (s,d) to Actives
+             and discard (s,1) and (d,1)  (maybe overly conservative)
+
+   When a DebugInfo disappears:
+   - delete all specs acquired from the seginfo
+   - delete all actives derived from the just-deleted specs
+   - if each active (s,d) deleted, discard (s,1) and (d,1)
+*/
+
+
+/*------------------------------------------------------------*/
+/*--- REDIRECTION SPECIFICATIONS                           ---*/
+/*------------------------------------------------------------*/
+
+/* A specification of a redirection we want to do.  Note that because
+   both the "from" soname and function name may contain wildcards, the
+   spec can match an arbitrary number of times. 
+
+   16 Nov 2007: Comments re .mandatory field: The initial motivation
+   for this is making Memcheck work sanely on glibc-2.6.X ppc32-linux.
+   We really need to intercept 'strlen' in ld.so right from startup.
+   If ld.so does not have a visible 'strlen' symbol, Memcheck
+   generates an impossible number of errors resulting from highly
+   tuned strlen implementation in ld.so, and is completely unusable
+   -- the resulting undefinedness eventually seeps everywhere. */
+typedef
+   struct _Spec {
+      struct _Spec* next;  /* linked list */
+      /* FIXED PARTS -- set when created and not changed */
+      HChar* from_sopatt;  /* from soname pattern  */
+      HChar* from_fnpatt;  /* from fnname pattern  */
+      Addr   to_addr;      /* where redirecting to */
+      Bool   isWrap;       /* wrap or replacement? */
+      const HChar** mandatory; /* non-NULL ==> abort V and print the
+                                  strings if from_sopatt is loaded but
+                                  from_fnpatt cannot be found */
+      /* VARIABLE PARTS -- used transiently whilst processing redirections */
+      Bool   mark; /* set if spec requires further processing */
+      Bool   done; /* set if spec was successfully matched */
+   }
+   Spec;
+
+/* Top-level data structure.  It contains a pointer to a DebugInfo and
+   also a list of the specs harvested from that DebugInfo.  Note that
+   seginfo is allowed to be NULL, meaning that the specs are
+   pre-loaded ones at startup and are not associated with any
+   particular seginfo. */
+typedef
+   struct _TopSpec {
+      struct _TopSpec* next; /* linked list */
+      DebugInfo* seginfo;    /* symbols etc */
+      Spec*      specs;      /* specs pulled out of seginfo */
+      Bool       mark; /* transient temporary used during deletion */
+   }
+   TopSpec;
+
+/* This is the top level list of redirections.  m_debuginfo maintains
+   a list of DebugInfos, and the idea here is to maintain a list with
+   the same number of elements (in fact, with one more element, so as
+   to record abovementioned preloaded specifications.) */
+static TopSpec* topSpecs = NULL;
+
+
+/*------------------------------------------------------------*/
+/*--- CURRENTLY ACTIVE REDIRECTIONS                        ---*/
+/*------------------------------------------------------------*/
+
+/* Represents a currently active binding.  If either parent_spec or
+   parent_sym is NULL, then this binding was hardwired at startup and
+   should not be deleted.  Same is true if either parent's seginfo
+   field is NULL. */
+typedef
+   struct {
+      Addr     from_addr;   /* old addr -- MUST BE THE FIRST WORD! */
+      Addr     to_addr;     /* where redirecting to */
+      TopSpec* parent_spec; /* the TopSpec which supplied the Spec */
+      TopSpec* parent_sym;  /* the TopSpec which supplied the symbol */
+      Bool     isWrap;      /* wrap or replacement? */
+      Bool     isIFunc;     /* indirect function? */
+   }
+   Active;
+
+/* The active set is a fast lookup table */
+static OSet* activeSet = NULL;
+
+/* Wrapper routine for indirect functions */
+static Addr iFuncWrapper;
+
+/*------------------------------------------------------------*/
+/*--- FWDses                                               ---*/
+/*------------------------------------------------------------*/
+
+static void maybe_add_active ( Active /*by value; callee copies*/ );
+
+static void*  dinfo_zalloc(HChar* ec, SizeT);
+static void   dinfo_free(void*);
+static HChar* dinfo_strdup(HChar* ec, HChar*);
+static Bool   is_plausible_guest_addr(Addr);
+static Bool   is_aix5_glink_idiom(Addr);
+
+static void   show_redir_state ( HChar* who );
+static void   show_active ( HChar* left, Active* act );
+
+static void   handle_maybe_load_notifier( const UChar* soname, 
+                                                HChar* symbol, Addr addr );
+
+static void   handle_require_text_symbols ( DebugInfo* );
+
+/*------------------------------------------------------------*/
+/*--- NOTIFICATIONS                                        ---*/
+/*------------------------------------------------------------*/
+
+static 
+void generate_and_add_actives ( 
+        /* spec list and the owning TopSpec */
+        Spec*    specs, 
+        TopSpec* parent_spec,
+       /* debuginfo and the owning TopSpec */
+        DebugInfo* di,
+        TopSpec* parent_sym 
+     );
+
+/* Notify m_redir of the arrival of a new DebugInfo.  This is fairly
+   complex, but the net effect is to (1) add a new entry to the
+   topspecs list, and (2) figure out what new binding are now active,
+   and, as a result, add them to the actives mapping. */
+
+#define N_DEMANGLED 256
+
+void VG_(redir_notify_new_DebugInfo)( DebugInfo* newsi )
+{
+   Bool         ok, isWrap;
+   Int          i, nsyms;
+   Spec*        specList;
+   Spec*        spec;
+   TopSpec*     ts;
+   TopSpec*     newts;
+   HChar*       sym_name;
+   Addr         sym_addr, sym_toc;
+   HChar        demangled_sopatt[N_DEMANGLED];
+   HChar        demangled_fnpatt[N_DEMANGLED];
+   Bool         check_ppcTOCs = False;
+   Bool         isText;
+   const UChar* newsi_soname;
+
+#  if defined(VG_PLAT_USES_PPCTOC)
+   check_ppcTOCs = True;
+#  endif
+
+   vg_assert(newsi);
+   newsi_soname = VG_(DebugInfo_get_soname)(newsi);
+   vg_assert(newsi_soname != NULL);
+
+   /* stay sane: we don't already have this. */
+   for (ts = topSpecs; ts; ts = ts->next)
+      vg_assert(ts->seginfo != newsi);
+
+   /* scan this DebugInfo's symbol table, pulling out and demangling
+      any specs found */
+
+   specList = NULL; /* the spec list we're building up */
+
+   nsyms = VG_(DebugInfo_syms_howmany)( newsi );
+   for (i = 0; i < nsyms; i++) {
+      VG_(DebugInfo_syms_getidx)( newsi, i, &sym_addr, &sym_toc,
+                                  NULL, &sym_name, &isText, NULL );
+      ok = VG_(maybe_Z_demangle)( sym_name, demangled_sopatt, N_DEMANGLED,
+                                  demangled_fnpatt, N_DEMANGLED, &isWrap );
+      /* ignore data symbols */
+      if (!isText)
+         continue;
+      if (!ok) {
+         /* It's not a full-scale redirect, but perhaps it is a load-notify
+            fn?  Let the load-notify department see it. */
+         handle_maybe_load_notifier( newsi_soname, sym_name, sym_addr );
+         continue; 
+      }
+      if (check_ppcTOCs && sym_toc == 0) {
+         /* This platform uses toc pointers, but none could be found
+            for this symbol, so we can't safely redirect/wrap to it.
+            Just skip it; we'll make a second pass over the symbols in
+            the following loop, and complain at that point. */
+         continue;
+      }
+      spec = dinfo_zalloc("redir.rnnD.1", sizeof(Spec));
+      vg_assert(spec);
+      spec->from_sopatt = dinfo_strdup("redir.rnnD.2", demangled_sopatt);
+      spec->from_fnpatt = dinfo_strdup("redir.rnnD.3", demangled_fnpatt);
+      vg_assert(spec->from_sopatt);
+      vg_assert(spec->from_fnpatt);
+      spec->to_addr = sym_addr;
+      spec->isWrap = isWrap;
+      /* check we're not adding manifestly stupid destinations */
+      vg_assert(is_plausible_guest_addr(sym_addr));
+      spec->next = specList;
+      spec->mark = False; /* not significant */
+      spec->done = False; /* not significant */
+      specList = spec;
+   }
+
+   if (check_ppcTOCs) {
+      for (i = 0; i < nsyms; i++) {
+         VG_(DebugInfo_syms_getidx)( newsi, i, &sym_addr, &sym_toc,
+                                     NULL, &sym_name, &isText, NULL );
+         ok = isText
+              && VG_(maybe_Z_demangle)( 
+                    sym_name, demangled_sopatt, N_DEMANGLED,
+                    demangled_fnpatt, N_DEMANGLED, &isWrap );
+         if (!ok)
+            /* not a redirect.  Ignore. */
+            continue;
+         if (sym_toc != 0)
+            /* has a valid toc pointer.  Ignore. */
+            continue;
+
+         for (spec = specList; spec; spec = spec->next) 
+            if (0 == VG_(strcmp)(spec->from_sopatt, demangled_sopatt)
+                && 0 == VG_(strcmp)(spec->from_fnpatt, demangled_fnpatt))
+               break;
+         if (spec)
+            /* a redirect to some other copy of that symbol, which
+               does have a TOC value, already exists */
+            continue;
+
+         /* Complain */
+         VG_(message)(Vg_DebugMsg,
+                      "WARNING: no TOC ptr for redir/wrap to %s %s\n",
+                      demangled_sopatt, demangled_fnpatt);
+      }
+   }
+
+   /* Ok.  Now specList holds the list of specs from the DebugInfo.
+      Build a new TopSpec, but don't add it to topSpecs yet. */
+   newts = dinfo_zalloc("redir.rnnD.4", sizeof(TopSpec));
+   vg_assert(newts);
+   newts->next    = NULL; /* not significant */
+   newts->seginfo = newsi;
+   newts->specs   = specList;
+   newts->mark    = False; /* not significant */
+
+   /* We now need to augment the active set with the following partial
+      cross product:
+
+      (1) actives formed by matching the new specs in specList against
+          all symbols currently listed in topSpecs
+
+      (2) actives formed by matching the new symbols in newsi against
+          all specs currently listed in topSpecs
+
+      (3) actives formed by matching the new symbols in newsi against
+          the new specs in specList
+
+      This is necessary in order to maintain the invariant that
+      Actives contains all bindings generated by matching ALL specs in
+      topSpecs against ALL symbols in topSpecs (that is, a cross
+      product of ALL known specs against ALL known symbols).
+   */
+   /* Case (1) */
+   for (ts = topSpecs; ts; ts = ts->next) {
+      if (ts->seginfo)
+         generate_and_add_actives( specList,    newts,
+                                   ts->seginfo, ts );
+   }
+
+   /* Case (2) */
+   for (ts = topSpecs; ts; ts = ts->next) {
+      generate_and_add_actives( ts->specs, ts, 
+                                newsi,     newts );
+   }
+
+   /* Case (3) */
+   generate_and_add_actives( specList, newts, 
+                             newsi,    newts );
+
+   /* Finally, add the new TopSpec. */
+   newts->next = topSpecs;
+   topSpecs = newts;
+
+   if (VG_(clo_trace_redir))
+      show_redir_state("after VG_(redir_notify_new_DebugInfo)");
+
+   /* Really finally (quite unrelated to all the above) check the
+      names in the module against any --require-text-symbol=
+      specifications we might have. */
+   handle_require_text_symbols(newsi);
+}
+
+#undef N_DEMANGLED
+
+/* Add a new target for an indirect function. Adds a new redirection
+   for the indirection function with address old_from that redirects
+   the ordinary function with address new_from to the target address
+   of the original redirection. */
+
+void VG_(redir_add_ifunc_target)( Addr old_from, Addr new_from )
+{
+    Active *old, new;
+
+    old = VG_(OSetGen_Lookup)(activeSet, &old_from);
+    vg_assert(old);
+    vg_assert(old->isIFunc);
+
+    new = *old;
+    new.from_addr = new_from;
+    new.isIFunc = False;
+    maybe_add_active (new);
+
+    if (VG_(clo_trace_redir)) {
+       VG_(message)( Vg_DebugMsg,
+                     "Adding redirect for indirect function 0x%llx from 0x%llx -> 0x%llx\n",
+                     (ULong)old_from, (ULong)new_from, (ULong)new.to_addr );
+    }
+}
+
+/* Do one element of the basic cross product: add to the active set,
+   all matches resulting from comparing all the given specs against
+   all the symbols in the given seginfo.  If a conflicting binding
+   would thereby arise, don't add it, but do complain. */
+
+static 
+void generate_and_add_actives ( 
+        /* spec list and the owning TopSpec */
+        Spec*    specs, 
+        TopSpec* parent_spec,
+       /* seginfo and the owning TopSpec */
+        DebugInfo* di,
+        TopSpec* parent_sym 
+     )
+{
+   Spec*  sp;
+   Bool   anyMark, isText, isIFunc;
+   Active act;
+   Int    nsyms, i;
+   Addr   sym_addr;
+   HChar* sym_name;
+
+   /* First figure out which of the specs match the seginfo's soname.
+      Also clear the 'done' bits, so that after the main loop below
+      tell which of the Specs really did get done. */
+   anyMark = False;
+   for (sp = specs; sp; sp = sp->next) {
+      sp->done = False;
+      sp->mark = VG_(string_match)( sp->from_sopatt, 
+                                    VG_(DebugInfo_get_soname)(di) );
+      anyMark = anyMark || sp->mark;
+   }
+
+   /* shortcut: if none of the sonames match, there will be no bindings. */
+   if (!anyMark)
+      return;
+
+   /* Iterate outermost over the symbols in the seginfo, in the hope
+      of trashing the caches less. */
+   nsyms = VG_(DebugInfo_syms_howmany)( di );
+   for (i = 0; i < nsyms; i++) {
+      VG_(DebugInfo_syms_getidx)( di, i, &sym_addr, NULL, NULL,
+                                  &sym_name, &isText, &isIFunc );
+
+      /* ignore data symbols */
+      if (!isText)
+         continue;
+
+      /* On AIX, we cannot redirect calls to a so-called glink
+         function for reasons which are not obvious - something to do
+         with saving r2 across the call.  Not a problem, as we don't
+         want to anyway; presumably it is the target of the glink we
+         need to redirect.  Hence just spot them and ignore them.
+         They are always of a very specific (more or less
+         ABI-mandated) form. */
+      if (is_aix5_glink_idiom(sym_addr))
+         continue;
+
+      for (sp = specs; sp; sp = sp->next) {
+         if (!sp->mark)
+            continue; /* soname doesn't match */
+         if (VG_(string_match)( sp->from_fnpatt, sym_name )) {
+            /* got a new binding.  Add to collection. */
+            act.from_addr   = sym_addr;
+            act.to_addr     = sp->to_addr;
+            act.parent_spec = parent_spec;
+            act.parent_sym  = parent_sym;
+            act.isWrap      = sp->isWrap;
+            act.isIFunc     = isIFunc;
+            sp->done = True;
+            maybe_add_active( act );
+         }
+      } /* for (sp = specs; sp; sp = sp->next) */
+   } /* for (i = 0; i < nsyms; i++)  */
+
+   /* Now, finally, look for Specs which were marked to be done, but
+      didn't get matched.  If any such are mandatory we must abort the
+      system at this point. */
+   for (sp = specs; sp; sp = sp->next) {
+      if (!sp->mark)
+         continue;
+      if (sp->mark && (!sp->done) && sp->mandatory)
+         break;
+   }
+   if (sp) {
+      const HChar** strp;
+      HChar* v = "valgrind:  ";
+      vg_assert(sp->mark);
+      vg_assert(!sp->done);
+      vg_assert(sp->mandatory);
+      VG_(printf)("\n");
+      VG_(printf)(
+      "%sFatal error at startup: a function redirection\n", v);
+      VG_(printf)(
+      "%swhich is mandatory for this platform-tool combination\n", v);
+      VG_(printf)(
+      "%scannot be set up.  Details of the redirection are:\n", v);
+      VG_(printf)(
+      "%s\n", v);
+      VG_(printf)(
+      "%sA must-be-redirected function\n", v);
+      VG_(printf)(
+      "%swhose name matches the pattern:      %s\n", v, sp->from_fnpatt);
+      VG_(printf)(
+      "%sin an object with soname matching:   %s\n", v, sp->from_sopatt);
+      VG_(printf)(
+      "%swas not found whilst processing\n", v);
+      VG_(printf)(
+      "%ssymbols from the object with soname: %s\n",
+      v, VG_(DebugInfo_get_soname)(di));
+      VG_(printf)(
+      "%s\n", v);
+
+      for (strp = sp->mandatory; *strp; strp++)
+         VG_(printf)(
+         "%s%s\n", v, *strp);
+
+      VG_(printf)(
+      "%s\n", v);
+      VG_(printf)(
+      "%sCannot continue -- exiting now.  Sorry.\n", v);
+      VG_(printf)("\n");
+      VG_(exit)(1);
+   }
+}
+
+
+/* Add an act (passed by value; is copied here) and deal with
+   conflicting bindings. */
+static void maybe_add_active ( Active act )
+{
+   HChar*  what = NULL;
+   Active* old;
+
+   /* Complain and ignore manifestly bogus 'from' addresses.
+
+      Kludge: because this can get called befor the trampoline area (a
+      bunch of magic 'to' addresses) has its ownership changed from V
+      to C, we can't check the 'to' address similarly.  Sigh.
+
+      amd64-linux hack: the vsysinfo pages appear to have no
+      permissions
+         ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0
+      so skip the check for them.  */
+   if (!is_plausible_guest_addr(act.from_addr)
+#      if defined(VGP_amd64_linux)
+       && act.from_addr != 0xFFFFFFFFFF600000ULL
+       && act.from_addr != 0xFFFFFFFFFF600400ULL
+#      endif
+#      if defined(VGO_l4re)
+       && act.from_addr != SYSCALL_PAGE
+#      endif
+      ) {
+      what = "redirection from-address is in non-executable area";
+      goto bad;
+   }
+
+   old = VG_(OSetGen_Lookup)( activeSet, &act.from_addr );
+   if (old) {
+      /* Dodgy.  Conflicting binding. */
+      vg_assert(old->from_addr == act.from_addr);
+      if (old->to_addr != act.to_addr) {
+         /* we have to ignore it -- otherwise activeSet would contain
+            conflicting bindings. */
+         what = "new redirection conflicts with existing -- ignoring it";
+         goto bad;
+      } else {
+         /* This appears to be a duplicate of an existing binding.
+            Safe(ish) -- ignore. */
+         /* XXXXXXXXXXX COMPLAIN if new and old parents differ */
+      }
+   } else {
+      Active* a = VG_(OSetGen_AllocNode)(activeSet, sizeof(Active));
+      vg_assert(a);
+      *a = act;
+      VG_(OSetGen_Insert)(activeSet, a);
+      /* Now that a new from->to redirection is in force, we need to
+         get rid of any translations intersecting 'from' in order that
+         they get redirected to 'to'.  So discard them.  Just for
+         paranoia (but, I believe, unnecessarily), discard 'to' as
+         well. */
+      VG_(discard_translations)( (Addr64)act.from_addr, 1,
+                                 "redir_new_DebugInfo(from_addr)");
+      VG_(discard_translations)( (Addr64)act.to_addr, 1,
+                                 "redir_new_DebugInfo(to_addr)");
+   }
+   return;
+
+  bad:
+   vg_assert(what);
+   if (VG_(clo_verbosity) > 1) {
+      VG_(message)(Vg_UserMsg, "WARNING: %s\n", what);
+      show_active(             "    new: ", &act);
+   }
+}
+
+
+/* Notify m_redir of the deletion of a DebugInfo.  This is relatively
+   simple -- just get rid of all actives derived from it, and free up
+   the associated list elements. */
+
+void VG_(redir_notify_delete_DebugInfo)( DebugInfo* delsi )
+{
+   TopSpec* ts;
+   TopSpec* tsPrev;
+   Spec*    sp;
+   Spec*    sp_next;
+   OSet*    tmpSet;
+   Active*  act;
+   Bool     delMe;
+   Addr     addr;
+
+   vg_assert(delsi);
+
+   /* Search for it, and make tsPrev point to the previous entry, if
+      any. */
+   tsPrev = NULL;
+   ts     = topSpecs;
+   while (True) {
+     if (ts == NULL) break;
+     if (ts->seginfo == delsi) break;
+     tsPrev = ts;
+     ts = ts->next;
+   }
+
+   vg_assert(ts); /* else we don't have the deleted DebugInfo */
+   vg_assert(ts->seginfo == delsi);
+
+   /* Traverse the actives, copying the addresses of those we intend
+      to delete into tmpSet. */
+   tmpSet = VG_(OSetWord_Create)(dinfo_zalloc, "redir.rndD.1", dinfo_free);
+
+   ts->mark = True;
+
+   VG_(OSetGen_ResetIter)( activeSet );
+   while ( (act = VG_(OSetGen_Next)(activeSet)) ) {
+      delMe = act->parent_spec != NULL
+              && act->parent_sym != NULL
+              && act->parent_spec->seginfo != NULL
+              && act->parent_sym->seginfo != NULL
+              && (act->parent_spec->mark || act->parent_sym->mark);
+
+      /* While we're at it, a bit of paranoia: delete any actives
+         which don't have both feet in valid client executable areas.
+         But don't delete hardwired-at-startup ones; these are denoted
+         by having parent_spec or parent_sym being NULL.  */
+      if ( (!delMe)
+           && act->parent_spec != NULL
+           && act->parent_sym  != NULL ) {
+         if (!is_plausible_guest_addr(act->from_addr))
+            delMe = True;
+         if (!is_plausible_guest_addr(act->to_addr))
+            delMe = True;
+      }
+
+      if (delMe) {
+         VG_(OSetWord_Insert)( tmpSet, act->from_addr );
+         /* While we have our hands on both the 'from' and 'to'
+            of this Active, do paranoid stuff with tt/tc. */
+         VG_(discard_translations)( (Addr64)act->from_addr, 1,
+                                    "redir_del_DebugInfo(from_addr)");
+         VG_(discard_translations)( (Addr64)act->to_addr, 1,
+                                    "redir_del_DebugInfo(to_addr)");
+      }
+   }
+
+   /* Now traverse tmpSet, deleting corresponding elements in activeSet. */
+   VG_(OSetWord_ResetIter)( tmpSet );
+   while ( VG_(OSetWord_Next)(tmpSet, &addr) ) {
+      act = VG_(OSetGen_Remove)( activeSet, &addr );
+      vg_assert(act);
+      VG_(OSetGen_FreeNode)( activeSet, act );
+   }
+
+   VG_(OSetWord_Destroy)( tmpSet );
+
+   /* The Actives set is now cleaned up.  Free up this TopSpec and
+      everything hanging off it. */
+   for (sp = ts->specs; sp; sp = sp_next) {
+      if (sp->from_sopatt) dinfo_free(sp->from_sopatt);
+      if (sp->from_fnpatt) dinfo_free(sp->from_fnpatt);
+      sp_next = sp->next;
+      dinfo_free(sp);
+   }
+
+   if (tsPrev == NULL) {
+      /* first in list */
+      topSpecs = ts->next;
+   } else {
+      tsPrev->next = ts->next;
+   }
+   dinfo_free(ts);
+
+   if (VG_(clo_trace_redir))
+      show_redir_state("after VG_(redir_notify_delete_DebugInfo)");
+}
+
+
+/*------------------------------------------------------------*/
+/*--- QUERIES (really the whole point of this module)      ---*/
+/*------------------------------------------------------------*/
+
+/* This is the crucial redirection function.  It answers the question:
+   should this code address be redirected somewhere else?  It's used
+   just before translating a basic block. */
+Addr VG_(redir_do_lookup) ( Addr orig, Bool* isWrap )
+{
+   Active* r = VG_(OSetGen_Lookup)(activeSet, &orig);
+   if (r == NULL)
+      return orig;
+
+   vg_assert(r->to_addr != 0);
+   if (isWrap)
+      *isWrap = r->isWrap || r->isIFunc;
+   if (r->isIFunc) {
+      vg_assert(iFuncWrapper);
+      return iFuncWrapper;
+   }
+   return r->to_addr;
+}
+
+
+/*------------------------------------------------------------*/
+/*--- INITIALISATION                                       ---*/
+/*------------------------------------------------------------*/
+
+/* Add a never-delete-me Active. */
+
+__attribute__((unused)) /* only used on amd64 */
+static void add_hardwired_active ( Addr from, Addr to )
+{
+   Active act;
+   act.from_addr   = from;
+   act.to_addr     = to;
+   act.parent_spec = NULL;
+   act.parent_sym  = NULL;
+   act.isWrap      = False;
+   act.isIFunc     = False;
+   maybe_add_active( act );
+}
+
+
+/* Add a never-delete-me Spec.  This is a bit of a kludge.  On the
+   assumption that this is called only at startup, only handle the
+   case where topSpecs is completely empty, or if it isn't, it has
+   just one entry and that is the one with NULL seginfo -- that is the
+   entry that holds these initial specs. */
+
+__attribute__((unused)) /* not used on all platforms */
+static void add_hardwired_spec ( HChar* sopatt, HChar* fnpatt, 
+                                 Addr   to_addr,
+                                 const HChar** mandatory )
+{
+   Spec* spec = dinfo_zalloc("redir.ahs.1", sizeof(Spec));
+   vg_assert(spec);
+
+   if (topSpecs == NULL) {
+      topSpecs = dinfo_zalloc("redir.ahs.2", sizeof(TopSpec));
+      vg_assert(topSpecs);
+      /* symtab_zalloc sets all fields to zero */
+   }
+
+   vg_assert(topSpecs != NULL);
+   vg_assert(topSpecs->next == NULL);
+   vg_assert(topSpecs->seginfo == NULL);
+   /* FIXED PARTS */
+   spec->from_sopatt = sopatt;
+   spec->from_fnpatt = fnpatt;
+   spec->to_addr     = to_addr;
+   spec->isWrap      = False;
+   spec->mandatory   = mandatory;
+   /* VARIABLE PARTS */
+   spec->mark        = False; /* not significant */
+   spec->done        = False; /* not significant */
+
+   spec->next = topSpecs->specs;
+   topSpecs->specs = spec;
+}
+
+
+__attribute__((unused)) /* not used on all platforms */
+static void add_hardwired_wrap ( HChar* sopatt, HChar* fnpatt, 
+                                 Addr   to_addr,
+                                 const HChar** mandatory )
+{
+   Spec* spec = dinfo_zalloc("redir.ahs.1", sizeof(Spec));
+   vg_assert(spec);
+
+   if (topSpecs == NULL) {
+      topSpecs = dinfo_zalloc("redir.ahs.2", sizeof(TopSpec));
+      vg_assert(topSpecs);
+      /* symtab_zalloc sets all fields to zero */
+   }
+
+   vg_assert(topSpecs != NULL);
+   vg_assert(topSpecs->next == NULL);
+   vg_assert(topSpecs->seginfo == NULL);
+   /* FIXED PARTS */
+   spec->from_sopatt = sopatt;
+   spec->from_fnpatt = fnpatt;
+   spec->to_addr     = to_addr;
+   spec->isWrap      = True;
+   spec->mandatory   = mandatory;
+   /* VARIABLE PARTS */
+   spec->mark        = False; /* not significant */
+   spec->done        = False; /* not significant */
+
+   spec->next = topSpecs->specs;
+   topSpecs->specs = spec;
+}
+
+
+__attribute__((unused)) /* not used on all platforms */
+static const HChar* complain_about_stripped_glibc_ldso[]
+= { "Possible fixes: (1, short term): install glibc's debuginfo",
+    "package on this machine.  (2, longer term): ask the packagers",
+    "for your Linux distribution to please in future ship a non-",
+    "stripped ld.so (or whatever the dynamic linker .so is called)",
+    "that exports the above-named function using the standard",
+    "calling conventions for this platform.",
+    NULL
+  };
+
+
+/* Initialise the redir system, and create the initial Spec list and
+   for amd64-linux a couple of permanent active mappings.  The initial
+   Specs are not converted into Actives yet, on the (checked)
+   assumption that no DebugInfos have so far been created, and so when
+   they are created, that will happen. */
+
+void VG_(redir_initialise) ( void )
+{
+   // Assert that there are no DebugInfos so far
+   vg_assert( VG_(next_DebugInfo)(NULL) == NULL );
+
+   // Initialise active mapping.
+   activeSet = VG_(OSetGen_Create)(offsetof(Active, from_addr),
+                                   NULL,     // Use fast comparison
+                                   dinfo_zalloc,
+                                   "redir.ri.1", 
+                                   dinfo_free);
+
+   // The rest of this function just adds initial Specs.
+
+#  if defined(VGP_x86_linux)
+   /* If we're using memcheck, use this intercept right from the
+      start, otherwise ld.so (glibc-2.3.5) makes a lot of noise. */
+   if (0==VG_(strcmp)("Memcheck", VG_(details).name)) {
+      add_hardwired_spec(
+         "ld-linux.so.2", "index",
+         (Addr)&VG_(x86_linux_REDIR_FOR_index),
+         NULL
+      );
+   }
+
+#  elif defined(VGP_amd64_linux)
+   /* Redirect vsyscalls to local versions */
+   add_hardwired_active(
+      0xFFFFFFFFFF600000ULL,
+      (Addr)&VG_(amd64_linux_REDIR_FOR_vgettimeofday) 
+   );
+   add_hardwired_active( 
+      0xFFFFFFFFFF600400ULL,
+      (Addr)&VG_(amd64_linux_REDIR_FOR_vtime) 
+   );
+
+   /* If we're using memcheck, use these intercepts right from
+      the start, otherwise ld.so makes a lot of noise. */
+   if (0==VG_(strcmp)("Memcheck", VG_(details).name)) {
+
+      add_hardwired_spec(
+         "ld-linux-x86-64.so.2", "strlen",
+         (Addr)&VG_(amd64_linux_REDIR_FOR_strlen),
+#        if defined(GLIBC_2_2) || defined(GLIBC_2_3) || defined(GLIBC_2_4) \
+            || defined(GLIBC_2_5) || defined(GLIBC_2_6) || defined(GLIBC_2_7) \
+            || defined(GLIBC_2_8) || defined(GLIBC_2_9)
+         NULL
+#        else
+         /* for glibc-2.10 and later, this is mandatory - can't sanely
+            continue without it */
+         complain_about_stripped_glibc_ldso
+#        endif
+      );
+   }
+
+#  elif defined(VGP_ppc32_linux)
+   /* If we're using memcheck, use these intercepts right from
+      the start, otherwise ld.so makes a lot of noise. */
+   if (0==VG_(strcmp)("Memcheck", VG_(details).name)) {
+
+      /* this is mandatory - can't sanely continue without it */
+      add_hardwired_spec(
+         "ld.so.1", "strlen",
+         (Addr)&VG_(ppc32_linux_REDIR_FOR_strlen),
+         complain_about_stripped_glibc_ldso
+      );   
+      add_hardwired_spec(
+         "ld.so.1", "strcmp",
+         (Addr)&VG_(ppc32_linux_REDIR_FOR_strcmp),
+         NULL /* not mandatory - so why bother at all? */
+         /* glibc-2.6.1 (openSUSE 10.3, ppc32) seems fine without it */
+      );
+      add_hardwired_spec(
+         "ld.so.1", "index",
+         (Addr)&VG_(ppc32_linux_REDIR_FOR_strchr),
+         NULL /* not mandatory - so why bother at all? */
+         /* glibc-2.6.1 (openSUSE 10.3, ppc32) seems fine without it */
+      );
+   }
+
+#  elif defined(VGP_ppc64_linux)
+   /* If we're using memcheck, use these intercepts right from
+      the start, otherwise ld.so makes a lot of noise. */
+   if (0==VG_(strcmp)("Memcheck", VG_(details).name)) {
+
+      /* this is mandatory - can't sanely continue without it */
+      add_hardwired_spec(
+         "ld64.so.1", "strlen",
+         (Addr)VG_(fnptr_to_fnentry)( &VG_(ppc64_linux_REDIR_FOR_strlen) ),
+         complain_about_stripped_glibc_ldso
+      );
+
+      add_hardwired_spec(
+         "ld64.so.1", "index",
+         (Addr)VG_(fnptr_to_fnentry)( &VG_(ppc64_linux_REDIR_FOR_strchr) ),
+         NULL /* not mandatory - so why bother at all? */
+         /* glibc-2.5 (FC6, ppc64) seems fine without it */
+      );
+   }
+
+#  elif defined(VGP_arm_linux)
+   /* If we're using memcheck, use these intercepts right from
+      the start, otherwise ld.so makes a lot of noise. */
+   if (0==VG_(strcmp)("Memcheck", VG_(details).name)) {
+      add_hardwired_spec(
+         "ld-linux.so.3", "strlen",
+         (Addr)&VG_(arm_linux_REDIR_FOR_strlen),
+         NULL
+      );
+      //add_hardwired_spec(
+      //   "ld-linux.so.3", "index",
+      //   (Addr)&VG_(arm_linux_REDIR_FOR_index),
+      //   NULL
+      //);
+      add_hardwired_spec(
+         "ld-linux.so.3", "memcpy",
+         (Addr)&VG_(arm_linux_REDIR_FOR_memcpy),
+         NULL 
+      );
+   }
+   /* nothing so far */
+
+#  elif defined(VGP_ppc32_aix5)
+   /* nothing so far */
+
+#  elif defined(VGP_ppc64_aix5)
+   /* nothing so far */
+
+#  elif defined(VGO_darwin)
+   /* If we're using memcheck, use these intercepts right from
+      the start, otherwise dyld makes a lot of noise. */
+   if (0==VG_(strcmp)("Memcheck", VG_(details).name)) {
+      add_hardwired_spec("dyld", "strcmp",
+                         (Addr)&VG_(darwin_REDIR_FOR_strcmp), NULL);
+      add_hardwired_spec("dyld", "strlen",
+                         (Addr)&VG_(darwin_REDIR_FOR_strlen), NULL);
+      add_hardwired_spec("dyld", "strcat",
+                         (Addr)&VG_(darwin_REDIR_FOR_strcat), NULL);
+      add_hardwired_spec("dyld", "strcpy",
+                         (Addr)&VG_(darwin_REDIR_FOR_strcpy), NULL);
+      add_hardwired_spec("dyld", "strlcat",
+                         (Addr)&VG_(darwin_REDIR_FOR_strlcat), NULL);
+#     if defined(VGP_amd64_darwin)
+      // DDD: #warning fixme rdar://6166275
+      add_hardwired_spec("dyld", "arc4random",
+                         (Addr)&VG_(darwin_REDIR_FOR_arc4random), NULL);
+#     endif
+   }
+
+#  elif defined(VGO_l4re)
+   add_hardwired_active((Addr) SYSCALL_PAGE,
+                        (Addr) (Addr) &VG_(x86_l4re_REDIR_FOR_syscall_page));
+   add_hardwired_wrap("NONE",
+                                         "_dl_open",
+                                         (Addr)&VG_(x86_l4re_REDIR_FOR_dl_open), NULL);
+   add_hardwired_wrap("NONE",
+                                         "_dl_close",
+                                         (Addr)&VG_(x86_l4re_REDIR_FOR_dl_close), NULL);
+   add_hardwired_wrap("NONE",
+                                         "_dl_mmap",
+                                         (Addr)&VG_(x86_l4re_REDIR_FOR_dl_mmap), NULL);
+#  else
+#    error Unknown platform
+#  endif
+
+   if (VG_(clo_trace_redir))
+      show_redir_state("after VG_(redir_initialise)");
+}
+
+
+/*------------------------------------------------------------*/
+/*--- MISC HELPERS                                         ---*/
+/*------------------------------------------------------------*/
+
+static void* dinfo_zalloc(HChar* ec, SizeT n) {
+   void* p;
+   vg_assert(n > 0);
+   p = VG_(arena_malloc)(VG_AR_DINFO, ec, n);
+   tl_assert(p);
+   VG_(memset)(p, 0, n);
+   return p;
+}
+
+static void dinfo_free(void* p) {
+   tl_assert(p);
+   return VG_(arena_free)(VG_AR_DINFO, p);
+}
+
+static HChar* dinfo_strdup(HChar* ec, HChar* str)
+{
+   return VG_(arena_strdup)(VG_AR_DINFO, ec, str);
+}
+
+/* Really this should be merged with translations_allowable_from_seg
+   in m_translate. */
+static Bool is_plausible_guest_addr(Addr a)
+{
+   NSegment const* seg = VG_(am_find_nsegment)(a);
+   return seg != NULL
+          && (seg->kind == SkAnonC || seg->kind == SkFileC)
+          && (seg->hasX || seg->hasR); /* crude x86-specific hack */
+}
+
+/* A function which spots AIX 'glink' functions.  A 'glink' function
+   is a stub function which has something to do with AIX-style dynamic
+   linking, and jumps to the real target (with which it typically
+   shares the same name).  See also comment where this function is
+   used (above). */
+static Bool is_aix5_glink_idiom ( Addr sym_addr )
+{
+#  if defined(VGP_ppc32_aix5)
+   UInt* w = (UInt*)sym_addr;
+   if (VG_IS_4_ALIGNED(w)
+       && is_plausible_guest_addr((Addr)(w+0))
+       && is_plausible_guest_addr((Addr)(w+6))
+       && (w[0] & 0xFFFF0000) == 0x81820000 /* lwz r12,func@toc(r2) */
+       && w[1] == 0x90410014                /* stw r2,20(r1) */
+       && w[2] == 0x800c0000                /* lwz r0,0(r12) */
+       && w[3] == 0x804c0004                /* lwz r2,4(r12) */
+       && w[4] == 0x7c0903a6                /* mtctr r0 */
+       && w[5] == 0x4e800420                /* bctr */
+       && w[6] == 0x00000000                /* illegal */)
+      return True;
+#  elif defined(VGP_ppc64_aix5)
+   UInt* w = (UInt*)sym_addr;
+   if (VG_IS_4_ALIGNED(w)
+       && is_plausible_guest_addr((Addr)(w+0))
+       && is_plausible_guest_addr((Addr)(w+6))
+       && (w[0] & 0xFFFF0000) == 0xE9820000 /* ld  r12,func@toc(r2) */
+       && w[1] == 0xF8410028                /* std r2,40(r1) */
+       && w[2] == 0xE80C0000                /* ld  r0,0(r12) */
+       && w[3] == 0xE84C0008                /* ld  r2,8(r12) */
+       && w[4] == 0x7c0903a6                /* mtctr r0 */
+       && w[5] == 0x4e800420                /* bctr */
+       && w[6] == 0x00000000                /* illegal */)
+      return True;
+#  endif
+   return False;
+}
+
+
+/*------------------------------------------------------------*/
+/*--- NOTIFY-ON-LOAD FUNCTIONS                             ---*/
+/*------------------------------------------------------------*/
+
+static 
+void handle_maybe_load_notifier( const UChar* soname, 
+                                       HChar* symbol, Addr addr )
+{
+#  if defined(VGP_x86_linux)
+   /* x86-linux only: if we see _dl_sysinfo_int80, note its address.
+      See comment on declaration of VG_(client__dl_sysinfo_int80) for
+      the reason.  As far as I can tell, the relevant symbol is always
+      in object with soname "ld-linux.so.2". */
+   if (symbol && symbol[0] == '_' 
+              && 0 == VG_(strcmp)(symbol, "_dl_sysinfo_int80")
+              && 0 == VG_(strcmp)(soname, "ld-linux.so.2")) {
+      if (VG_(client__dl_sysinfo_int80) == 0)
+         VG_(client__dl_sysinfo_int80) = addr;
+   }
+#  endif
+
+   /* Normal load-notifier handling after here.  First, ignore all
+      symbols lacking the right prefix. */
+   if (0 != VG_(strncmp)(symbol, VG_NOTIFY_ON_LOAD_PREFIX, 
+                                 VG_NOTIFY_ON_LOAD_PREFIX_LEN))
+      /* Doesn't have the right prefix */
+      return;
+
+   if (VG_(strcmp)(symbol, VG_STRINGIFY(VG_NOTIFY_ON_LOAD(freeres))) == 0)
+      VG_(client___libc_freeres_wrapper) = addr;
+   else if (VG_(strcmp)(symbol, VG_STRINGIFY(VG_NOTIFY_ON_LOAD(ifunc_wrapper))) == 0)
+      iFuncWrapper = addr;
+   else
+      vg_assert2(0, "unrecognised load notification function: %s", symbol);
+}
+
+
+/*------------------------------------------------------------*/
+/*--- REQUIRE-TEXT-SYMBOL HANDLING                         ---*/
+/*------------------------------------------------------------*/
+
+/* In short: check that the currently-being-loaded object has text
+   symbols that satisfy any --require-text-symbol= specifications that
+   apply to it, and abort the run with an error message if not.
+*/
+static void handle_require_text_symbols ( DebugInfo* di )
+{
+   /* First thing to do is figure out which, if any,
+      --require-text-symbol specification strings apply to this
+      object.  Most likely none do, since it is not expected to
+      frequently be used.  Work through the list of specs and
+      accumulate in fnpatts[] the fn patterns that pertain to this
+      object. */
+   HChar* fnpatts[VG_CLO_MAX_REQ_TSYMS];
+   Int    fnpatts_used = 0;
+   Int    i, j;
+   const HChar* di_soname = VG_(DebugInfo_get_soname)(di);
+   vg_assert(di_soname); // must be present
+
+   VG_(memset)(&fnpatts, 0, sizeof(fnpatts));
+
+   vg_assert(VG_(clo_n_req_tsyms) >= 0);
+   vg_assert(VG_(clo_n_req_tsyms) <= VG_CLO_MAX_REQ_TSYMS);
+   for (i = 0; i < VG_(clo_n_req_tsyms); i++) {
+      HChar* spec = VG_(clo_req_tsyms)[i];
+      vg_assert(spec && VG_(strlen)(spec) >= 4);
+      // clone the spec, so we can stick a zero at the end of the sopatt
+      spec = VG_(strdup)("m_redir.hrts.1", spec);
+      HChar sep = spec[0];
+      HChar* sopatt = &spec[1];
+      HChar* fnpatt = VG_(strchr)(sopatt, sep);
+      // the initial check at clo processing in time in m_main
+      // should ensure this.
+      vg_assert(fnpatt && *fnpatt == sep);
+      *fnpatt = 0;
+      fnpatt++;
+      if (VG_(string_match)(sopatt, di_soname))
+         fnpatts[fnpatts_used++]
+            = VG_(strdup)("m_redir.hrts.2", fnpatt);
+      VG_(free)(spec);
+   }
+
+   if (fnpatts_used == 0)
+      return;  /* no applicable spec strings */
+
+   /* So finally, fnpatts[0 .. fnpatts_used - 1] contains the set of
+      (patterns for) text symbol names that must be found in this
+      object, in order to continue.  That is, we must find at least
+      one text symbol name that matches each pattern, else we must
+      abort the run. */
+
+   if (0) VG_(printf)("for %s\n", di_soname);
+   for (i = 0; i < fnpatts_used; i++)
+      if (0) VG_(printf)("   fnpatt: %s\n", fnpatts[i]);
+
+   /* For each spec, look through the syms to find one that matches.
+      This isn't terribly efficient but it happens rarely, so no big
+      deal. */
+   for (i = 0; i < fnpatts_used; i++) {
+      Bool   found = False;
+      HChar* fnpatt = fnpatts[i];
+      Int    nsyms = VG_(DebugInfo_syms_howmany)(di);
+      for (j = 0; j < nsyms; j++) {
+         Bool   isText   = False;
+         HChar* sym_name = NULL;
+         VG_(DebugInfo_syms_getidx)( di, j, NULL, NULL,
+                                     NULL, &sym_name, &isText, NULL );
+         /* ignore data symbols */
+         if (0) VG_(printf)("QQQ %s\n", sym_name);
+         vg_assert(sym_name);
+         if (!isText)
+            continue;
+         if (VG_(string_match)(fnpatt, sym_name)) {
+            found = True;
+            break;
+         }
+      }
+
+      if (!found) {
+         HChar* v = "valgrind:  ";
+         VG_(printf)("\n");
+         VG_(printf)(
+         "%sFatal error at when loading library with soname\n", v);
+         VG_(printf)(
+         "%s   %s\n", v, di_soname);
+         VG_(printf)(
+         "%sCannot find any text symbol with a name "
+         "that matches the pattern\n", v);
+         VG_(printf)("%s   %s\n", v, fnpatt);
+         VG_(printf)("%sas required by a --require-text-symbol= "
+         "specification.\n", v);
+         VG_(printf)("\n");
+         VG_(printf)(
+         "%sCannot continue -- exiting now.\n", v);
+         VG_(printf)("\n");
+         VG_(exit)(1);
+      }
+   }
+
+   /* All required specs were found.  Just free memory and return. */
+   for (i = 0; i < fnpatts_used; i++)
+      VG_(free)(fnpatts[i]);
+}
+
+
+/*------------------------------------------------------------*/
+/*--- SANITY/DEBUG                                         ---*/
+/*------------------------------------------------------------*/
+
+static void show_spec ( HChar* left, Spec* spec )
+{
+   VG_(message)( Vg_DebugMsg, 
+                 "%s%25s %30s %s-> 0x%08llx\n",
+                 left,
+                 spec->from_sopatt, spec->from_fnpatt,
+                 spec->isWrap ? "W" : "R",
+                 (ULong)spec->to_addr );
+}
+
+static void show_active ( HChar* left, Active* act )
+{
+   Bool ok;
+   HChar name1[64] = "";
+   HChar name2[64] = "";
+   name1[0] = name2[0] = 0;
+   ok = VG_(get_fnname_w_offset)(act->from_addr, name1, 64);
+   if (!ok) VG_(strcpy)(name1, "???");
+   ok = VG_(get_fnname_w_offset)(act->to_addr, name2, 64);
+   if (!ok) VG_(strcpy)(name2, "???");
+
+   VG_(message)(Vg_DebugMsg, "%s0x%08llx (%20s) %s-> 0x%08llx %s\n", 
+                             left, 
+                             (ULong)act->from_addr, name1,
+                             act->isWrap ? "W" : "R",
+                             (ULong)act->to_addr, name2 );
+}
+
+static void show_redir_state ( HChar* who )
+{
+   TopSpec* ts;
+   Spec*    sp;
+   Active*  act;
+   VG_(message)(Vg_DebugMsg, "<<\n");
+   VG_(message)(Vg_DebugMsg, "   ------ REDIR STATE %s ------\n", who);
+   for (ts = topSpecs; ts; ts = ts->next) {
+      VG_(message)(Vg_DebugMsg, 
+                   "   TOPSPECS of soname %s\n",
+                   ts->seginfo
+                      ? (HChar*)VG_(DebugInfo_get_soname)(ts->seginfo)
+                      : "(hardwired)" );
+      for (sp = ts->specs; sp; sp = sp->next)
+         show_spec("     ", sp);
+   }
+   VG_(message)(Vg_DebugMsg, "   ------ ACTIVE ------\n");
+   VG_(OSetGen_ResetIter)( activeSet );
+   while ( (act = VG_(OSetGen_Next)(activeSet)) ) {
+      show_active("    ", act);
+   }
+
+   VG_(message)(Vg_DebugMsg, ">>\n");
+}
+
+/*--------------------------------------------------------------------*/
+/*--- end                                                          ---*/
+/*--------------------------------------------------------------------*/