From: Tom Hughes Date: Thu, 29 Oct 2009 09:27:11 +0000 (+0000) Subject: Add support for ELF indirect functions. These are symbols of X-Git-Tag: svn/VALGRIND_3_6_0~489 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=00d987f91a612de5a56055d1bdb7f2cce9e1c4a8;p=thirdparty%2Fvalgrind.git 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 --- diff --git a/coregrind/m_debuginfo/debuginfo.c b/coregrind/m_debuginfo/debuginfo.c index eed2ab1f46..66ae82b702 100644 --- a/coregrind/m_debuginfo/debuginfo.c +++ b/coregrind/m_debuginfo/debuginfo.c @@ -3435,14 +3435,16 @@ void VG_(DebugInfo_syms_getidx) ( const DebugInfo *si, /*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 f6e6e82b02..cff91f7d14 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 02cea02bb6..4011852c63 100644 --- a/coregrind/m_debuginfo/readelf.c +++ b/coregrind/m_debuginfo/readelf.c @@ -214,7 +214,8 @@ Bool get_elf_symbol_info ( 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 @@ Bool get_elf_symbol_info ( *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 @@ Bool get_elf_symbol_info ( && (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 @@ Bool get_elf_symbol_info ( *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 @@ void read_elf_symtab__normal( 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 @@ void read_elf_symtab__normal( &sym_avma_really, &sym_size, &sym_tocptr, - &from_opd, &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; + &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.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 @@ typedef Int size; Bool from_opd; Bool is_text; + Bool is_ifunc; } TempSym; @@ -671,7 +686,7 @@ void read_elf_symtab__ppc64_linux( 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 @@ void read_elf_symtab__ppc64_linux( &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 @@ void read_elf_symtab__ppc64_linux( 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 @@ void read_elf_symtab__ppc64_linux( 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 98a502fe76..d4cab7efbb 100644 --- a/coregrind/m_redir.c +++ b/coregrind/m_redir.c @@ -268,12 +268,15 @@ typedef 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 @@ void VG_(redir_notify_new_DebugInfo)( DebugInfo* newsi ) 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 @@ void VG_(redir_notify_new_DebugInfo)( DebugInfo* newsi ) 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 @@ void VG_(redir_notify_new_DebugInfo)( DebugInfo* 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 @@ -487,7 +514,7 @@ void generate_and_add_actives ( ) { Spec* sp; - Bool anyMark, isText; + Bool anyMark, isText, isIFunc; Active act; Int nsyms, i; Addr sym_addr; @@ -513,7 +540,7 @@ void generate_and_add_actives ( 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 @@ void generate_and_add_actives ( 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 @@ Addr VG_(redir_do_lookup) ( Addr orig, Bool* isWrap ) 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 @@ void handle_maybe_load_notifier( const UChar* soname, 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 295e0ab03d..c10456c5f6 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 @@ void do_client_request ( ThreadId tid ) 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 563904bdfe..412f89e1cf 100644 --- a/coregrind/pub_core_clreq.h +++ b/coregrind/pub_core_clreq.h @@ -50,6 +50,9 @@ typedef /* 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 c993c27277..c931d85fcc 100644 --- a/coregrind/pub_core_redir.h +++ b/coregrind/pub_core_redir.h @@ -58,6 +58,8 @@ extern void VG_(redir_notify_delete_DebugInfo)( DebugInfo* ); /* 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 6f0f04916d..1500e80525 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 @@ void VG_NOTIFY_ON_LOAD(freeres)( void ) *(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 a02b79015d..6b5acd7ece 100644 --- a/include/pub_tool_debuginfo.h +++ b/include/pub_tool_debuginfo.h @@ -212,7 +212,8 @@ void VG_(DebugInfo_syms_getidx) ( const DebugInfo *di, /*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 c15717adbc..abd838f9c2 100644 --- a/memcheck/mc_replace_strmem.c +++ b/memcheck/mc_replace_strmem.c @@ -116,6 +116,7 @@ Bool is_overlap ( void* dst, const void* src, SizeT dstlen, SizeT srclen ) 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 @@ STRRCHR(VG_Z_DYLD, rindex) 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 @@ STRCHR(VG_Z_DYLD, index) } 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 @@ STRLCAT(VG_Z_DYLD, strlcat) } 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 @@ STRNLEN(VG_Z_LIBC_SONAME, strnlen) 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 @@ STRLEN(VG_Z_LD_LINUX_X86_64_SO_2, strlen) } 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 @@ STRCPY(VG_Z_DYLD, strcpy) } 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 @@ STRLCPY(VG_Z_DYLD, strlcpy) } 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 @@ STRNCMP(VG_Z_DYLD, strncmp) 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 @@ MEMCMP(VG_Z_DYLD, bcmp) 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_STRCHRNUL(VG_Z_LIBC_SONAME, strchrnul) } 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. */