diff mbox

[RFC] Add IFUNC support for MIPS (v4)

Message ID 55D7A3C5.8050600@imgtec.com
State New
Headers show

Commit Message

Faraz Shahbazker Aug. 21, 2015, 10:18 p.m. UTC
Changes with respect to previous patch:
- Change builtin_expect to glibc_[un]likely and misc formatting issues.
- Add error for relocation on symbol with no general GOT region.
- Allow explicit relocations for non-IFUNC global symbols in general GOT region.

ChangeLog:

* elf/elf.h
	(R_MIPS_IRELATIVE): New relocation type.
	(R_MIPS_NUM): Bump up to 129.
	(DT_MIPS_GENERAL_GOTNO): New dynamic tags.
	(DT_MIPS_NUM): Bump to 0x37.

* sysdeps/mips/dl-irel.h: New file.
	(elf_ifunc_invoke): New function.
	(elf_irel): Likewise.

* sysdeps/mips/dl-machine.h
	Include new dl-irel.h
	(ELF_MACHINE_BEFORE_RTLD_RELOC): Use DT_MIPS_GENERAL_GOTNO tag, if
	present, to find the start of the normally relocated GOT.
	(elf_machine_reloc): Add skip_ifunc to parameter.
	Add case for R_MIPS_IRELATIVE. Modify REL32 to check for pre-emption
	if symbol is IFUNC and then perform IFUNC indirection.
	(elf_machine_rel): Add skip_ifunc to call to elf_machine_reloc().
	(elf_machine_rela):Add skip_ifunc to call to elf_machine_reloc().
	(RESOLVE_GOTSYM): Add check for STT_GNU_IFUNC.
	(elf_machine_got_rel): Add check for STT_GNU_IFUNC and IFUNC resolution
	step. Use DT_MIPS_GENERAL_GOTNO tag, if present, to find the start
	of the normally relocated GOT.

* sysdeps/mips/dl-trampoline.c
	(__dl_runtime_resolve): Add check for STT_GNU_IFUNC.

* sysdeps/unix/sysv/linux/mips/ldsodefs.h
	(VALID_ELF_ABIVERSION): Upped allowed ABI version number to 4

* sysdeps/unix/sysv/linux/mips/libc-abis
	(IFUNC): New ABI compatibility level.
---
 elf/elf.h                               |    7 ++-
 sysdeps/mips/dl-irel.h                  |   63 ++++++++++++++++++++++
 sysdeps/mips/dl-machine.h               |   87 +++++++++++++++++++++----------
 sysdeps/mips/dl-trampoline.c            |    3 ++
 sysdeps/unix/sysv/linux/mips/ldsodefs.h |    2 +-
 sysdeps/unix/sysv/linux/mips/libc-abis  |    3 ++
 6 files changed, 134 insertions(+), 31 deletions(-)
 create mode 100644 sysdeps/mips/dl-irel.h

Comments

Richard Sandiford Aug. 22, 2015, 9:19 a.m. UTC | #1
Faraz Shahbazker <faraz.shahbazker@imgtec.com> writes:
> @@ -579,25 +584,31 @@ elf_machine_reloc (struct link_map *map, ElfW(Addr) r_info,
>  
>  	    if ((ElfW(Word))symidx < gotsym)
>  	      {
> -		/* This wouldn't work for a symbol imported from other
> -		   libraries for which there's no GOT entry, but MIPS
> -		   requires every symbol referenced in a dynamic
> -		   relocation to have a GOT entry in the primary GOT,
> -		   so we only get here for locally-defined symbols.
> -		   For section symbols, we should *NOT* be adding
> -		   sym->st_value (per the definition of the meaning of
> -		   S in reloc expressions in the ELF64 MIPS ABI),
> -		   since it should have already been added to
> -		   reloc_value by the linker, but older versions of
> -		   GNU ld didn't add it, and newer versions don't emit
> -		   useless relocations to section symbols any more, so
> -		   it is safe to keep on adding sym->st_value, even
> -		   though it's not ABI compliant.  Some day we should
> -		   bite the bullet and stop doing this.  */

We still need the "For section symbols..." bit onwards (and the
associated code) for STB_LOCAL in the case where DT_MIPS_GENERAL_GOTNO
isn't defined.  STB_LOCAL symbols shouldn't get the error.

We should probably drop the last sentence though :-)  I don't see there's
anything to gain by dropping support for old objects.  We simply don't
get here for new objects, so changing the calculation wouldn't have
any significant benefit.

> +		/* Linker will not normally emit symbolic relocations for
> +		   locally-defined global symbols.  If we reach here, we must
> +		   be dealing with relocation to a global symbols which is
> +		   explicitly relocated via the general GOT region and
> +		   pre-emptible.  */

How about something like:

		/* The original MIPS ABI required every global symbol used
		   in a relocation to be in the global GOT.  We would then
		   only expect to get here for local symbols.

(this part is just a rewording of the first part of the "This wouldn't
work..." comment above)

		   This restriction is removed for objects that use
		   DT_MIPS_GENERAL_GOTNO, since newer relocations and
		   symbol types do not fit easily in the original ABI scheme.
		   Relocations against symbols below DT_MIPS_GOTSYM bind in
		   just the same way as relocations against symbols in the
		   global GOT; the only difference is that we are not able
		   to use the global GOT as a directly-indexed lookup cache.
		   Symbols below DT_MIPS_GOTSYM might be in the general GOT
		   region or might not have a GOT entry at all.  */

>  #ifndef RTLD_BOOTSTRAP
> -		if (map != &GL(dl_rtld_map))
> +		if (map->l_info[DT_MIPS (GENERAL_GOTNO)] == NULL)

I think an unlikely tag would be justified here (even though it isn't
when finding the start of the local GOT).  Only very old objects will
have local relocations against symbols other than 0.

> +		  {
> +		    const char *strtab;
> +		    strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
> +
> +		    _dl_error_printf ("\
> +%s: Explicitly relocated symbol `%s' requires dynamic tag MIPS_GENERAL_GOTNO\n",
> +				      RTLD_PROGNAME, strtab + sym->st_name);
> +		  }
> +		else
> +		  {
> +		    struct link_map *rmap = RESOLVE_MAP (&sym, version, r_type);
> +		    if ((ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC))
> +		      reloc_value = elf_ifunc_invoke (sym->st_value
> +						      + rmap->l_addr);
> +		    else
> +		      reloc_value = sym->st_value + rmap->l_addr;

It might be worth having a comment at the start of this "else" along
the lines of:

		    /* The behavior for section symbols described above
		       is now so firmly established that it is explicitly
		       adopted by objects with DT_MIPS_GLOBAL_GOTNO.
		       We therefore don't have a special case for
		       section symbols.  */

Looks good to me otherwise from an ABI POV.  (Not an approval -- I can't
do that.)

Thanks for your patience with this.

Richard
diff mbox

Patch

diff --git a/elf/elf.h b/elf/elf.h
index fbadda4..960834a 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -1653,8 +1653,9 @@  typedef struct
 #define R_MIPS_GLOB_DAT		51
 #define R_MIPS_COPY		126
 #define R_MIPS_JUMP_SLOT        127
+#define R_MIPS_IRELATIVE        128
 /* Keep this the last entry.  */
-#define R_MIPS_NUM		128
+#define R_MIPS_NUM		129
 
 /* Legal values for p_type field of Elf32_Phdr.  */
 
@@ -1731,7 +1732,9 @@  typedef struct
    in a PIE as it stores a relative offset from the address of the tag
    rather than an absolute address.  */
 #define DT_MIPS_RLD_MAP_REL  0x70000035
-#define DT_MIPS_NUM	     0x36
+ /* Number of explicitly relocated GOT entries */
+#define DT_MIPS_GENERAL_GOTNO  0x70000036
+#define DT_MIPS_NUM	     0x37
 
 /* Legal values for DT_MIPS_FLAGS Elf32_Dyn entry.  */
 
diff --git a/sysdeps/mips/dl-irel.h b/sysdeps/mips/dl-irel.h
new file mode 100644
index 0000000..47f3257
--- /dev/null
+++ b/sysdeps/mips/dl-irel.h
@@ -0,0 +1,63 @@ 
+/* Machine-dependent ELF indirect relocation inline functions.
+   MIPS version.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library.  If not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_IREL_H
+#define _DL_IREL_H
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sgidefs.h>
+#include <link.h>
+#include <elf.h>
+#include <ldsodefs.h>
+
+#define ELF_MACHINE_IREL	1
+
+static inline ElfW(Addr)
+__attribute ((always_inline))
+elf_ifunc_invoke (ElfW(Addr) addr)
+{
+  /* Print some debugging info if wanted.  */
+  if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SYMBOLS))
+    {
+      ElfW(Addr) t_addr =
+	((ElfW(Addr) (*) (unsigned long int)) (addr)) (GLRO(dl_hwcap));
+      GLRO(dl_debug_printf) ("In elf_ifunc_invoke(0x%lx), return(0x%lx)\n",
+			     (unsigned long int)addr,
+			     (unsigned long int)t_addr);
+    }
+
+  return ((ElfW(Addr) (*) (unsigned long int)) (addr)) (GLRO(dl_hwcap));
+}
+
+/* Allow either R_MIPS_RELATIVE or the nop R_MIPS_NONE.  */
+static inline void
+__attribute ((always_inline))
+elf_irel (const ElfW(Rel) *reloc)
+{
+  ElfW(Addr) *const reloc_addr = (void *) reloc->r_offset;
+  const unsigned long int r_type = ELFW(R_TYPE) (reloc->r_info);
+
+  if (__glibc_likely (r_type == R_MIPS_IRELATIVE))
+    *reloc_addr = elf_ifunc_invoke (*reloc_addr);
+  else if (r_type)
+    __libc_fatal ("unexpected reloc type in static binary");
+}
+
+#endif /* dl-irel.h */
diff --git a/sysdeps/mips/dl-machine.h b/sysdeps/mips/dl-machine.h
index 52cd742..e597c39 100644
--- a/sysdeps/mips/dl-machine.h
+++ b/sysdeps/mips/dl-machine.h
@@ -33,6 +33,7 @@ 
 #include <sysdep.h>
 #include <sys/asm.h>
 #include <dl-tls.h>
+#include <dl-irel.h>
 
 /* The offset of gp from GOT might be system-dependent.  It's set by
    ld.  The same value is also */
@@ -200,10 +201,13 @@  do {									\
   if (__builtin_expect (map->l_addr == 0, 1))				\
     break;								\
 									\
-  /* got[0] is reserved. got[1] is also reserved for the dynamic object	\
-     generated by gnu ld. Skip these reserved entries from		\
-     relocation.  */							\
-  i = (got[1] & ELF_MIPS_GNU_GOT1_MASK)? 2 : 1;				\
+  if (map->l_info[DT_MIPS (GENERAL_GOTNO)] != NULL)			\
+    i = map->l_info[DT_MIPS (GENERAL_GOTNO)]->d_un.d_val;		\
+  else									\
+    /* got[0] is reserved. got[1] is also reserved for the dynamic	\
+       object generated by gnu ld. Skip these reserved entries from	\
+       relocation.  */							\
+    i = (got[1] & ELF_MIPS_GNU_GOT1_MASK)? 2 : 1;			\
   n = map->l_info[DT_MIPS (LOCAL_GOTNO)]->d_un.d_val;			\
 									\
   /* Add the run-time displacement to all local got entries. */		\
@@ -493,7 +497,8 @@  auto inline void
 __attribute__ ((always_inline))
 elf_machine_reloc (struct link_map *map, ElfW(Addr) r_info,
 		   const ElfW(Sym) *sym, const struct r_found_version *version,
-		   void *reloc_addr, ElfW(Addr) r_addend, int inplace_p)
+		   void *reloc_addr, ElfW(Addr) r_addend, int inplace_p,
+		   int skip_ifunc)
 {
   const unsigned long int r_type = ELFW(R_TYPE) (r_info);
   ElfW(Addr) *addr_field = (ElfW(Addr) *) reloc_addr;
@@ -579,25 +584,31 @@  elf_machine_reloc (struct link_map *map, ElfW(Addr) r_info,
 
 	    if ((ElfW(Word))symidx < gotsym)
 	      {
-		/* This wouldn't work for a symbol imported from other
-		   libraries for which there's no GOT entry, but MIPS
-		   requires every symbol referenced in a dynamic
-		   relocation to have a GOT entry in the primary GOT,
-		   so we only get here for locally-defined symbols.
-		   For section symbols, we should *NOT* be adding
-		   sym->st_value (per the definition of the meaning of
-		   S in reloc expressions in the ELF64 MIPS ABI),
-		   since it should have already been added to
-		   reloc_value by the linker, but older versions of
-		   GNU ld didn't add it, and newer versions don't emit
-		   useless relocations to section symbols any more, so
-		   it is safe to keep on adding sym->st_value, even
-		   though it's not ABI compliant.  Some day we should
-		   bite the bullet and stop doing this.  */
+		/* Linker will not normally emit symbolic relocations for
+		   locally-defined global symbols.  If we reach here, we must
+		   be dealing with relocation to a global symbols which is
+		   explicitly relocated via the general GOT region and
+		   pre-emptible.  */
 #ifndef RTLD_BOOTSTRAP
-		if (map != &GL(dl_rtld_map))
+		if (map->l_info[DT_MIPS (GENERAL_GOTNO)] == NULL)
+		  {
+		    const char *strtab;
+		    strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
+
+		    _dl_error_printf ("\
+%s: Explicitly relocated symbol `%s' requires dynamic tag MIPS_GENERAL_GOTNO\n",
+				      RTLD_PROGNAME, strtab + sym->st_name);
+		  }
+		else
+		  {
+		    struct link_map *rmap = RESOLVE_MAP (&sym, version, r_type);
+		    if ((ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC))
+		      reloc_value = elf_ifunc_invoke (sym->st_value
+						      + rmap->l_addr);
+		    else
+		      reloc_value = sym->st_value + rmap->l_addr;
+		  }
 #endif
-		  reloc_value += sym->st_value + map->l_addr;
 	      }
 	    else
 	      {
@@ -698,6 +709,14 @@  elf_machine_reloc (struct link_map *map, ElfW(Addr) r_info,
 	break;
       }
 
+    case R_MIPS_IRELATIVE:
+      /* The resolver routine is the symbol referenced by this relocation.
+	 To get the address of the function to use at runtime, the resolver
+	 routine is called and its return value is the address of the target
+	 functon which is final relocation value.  */
+      *addr_field = elf_ifunc_invoke (map->l_addr + *addr_field);
+      break;
+
 #if _MIPS_SIM == _ABI64
     case R_MIPS_64:
       /* For full compliance with the ELF64 ABI, one must precede the
@@ -727,7 +746,8 @@  elf_machine_rel (struct link_map *map, const ElfW(Rel) *reloc,
 		 const ElfW(Sym) *sym, const struct r_found_version *version,
 		 void *const reloc_addr, int skip_ifunc)
 {
-  elf_machine_reloc (map, reloc->r_info, sym, version, reloc_addr, 0, 1);
+  elf_machine_reloc (map, reloc->r_info, sym, version, reloc_addr, 0, 1,
+		     skip_ifunc);
 }
 
 auto inline void
@@ -768,7 +788,7 @@  elf_machine_rela (struct link_map *map, const ElfW(Rela) *reloc,
 		  void *const reloc_addr, int skip_ifunc)
 {
   elf_machine_reloc (map, reloc->r_info, sym, version, reloc_addr,
-		     reloc->r_addend, 0);
+		     reloc->r_addend, 0, skip_ifunc);
 }
 
 auto inline void
@@ -795,8 +815,16 @@  elf_machine_got_rel (struct link_map *map, int lazy)
       const struct r_found_version *version __attribute__ ((unused))	  \
 	= vernum ? &map->l_versions[vernum[sym_index] & 0x7fff] : NULL;	  \
       struct link_map *sym_map;						  \
+      ElfW(Addr) value = 0;						  \
       sym_map = RESOLVE_MAP (&ref, version, reloc);			  \
-      ref ? sym_map->l_addr + ref->st_value : 0;			  \
+      if (__glibc_likely (ref != NULL))				  \
+	{								  \
+	  value = sym_map->l_addr + ref->st_value;			  \
+	  if (__glibc_unlikely (ELFW(ST_TYPE) (ref->st_info)		  \
+				== STT_GNU_IFUNC))			  \
+	      value = elf_ifunc_invoke (value);				  \
+	}								  \
+      value;								  \
     })
 
   if (map->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
@@ -810,9 +838,12 @@  elf_machine_got_rel (struct link_map *map, int lazy)
   /* The dynamic linker's local got entries have already been relocated.  */
   if (map != &GL(dl_rtld_map))
     {
-      /* got[0] is reserved. got[1] is also reserved for the dynamic object
-	 generated by gnu ld. Skip these reserved entries from relocation.  */
-      i = (got[1] & ELF_MIPS_GNU_GOT1_MASK)? 2 : 1;
+      if (map->l_info[DT_MIPS (GENERAL_GOTNO)] != NULL)
+	i = map->l_info[DT_MIPS (GENERAL_GOTNO)]->d_un.d_val;
+      else
+	/* got[0] is reserved. got[1] is also reserved for the dynamic object
+	   generated by gnu ld. Skip these reserved entries from relocation.  */
+	i = (got[1] & ELF_MIPS_GNU_GOT1_MASK)? 2 : 1;
 
       /* Add the run-time displacement to all local got entries if
 	 needed.  */
diff --git a/sysdeps/mips/dl-trampoline.c b/sysdeps/mips/dl-trampoline.c
index 25b1709..0f1829c1 100644
--- a/sysdeps/mips/dl-trampoline.c
+++ b/sysdeps/mips/dl-trampoline.c
@@ -193,6 +193,9 @@  __dl_runtime_resolve (ElfW(Word) sym_index,
       /* Currently value contains the base load address of the object
 	 that defines sym.  Now add in the symbol offset.  */
       value = (sym ? sym_map->l_addr + sym->st_value : 0);
+      if (sym != NULL
+          && __glibc_unlikely (ELFW(ST_TYPE) (sym->st_info)  == STT_GNU_IFUNC))
+        value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));
     }
   else
     /* We already found the symbol.  The module (and therefore its load
diff --git a/sysdeps/unix/sysv/linux/mips/ldsodefs.h b/sysdeps/unix/sysv/linux/mips/ldsodefs.h
index 42b92b3..385db69 100644
--- a/sysdeps/unix/sysv/linux/mips/ldsodefs.h
+++ b/sysdeps/unix/sysv/linux/mips/ldsodefs.h
@@ -34,7 +34,7 @@  extern void _dl_static_init (struct link_map *map);
 #undef VALID_ELF_ABIVERSION
 #define VALID_ELF_ABIVERSION(osabi,ver)			\
   (ver == 0						\
-   || (osabi == ELFOSABI_SYSV && ver < 4)		\
+   || (osabi == ELFOSABI_SYSV && ver < 5)		\
    || (osabi == ELFOSABI_GNU && ver < LIBC_ABI_MAX))
 
 #endif /* ldsodefs.h */
diff --git a/sysdeps/unix/sysv/linux/mips/libc-abis b/sysdeps/unix/sysv/linux/mips/libc-abis
index 14ff603..0644720 100644
--- a/sysdeps/unix/sysv/linux/mips/libc-abis
+++ b/sysdeps/unix/sysv/linux/mips/libc-abis
@@ -14,3 +14,6 @@  UNIQUE
 #
 # MIPS O32 FP64
 MIPS_O32_FP64   mips*-*-linux*
+#
+# MIPS IFUNC
+IFUNC		mips*-*-linux*