This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[patch] STT_GNU_IFUNC support


Hi,

recent systems (at least Fedora 12; Nov 2009) using STT_GNU_IFUNC have
regressed FSF GDB (incl. HEAD):

(gdb) p strcmp ("a", "a")
$1 = 162645472
(gdb) disass strcmp
Dump of assembler code for function strcmp:
   0x0000003009a7d870 <+0>:	cmpl   $0x0,0x2fa9a9(%rip)        # 0x3009d78220 <__cpu_features>
   0x0000003009a7d877 <+7>:	jne    0x3009a7d87e <strcmp+14>
   0x0000003009a7d879 <+9>:	callq  0x3009a1ee60 <__init_cpu_features>
...
  7016: 0000003009a7d870    60 IFUNC   GLOBAL DEFAULT   12 strcmp
  1242: 0000003009b1c5e0  3737 FUNC    LOCAL  DEFAULT   12 __strcmp_sse42
  1243: 0000003009a7d8b0  5177 FUNC    LOCAL  DEFAULT   12 __strcmp_sse2
  5114: 0000003009b23800  4697 FUNC    LOCAL  DEFAULT   12 __strcmp_ssse3

Some performance-critical functions are now just returning pointer to the real
function to execute; it gets transparently resolved by ld.so.

------------------------------------------------------------------------------

STT_GNU_IFUNC

      This symbol type is the same as STT_FUNC except that it always
      points to a function or piece of executable code which takes no
      arguments and returns a function pointer.  If an STT_GNU_IFUNC
      symbol is referred to by a relocation, then evaluation of that
      relocation is delayed until load-time.  The value used in the
      relocation is the function pointer returned by an invocation
      of the STT_GNU_IFUNC symbol.

      The purpose of this symbol type is to allow the run-time to
      select between multiple versions of the implementation of a
      specific function.  The selection made in general will take the
      currently available hardware into account and select the most
      appropriate version.

STT_GNU_IFUNC is defined in OS-specific range:

#define STT_LOOS        10      /* OS-specific semantics */
#define STT_GNU_IFUNC   10      /* Symbol is an indirect code object */
#define STT_HIOS        12      /* OS-specific semantics */

STT_GNU_IFUNC symbols can be in shared object, dynamic executable and
static executable.  DT_GNU_XXX tags are used to mark the indirect
relocation table for relocations against locally defined STT_GNU_IFUNC
symbols.

------------------------------------------------------------------------------

This implementation resolves ifunc in an uncached way by an inferior call any
time the symbol resolution is needed.  It does not try to pick out the jump
address value from ".got.plt" as filled there by ld.so.  (GDB stepping into
library functions also does not try to do so.)

As I find gnu-ifunc functions to be mostly the same kind of indirection as the
ppc64-and-others function descriptors I have hooked it at the same GDB
infrastructure.  This makes it a bit problematic to find out the real gnu-ifunc
resolver address but regular users should not be interested in it and one can
query it using "info addr" (the same already applies to function descriptors).

# With running (stopped) inferior to be able to call the gnu-ifunc resolvers.
(gdb) p strcmp
$1 = {<text variable, no debug info>} 0x3009b1c5e0 <__strcmp_sse42>
(gdb) info addr strcmp
Symbol "strcmp" is at 0x3009a7d870 in a file compiled without debugging.

# Without started inferior thus unable to call the gnu-ifunc resolvers.
$ ./gdb -nx /lib64/libc.so.6
(gdb) p strcmp
$1 = {<text gnu-ifunc variable, no debug info>} 0x3009a7d870 <strcmp>


Due to the function descriptors similarity I had to hook it into the OSABI
framework which does not support any inheritance.  Single OSABI is chosen.
gnu-ifunc is only OS-dependent (GNU) and not arch dependent, though.
Therefore gnu-ifunc had to be hooked into all the OS-ARCH combinations where
the GNU OS gets installed as OSABI (=> *-linux-tdep.c).


As other GNU tools already support STT_GNU_IFUNC in stable releases it would be
nice for gdb-7.1 but I understand it is probably too late:

	binutils supports IFUNC since (first of the check-ins):
	commit 493c5fa01eebba9c31de96d60fcd1fbfea604f81
	Author: Nick Clifton <nickc@redhat.com>
	Date:   Thu Apr 30 15:47:13 2009 +0000
	released version 2.19 - Oct 2009

	glibc has used it since (first of the check-ins):
	commit 425ce2edb9d11cc1ff650fac16dfbc450241896a
	Author: Ulrich Drepper <drepper@redhat.com>
	Date:   Fri Mar 13 23:53:18 2009 +0000
	released version 2.10 - May 2009



No regressions on {x86_64,x86_64-m32,i686}-fedora12-linux-gnu.  Testcase
PASSing tested on {x86_64,x86_64-m32,i686}-fedora12-linux-gnu and
{ppc64-m64,ppc64-m32,ppc}-fedora13pre-linux-gnu.  On older systems the new
testcase will fail with:
	Running ./gdb.base/gnu-ifunc.exp ...
	gdb compile failed, /tmp/ccuTiefA.s: Assembler messages:
	/tmp/ccuTiefA.s:63: Error: unrecognized symbol type "gnu_indirect_function"
	(=> an "untested" testcase)


Thanks,
Jan


gdb/
2010-02-14  Jan Kratochvil  <jan.kratochvil@redhat.com>

	Transparent STT_GNU_IFUNC support.
	* elfread.c (record_minimal_symbol): Apply also for mst_text_gnu_ifunc.
	(elf_symtab_read) <sym->flags & BSF_GNU_INDIRECT_FUNCTION>: New.
	* gdbtypes.c (init_type): Support TYPE_FLAG_GNU_IFUNC.
	(gdbtypes_post_init): Initialize builtin_func_func.
	(objfile_type): Initialize nodebug_text_gnu_ifunc_symbol.
	* gdbtypes.h (enum type_flag_value) <TYPE_FLAG_GNU_IFUNC>
	(TYPE_GNU_IFUNC)
	(struct main_type) <flag_gnu_ifunc>
	(struct builtin_type) <builtin_func_func>
	(struct objfile_type) <nodebug_text_gnu_ifunc_symbol>: New.
	* linux-tdep.c: Include value.h and infcall.h.
	(linux_convert_from_func_and_ptr, linux_convert_from_func_ptr_addr):
	New.
	* linux-tdep.h (linux_convert_from_func_and_ptr)
	(linux_convert_from_func_ptr_addr): New.
	* minsyms.c (lookup_minimal_symbol_text, prim_record_minimal_symbol)
	(find_solib_trampoline_target): Support also mst_text_gnu_ifunc.
	(in_gnu_ifunc_stub): New.
	* objc-lang.c (find_methods): Call gdbarch_convert_from_func_ptr_addr
	later.  New comment why.
	* parse.c (write_exp_msymbol): New variable ifunc_msym.  Support also
	mst_text_gnu_ifunc as the resolved entry point type.  Support also
	mst_text_gnu_ifunc for the write_exp_elt_type call.
	* solib-svr4.c (svr4_in_dynsym_resolve_code): Call also
	in_gnu_ifunc_stub.
	* symmisc.c (dump_msymbols) <mst_text_gnu_ifunc>: New.
	* symtab.c (search_symbols): Support also mst_text_gnu_ifunc.
	* symtab.h (enum minimal_symbol_type) <mst_text_gnu_ifunc>
	(in_gnu_ifunc_stub): New.

	* alpha-linux-tdep.c: Include linux-tdep.h.
	(alpha_linux_init_abi): Install linux_convert_from_func_ptr_addr.
	* amd64-linux-tdep.c (amd64_linux_init_abi): Install
	linux_convert_from_func_ptr_addr.
	* arm-linux-tdep.c (arm_linux_init_abi): Install
	linux_convert_from_func_ptr_addr.
	* configure.tgt (alpha*-*-linux*, am33_2.0*-*-linux*, ia64-*-linux*)
	(m32r*-*-linux*, microblaze*-linux-*, mips*-*-linux*, sh*-*-linux*)
	(sparc-*-linux*, sparc64-*-linux*, xtensa*-*-linux*): Add linux-tdep.o
	to gdb_target_obs.
	* frv-linux-tdep.c: Include linux-tdep.h.
	(frv_linux_convert_from_func_ptr_addr): New.
	(frv_linux_init_abi): Install it or linux_convert_from_func_ptr_addr.
	* frv-tdep.c (frv_convert_from_func_ptr_addr): Remove static qualifier.
	New comment.
	* frv-tdep.h (frv_convert_from_func_ptr_addr): New declaration.
	* hppa-linux-tdep.c: Include linux-tdep.h.
	(hppa32_linux_convert_from_func_ptr_addr): New.
	(hppa_linux_init_abi): Install it or linux_convert_from_func_ptr_addr.
	* hppa-tdep.c (hppa32_convert_from_func_ptr_addr): Remove static
	qualifier.  New comment.
	* hppa-tdep.h (hppa32_convert_from_func_ptr_addr): New declaration.
	* i386-linux-tdep.c (i386_linux_init_abi): Install
	linux_convert_from_func_ptr_addr.
	* ia64-linux-tdep.c: Include linux-tdep.h.
	(ia64_linux_init_abi): Install linux_convert_from_func_ptr_addr.
	* m32r-linux-tdep.c: Include linux-tdep.h.
	(m32r_linux_init_abi): Install linux_convert_from_func_ptr_addr.
	* microblaze-linux-tdep.c: Include linux-tdep.h.
	(microblaze_linux_init_abi): Install linux_convert_from_func_ptr_addr.
	* mips-linux-tdep.c: Include linux-tdep.h.
	(mips_linux_init_abi): Install linux_convert_from_func_ptr_addr.
	* mn10300-linux-tdep.c: Include linux-tdep.h.
	(am33_linux_init_osabi): Install linux_convert_from_func_ptr_addr.
	* ppc-linux-tdep.c: Include linux-tdep.h.
	(ppc64_linux_convert_from_func_ptr_addr): Rename to ...
	(convert_from_func_ptr_addr): ... this name.
	(ppc64_linux_convert_from_func_ptr_addr): New wrapper of it.
	(ppc_linux_init_abi) <tdep->wordsize == 4>: Install
	linux_convert_from_func_ptr_addr.
	* sh-linux-tdep.c: Include linux-tdep.h.
	(sh_linux_init_abi): Include linux_convert_from_func_ptr_addr.
	* sparc-linux-tdep.c: Include linux-tdep.h.
	(sparc32_linux_init_abi): Install linux_convert_from_func_ptr_addr.
	* sparc64-linux-tdep.c: Include linux-tdep.h.
	(sparc64_linux_init_abi): Install linux_convert_from_func_ptr_addr.
	* xtensa-linux-tdep.c: Include linux-tdep.h.
	(xtensa_linux_init_abi): Install linux_convert_from_func_ptr_addr.

gdb/testsuite/
2010-02-14  Jan Kratochvil  <jan.kratochvil@redhat.com>

	Transparent STT_GNU_IFUNC support.
	* gdb.base/gnu-ifunc-lib.c, gdb.base/gnu-ifunc.c,
	gdb.base/gnu-ifunc.exp: New.

--- a/gdb/elfread.c
+++ b/gdb/elfread.c
@@ -179,7 +179,8 @@ record_minimal_symbol (const char *name, int name_len, int copy_name,
 {
   struct gdbarch *gdbarch = get_objfile_arch (objfile);
 
-  if (ms_type == mst_text || ms_type == mst_file_text)
+  if (ms_type == mst_text || ms_type == mst_file_text
+      || ms_type == mst_text_gnu_ifunc)
     address = gdbarch_smash_text_address (gdbarch, address);
 
   return prim_record_minimal_symbol_full (name, name_len, copy_name, address,
@@ -388,7 +389,10 @@ elf_symtab_read (struct objfile *objfile, int type,
 	    {
 	      if (sym->flags & (BSF_GLOBAL | BSF_WEAK))
 		{
-		  ms_type = mst_text;
+		  if (sym->flags & BSF_GNU_INDIRECT_FUNCTION)
+		    ms_type = mst_text_gnu_ifunc;
+		  else
+		    ms_type = mst_text;
 		}
 	      else if ((sym->name[0] == '.' && sym->name[1] == 'L')
 		       || ((sym->flags & BSF_LOCAL)
--- a/gdb/gdbtypes.c
+++ b/gdb/gdbtypes.c
@@ -1791,6 +1791,8 @@ init_type (enum type_code code, int length, int flags,
     TYPE_NOTTEXT (type) = 1;
   if (flags & TYPE_FLAG_FIXED_INSTANCE)
     TYPE_FIXED_INSTANCE (type) = 1;
+  if (flags & TYPE_FLAG_GNU_IFUNC)
+    TYPE_GNU_IFUNC (type) = 1;
 
   if (name)
     TYPE_NAME (type) = obsavestring (name, strlen (name),
@@ -3475,6 +3477,8 @@ gdbtypes_post_init (struct gdbarch *gdbarch)
     = lookup_pointer_type (builtin_type->builtin_void);
   builtin_type->builtin_func_ptr
     = lookup_pointer_type (lookup_function_type (builtin_type->builtin_void));
+  builtin_type->builtin_func_func
+    = lookup_function_type (builtin_type->builtin_func_ptr);
 
   /* This type represents a GDB internal function.  */
   builtin_type->internal_fn
@@ -3588,6 +3592,11 @@ objfile_type (struct objfile *objfile)
 		 "<text variable, no debug info>", objfile);
   TYPE_TARGET_TYPE (objfile_type->nodebug_text_symbol)
     = objfile_type->builtin_int;
+  objfile_type->nodebug_text_gnu_ifunc_symbol
+    = init_type (TYPE_CODE_FUNC, 1, TYPE_FLAG_GNU_IFUNC,
+		 "<text gnu-ifunc variable, no debug info>", objfile);
+  TYPE_TARGET_TYPE (objfile_type->nodebug_text_gnu_ifunc_symbol)
+    = objfile_type->nodebug_text_symbol;
   objfile_type->nodebug_data_symbol
     = init_type (TYPE_CODE_INT,
 		 gdbarch_int_bit (gdbarch) / HOST_CHAR_BIT, 0,
--- a/gdb/gdbtypes.h
+++ b/gdb/gdbtypes.h
@@ -171,6 +171,7 @@ enum type_flag_value
   TYPE_FLAG_FIXED_INSTANCE = (1 << 15),
   TYPE_FLAG_STUB_SUPPORTED = (1 << 16),
   TYPE_FLAG_NOTTEXT = (1 << 17),
+  TYPE_FLAG_GNU_IFUNC = (1 << 18),
 
   /* Used for error-checking.  */
   TYPE_FLAG_MIN = TYPE_FLAG_UNSIGNED
@@ -271,6 +272,12 @@ enum type_instance_flag_value
 
 #define TYPE_NOTTEXT(t)		(TYPE_MAIN_TYPE (t)->flag_nottext)
 
+/* Used only for TYPE_CODE_FUNC where it specifies the real function
+   address is returned by this function call.  TYPE_TARGET_TYPE determines the
+   final returned function type to be presented to user.  */
+
+#define TYPE_GNU_IFUNC(t)	(TYPE_MAIN_TYPE (t)->flag_gnu_ifunc)
+
 /* Type owner.  If TYPE_OBJFILE_OWNED is true, the type is owned by
    the objfile retrieved as TYPE_OBJFILE.  Otherweise, the type is
    owned by an architecture; TYPE_OBJFILE is NULL in this case.  */
@@ -390,6 +397,7 @@ struct main_type
   unsigned int flag_vector : 1;
   unsigned int flag_stub_supported : 1;
   unsigned int flag_nottext : 1;
+  unsigned int flag_gnu_ifunc : 1;
   unsigned int flag_fixed_instance : 1;
   unsigned int flag_objfile_owned : 1;
   /* True if this type was declared with "class" rather than
@@ -1139,6 +1147,10 @@ struct builtin_type
      (*) () can server as a generic function pointer.  */
   struct type *builtin_func_ptr;
 
+  /* `function returning pointer to function (returning void)' type.
+     The final void return type is not significant for it.  */
+  struct type *builtin_func_func;
+
 
   /* Special-purpose types.  */
 
@@ -1179,6 +1191,7 @@ struct objfile_type
 
   /* Types used for symbols with no debug information.  */
   struct type *nodebug_text_symbol;
+  struct type *nodebug_text_gnu_ifunc_symbol;
   struct type *nodebug_data_symbol;
   struct type *nodebug_unknown_symbol;
   struct type *nodebug_tls_symbol;
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -23,6 +23,8 @@
 #include "auxv.h"
 #include "target.h"
 #include "elf/common.h"
+#include "value.h"
+#include "infcall.h"
 
 /* This function is suitable for architectures that don't
    extend/override the standard siginfo structure.  */
@@ -152,3 +154,71 @@ linux_has_shared_address_space (void)
 
   return target_is_uclinux;
 }
+
+/* Call gnu-ifunc (STT_GNU_IFUNC - a function returning addresss of a real
+   function to call) to resolve breakpoint at its returned function.  FUNC_PTR
+   is the function pointer (possibly function descriptor), PC is the function
+   entry.  Use linux_convert_from_func_ptr_addr if no function descriptors need
+   to be resolved on the platform.  FUNC_PTR is equal to PC on platforms
+   without function descriptors.  Function returns FUNC_PTR if no gnu-ifunc has
+   been found, otherwise it returns address of the gnu-ifunc-resolved function
+   to call.  Returned address is always the type of address possibly being
+   a function descriptor.  */
+
+CORE_ADDR
+linux_convert_from_func_and_ptr (struct gdbarch *gdbarch, CORE_ADDR func_ptr,
+                                 CORE_ADDR pc)
+{
+  struct type *func_func_type = builtin_type (gdbarch)->builtin_func_func;
+  struct minimal_symbol *msymbol;
+  struct value *function, *address;
+
+  if (!target_has_execution)
+    return func_ptr;
+
+  if (func_ptr != pc)
+    {
+      /* Verify FUNC_PTR is a function descriptor.  */
+
+      msymbol = lookup_minimal_symbol_by_pc (func_ptr);
+      if (msymbol == NULL)
+	return func_ptr;
+      if (MSYMBOL_TYPE (msymbol) != mst_data)
+	return func_ptr;
+      if (SYMBOL_VALUE_ADDRESS (msymbol) != func_ptr)
+	return func_ptr;
+    }
+
+  /* Verify PC is a function entry point.  */
+
+  msymbol = lookup_minimal_symbol_by_pc (pc);
+  if (msymbol == NULL)
+    return func_ptr;
+  if (MSYMBOL_TYPE (msymbol) != mst_text_gnu_ifunc)
+    return func_ptr;
+  if (SYMBOL_VALUE_ADDRESS (msymbol) != pc)
+    return func_ptr;
+
+  function = allocate_value (func_func_type);
+  set_value_address (function, pc);
+
+  /* gnu-ifuncs have no arguments.  FUNCTION is the code instruction address
+     while ADDRESS is a function descriptor.  */
+  address = call_function_by_hand (function, 0, NULL);
+
+  return value_as_address (address);
+}
+
+/* Call gnu-ifunc (STT_GNU_IFUNC - a function returning addresss of a real
+   function to call) to resolve breakpoint at its returned function.  Use
+   linux_convert_from_func_and_ptr if the platform requires also a function
+   descriptor resolution.  ADDR is address of the possibly-gnu-ifunc function.
+   Function returns ADDR if no gnu-ifunc has been found, otherwise it returns
+   address of the resolved function to call.  */
+
+CORE_ADDR
+linux_convert_from_func_ptr_addr (struct gdbarch *gdbarch, CORE_ADDR addr,
+				  struct target_ops *targ)
+{
+  return linux_convert_from_func_and_ptr (gdbarch, addr, addr);
+}
--- a/gdb/linux-tdep.h
+++ b/gdb/linux-tdep.h
@@ -22,4 +22,11 @@
 
 struct type *linux_get_siginfo_type (struct gdbarch *);
 
+CORE_ADDR linux_convert_from_func_and_ptr (struct gdbarch *gdbarch,
+					   CORE_ADDR func_ptr, CORE_ADDR pc);
+
+CORE_ADDR linux_convert_from_func_ptr_addr (struct gdbarch *gdbarch,
+					    CORE_ADDR addr,
+					    struct target_ops *targ);
+
 #endif /* linux-tdep.h */
--- a/gdb/minsyms.c
+++ b/gdb/minsyms.c
@@ -331,8 +331,9 @@ lookup_minimal_symbol_text (const char *name, struct objfile *objf)
 	       msymbol = msymbol->hash_next)
 	    {
 	      if (strcmp (SYMBOL_LINKAGE_NAME (msymbol), name) == 0 &&
-		  (MSYMBOL_TYPE (msymbol) == mst_text ||
-		   MSYMBOL_TYPE (msymbol) == mst_file_text))
+		  (MSYMBOL_TYPE (msymbol) == mst_text
+		   || MSYMBOL_TYPE (msymbol) == mst_text_gnu_ifunc
+		   || MSYMBOL_TYPE (msymbol) == mst_file_text))
 		{
 		  switch (MSYMBOL_TYPE (msymbol))
 		    {
@@ -694,6 +695,16 @@ lookup_minimal_symbol_by_pc (CORE_ADDR pc)
   return lookup_minimal_symbol_by_pc_section (pc, NULL);
 }
 
+/* Return non-zero iff PC is in function implementing gnu-ifunc selection.  */
+
+int
+in_gnu_ifunc_stub (CORE_ADDR pc)
+{
+  struct minimal_symbol *msymbol = lookup_minimal_symbol_by_pc (pc);
+
+  return msymbol && MSYMBOL_TYPE (msymbol) == mst_text_gnu_ifunc;
+}
+
 /* Find the minimal symbol named NAME, and return both the minsym
    struct and its objfile.  This only checks the linkage name.  Sets
    *OBJFILE_P and returns the minimal symbol, if it is found.  If it
@@ -763,6 +774,7 @@ prim_record_minimal_symbol (const char *name, CORE_ADDR address,
   switch (ms_type)
     {
     case mst_text:
+    case mst_text_gnu_ifunc:
     case mst_file_text:
     case mst_solib_trampoline:
       section = SECT_OFF_TEXT (objfile);
@@ -1228,7 +1240,8 @@ find_solib_trampoline_target (struct frame_info *frame, CORE_ADDR pc)
     {
       ALL_MSYMBOLS (objfile, msymbol)
       {
-	if (MSYMBOL_TYPE (msymbol) == mst_text
+	if ((MSYMBOL_TYPE (msymbol) == mst_text
+	    || MSYMBOL_TYPE (msymbol) == mst_text_gnu_ifunc)
 	    && strcmp (SYMBOL_LINKAGE_NAME (msymbol),
 		       SYMBOL_LINKAGE_NAME (tsymbol)) == 0)
 	  return SYMBOL_VALUE_ADDRESS (msymbol);
--- a/gdb/objc-lang.c
+++ b/gdb/objc-lang.c
@@ -1178,16 +1178,6 @@ find_methods (struct symtab *symtab, char type,
 
 	  QUIT;
 
-	  /* The minimal symbol might point to a function descriptor;
-	     resolve it to the actual code address instead.  */
-	  pc = gdbarch_convert_from_func_ptr_addr (gdbarch, pc,
-						   &current_target);
-
-	  if (symtab)
-	    if (pc < BLOCK_START (block) || pc >= BLOCK_END (block))
-	      /* Not in the specified symtab.  */
-	      continue;
-
 	  symname = SYMBOL_NATURAL_NAME (msymbol);
 	  if (symname == NULL)
 	    continue;
@@ -1223,6 +1213,17 @@ find_methods (struct symtab *symtab, char type,
 	      ((nselector == NULL) || (strcmp (selector, nselector) != 0)))
 	    continue;
 
+	  /* The minimal symbol might point to a function descriptor;
+	     resolve it to the actual code address instead.  Delay the call as
+	     its resolution may be expensive.  */
+	  pc = gdbarch_convert_from_func_ptr_addr (gdbarch, pc,
+						   &current_target);
+
+	  if (symtab)
+	    if (pc < BLOCK_START (block) || pc >= BLOCK_END (block))
+	      /* Not in the specified symtab.  */
+	      continue;
+
 	  sym = find_pc_function (pc);
 	  if (sym != NULL)
 	    {
--- a/gdb/parse.c
+++ b/gdb/parse.c
@@ -489,9 +489,21 @@ write_exp_msymbol (struct minimal_symbol *msymbol)
   pc = gdbarch_convert_from_func_ptr_addr (gdbarch, addr, &current_target);
   if (pc != addr)
     {
+      struct minimal_symbol *ifunc_msym = lookup_minimal_symbol_by_pc (pc);
+
       /* In this case, assume we have a code symbol instead of
 	 a data symbol.  */
-      type = mst_text;
+
+      if (ifunc_msym != NULL && MSYMBOL_TYPE (ifunc_msym) == mst_text_gnu_ifunc
+	  && SYMBOL_VALUE_ADDRESS (ifunc_msym) == pc)
+	{
+	  /* A function descriptor has been resolved but PC is still in the
+	     gnu-ifunc resolver body (such as because inferior does not run to
+	     be able to call it).  */
+	  type = mst_text_gnu_ifunc;
+	}
+      else
+	type = mst_text;
       section = NULL;
       addr = pc;
     }
@@ -523,6 +535,11 @@ write_exp_msymbol (struct minimal_symbol *msymbol)
       write_exp_elt_type (objfile_type (objfile)->nodebug_text_symbol);
       break;
 
+    case mst_text_gnu_ifunc:
+      write_exp_elt_type (objfile_type (objfile)
+					       ->nodebug_text_gnu_ifunc_symbol);
+      break;
+
     case mst_data:
     case mst_file_data:
     case mst_bss:
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -1235,7 +1235,8 @@ svr4_in_dynsym_resolve_code (CORE_ADDR pc)
 	   && pc < info->interp_text_sect_high)
 	  || (pc >= info->interp_plt_sect_low
 	      && pc < info->interp_plt_sect_high)
-	  || in_plt_section (pc, NULL));
+	  || in_plt_section (pc, NULL)
+	  || in_gnu_ifunc_stub (pc));
 }
 
 /* Given an executable's ABFD and target, compute the entry-point
--- a/gdb/symmisc.c
+++ b/gdb/symmisc.c
@@ -294,6 +294,9 @@ dump_msymbols (struct objfile *objfile, struct ui_file *outfile)
 	case mst_text:
 	  ms_type = 'T';
 	  break;
+	case mst_text_gnu_ifunc:
+	  ms_type = 'i';
+	  break;
 	case mst_solib_trampoline:
 	  ms_type = 'S';
 	  break;
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -3201,7 +3201,7 @@ search_symbols (char *regexp, domain_enum kind, int nfiles, char *files[],
   {mst_file_data, mst_solib_trampoline, mst_abs, mst_unknown};
   static enum minimal_symbol_type types4[]
   =
-  {mst_file_bss, mst_text, mst_abs, mst_unknown};
+  {mst_file_bss, mst_text_gnu_ifunc, mst_abs, mst_unknown};
   enum minimal_symbol_type ourtype;
   enum minimal_symbol_type ourtype2;
   enum minimal_symbol_type ourtype3;
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -280,6 +280,8 @@ enum minimal_symbol_type
 {
   mst_unknown = 0,		/* Unknown type, the default */
   mst_text,			/* Generally executable instructions */
+  mst_text_gnu_ifunc,		/* Executable code returning address
+				   of executable code */
   mst_data,			/* Generally initialized data */
   mst_bss,			/* Generally uninitialized data */
   mst_abs,			/* Generally absolute (nonrelocatable) */
@@ -1159,6 +1161,8 @@ extern struct minimal_symbol *lookup_minimal_symbol_by_pc_name
 
 extern struct minimal_symbol *lookup_minimal_symbol_by_pc (CORE_ADDR);
 
+extern int in_gnu_ifunc_stub (CORE_ADDR pc);
+
 extern struct minimal_symbol *
     lookup_minimal_symbol_and_objfile (const char *,
 				       struct objfile **);
--- a/gdb/alpha-linux-tdep.c
+++ b/gdb/alpha-linux-tdep.c
@@ -26,6 +26,7 @@
 #include "symtab.h"
 #include "regset.h"
 #include "regcache.h"
+#include "linux-tdep.h"
 
 #include "alpha-tdep.h"
 
@@ -236,6 +237,9 @@ alpha_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
   set_gdbarch_regset_from_core_section
     (gdbarch, alpha_linux_regset_from_core_section);
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -1481,6 +1481,9 @@ amd64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   amd64_linux_record_tdep.arg6 = AMD64_R9_REGNUM;
 
   tdep->i386_syscall_record = amd64_linux_syscall_record;
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 
--- a/gdb/arm-linux-tdep.c
+++ b/gdb/arm-linux-tdep.c
@@ -918,6 +918,9 @@ arm_linux_init_abi (struct gdbarch_info info,
   set_gdbarch_displaced_step_free_closure (gdbarch,
 					   simple_displaced_step_free_closure);
   set_gdbarch_displaced_step_location (gdbarch, displaced_step_at_entry_point);
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- a/gdb/configure.tgt
+++ b/gdb/configure.tgt
@@ -40,7 +40,7 @@ alpha*-*-osf*)
 alpha*-*-linux*)
 	# Target: Little-endian Alpha running Linux
 	gdb_target_obs="alpha-tdep.o alpha-mdebug-tdep.o alpha-linux-tdep.o \
-			solib.o solib-svr4.o"
+			solib.o solib-svr4.o linux-tdep.o"
 	;;
 alpha*-*-freebsd* | alpha*-*-kfreebsd*-gnu)
 	# Target: FreeBSD/alpha
@@ -67,7 +67,7 @@ alpha*-*-*)
 am33_2.0*-*-linux*)
 	# Target: Matsushita mn10300 (AM33) running Linux
 	gdb_target_obs="mn10300-tdep.o mn10300-linux-tdep.o corelow.o \
-			solib.o solib-svr4.o"
+			solib.o solib-svr4.o linux-tdep.o"
 	;;
 
 arm*-wince-pe | arm*-*-mingw32ce*)
@@ -233,7 +233,7 @@ i[34567]86-*-*)
 
 ia64-*-linux*)
 	# Target: Intel IA-64 running GNU/Linux
-	gdb_target_obs="ia64-tdep.o ia64-linux-tdep.o \
+	gdb_target_obs="ia64-tdep.o ia64-linux-tdep.o linux-tdep.o \
 			solib.o solib-svr4.o symfile-mem.o"
 	build_gdbserver=yes
 	;;
@@ -263,7 +263,8 @@ m32c-*-*)
 m32r*-*-linux*)
 	# Target: Renesas M32R running GNU/Linux
 	gdb_target_obs="m32r-tdep.o m32r-linux-tdep.o remote-m32r-sdi.o \
-			glibc-tdep.o solib.o solib-svr4.o symfile-mem.o"
+			glibc-tdep.o solib.o solib-svr4.o symfile-mem.o \
+			linux-tdep.o"
 	gdb_sim=../sim/m32r/libsim.a
 	build_gdbserver=yes
 	;;
@@ -317,7 +318,7 @@ microblaze*-linux-*)
 	# Target: Xilinx MicroBlaze running Linux
 	gdb_target_obs="microblaze-tdep.o microblaze-linux-tdep.o microblaze-rom.o \
 			monitor.o dsrec.o solib.o solib-svr4.o corelow.o \
-			symfile-mem.o"
+			symfile-mem.o linux-tdep.o"
 	gdb_sim=../sim/microblaze/libsim.a
 	;;
 microblaze*-xilinx-*)
@@ -337,7 +338,8 @@ mips*-sgi-irix6*)
 mips*-*-linux*)
 	# Target: Linux/MIPS
 	gdb_target_obs="mips-tdep.o mips-linux-tdep.o glibc-tdep.o \
-			corelow.o solib.o solib-svr4.o symfile-mem.o"
+			corelow.o solib.o solib-svr4.o symfile-mem.o \
+			linux-tdep.o"
 	gdb_sim=../sim/mips/libsim.a
 	build_gdbserver=yes
 	;;
@@ -427,7 +429,7 @@ sh*-*-linux*)
 	# Target: GNU/Linux Super-H
 	gdb_target_obs="sh-tdep.o sh64-tdep.o sh-linux-tdep.o monitor.o \
 			dsrec.o solib.o solib-svr4.o symfile-mem.o \
-			glibc-tdep.o corelow.o"
+			glibc-tdep.o corelow.o linux-tdep.o"
 	gdb_sim=../sim/sh/libsim.a
 	build_gdbserver=yes
 	;;
@@ -455,7 +457,8 @@ sh*)
 sparc-*-linux*)
 	# Target: GNU/Linux SPARC
 	gdb_target_obs="sparc-tdep.o sparc-sol2-tdep.o sol2-tdep.o \
-			sparc-linux-tdep.o solib.o solib-svr4.o symfile-mem.o"
+			sparc-linux-tdep.o solib.o solib-svr4.o symfile-mem.o \
+			linux-tdep.o"
 	if test "x$enable_64_bit_bfd" = "xyes"; then
 	    # Target: GNU/Linux UltraSPARC
 	    gdb_target_obs="sparc64-tdep.o sparc64-sol2-tdep.o \
@@ -466,7 +469,7 @@ sparc64-*-linux*)
 	# Target: GNU/Linux UltraSPARC
 	gdb_target_obs="sparc64-tdep.o sparc64-sol2-tdep.o sol2-tdep.o \
 			sparc64-linux-tdep.o sparc-tdep.o sparc-sol2-tdep.o \
-			sparc-linux-tdep.o solib.o solib-svr4.o"
+			sparc-linux-tdep.o solib.o solib-svr4.o linux-tdep.o"
 	build_gdbserver=yes
 	;;
 sparc*-*-freebsd* | sparc*-*-kfreebsd*-gnu)
@@ -601,7 +604,8 @@ x86_64-*-openbsd*)
 xtensa*-*-linux*)	gdb_target=linux
 	# Target: GNU/Linux Xtensa
 	gdb_target_obs="xtensa-tdep.o xtensa-config.o xtensa-linux-tdep.o \
-			solib.o solib-svr4.o corelow.o symfile-mem.o"
+			solib.o solib-svr4.o corelow.o symfile-mem.o \
+			linux-tdep.o"
 	build_gdbserver=yes
 	;;
 xtensa*)
--- a/gdb/frv-linux-tdep.c
+++ b/gdb/frv-linux-tdep.c
@@ -32,6 +32,7 @@
 #include "frame-unwind.h"
 #include "regset.h"
 #include "gdb_string.h"
+#include "linux-tdep.h"
 
 /* Define the size (in bytes) of an FR-V instruction.  */
 static const int frv_instr_size = 4;
@@ -486,7 +487,23 @@ frv_linux_regset_from_core_section (struct gdbarch *gdbarch,
   return NULL;
 }
 
-
+/* Resolve both gnu-ifunc function addresses and function descriptors.  */
+
+static CORE_ADDR
+frv_linux_convert_from_func_ptr_addr (struct gdbarch *gdbarch,
+				      CORE_ADDR addr,
+				      struct target_ops *targ)
+{
+  CORE_ADDR pc = frv_convert_from_func_ptr_addr (gdbarch, addr, targ);
+  CORE_ADDR resolved_addr;
+
+  resolved_addr = linux_convert_from_func_and_ptr (gdbarch, addr, pc);
+  if (resolved_addr != addr)
+    pc = frv_convert_from_func_ptr_addr (gdbarch, resolved_addr, targ);
+
+  return pc;
+}
+
 static void
 frv_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -494,6 +511,13 @@ frv_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   frame_unwind_append_unwinder (gdbarch, &frv_linux_sigtramp_frame_unwind); 
   set_gdbarch_regset_from_core_section (gdbarch,
                                         frv_linux_regset_from_core_section);
+
+  if (frv_abi (gdbarch) == FRV_ABI_FDPIC)
+    set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  frv_linux_convert_from_func_ptr_addr);
+  else
+    set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					    linux_convert_from_func_ptr_addr);
 }
 
 static enum gdb_osabi
--- a/gdb/frv-tdep.c
+++ b/gdb/frv-tdep.c
@@ -1169,7 +1169,10 @@ find_func_descr (struct gdbarch *gdbarch, CORE_ADDR entry_point)
   return descr;
 }
 
-static CORE_ADDR
+/* Resolve function descriptors.  Return ADDR if no function descriptor was
+   found.  */
+
+CORE_ADDR
 frv_convert_from_func_ptr_addr (struct gdbarch *gdbarch, CORE_ADDR addr,
                                 struct target_ops *targ)
 {
--- a/gdb/frv-tdep.h
+++ b/gdb/frv-tdep.h
@@ -118,3 +118,6 @@ CORE_ADDR frv_fetch_objfile_link_map (struct objfile *objfile);
 struct target_so_ops;
 extern struct target_so_ops frv_so_ops;
 
+CORE_ADDR frv_convert_from_func_ptr_addr (struct gdbarch *gdbarch,
+					  CORE_ADDR addr,
+					  struct target_ops *targ);
--- a/gdb/hppa-linux-tdep.c
+++ b/gdb/hppa-linux-tdep.c
@@ -32,6 +32,7 @@
 #include "regset.h"
 #include "regcache.h"
 #include "hppa-tdep.h"
+#include "linux-tdep.h"
 
 #include "elf/common.h"
 
@@ -513,7 +514,23 @@ hppa_linux_regset_from_core_section (struct gdbarch *gdbarch,
 
   return NULL;
 }
-
+
+/* Resolve both gnu-ifunc function addresses and function descriptors.  */
+
+static CORE_ADDR
+hppa32_linux_convert_from_func_ptr_addr (struct gdbarch *gdbarch,
+					 CORE_ADDR addr,
+					 struct target_ops *targ)
+{
+  CORE_ADDR pc = hppa32_convert_from_func_ptr_addr (gdbarch, addr, targ);
+  CORE_ADDR resolved_addr;
+
+  resolved_addr = linux_convert_from_func_and_ptr (gdbarch, addr, pc);
+  if (resolved_addr != addr)
+    pc = hppa32_convert_from_func_ptr_addr (gdbarch, resolved_addr, targ);
+
+  return pc;
+}
 
 /* Forward declarations.  */
 extern initialize_file_ftype _initialize_hppa_linux_tdep;
@@ -555,6 +572,13 @@ hppa_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   /* Enable TLS support.  */
   set_gdbarch_fetch_tls_load_module_address (gdbarch,
                                              svr4_fetch_objfile_link_map);
+
+  if (tdep->bytes_per_address == 4)
+    set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+				       hppa32_linux_convert_from_func_ptr_addr);
+  else
+    set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					    linux_convert_from_func_ptr_addr);
 }
 
 void
--- a/gdb/hppa-tdep.c
+++ b/gdb/hppa-tdep.c
@@ -1245,9 +1245,11 @@ hppa64_return_value (struct gdbarch *gdbarch, struct type *func_type,
 
   return RETURN_VALUE_REGISTER_CONVENTION;
 }
-
 
-static CORE_ADDR
+/* Resolve function descriptors.  Return ADDR if no function descriptor was
+   found.  */
+
+CORE_ADDR
 hppa32_convert_from_func_ptr_addr (struct gdbarch *gdbarch, CORE_ADDR addr,
 				   struct target_ops *targ)
 {
--- a/gdb/hppa-tdep.h
+++ b/gdb/hppa-tdep.h
@@ -246,4 +246,8 @@ extern int hppa_in_solib_call_trampoline (struct gdbarch *gdbarch,
 					  CORE_ADDR pc, char *name);
 extern CORE_ADDR hppa_skip_trampoline_code (struct frame_info *, CORE_ADDR pc);
 
+extern CORE_ADDR hppa32_convert_from_func_ptr_addr (struct gdbarch *gdbarch,
+						    CORE_ADDR addr,
+						    struct target_ops *targ);
+
 #endif  /* hppa-tdep.h */
--- a/gdb/i386-linux-tdep.c
+++ b/gdb/i386-linux-tdep.c
@@ -798,6 +798,9 @@ i386_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
                                   i386_linux_get_syscall_number);
 
   set_gdbarch_get_siginfo_type (gdbarch, linux_get_siginfo_type);
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- a/gdb/ia64-linux-tdep.c
+++ b/gdb/ia64-linux-tdep.c
@@ -26,6 +26,7 @@
 #include "osabi.h"
 #include "solib-svr4.h"
 #include "symtab.h"
+#include "linux-tdep.h"
 
 /* The sigtramp code is in a non-readable (executable-only) region
    of memory called the ``gate page''.  The addresses in question
@@ -139,6 +140,9 @@ ia64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   /* Enable TLS support.  */
   set_gdbarch_fetch_tls_load_module_address (gdbarch,
                                              svr4_fetch_objfile_link_map);
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- a/gdb/m32r-linux-tdep.c
+++ b/gdb/m32r-linux-tdep.c
@@ -30,6 +30,7 @@
 #include "gdb_string.h"
 
 #include "glibc-tdep.h"
+#include "linux-tdep.h"
 #include "solib-svr4.h"
 #include "symtab.h"
 
@@ -422,6 +423,9 @@ m32r_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   /* Enable TLS support.  */
   set_gdbarch_fetch_tls_load_module_address (gdbarch,
                                              svr4_fetch_objfile_link_map);
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- a/gdb/microblaze-linux-tdep.c
+++ b/gdb/microblaze-linux-tdep.c
@@ -35,6 +35,7 @@
 #include "trad-frame.h"
 #include "frame-unwind.h"
 #include "tramp-frame.h"
+#include "linux-tdep.h"
 
 
 static int
@@ -133,6 +134,9 @@ microblaze_linux_init_abi (struct gdbarch_info info,
   /* Trampolines.  */
   tramp_frame_prepend_unwinder (gdbarch,
 				&microblaze_linux_sighandler_tramp_frame);
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 void
--- a/gdb/mips-linux-tdep.c
+++ b/gdb/mips-linux-tdep.c
@@ -38,6 +38,7 @@
 #include "target-descriptions.h"
 #include "mips-linux-tdep.h"
 #include "glibc-tdep.h"
+#include "linux-tdep.h"
 
 static struct target_so_ops mips_svr4_so_ops;
 
@@ -1225,6 +1226,9 @@ mips_linux_init_abi (struct gdbarch_info info,
 	tdesc_numbered_register (feature, tdesc_data, MIPS_RESTART_REGNUM,
 				 "restart");
     }
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- a/gdb/mn10300-linux-tdep.c
+++ b/gdb/mn10300-linux-tdep.c
@@ -32,6 +32,7 @@
 #include "frame.h"
 #include "trad-frame.h"
 #include "tramp-frame.h"
+#include "linux-tdep.h"
 
 #include <stdlib.h>
 
@@ -718,6 +719,9 @@ am33_linux_init_osabi (struct gdbarch_info gdbinfo, struct gdbarch *gdbarch)
 
   tramp_frame_prepend_unwinder (gdbarch, &am33_linux_sigframe);
   tramp_frame_prepend_unwinder (gdbarch, &am33_linux_rt_sigframe);
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- a/gdb/ppc-linux-tdep.c
+++ b/gdb/ppc-linux-tdep.c
@@ -48,6 +48,7 @@
 #include "arch-utils.h"
 #include "spu-tdep.h"
 #include "xml-syscall.h"
+#include "linux-tdep.h"
 
 #include "features/rs6000/powerpc-32l.c"
 #include "features/rs6000/powerpc-altivec32l.c"
@@ -608,11 +609,7 @@ ppc64_skip_trampoline_code (struct frame_info *frame, CORE_ADDR pc)
   return target? target : pc;
 }
 
-
-/* Support for convert_from_func_ptr_addr (ARCH, ADDR, TARG) on PPC64
-   GNU/Linux.
-
-   Usually a function pointer's representation is simply the address
+/* Usually a function pointer's representation is simply the address
    of the function.  On GNU/Linux on the PowerPC however, a function
    pointer may be a pointer to a function descriptor.
 
@@ -636,9 +633,8 @@ ppc64_skip_trampoline_code (struct frame_info *frame, CORE_ADDR pc)
    random addresses such as occur when there is no symbol table.  */
 
 static CORE_ADDR
-ppc64_linux_convert_from_func_ptr_addr (struct gdbarch *gdbarch,
-					CORE_ADDR addr,
-					struct target_ops *targ)
+convert_from_func_ptr_addr (struct gdbarch *gdbarch, CORE_ADDR addr,
+			    struct target_ops *targ)
 {
   enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
   struct target_section *s = target_section_by_addr (targ, addr);
@@ -679,6 +675,27 @@ ppc64_linux_convert_from_func_ptr_addr (struct gdbarch *gdbarch,
   return addr;
 }
 
+/* Support for convert_from_func_ptr_addr (ARCH, ADDR, TARG) on PPC64
+   GNU/Linux.
+
+   Call convert_from_func_ptr_addr for resolving PPC64 function descriptors but
+   support also gnu-ifunc functions on the GNU/Linux platform.  */
+
+static CORE_ADDR
+ppc64_linux_convert_from_func_ptr_addr (struct gdbarch *gdbarch,
+					CORE_ADDR addr,
+					struct target_ops *targ)
+{
+  CORE_ADDR pc = convert_from_func_ptr_addr (gdbarch, addr, targ);
+  CORE_ADDR resolved_addr;
+
+  resolved_addr = linux_convert_from_func_and_ptr (gdbarch, addr, pc);
+  if (resolved_addr != addr)
+    pc = convert_from_func_ptr_addr (gdbarch, resolved_addr, targ);
+
+  return pc;
+}
+
 /* Wrappers to handle Linux-only registers.  */
 
 static void
@@ -1478,6 +1495,9 @@ ppc_linux_init_abi (struct gdbarch_info info,
 
   if (tdep->wordsize == 4)
     {
+      set_gdbarch_convert_from_func_ptr_addr
+	(gdbarch, linux_convert_from_func_ptr_addr);
+
       /* Until November 2001, gcc did not comply with the 32 bit SysV
 	 R4 ABI requirement that structures less than or equal to 8
 	 bytes should be returned in registers.  Instead GCC was using
--- a/gdb/sh-linux-tdep.c
+++ b/gdb/sh-linux-tdep.c
@@ -25,6 +25,7 @@
 
 #include "glibc-tdep.h"
 #include "sh-tdep.h"
+#include "linux-tdep.h"
 
 #define REGSx16(base) \
   {(base),      0}, \
@@ -89,6 +90,9 @@ sh_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
       tdep->core_gregmap = (struct sh_corefile_regmap *)gregs_table;
       tdep->core_fpregmap = (struct sh_corefile_regmap *)fpregs_table;
     }
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- a/gdb/sparc-linux-tdep.c
+++ b/gdb/sparc-linux-tdep.c
@@ -32,6 +32,7 @@
 #include "symtab.h"
 #include "trad-frame.h"
 #include "tramp-frame.h"
+#include "linux-tdep.h"
 
 #include "sparc-tdep.h"
 
@@ -279,6 +280,9 @@ sparc32_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   dwarf2_append_unwinders (gdbarch);
 
   set_gdbarch_write_pc (gdbarch, sparc_linux_write_pc);
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- a/gdb/sparc64-linux-tdep.c
+++ b/gdb/sparc64-linux-tdep.c
@@ -31,6 +31,7 @@
 #include "symtab.h"
 #include "trad-frame.h"
 #include "tramp-frame.h"
+#include "linux-tdep.h"
 
 #include "sparc64-tdep.h"
 
@@ -244,6 +245,9 @@ sparc64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   tdep->step_trap = sparc64_linux_step_trap;
 
   set_gdbarch_write_pc (gdbarch, sparc64_linux_write_pc);
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 
--- a/gdb/xtensa-linux-tdep.c
+++ b/gdb/xtensa-linux-tdep.c
@@ -22,6 +22,7 @@
 
 #include "solib-svr4.h"
 #include "symtab.h"
+#include "linux-tdep.h"
 
 /* OS specific initialization of gdbarch.  */
 
@@ -30,6 +31,9 @@ xtensa_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
   set_solib_svr4_fetch_link_map_offsets
     (gdbarch, svr4_ilp32_fetch_link_map_offsets);
+
+  set_gdbarch_convert_from_func_ptr_addr (gdbarch,
+					  linux_convert_from_func_ptr_addr);
 }
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gnu-ifunc-lib.c
@@ -0,0 +1,58 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2009, 2010 Free Software Foundation, Inc.
+
+   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 3 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, see <http://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+
+typedef int (*final_t) (int arg);
+
+static int
+init_stub (int arg)
+{
+  return 0;
+}
+
+static int
+final (int arg)
+{
+  return arg + 1;
+}
+
+/* Make differentiation of how the gnu_ifunc call resolves before and after
+   calling gnu_ifunc_pre.  This ensures the resolved function address is not
+   being cached anywhere for the debugging purposes.  */
+
+static volatile int gnu_ifunc_initialized;
+
+void
+gnu_ifunc_pre (void)
+{
+  assert (!gnu_ifunc_initialized);
+
+  gnu_ifunc_initialized = 1;
+}
+
+final_t gnu_ifuncX (void) asm ("gnu_ifunc");
+asm (".type gnu_ifunc, @gnu_indirect_function");
+
+final_t
+gnu_ifuncX (void)
+{
+  if (!gnu_ifunc_initialized)
+    return init_stub;
+  else
+    return final;
+}
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gnu-ifunc.c
@@ -0,0 +1,36 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2009, 2010 Free Software Foundation, Inc.
+
+   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 3 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, see <http://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+
+extern int gnu_ifunc (int arg);
+extern void gnu_ifunc_pre (void);
+
+int
+main (void)
+{
+  int i;
+
+  gnu_ifunc_pre ();
+  
+  i = gnu_ifunc (1);	/* break-at-call */
+  assert (i == 2);
+
+  gnu_ifunc (2);	/* break-at-nextcall */
+
+  return 0;	/* break-at-exit */
+}
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gnu-ifunc.exp
@@ -0,0 +1,132 @@
+# Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+if {[skip_shlib_tests]} {
+    return 0
+}
+
+set testfile "gnu-ifunc"
+set srcfile ${testfile}.c
+set binfile ${objdir}/${subdir}/${testfile}
+
+set libfile "${testfile}-lib"
+set libsrc ${libfile}.c
+set lib_so ${objdir}/${subdir}/${libfile}.so
+
+set lib_nodebug_so_base ${libfile}-nodebug.so
+set lib_nodebug_so ${objdir}/${subdir}/${lib_nodebug_so_base}
+
+# We need DWARF for the "final" function as we "step" into the function and GDB
+# would step-over the "final" function if there would be no line number debug
+# information (DWARF) available.
+#
+# We must not have DWARF for the "gnu_ifunc" function as DWARF has no way to
+# express the gnu-ifunc type and it would be considered as a regular function
+# due to DWARF by GDB.  In ELF gnu-ifunc is expressed by the STT_GNU_IFUNC type.
+#
+# As both functions need to be in the same shared library file and
+# gdb_compile_shlib has no way to specify source-specific compilation options
+# we hide the DWARF debug information for "gnu_ifunc" by defining it as
+# C "gnu_ifuncX" function with asm alias "gnu_ifunc".
+#
+# Still as "gnu_ifunc" gets the wrong "gnu_ifuncX" DWARF debug information it
+# confuses the ELF symbol address->symbol resoluted for printing the symbol
+# value.  Therefore use lib_nodebug_opts later to check the address->symbol
+# resolution without DWARF present there.
+#
+# Additionally GCC would generate incorrect DW_AT_MIPS_linkage_name <*gnu_ifunc>
+# for the "gnu_ifunc" DWARF symbol, therefore with disabled DWARF it is no
+# longer a problem.
+
+set lib_opts [list debug]
+set lib_nodebug_opts [list]
+set exec_opts [list debug shlib=$lib_so]
+
+if [get_compiler_info ${binfile}] {
+    return -1
+}
+
+if { [gdb_compile_shlib ${srcdir}/${subdir}/$libsrc $lib_so $lib_opts] != ""
+     || [gdb_compile ${srcdir}/${subdir}/$srcfile $binfile executable $exec_opts] != ""
+     || [gdb_compile_shlib ${srcdir}/${subdir}/$libsrc $lib_nodebug_so $lib_nodebug_opts] != ""} {
+    untested "Could not compile either $libsrc or $srcfile."
+    return -1
+}
+
+# Start with a fresh gdb.
+
+clean_restart $testfile
+gdb_load_shlibs ${lib_so}
+
+if ![runto_main] then {
+    fail "Can't run to main"
+    return 1;
+}
+
+# The "if" condition is artifical to test regression of a former patch.
+gdb_breakpoint "[gdb_get_line_number "break-at-nextcall"] if i && gnu_ifunc (i) != 42"
+
+gdb_breakpoint [gdb_get_line_number "break-at-call"]
+gdb_continue_to_breakpoint "break-at-call" ".*break-at-call.*"
+
+# Test GDB will automatically indirect the call.
+
+gdb_test "p gnu_ifunc (3)" " = 4"
+
+# Test GDB will skip the gnu_ifunc resolver on first call.
+
+gdb_test "step" "\r\nfinal .*"
+
+# Test GDB will not break before the final chosen implementation.
+
+# Also test a former patch regression:
+# Continuing.
+# Error in testing breakpoint condition:
+# Attempt to take address of value not located in memory.
+# 
+# Breakpoint 2, main () at ./gdb.base/gnu-ifunc.c:33
+
+gdb_test "continue" "Continuing.\r\n\r\nBreakpoint .* (at|in) .*break-at-nextcall.*" \
+	 "continue to break-at-nextcall"
+
+gdb_breakpoint "gnu_ifunc"
+
+gdb_continue_to_breakpoint "nextcall gnu_ifunc"
+
+gdb_test "frame" "#0 +(0x\[0-9a-f\]+ in +)?final \\(.*" "nextcall gnu_ifunc skipped"
+
+
+# Compare the two different addresses.  "info addr" is the only way to query the
+# gnu_ifunc resolver address without automatically getting from GDB the already
+# resolved function address.
+
+gdb_test "p gnu_ifunc" " = {<text variable, no debug info>} 0x\[0-9a-f\]+ <final>" "p gnu_ifunc executing"
+gdb_test "info sym gnu_ifunc" "final in section .*" "info sym gnu_ifunc executing"
+
+set test "info addr gnu_ifunc"
+gdb_test_multiple $test $test {
+    -re "Symbol \"gnu_ifunc\" is at (0x\[0-9a-f\]+) in .*$gdb_prompt $" {
+	pass $test
+    }
+}
+gdb_test "info sym $expect_out(1,string)" "gnu_ifunc in section .*" "info sym <gnu_ifunc-address>"
+
+
+# Check the lib_nodebug_opts variant.
+
+clean_restart $lib_nodebug_so_base
+
+gdb_test "p gnu_ifunc" " = {<text gnu-ifunc variable, no debug info>} 0x\[0-9a-f\]+ <\.?gnu_ifunc>" "p gnu_ifunc not executing without debug"
+gdb_test "info sym gnu_ifunc" "gnu_ifunc in section .*" "info sym gnu_ifunc not executing without debug"


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]