Add support for ELF indirect functions. These are symbols of
type STT_GNU_IFUNC which, instead of pointing directly at the
function, point at a routine which will return the address of
the real function. Redirection of indirect functions is handled
by valgrind as follows:
- When a redirection specification matches an indirect
function symbol an active redirection is added in the
normal way, but with the isIFunc flag set.
- When a call is made to an address which matches an
active redirection with the isIFunc flag set the call
is redirected, but not to the target address of the
redirection - instead it is sent to a small wrapper
routine that is preloaded into the client.
- The wrapper routine calls the original client routine
and collects the result, which it reports to valgrind
using a client request, and then returns the result to
the caller.
- When valgrind gets the client request it looks up the
active redirection for the indirect function and then
adds a new active redirection which redirects from the
address returned by the indirection function to the
redirection target. This new redirection does not have
the isIFunc flag set so behaves as a normal redirection.
In addition to the above we also add a few new redirections to
memcheck to capture internal calls made by glibc to things like
strlen, as these internal calls do not go through the indirect
function and instead go direct to the chosen implementation.
Based on a patch from Dodji Seketeli and comments from Jakub
Jelinek, this commit closes bug 206013.
git-svn-id: svn://svn.valgrind.org/valgrind/trunk@10920 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/coregrind/m_debuginfo/debuginfo.c b/coregrind/m_debuginfo/debuginfo.c
index eed2ab1..66ae82b 100644
--- a/coregrind/m_debuginfo/debuginfo.c
+++ b/coregrind/m_debuginfo/debuginfo.c
@@ -3435,14 +3435,16 @@
/*OUT*/Addr* tocptr,
/*OUT*/UInt* size,
/*OUT*/HChar** name,
- /*OUT*/Bool* isText )
+ /*OUT*/Bool* isText,
+ /*OUT*/Bool* isIFunc )
{
vg_assert(idx >= 0 && idx < si->symtab_used);
- if (avma) *avma = si->symtab[idx].addr;
- if (tocptr) *tocptr = si->symtab[idx].tocptr;
- if (size) *size = si->symtab[idx].size;
- if (name) *name = (HChar*)si->symtab[idx].name;
- if (isText) *isText = si->symtab[idx].isText;
+ if (avma) *avma = si->symtab[idx].addr;
+ if (tocptr) *tocptr = si->symtab[idx].tocptr;
+ if (size) *size = si->symtab[idx].size;
+ if (name) *name = (HChar*)si->symtab[idx].name;
+ if (isText) *isText = si->symtab[idx].isText;
+ if (isIFunc) *isIFunc = si->symtab[idx].isIFunc;
}
diff --git a/coregrind/m_debuginfo/priv_storage.h b/coregrind/m_debuginfo/priv_storage.h
index f6e6e82..cff91f7 100644
--- a/coregrind/m_debuginfo/priv_storage.h
+++ b/coregrind/m_debuginfo/priv_storage.h
@@ -48,15 +48,16 @@
/* A structure to hold an ELF/XCOFF symbol (very crudely). */
typedef
struct {
- Addr addr; /* lowest address of entity */
- Addr tocptr; /* ppc64-linux only: value that R2 should have */
- UChar *name; /* name */
+ Addr addr; /* lowest address of entity */
+ Addr tocptr; /* ppc64-linux only: value that R2 should have */
+ UChar *name; /* name */
// XXX: this could be shrunk (on 32-bit platforms) by using 31 bits for
// the size and 1 bit for the isText. If you do this, make sure that
// all assignments to isText use 0 or 1 (or True or False), and that a
// positive number larger than 1 is never used to represent True.
- UInt size; /* size in bytes */
+ UInt size; /* size in bytes */
Bool isText;
+ Bool isIFunc; /* symbol is an indirect function? */
}
DiSym;
diff --git a/coregrind/m_debuginfo/readelf.c b/coregrind/m_debuginfo/readelf.c
index 02cea02..4011852 100644
--- a/coregrind/m_debuginfo/readelf.c
+++ b/coregrind/m_debuginfo/readelf.c
@@ -214,7 +214,8 @@
used on entry */
Bool* from_opd_out, /* ppc64-linux only: did we deref an
.opd entry? */
- Bool* is_text_out /* is this a text symbol? */
+ Bool* is_text_out, /* is this a text symbol? */
+ Bool* is_ifunc /* is this a STT_GNU_IFUNC function ?*/
)
{
Bool plausible;
@@ -232,6 +233,7 @@
*sym_size_out = (Int)sym->st_size;
*sym_tocptr_out = 0; /* unknown/inapplicable */
*from_opd_out = False;
+ *is_ifunc = False;
/* Figure out if we're interested in the symbol. Firstly, is it of
the right flavour? */
@@ -243,6 +245,9 @@
&&
(ELFXX_ST_TYPE(sym->st_info) == STT_FUNC
|| ELFXX_ST_TYPE(sym->st_info) == STT_OBJECT
+#ifdef STT_GNU_IFUNC
+ || ELFXX_ST_TYPE(sym->st_info) == STT_GNU_IFUNC
+#endif
);
/* Work out the svma and bias for each section as it will appear in
@@ -325,6 +330,14 @@
*sym_avma_out += text_bias;
}
+# ifdef STT_GNU_IFUNC
+ /* Check for indirect functions. */
+ if (*is_text_out
+ && ELFXX_ST_TYPE(sym->st_info) == STT_GNU_IFUNC) {
+ *is_ifunc = True;
+ }
+# endif
+
# if defined(VGP_ppc64_linux)
/* Allow STT_NOTYPE in the very special case where we're running on
ppc64-linux and the symbol is one which the .opd-chasing hack
@@ -570,7 +583,7 @@
Char *sym_name, *sym_name_really;
Int sym_size;
Addr sym_tocptr;
- Bool from_opd, is_text;
+ Bool from_opd, is_text, is_ifunc;
DiSym risym;
ElfXX_Sym *sym;
@@ -602,13 +615,14 @@
&sym_avma_really,
&sym_size,
&sym_tocptr,
- &from_opd, &is_text)) {
+ &from_opd, &is_text, &is_ifunc)) {
- risym.addr = sym_avma_really;
- risym.size = sym_size;
- risym.name = ML_(addStr) ( di, sym_name_really, -1 );
- risym.tocptr = sym_tocptr;
- risym.isText = is_text;
+ risym.addr = sym_avma_really;
+ risym.size = sym_size;
+ risym.name = ML_(addStr) ( di, sym_name_really, -1 );
+ risym.tocptr = sym_tocptr;
+ risym.isText = is_text;
+ risym.isIFunc = is_ifunc;
vg_assert(risym.name != NULL);
vg_assert(risym.tocptr == 0); /* has no role except on ppc64-linux */
ML_(addSym) ( di, &risym );
@@ -646,6 +660,7 @@
Int size;
Bool from_opd;
Bool is_text;
+ Bool is_ifunc;
}
TempSym;
@@ -671,7 +686,7 @@
Char *sym_name, *sym_name_really;
Int sym_size;
Addr sym_tocptr;
- Bool from_opd, modify_size, modify_tocptr, is_text;
+ Bool from_opd, modify_size, modify_tocptr, is_text, is_ifunc;
DiSym risym;
ElfXX_Sym *sym;
OSet *oset;
@@ -713,7 +728,7 @@
&sym_avma_really,
&sym_size,
&sym_tocptr,
- &from_opd, &is_text)) {
+ &from_opd, &is_text, &is_ifunc)) {
/* Check if we've seen this (name,addr) key before. */
key.addr = sym_avma_really;
@@ -785,6 +800,7 @@
elem->size = sym_size;
elem->from_opd = from_opd;
elem->is_text = is_text;
+ elem->is_ifunc = is_ifunc;
VG_(OSetGen_Insert)(oset, elem);
if (di->trace_symtab) {
VG_(printf)(" to-oset [%4ld]: "
@@ -808,11 +824,12 @@
VG_(OSetGen_ResetIter)( oset );
while ( (elem = VG_(OSetGen_Next)(oset)) ) {
- risym.addr = elem->key.addr;
- risym.size = elem->size;
- risym.name = ML_(addStr) ( di, elem->key.name, -1 );
- risym.tocptr = elem->tocptr;
- risym.isText = elem->is_text;
+ risym.addr = elem->key.addr;
+ risym.size = elem->size;
+ risym.name = ML_(addStr) ( di, elem->key.name, -1 );
+ risym.tocptr = elem->tocptr;
+ risym.isText = elem->is_text;
+ risym.isIFunc = elem->is_ifunc;
vg_assert(risym.name != NULL);
ML_(addSym) ( di, &risym );
diff --git a/coregrind/m_redir.c b/coregrind/m_redir.c
index 98a502f..d4cab7e 100644
--- a/coregrind/m_redir.c
+++ b/coregrind/m_redir.c
@@ -268,12 +268,15 @@
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 ---*/
@@ -350,8 +353,8 @@
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 );
+ 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 */
@@ -388,8 +391,8 @@
if (check_ppcTOCs) {
for (i = 0; i < nsyms; i++) {
- VG_(DebugInfo_syms_getidx)( newsi, i, &sym_addr, &sym_toc,
- NULL, &sym_name, &isText );
+ 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,
@@ -470,6 +473,30 @@
#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
@@ -487,7 +514,7 @@
)
{
Spec* sp;
- Bool anyMark, isText;
+ Bool anyMark, isText, isIFunc;
Active act;
Int nsyms, i;
Addr sym_addr;
@@ -513,7 +540,7 @@
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 );
+ &sym_name, &isText, &isIFunc );
/* ignore data symbols */
if (!isText)
@@ -539,6 +566,7 @@
act.parent_spec = parent_spec;
act.parent_sym = parent_sym;
act.isWrap = sp->isWrap;
+ act.isIFunc = isIFunc;
sp->done = True;
maybe_add_active( act );
}
@@ -780,7 +808,9 @@
vg_assert(r->to_addr != 0);
if (isWrap)
- *isWrap = r->isWrap;
+ *isWrap = r->isWrap || r->isIFunc;
+ if (r->isIFunc)
+ return iFuncWrapper;
return r->to_addr;
}
@@ -1096,6 +1126,8 @@
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);
}
diff --git a/coregrind/m_scheduler/scheduler.c b/coregrind/m_scheduler/scheduler.c
index 295e0ab..c10456c 100644
--- a/coregrind/m_scheduler/scheduler.c
+++ b/coregrind/m_scheduler/scheduler.c
@@ -89,6 +89,7 @@
#include "pub_core_debuginfo.h" // VG_(di_notify_pdb_debuginfo)
#include "priv_sema.h"
#include "pub_core_scheduler.h" // self
+#include "pub_core_redir.h"
/* ---------------------------------------------------------------------
@@ -1399,6 +1400,11 @@
SET_CLREQ_RETVAL( tid, count );
break; }
+ case VG_USERREQ__ADD_IFUNC_TARGET: {
+ VG_(redir_add_ifunc_target)( arg[1], arg[2] );
+ SET_CLREQ_RETVAL( tid, 0);
+ break; }
+
case VG_USERREQ__PRINTF_BACKTRACE: {
Int count =
VG_(vmessage)( Vg_ClientMsg, (char *)arg[1], (void*)arg[2] );
diff --git a/coregrind/pub_core_clreq.h b/coregrind/pub_core_clreq.h
index 563904b..412f89e 100644
--- a/coregrind/pub_core_clreq.h
+++ b/coregrind/pub_core_clreq.h
@@ -50,6 +50,9 @@
/* Internal equivalent of VALGRIND_PRINTF . */
VG_USERREQ__INTERNAL_PRINTF = 0x3103,
+ /* Add a target for an indirect function redirection. */
+ VG_USERREQ__ADD_IFUNC_TARGET = 0x3104,
+
} Vg_InternalClientRequest;
// Function for printing from code within Valgrind, but which runs on the
diff --git a/coregrind/pub_core_redir.h b/coregrind/pub_core_redir.h
index c993c27..c931d85 100644
--- a/coregrind/pub_core_redir.h
+++ b/coregrind/pub_core_redir.h
@@ -58,6 +58,8 @@
/* Initialise the module, and load initial "hardwired" redirects. */
extern void VG_(redir_initialise)( void );
+/* Notify the module of a new target for an indirect function. */
+extern void VG_(redir_add_ifunc_target)( Addr old_from, Addr new_from );
//--------------------------------------------------------------------
// Queries
diff --git a/coregrind/vg_preloaded.c b/coregrind/vg_preloaded.c
index 6f0f049..1500e80 100644
--- a/coregrind/vg_preloaded.c
+++ b/coregrind/vg_preloaded.c
@@ -47,12 +47,12 @@
#include "pub_core_debuginfo.h" // Needed for pub_core_redir.h
#include "pub_core_redir.h" // For VG_NOTIFY_ON_LOAD
+#if defined(VGO_linux) || defined(VGO_aix5)
+
/* ---------------------------------------------------------------------
Hook for running __libc_freeres once the program exits.
------------------------------------------------------------------ */
-#if defined(VGO_linux) || defined(VGO_aix5)
-
void VG_NOTIFY_ON_LOAD(freeres)( void );
void VG_NOTIFY_ON_LOAD(freeres)( void )
{
@@ -68,6 +68,31 @@
*(int *)0 = 'x';
}
+/* ---------------------------------------------------------------------
+ Wrapper for indirect functions which need to be redirected.
+ ------------------------------------------------------------------ */
+
+void * VG_NOTIFY_ON_LOAD(ifunc_wrapper) (void);
+void * VG_NOTIFY_ON_LOAD(ifunc_wrapper) (void)
+{
+ OrigFn fn;
+ Addr result = 0;
+ int res;
+
+ /* Call the original indirect function and get it's result */
+ VALGRIND_GET_ORIG_FN(fn);
+ CALL_FN_W_v(result, fn);
+
+ /* Ask the valgrind core running on the real CPU (as opposed to this
+ code which runs on the emulated CPU) to update the redirection that
+ led to this function. This client request eventually gives control to
+ the function VG_(redir_add_ifunc_target) in m_redir.c */
+ VALGRIND_DO_CLIENT_REQUEST(res, 0,
+ VG_USERREQ__ADD_IFUNC_TARGET,
+ fn.nraddr, result, 0, 0, 0);
+ return result;
+}
+
#elif defined(VGO_darwin)
/* ---------------------------------------------------------------------
diff --git a/include/pub_tool_debuginfo.h b/include/pub_tool_debuginfo.h
index a02b790..6b5acd7 100644
--- a/include/pub_tool_debuginfo.h
+++ b/include/pub_tool_debuginfo.h
@@ -212,7 +212,8 @@
/*OUT*/Addr* tocptr,
/*OUT*/UInt* size,
/*OUT*/HChar** name,
- /*OUT*/Bool* isText );
+ /*OUT*/Bool* isText,
+ /*OUT*/Bool* isIFunc );
/* A simple enumeration to describe the 'kind' of various kinds of
segments that arise from the mapping of object files. */
diff --git a/memcheck/mc_replace_strmem.c b/memcheck/mc_replace_strmem.c
index c15717a..abd838f 100644
--- a/memcheck/mc_replace_strmem.c
+++ b/memcheck/mc_replace_strmem.c
@@ -116,6 +116,7 @@
STRRCHR(VG_Z_LIBC_SONAME, strrchr)
STRRCHR(VG_Z_LIBC_SONAME, rindex)
#if defined(VGO_linux)
+STRRCHR(VG_Z_LIBC_SONAME, __GI_strrchr)
STRRCHR(VG_Z_LD_LINUX_SO_2, rindex)
#elif defined(VGO_darwin)
STRRCHR(VG_Z_DYLD, strrchr)
@@ -140,6 +141,7 @@
STRCHR(VG_Z_LIBC_SONAME, strchr)
STRCHR(VG_Z_LIBC_SONAME, index)
#if defined(VGO_linux)
+STRCHR(VG_Z_LIBC_SONAME, __GI_strchr)
STRCHR(VG_Z_LD_LINUX_SO_2, strchr)
STRCHR(VG_Z_LD_LINUX_SO_2, index)
STRCHR(VG_Z_LD_LINUX_X86_64_SO_2, strchr)
@@ -172,7 +174,9 @@
}
STRCAT(VG_Z_LIBC_SONAME, strcat)
-
+#if defined(VGO_linux)
+STRCAT(VG_Z_LIBC_SONAME, __GI_strcat)
+#endif
#define STRNCAT(soname, fnname) \
char* VG_REPLACE_FUNCTION_ZU(soname,fnname) \
@@ -257,6 +261,9 @@
}
STRNLEN(VG_Z_LIBC_SONAME, strnlen)
+#if defined(VGO_linux)
+STRNLEN(VG_Z_LIBC_SONAME, __GI_strnlen)
+#endif
// Note that this replacement often doesn't get used because gcc inlines
@@ -274,6 +281,7 @@
STRLEN(VG_Z_LIBC_SONAME, strlen)
#if defined(VGO_linux)
+STRLEN(VG_Z_LIBC_SONAME, __GI_strlen)
STRLEN(VG_Z_LD_LINUX_SO_2, strlen)
STRLEN(VG_Z_LD_LINUX_X86_64_SO_2, strlen)
#endif
@@ -301,7 +309,9 @@
}
STRCPY(VG_Z_LIBC_SONAME, strcpy)
-#if defined(VGO_darwin)
+#if defined(VGO_linux)
+STRCPY(VG_Z_LIBC_SONAME, __GI_strcpy)
+#elif defined(VGO_darwin)
STRCPY(VG_Z_DYLD, strcpy)
#endif
@@ -327,7 +337,9 @@
}
STRNCPY(VG_Z_LIBC_SONAME, strncpy)
-#if defined(VGO_darwin)
+#if defined(VGO_linux)
+STRNCPY(VG_Z_LIBC_SONAME, __GI_strncpy)
+#elif defined(VGO_darwin)
STRNCPY(VG_Z_DYLD, strncpy)
#endif
@@ -384,7 +396,9 @@
}
STRNCMP(VG_Z_LIBC_SONAME, strncmp)
-#if defined(VGO_darwin)
+#if defined(VGO_linux)
+STRNCMP(VG_Z_LIBC_SONAME, __GI_strncmp)
+#elif defined(VGO_darwin)
STRNCMP(VG_Z_DYLD, strncmp)
#endif
@@ -411,6 +425,7 @@
STRCMP(VG_Z_LIBC_SONAME, strcmp)
#if defined(VGO_linux)
+STRCMP(VG_Z_LIBC_SONAME, __GI_strcmp)
STRCMP(VG_Z_LD_LINUX_X86_64_SO_2, strcmp)
STRCMP(VG_Z_LD64_SO_1, strcmp)
#endif
@@ -557,6 +572,7 @@
STPCPY(VG_Z_LIBC_SONAME, stpcpy)
#if defined(VGO_linux)
+STPCPY(VG_Z_LIBC_SONAME, __GI_stpcpy)
STPCPY(VG_Z_LD_LINUX_SO_2, stpcpy)
STPCPY(VG_Z_LD_LINUX_X86_64_SO_2, stpcpy)
#elif defined(VGO_darwin)
@@ -709,7 +725,9 @@
}
GLIBC232_RAWMEMCHR(VG_Z_LIBC_SONAME, rawmemchr)
-
+#if defined (VGO_linux)
+GLIBC232_RAWMEMCHR(VG_Z_LIBC_SONAME, __GI___rawmemchr)
+#endif
/* glibc variant of strcpy that checks the dest is big enough.
Copied from glibc-2.5/debug/test-strcpy_chk.c. */