Modularised m_redir. As a side-effect, managed to remove
$PLATFORM/core_platform.c and $PLATFORM/libplatform.a, hooray.
git-svn-id: svn://svn.valgrind.org/valgrind/trunk@3808 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/coregrind/m_redir.c b/coregrind/m_redir.c
new file mode 100644
index 0000000..6ffbb8e
--- /dev/null
+++ b/coregrind/m_redir.c
@@ -0,0 +1,726 @@
+/*--------------------------------------------------------------------*/
+/*--- Management of function redirection and wrapping. ---*/
+/*--- vg_redir.c ---*/
+/*--------------------------------------------------------------------*/
+
+/*
+ This file is part of Valgrind, an extensible x86 protected-mode
+ emulator for monitoring program execution on x86-Unixes.
+
+ Copyright (C) 2000-2005 Julian Seward
+ jseward@acm.org
+ Copyright (C) 2003-2005 Jeremy Fitzhardinge
+ jeremy@goop.org
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307, USA.
+
+ The GNU General Public License is contained in the file COPYING.
+*/
+#include "core.h"
+#include "vg_symtab2.h"
+
+#include "pub_core_aspacemgr.h"
+#include "pub_core_skiplist.h"
+#include "pub_core_options.h"
+#include "pub_core_redir.h"
+#include "pub_core_transtab.h"
+
+/*------------------------------------------------------------*/
+/*--- General purpose redirection. ---*/
+/*------------------------------------------------------------*/
+
+/*
+ wraps and redirections, indexed by from_addr
+
+ Redirection and wrapping are two distinct mechanisms which Valgrind
+ can use to change the client's control flow.
+
+ Redirection intercepts a call to a client function, and re-points it
+ to a new piece of code (presumably functionally equivalent). The
+ original code is never run.
+
+ Wrapping does call the client's original code, but calls "before"
+ and "after" functions which can inspect (and perhaps modify) the
+ function's arguments and return value.
+ */
+struct _CodeRedirect {
+ enum redir_type {
+ R_REDIRECT, /* plain redirection */
+ R_WRAPPER, /* wrap with valgrind-internal code */
+ R_CLIENT_WRAPPER, /* wrap with client-side code */
+ } type;
+
+ const Char *from_lib; /* library qualifier pattern */
+ const Char *from_sym; /* symbol */
+ Addr from_addr; /* old addr */
+
+ /* used for redirection */
+ const Char *to_lib; /* library qualifier pattern */
+ const Char *to_sym; /* symbol */
+ Addr to_addr; /* new addr */
+
+ /* used for wrapping */
+ const FuncWrapper *wrapper;
+
+ CodeRedirect *next; /* next pointer on unresolved list */
+};
+
+static Char *straddr(void *p)
+{
+ static Char buf[16];
+
+ VG_(sprintf)(buf, "%p", *(Addr *)p);
+
+ return buf;
+}
+
+static SkipList sk_resolved_redir = VG_SKIPLIST_INIT(CodeRedirect, from_addr,
+ VG_(cmp_Addr), straddr, VG_AR_SYMTAB);
+static CodeRedirect *unresolved_redir = NULL;
+
+static Bool match_lib(const Char *pattern, const SegInfo *si)
+{
+ /* pattern == NULL matches everything, otherwise use globbing
+
+ If the pattern starts with:
+ file:, then match filename
+ soname:, then match soname
+ something else, match filename
+ */
+ const Char *name = si->filename;
+
+ if (pattern == NULL)
+ return True;
+
+ if (VG_(strncmp)(pattern, "file:", 5) == 0) {
+ pattern += 5;
+ name = si->filename;
+ }
+ if (VG_(strncmp)(pattern, "soname:", 7) == 0) {
+ pattern += 7;
+ name = si->soname;
+ }
+
+ if (name == NULL)
+ return False;
+
+ return VG_(string_match)(pattern, name);
+}
+
+static inline Bool from_resolved(const CodeRedirect *redir)
+{
+ return redir->from_addr != 0;
+}
+
+static inline Bool to_resolved(const CodeRedirect *redir)
+{
+ if (redir->type == R_REDIRECT)
+ return redir->to_addr != 0;
+ vg_assert(redir->wrapper != NULL);
+ return True;
+}
+
+Bool VG_(is_resolved)(const CodeRedirect *redir)
+{
+ return from_resolved(redir) && to_resolved(redir);
+}
+
+static void add_resolved(CodeRedirect *redir)
+{
+ switch(redir->type) {
+ case R_REDIRECT:
+ if (VG_(clo_trace_redir)) {
+ VG_(message)(Vg_DebugMsg, " redir resolved (%s:%s=%p -> ",
+ redir->from_lib, redir->from_sym, redir->from_addr);
+ VG_(message)(Vg_DebugMsg, " %s:%s=%p)",
+ redir->to_lib, redir->to_sym, redir->to_addr);
+ }
+
+ if (VG_(search_transtab)(NULL, (Addr64)redir->from_addr, False)) {
+ /* For some given (from, to) redir, the "from" function got
+ called before the .so containing "to" became available. We
+ know this because there is already a translation for the
+ entry point of the original "from". So the redirect will
+ never actually take effect unless that translation is
+ discarded.
+
+ Note, we only really need to discard the first bb of the
+ old entry point, and so we avoid the problem of having to
+ figure out how big that bb was -- since it is at least 1
+ byte of original code, we can just pass 1 as the original
+ size to invalidate_translations() and it will indeed get
+ rid of the translation.
+
+ Note, this is potentially expensive -- discarding
+ translations causes complete unchaining.
+ */
+ if (VG_(clo_verbosity) > 2 && VG_(clo_trace_redir)) {
+ VG_(message)(Vg_UserMsg,
+ "Discarding translation due to redirect of already called function" );
+ VG_(message)(Vg_UserMsg,
+ " %s (%p -> %p)",
+ redir->from_sym, redir->from_addr, redir->to_addr );
+ }
+ VG_(discard_translations)((Addr64)redir->from_addr, 1);
+ }
+
+ {
+ CodeRedirect *r = VG_(SkipList_Find_Exact)(&sk_resolved_redir, &redir->from_addr);
+
+ if (r == NULL)
+ VG_(SkipList_Insert)(&sk_resolved_redir, redir);
+ else {
+ /* XXX leak redir */
+ if (VG_(clo_trace_redir))
+ VG_(message)(Vg_DebugMsg, " redir %s:%s:%p->%s:%s:%p duplicated\n",
+ redir->from_lib, redir->from_sym, redir->from_addr,
+ redir->to_lib, redir->to_sym, redir->to_addr);
+ }
+ }
+ break;
+
+ case R_WRAPPER:
+ if (VG_(clo_trace_redir)) {
+ VG_(message)(Vg_DebugMsg, " wrapper resolved (%s:%s=%p -> wrapper)",
+ redir->from_lib, redir->from_sym, redir->from_addr);
+ }
+
+ /* XXX redir leaked */
+ //VG_(wrap_function)(redir->from_addr, redir->wrapper);
+ break;
+
+ case R_CLIENT_WRAPPER:
+ VG_(core_panic)("not implemented");
+ break;
+ }
+}
+
+/* Resolve a redir using si if possible, and add it to the resolved
+ list */
+static Bool resolve_redir(CodeRedirect *redir, const SegInfo *si)
+{
+ Bool resolved;
+
+ vg_assert(si != NULL);
+ vg_assert(si->seg != NULL);
+
+ /* no redirection from Valgrind segments */
+ if (si->seg->flags & SF_VALGRIND)
+ return False;
+
+ resolved = VG_(is_resolved)(redir);
+
+ if (0 && VG_(clo_trace_redir))
+ VG_(printf)(" consider FROM binding %s:%s -> %s:%s in %s(%s)\n",
+ redir->from_lib, redir->from_sym,
+ redir->to_lib, redir->to_sym,
+ si->filename, si->soname);
+
+ vg_assert(!resolved);
+
+ if (!from_resolved(redir)) {
+ vg_assert(redir->from_sym != NULL);
+
+ if (match_lib(redir->from_lib, si)) {
+ redir->from_addr = VG_(reverse_search_one_symtab)(si, redir->from_sym);
+ if (VG_(clo_trace_redir) && redir->from_addr != 0)
+ VG_(printf)(" bind FROM: %p = %s:%s\n",
+ redir->from_addr,redir->from_lib, redir->from_sym );
+ }
+ }
+
+ if (!to_resolved(redir)) {
+ vg_assert(redir->type == R_REDIRECT);
+ vg_assert(redir->to_sym != NULL);
+
+ if (match_lib(redir->to_lib, si)) {
+ redir->to_addr = VG_(reverse_search_one_symtab)(si, redir->to_sym);
+ if (VG_(clo_trace_redir) && redir->to_addr != 0)
+ VG_(printf)(" bind TO: %p = %s:%s\n",
+ redir->to_addr,redir->to_lib, redir->to_sym );
+
+ }
+ }
+
+ resolved = from_resolved(redir) && to_resolved(redir);
+
+ if (0 && VG_(clo_trace_redir))
+ VG_(printf)("resolve_redir: %s:%s from=%p %s:%s to=%p\n",
+ redir->from_lib, redir->from_sym, redir->from_addr,
+ redir->to_lib, redir->to_sym, redir->to_addr);
+
+ if (resolved) add_resolved(redir);
+
+ return resolved;
+}
+
+static Bool resolve_redir_allsegs(CodeRedirect *redir)
+{
+ const SegInfo *si;
+
+ for(si = VG_(next_seginfo)(NULL);
+ si != NULL;
+ si = VG_(next_seginfo)(si))
+ {
+ if (resolve_redir(redir, si))
+ return True;
+ }
+ return False;
+}
+
+/* Go through the complete redir list, resolving as much as possible with this SegInfo.
+
+ This should be called when a new SegInfo symtab is loaded.
+ */
+void VG_(resolve_seg_redirs)(SegInfo *si)
+{
+ CodeRedirect **prevp = &unresolved_redir;
+ CodeRedirect *redir, *next;
+
+ if (VG_(clo_trace_redir))
+ VG_(printf)("Considering redirs to/from %s(soname=%s)\n",
+ si->filename, si->soname);
+
+ /* visit each unresolved redir - if it becomes resolved, then
+ remove it from the unresolved list */
+ for(redir = unresolved_redir; redir != NULL; redir = next) {
+ next = redir->next;
+
+ if (resolve_redir(redir, si)) {
+ *prevp = next;
+ redir->next = NULL;
+ } else
+ prevp = &redir->next;
+ }
+}
+
+/* Redirect a lib/symbol reference to a function at lib/symbol */
+static void add_redirect_sym_to_sym(const Char *from_lib, const Char *from_sym,
+ const Char *to_lib, const Char *to_sym)
+{
+ CodeRedirect *redir = VG_(SkipNode_Alloc)(&sk_resolved_redir);
+
+ redir->type = R_REDIRECT;
+
+ redir->from_lib = VG_(arena_strdup)(VG_AR_SYMTAB, from_lib);
+ redir->from_sym = VG_(arena_strdup)(VG_AR_SYMTAB, from_sym);
+ redir->from_addr = 0;
+
+ redir->to_lib = VG_(arena_strdup)(VG_AR_SYMTAB, to_lib);
+ redir->to_sym = VG_(arena_strdup)(VG_AR_SYMTAB, to_sym);
+ redir->to_addr = 0;
+
+ if (VG_(clo_verbosity) >= 2 && VG_(clo_trace_redir))
+ VG_(message)(Vg_UserMsg,
+ "REDIRECT %s(%s) to %s(%s)",
+ from_lib, from_sym, to_lib, to_sym);
+
+ /* Check against all existing segments to see if this redirection
+ can be resolved immediately */
+ if (!resolve_redir_allsegs(redir)) {
+ /* nope, add to list */
+ redir->next = unresolved_redir;
+ unresolved_redir = redir;
+ }
+}
+
+/* Redirect a lib/symbol reference to a function at addr */
+void VG_(add_redirect_sym_to_addr)(const Char *from_lib, const Char *from_sym,
+ Addr to_addr)
+{
+ CodeRedirect *redir = VG_(SkipNode_Alloc)(&sk_resolved_redir);
+
+ redir->type = R_REDIRECT;
+
+ redir->from_lib = VG_(arena_strdup)(VG_AR_SYMTAB, from_lib);
+ redir->from_sym = VG_(arena_strdup)(VG_AR_SYMTAB, from_sym);
+ redir->from_addr = 0;
+
+ redir->to_lib = NULL;
+ redir->to_sym = NULL;
+ redir->to_addr = to_addr;
+
+ if (VG_(clo_verbosity) >= 2 && VG_(clo_trace_redir))
+ VG_(message)(Vg_UserMsg,
+ "REDIRECT %s(%s) to %p",
+ from_lib, from_sym, to_addr);
+
+ /* Check against all existing segments to see if this redirection
+ can be resolved immediately */
+ if (!resolve_redir_allsegs(redir)) {
+ /* nope, add to list */
+ redir->next = unresolved_redir;
+ unresolved_redir = redir;
+ }
+}
+
+/* Redirect a function at from_addr to a function at to_addr */
+void VG_(add_redirect_addr_to_addr)(Addr from_addr, Addr to_addr)
+{
+ CodeRedirect *redir = VG_(SkipNode_Alloc)(&sk_resolved_redir);
+
+ redir->type = R_REDIRECT;
+
+ redir->from_lib = NULL;
+ redir->from_sym = NULL;
+ redir->from_addr = from_addr;
+
+ redir->to_lib = NULL;
+ redir->to_sym = NULL;
+ redir->to_addr = to_addr;
+
+ if (VG_(clo_verbosity) >= 2 && VG_(clo_trace_redir))
+ VG_(message)(Vg_UserMsg,
+ "REDIRECT %p to %p",
+ from_addr, to_addr);
+
+ add_resolved(redir);
+}
+
+CodeRedirect *VG_(add_wrapper)(const Char *from_lib, const Char *from_sym,
+ const FuncWrapper *wrapper)
+{
+ CodeRedirect *redir = VG_(SkipNode_Alloc)(&sk_resolved_redir);
+
+ if (0)
+ VG_(printf)("adding wrapper for %s:%s -> (%p,%p)\n",
+ from_lib, from_sym, wrapper->before, wrapper->after);
+
+ redir->type = R_WRAPPER;
+
+ redir->from_lib = VG_(arena_strdup)(VG_AR_SYMTAB, from_lib);
+ redir->from_sym = VG_(arena_strdup)(VG_AR_SYMTAB, from_sym);
+ redir->from_addr = 0;
+
+ redir->to_lib = NULL;
+ redir->to_sym = NULL;
+ redir->to_addr = 0;
+
+ redir->wrapper = wrapper;
+
+ /* Check against all existing segments to see if this redirection
+ can be resolved immediately */
+ if (!resolve_redir_allsegs(redir)) {
+ /* nope, add to list */
+ redir->next = unresolved_redir;
+ unresolved_redir = redir;
+ }
+
+ return redir;
+}
+
+/* If address 'a' is being redirected, return the redirected-to
+ address. */
+Addr VG_(code_redirect)(Addr a)
+{
+ CodeRedirect* r;
+
+ r = VG_(SkipList_Find_Exact)(&sk_resolved_redir, &a);
+ if (r == NULL)
+ return a;
+
+ vg_assert(r->to_addr != 0);
+
+ return r->to_addr;
+}
+
+void VG_(setup_code_redirect_table) ( void )
+{
+ /* Overenthusiastic use of PLT bypassing by the glibc people also
+ means we need to patch the following functions to our own
+ implementations of said, in mac_replace_strmem.c.
+ */
+ add_redirect_sym_to_sym("soname:libc.so.6", "stpcpy",
+ "*vgpreload_memcheck.so*", "stpcpy");
+
+ add_redirect_sym_to_sym("soname:ld-linux.so.2", "strlen",
+ "*vgpreload_memcheck.so*", "strlen");
+ add_redirect_sym_to_sym("soname:libc.so.6", "strlen",
+ "*vgpreload_memcheck.so*", "strlen");
+
+ add_redirect_sym_to_sym("soname:libc.so.6", "strnlen",
+ "*vgpreload_memcheck.so*", "strnlen");
+
+ add_redirect_sym_to_sym("soname:ld-linux.so.2", "stpcpy",
+ "*vgpreload_memcheck.so*", "stpcpy");
+ add_redirect_sym_to_sym("soname:libc.so.6", "stpcpy",
+ "*vgpreload_memcheck.so*", "stpcpy");
+
+ add_redirect_sym_to_sym("soname:libc.so.6", "strchr",
+ "*vgpreload_memcheck.so*", "strchr");
+ add_redirect_sym_to_sym("soname:ld-linux.so.2", "strchr",
+ "*vgpreload_memcheck.so*", "strchr");
+
+ /* apparently index is the same thing as strchr */
+ add_redirect_sym_to_sym("soname:ld-linux.so.2", "index",
+ "*vgpreload_memcheck.so*", "strchr");
+
+ add_redirect_sym_to_sym("soname:libc.so.6", "strchrnul",
+ "*vgpreload_memcheck.so*", "glibc232_strchrnul");
+
+ add_redirect_sym_to_sym("soname:libc.so.6", "rawmemchr",
+ "*vgpreload_memcheck.so*", "glibc232_rawmemchr");
+
+ /* amd64-linux (glibc 2.3.3, SuSE 9.2) */
+ /* apparently index is the same thing as strchr */
+ add_redirect_sym_to_sym("soname:libc.so.6", "index",
+ "*vgpreload_memcheck.so*", "strchr");
+ add_redirect_sym_to_sym("soname:ld-linux-x86-64.so.2", "index",
+ "*vgpreload_memcheck.so*", "strchr");
+
+ add_redirect_sym_to_sym("soname:libc.so.6", "strcpy",
+ "*vgpreload_memcheck.so*", "strcpy");
+
+ add_redirect_sym_to_sym("soname:ld-linux-x86-64.so.2", "strcmp",
+ "*vgpreload_memcheck.so*", "strcmp");
+ add_redirect_sym_to_sym("soname:libc.so.6", "strcmp",
+ "*vgpreload_memcheck.so*", "strcmp");
+
+ add_redirect_sym_to_sym("soname:ld-linux-x86-64.so.2", "strlen",
+ "*vgpreload_memcheck.so*", "strlen");
+
+#if defined(VGP_x86_linux)
+ /* Redirect _dl_sysinfo_int80, which is glibc's default system call
+ routine, to the routine in our trampoline page so that the
+ special sysinfo unwind hack in m_stacktrace.c will kick in. */
+ VG_(add_redirect_sym_to_addr)("soname:ld-linux.so.2", "_dl_sysinfo_int80",
+ VG_(client_trampoline_code)+VG_(tramp_syscall_offset));
+#elif defined(VGP_amd64_linux)
+ /* Redirect vsyscalls to local versions */
+ VG_(add_redirect_addr_to_addr)(0xFFFFFFFFFF600000ULL,
+ VG_(client_trampoline_code)+VG_(tramp_gettimeofday_offset));
+ VG_(add_redirect_addr_to_addr)(0xFFFFFFFFFF600400ULL,
+ VG_(client_trampoline_code)+VG_(tramp_time_offset));
+#else
+# error Unknown platform
+#endif
+}
+
+//:: /*------------------------------------------------------------*/
+//:: /*--- General function wrapping. ---*/
+//:: /*------------------------------------------------------------*/
+//::
+//:: /*
+//:: TODO:
+//:: - hook into the symtab machinery
+//:: - client-side wrappers?
+//:: - better interfaces for before() functions to get to arguments
+//:: - handle munmap of code (dlclose())
+//:: - handle thread exit
+//:: - handle longjmp
+//:: */
+//:: struct callkey {
+//:: ThreadId tid; /* calling thread */
+//:: Addr esp; /* address of args on stack */
+//:: Addr eip; /* return address */
+//:: };
+//::
+//:: struct call_instance {
+//:: struct callkey key;
+//::
+//:: const FuncWrapper *wrapper;
+//:: void *nonce;
+//:: };
+//::
+//:: static inline Addr addrcmp(Addr a, Addr b)
+//:: {
+//:: if (a < b)
+//:: return -1;
+//:: else if (a > b)
+//:: return 1;
+//:: else
+//:: return 0;
+//:: }
+//::
+//:: static inline Int cmp(UInt a, UInt b)
+//:: {
+//:: if (a < b)
+//:: return -1;
+//:: else if (a > b)
+//:: return 1;
+//:: else
+//:: return 0;
+//:: }
+//::
+//:: static Int keycmp(const void *pa, const void *pb)
+//:: {
+//:: const struct callkey *a = (const struct callkey *)pa;
+//:: const struct callkey *b = (const struct callkey *)pb;
+//:: Int ret;
+//::
+//:: if ((ret = cmp(a->tid, b->tid)))
+//:: return ret;
+//::
+//:: if ((ret = addrcmp(a->esp, b->esp)))
+//:: return ret;
+//::
+//:: return addrcmp(a->eip, b->eip);
+//:: }
+//::
+//:: /* List of wrapped call invocations which are currently active */
+//:: static SkipList wrapped_frames = VG_SKIPLIST_INIT(struct call_instance, key, keycmp,
+//:: NULL, VG_AR_SYMTAB);
+//::
+//:: static struct call_instance *find_call(Addr retaddr, Addr argsp, ThreadId tid)
+//:: {
+//:: struct callkey key = { tid, argsp, retaddr };
+//::
+//:: return VG_(SkipList_Find_Exact)(&wrapped_frames, &key);
+//:: }
+//::
+//:: static void wrapper_return(Addr retaddr);
+//::
+//:: /* Called from generated code via helper */
+//:: void VG_(wrap_before)(ThreadState *tst, const FuncWrapper *wrapper)
+//:: {
+//:: Addr retaddr = VGA_RETADDR(tst->arch);
+//:: Addr argp = (Addr)&VGA_FUNC_ARG(tst->arch, 0);
+//:: void *nonce = NULL;
+//:: Bool mf = VG_(my_fault);
+//:: VG_(my_fault) = True;
+//::
+//:: if (wrapper->before) {
+//:: va_list args = VGA_VA_LIST(tst->arch);
+//:: nonce = (*wrapper->before)(args);
+//:: }
+//::
+//:: if (wrapper->after) {
+//:: /* If there's an after function, make sure it gets called */
+//:: struct call_instance *call;
+//::
+//:: call = find_call(retaddr, argp, tst->tid);
+//::
+//:: if (call != NULL) {
+//:: /* Found a stale outstanding call; clean it up and recycle
+//:: the structure */
+//:: if (call->wrapper->after)
+//:: (*call->wrapper->after)(call->nonce, RT_LONGJMP, 0);
+//:: } else {
+//:: call = VG_(SkipNode_Alloc)(&wrapped_frames);
+//::
+//:: call->key.tid = tst->tid;
+//:: call->key.esp = argp;
+//:: call->key.eip = retaddr;
+//::
+//:: VG_(SkipList_Insert)(&wrapped_frames, call);
+//::
+//:: wrapper_return(retaddr);
+//:: }
+//::
+//:: call->wrapper = wrapper;
+//:: call->nonce = nonce;
+//:: } else
+//:: vg_assert(nonce == NULL);
+//::
+//:: VG_(my_fault) = mf;
+//:: }
+//::
+//:: /* Called from generated code via helper */
+//:: void VG_(wrap_after)(ThreadState *tst)
+//:: {
+//:: Addr EIP = VGA_INSTR_PTR(tst->arch); /* instruction after call */
+//:: Addr ESP = VGA_STACK_PTR(tst->arch); /* pointer to args */
+//:: Word ret = VGA_RETVAL(tst->arch); /* return value */
+//:: struct call_instance *call;
+//:: Bool mf = VG_(my_fault);
+//::
+//:: VG_(my_fault) = True;
+//:: call = find_call(EIP, ESP, tst->tid);
+//::
+//:: if (0)
+//:: VG_(printf)("wrap_after(%p,%p,%d) -> %p\n", EIP, ESP, tst->tid, call);
+//::
+//:: if (call != NULL) {
+//:: if (call->wrapper->after)
+//:: (*call->wrapper->after)(call->nonce, RT_RETURN, ret);
+//::
+//:: VG_(SkipList_Remove)(&wrapped_frames, &call->key);
+//:: VG_(SkipNode_Free)(&wrapped_frames, call);
+//:: }
+//:: VG_(my_fault) = mf;
+//:: }
+//::
+//::
+//:: struct wrapped_function {
+//:: Addr eip; /* eip of function entrypoint */
+//:: const FuncWrapper *wrapper;
+//:: };
+//::
+//:: struct wrapper_return {
+//:: Addr eip; /* return address */
+//:: };
+//::
+//:: /* A mapping from eip of wrapped function entrypoints to actual wrappers */
+//:: static SkipList wrapped_functions = VG_SKIPLIST_INIT(struct wrapped_function, eip, VG_(cmp_Addr),
+//:: NULL, VG_AR_SYMTAB);
+//::
+//:: /* A set of EIPs which are return addresses for wrapped functions */
+//:: static SkipList wrapper_returns = VG_SKIPLIST_INIT(struct wrapper_return, eip, VG_(cmp_Addr),
+//:: NULL, VG_AR_SYMTAB);
+//::
+//:: /* Wrap function starting at eip */
+//:: void VG_(wrap_function)(Addr eip, const FuncWrapper *wrapper)
+//:: {
+//:: struct wrapped_function *func;
+//::
+//:: if (0)
+//:: VG_(printf)("wrapping %p with (%p,%p)\n", eip, wrapper->before, wrapper->after);
+//::
+//:: func = VG_(SkipList_Find_Exact)(&wrapped_functions, &eip);
+//::
+//:: if (func == NULL) {
+//:: func = VG_(SkipNode_Alloc)(&wrapped_functions);
+//:: VG_(invalidate_translations)(eip, 1, True);
+//::
+//:: func->eip = eip;
+//:: VG_(SkipList_Insert)(&wrapped_functions, func);
+//:: }
+//::
+//:: func->wrapper = wrapper;
+//:: }
+//::
+//:: const FuncWrapper *VG_(is_wrapped)(Addr eip)
+//:: {
+//:: struct wrapped_function *func = VG_(SkipList_Find_Exact)(&wrapped_functions, &eip);
+//::
+//:: if (func)
+//:: return func->wrapper;
+//:: return NULL;
+//:: }
+//::
+//:: Bool VG_(is_wrapper_return)(Addr eip)
+//:: {
+//:: struct wrapper_return *ret = VG_(SkipList_Find_Exact)(&wrapper_returns, &eip);
+//::
+//:: return ret != NULL;
+//:: }
+//::
+//:: /* Mark eip as being the return address of a wrapper, so that the
+//:: codegen will generate the appropriate call. */
+//:: void wrapper_return(Addr eip)
+//:: {
+//:: struct wrapper_return *ret;
+//::
+//:: if (VG_(is_wrapper_return)(eip))
+//:: return;
+//::
+//:: VG_(invalidate_translations)(eip, 1, True);
+//::
+//:: ret = VG_(SkipNode_Alloc)(&wrapper_returns);
+//:: ret->eip = eip;
+//::
+//:: VG_(SkipList_Insert)(&wrapper_returns, ret);
+//:: }