[RFC] Add IFUNC support for MIPS (v3)
Faraz Shahbazker
faraz.shahbazker@imgtec.com
Fri Aug 14 20:40:00 GMT 2015
Changes with respect to previous patch:
- Add IFUNC to mips/libc-abis for ABIVERSION checking
- Move global IFUNCs to explicitly relocated GOT region.
- Resolve global symbols unconditionally
- GENERAL_GOTNO is likely to occur
* 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/libc-abis
(IFUNC): New ABI compatibility level.
---
elf/elf.h | 7 ++-
sysdeps/mips/dl-irel.h | 63 ++++++++++++++++++++++
sysdeps/mips/dl-machine.h | 93 +++++++++++++++++++++++---------
sysdeps/mips/dl-trampoline.c | 4 ++
sysdeps/unix/sysv/linux/mips/libc-abis | 3 ++
5 files changed, 142 insertions(+), 28 deletions(-)
create mode 100644 sysdeps/mips/dl-irel.h
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..7e1fdc4
--- /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 (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_SYMBOLS, 0))
+ {
+ 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 (__builtin_expect (r_type == R_MIPS_IRELATIVE, 1))
+ *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 8738564..3d2edf1 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 (__glibc_likely (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,21 +584,37 @@ 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. */
+#ifndef RTLD_BOOTSTRAP
+ /* Resolve IFUNC symbols with pre-emption. */
+ if (sym && __glibc_unlikely (ELFW(ST_TYPE) (sym->st_info)
+ == STT_GNU_IFUNC) && !skip_ifunc)
+ {
+ struct link_map *rmap = RESOLVE_MAP (&sym, version, r_type);
+
+ if (__glibc_likely (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;
+ }
+ else
+#endif
+ /* 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. */
#ifndef RTLD_BOOTSTRAP
if (map != &GL(dl_rtld_map))
#endif
@@ -698,6 +719,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 +756,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 +798,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 +825,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 +848,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 (__glibc_likely(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..d5f8891 100644
--- a/sysdeps/mips/dl-trampoline.c
+++ b/sysdeps/mips/dl-trampoline.c
@@ -193,6 +193,10 @@ __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
+ && __builtin_expect (ELFW(ST_TYPE) (sym->st_info)
+ == STT_GNU_IFUNC, 0))
+ 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/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*
--
1.7.9.5
-------------- next part --------------
====
MIPS IFUNC ABI specification
====
Introduction
----
This document describes the working of GNU indirect functions(IFUNC)
for MIPS.
Terminology
----
Shared file or shared object refers to any object file with e_type
ET_DYN. Unless otherwise distinguished, this includes dynamic shared libraries
(PIC) and position-independent executables (PIE).
Executable file or executable refers to any object file with e_type
ET_EXEC. Unless otherwise distinguished, this includes statically linked and
dynamically linked executables.
Relocations for IFUNC resolution
----
The relocation table may now contain a new relocation type generated by the
static linker:
R_MIPS_IRELATIVE(128)
This relocation marks the location of an IFUNC indirection that needs to be
resolved by the dynamic linker at load time. The resolution step calls the
IFUNC resolver function given at the relocation offset possibly with
additional processor-specific (hardware capability) arguments and writes the
address returned by the resolver back to the relocation offset.
GOT and IGOT sections - the indirection table
----
An IRELATIVE fix-up will target the existing GOT entry of the symbol, except
in the following cases:
1. Symbols defined in executable files have no GOT entries.
2. Symbols defined in a shared object, but not referenced within that object
have no GOT entries.
If the symbol has no GOT entry, an entry is allocated in the IGOT section
and used by the fix-up. This GOT/IGOT entry is initialized to point to the
resolver function at link time and modified to point to the resolved function
by the IRELATIVE fix-up.
IPLT section - indirection stubs
----
The IFUNC Procedure Linkage Table (IPLT) consists of a set of stubs generated
by the static linker to stand in for IFUNC references that can not be
resolved via a GOT/IGOT entry, for example:
1. Internal or external IFUNC references in an executable file where there is
no GOT section.
2. External references in shared objects where the resolver function cannot
be invoked at load time due to the specific order of loading.
Executable files contain non-PIC stubs and shared objects contain PIC-style
stubs. Non-PIC stubs can be called using absolute JAL instruction and then
redirect the call to the actual function via an absolute pointer in to the GOT
(or IGOT) section. PIC stubs provide the same functionality, but they must be
called using the SVr4 PIC ABI [JALR $25]. The redirection is done via a full
32-bit (or 64-bit) relative offset from $25 in to the GOT (or IGOT)
section. In either case, the actual position of the GOT/IGOT relative to the
IPLT section or $gp is irrelevant.
For executable files, IFUNC calls are always routed through IPLT stubs. Every
IFUNC definition must have a corresponding stub irrespective of its declared
visibility. For shared objects, the dynamic linker must decide whether or not
to route through the stub for each external IFUNC references in each
object. Only externally visible IFUNC definitions needs stubs; no stubs are
generated for definitions with static or non-default (STV_HIDDEN/STV_INTERNAL)
visibility.
Issues specific to shared objects
====
External IFUNC resolution
----
If an external IFUNC reference binds to a resolver within another object which
has not been relocated at the time when the caller object is loaded, it is not
possible to invoke this IFUNC resolver to determine the resolved function. In
this case, the dynamic linker must fall back to binding that reference to the
appropriate IPLT stub. When the callee object is eventually loaded, its GOT
entry would be updated to the resolved function. When the IPLT stub is
executed, it will simply pick the resolved address from GOT and jump to it.
This scheme requires the ability to map the dynamic symbol of an IFUNC to
the address of its IPLT stub. This mapping is achieved by sorting all global
IFUNC symbols to be contiguous within the dynamic symbol table and using a
pair of dynamic tags:
MIPS_IPLT: address of the first IPLT stub
MIPS_IFUNC_INDX: index of the first dynamic IFUNC symbol.
For any dynamic IFUNC symbol, the difference of its index and
MIPS_IFUNC_INDEX, scaled by the size of an IPLT stub and added back to
MIPS_IPLT address yields that symbol's corresponding IPLT stub.
PIC stub size varies with architecture and ISA as follows:
- mips16/micromips32: 16 bytes
- mips32 o32: 20 bytes
- mips64 n32: 20 bytes
- mips64 n64: 36 bytes
** With further optimization, it may be possible to reduce the sizes of
o32/n32 stubs to 16 bytes and n64 stubs to 32 bytes.
dynsym table - dynamic symbol order
----
For mapping unresolvable IFUNC references to IPLT stubs, all IFUNC symbols
must be sorted to appear contiguous within the dynamic symbol table. Entries
in the dynamic symbol table are sorted as
- unreferenced symbols
- GOT referenced symbols
- reloc-only symbols.
To remain consistent with the internal order of symbols, the
dynamic symbol table is sorted in to 2 contiguous IFUNC sequences, so that
order is:
- unreferenced regular symbols
- unreferenced IFUNC symbols
- GOT-referenced IFUNC symbols
- GOT-referenced regular symbols
- reloc-only regular symbols
- reloc-only IFUNC symbols
In this way, the relative meta-order of the 3 symbol categories is
maintained. The first contiguous sequence of IFUNC symbols is marked by the
tag MIPS_IFUNC_INDX, with MIPS_IPLT pointing to the start address of the
corresponding IPLT stub sequence. The 2nd contiguous sequence is marked by
the tag MIPS_IFUNCREL_INDX, with MIPS_IPLTREL pointing to the start address of
the corresponding stub sequence. The 2nd sequence and its dynamic tags are
only needed in the multi-GOT case, where a single primary GOT is insufficient.
Dynamic Section
----
Dynamic section entries give information to the dynamic linker. The following new
dynamic table entries are required for IFUNC resolution:
MIPS_IPLT(36)
Address of the first IPLT stub. This tag is needed for all shared
objects with global IFUNC symbols.
MIPS_IFUNC_INDX(37)
Index of the first dynamic IFUNC symbol. This tag is needed for all
shared objects with global IFUNC symbols.
MIPS_IPLTREL(38)
Address of the IPLT stub of the first IFUNC symbol with a reloc-only
GOT entry. This tag is needed for all shared objects with global
IFUNC symbols and multiple GOTs.
MIPS_IFUNCREL_INDX(39)
Index of the first dynamic IFUNC symbol with a reloc-only GOT
entry. This tag is needed for all shared objects with global
IFUNC symbols and multiple GOTs.
More information about the Libc-alpha
mailing list