rtld: Add STT_GNU_IFUNC and R_MACHINE_IRELATIVE_GNU support
authorJohn Marino <draco@marino.st>
Sat, 21 Jan 2012 23:00:15 +0000 (00:00 +0100)
committerJohn Marino <draco@marino.st>
Mon, 23 Jan 2012 16:13:18 +0000 (17:13 +0100)
Add support for STT_GNU_IFUNC and R_MACHINE_IRELATIVE GNU extensions to
rtld. This adds runtime bits neccessary for the use of the dispatch
functions from the dynamically-linked executables and shared libraries.

To allow use of external references from the dispatch function, resolution
of the R_MACHINE_IRESOLVE relocations in PLT is postponed until GOT entries
for PLT are prepared, and normal resolution of the GOT entries is finished.
Similar to how it is done by GNU, IRELATIVE relocations are resolved in
advance, instead of normal lazy handling for PLT.

Taken-from:
FreeBSD SVN 228435 (12 DEC 2011)
FreeBSD SVN 228503 (14 DEC 2011)
FreeBSD SVN 229508 (04 JAN 2012)

libexec/rtld-elf/i386/reloc.c
libexec/rtld-elf/rtld.c
libexec/rtld-elf/rtld.h
libexec/rtld-elf/x86_64/reloc.c
sys/cpu/i386/include/elf.h
sys/cpu/x86_64/include/elf.h
sys/sys/elf_common.h

index 5b0c68a..7fdcd1d 100644 (file)
@@ -214,42 +214,14 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, RtldLockState *lockstate)
                break;
 
            case R_386_TLS_TPOFF:
-               {
-                   const Elf_Sym *def;
-                   const Obj_Entry *defobj;
-
-                   def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj,
-                     false, cache, lockstate);
-                   if (def == NULL)
-                       goto done;
-
-                   /*
-                    * We lazily allocate offsets for static TLS as we
-                    * see the first relocation that references the
-                    * TLS block. This allows us to support (small
-                    * amounts of) static TLS in dynamically loaded
-                    * modules. If we run out of space, we generate an
-                    * error.
-                    */
-                   if (!defobj->tls_done) {
-                       if (!allocate_tls_offset((Obj_Entry*) defobj)) {
-                           _rtld_error("%s: No space available for static "
-                                       "Thread Local Storage", obj->path);
-                           goto done;
-                       }
-                   }
-
-                   *where += (Elf_Addr) (def->st_value - defobj->tlsoffset);
-               }
-               break;
-
            case R_386_TLS_TPOFF32:
                {
                    const Elf_Sym *def;
                    const Obj_Entry *defobj;
+                   Elf_Addr add;
 
                    def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj,
-                     false, cache);
+                     false, cache, lockstate);
                    if (def == NULL)
                        goto done;
 
@@ -268,8 +240,11 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, RtldLockState *lockstate)
                            goto done;
                        }
                    }
-
-                   *where += (Elf_Addr) (defobj->tlsoffset - def->st_value);
+                   add = (Elf_Addr) (def->st_value - defobj->tlsoffset);
+                   if (ELF_R_TYPE(rel->r_info) == R_386_TLS_TPOFF)
+                       *where += add;
+                   else
+                       *where -= add;
                }
                break;
 
@@ -333,6 +308,10 @@ reloc_plt(Obj_Entry *obj)
          *where += (Elf_Addr)obj->relocbase;
          break;
 
+       case R_386_IRELATIVE:
+         obj->irelative = true;
+         break;
+
        default:
          _rtld_error("Unknown relocation type %x in PLT",
            ELF_R_TYPE(rel->r_info));
@@ -364,10 +343,17 @@ reloc_jmpslots(Obj_Entry *obj, RtldLockState *lockstate)
              lockstate);
          if (def == NULL)
              return (-1);
+         if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) {
+             obj->gnu_ifunc = true;
+             continue;
+         }
          target = (Elf_Addr)(defobj->relocbase + def->st_value);
          reloc_jmpslot(where, target, defobj, obj, rel);
          break;
 
+       case R_386_IRELATIVE:
+         break;
+
        default:
          _rtld_error("Unknown relocation type %x in PLT",
            ELF_R_TYPE(rel->r_info));
@@ -379,6 +365,66 @@ reloc_jmpslots(Obj_Entry *obj, RtldLockState *lockstate)
     return 0;
 }
 
+int
+reloc_iresolve(Obj_Entry *obj, RtldLockState *lockstate)
+{
+    const Elf_Rel *rellim;
+    const Elf_Rel *rel;
+    Elf_Addr *where, target;
+
+    if (!obj->irelative)
+       return (0);
+    rellim = (const Elf_Rel *)((char *)obj->pltrel + obj->pltrelsize);
+    for (rel = obj->pltrel;  rel < rellim;  rel++) {
+       switch (ELF_R_TYPE(rel->r_info)) {
+       case R_386_IRELATIVE:
+         where = (Elf_Addr *)(obj->relocbase + rel->r_offset);
+         lock_release(rtld_bind_lock, lockstate);
+         target = ((Elf_Addr (*)(void))(obj->relocbase + *where))();
+         wlock_acquire(rtld_bind_lock, lockstate);
+         *where = target;
+         break;
+       }
+    }
+    obj->irelative = false;
+    return (0);
+}
+
+int
+reloc_gnu_ifunc(Obj_Entry *obj, RtldLockState *lockstate)
+{
+    const Elf_Rel *rellim;
+    const Elf_Rel *rel;
+
+    if (!obj->gnu_ifunc)
+       return (0);
+    rellim = (const Elf_Rel *)((char *)obj->pltrel + obj->pltrelsize);
+    for (rel = obj->pltrel;  rel < rellim;  rel++) {
+       Elf_Addr *where, target;
+       const Elf_Sym *def;
+       const Obj_Entry *defobj;
+
+       switch (ELF_R_TYPE(rel->r_info)) {
+       case R_386_JMP_SLOT:
+         where = (Elf_Addr *)(obj->relocbase + rel->r_offset);
+         def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, true, NULL,
+             lockstate);
+         if (def == NULL)
+             return (-1);
+         if (ELF_ST_TYPE(def->st_info) != STT_GNU_IFUNC)
+             continue;
+         lock_release(rtld_bind_lock, lockstate);
+         target = (Elf_Addr)rtld_resolve_ifunc(defobj, def);
+         wlock_acquire(rtld_bind_lock, lockstate);
+         reloc_jmpslot(where, target, defobj, obj, rel);
+         break;
+       }
+    }
+
+    obj->gnu_ifunc = false;
+    return (0);
+}
+
 /* GNU ABI */
 __attribute__((__regparm__(1)))
 void *
index 49afbb4..b0c570c 100644 (file)
@@ -117,6 +117,8 @@ static void objlist_push_tail(Objlist *, Obj_Entry *);
 static void objlist_remove(Objlist *, Obj_Entry *);
 static void *path_enumerate(const char *, path_enum_proc, void *);
 static int relocate_objects(Obj_Entry *, bool, Obj_Entry *, RtldLockState *);
+static int resolve_objects_ifunc(Obj_Entry *first, bool bind_now,
+    RtldLockState *lockstate);
 static int rtld_dirname(const char *, char *);
 static int rtld_dirname_abs(const char *, char *);
 static void rtld_exit(void);
@@ -659,6 +661,11 @@ resident_skip2:
 
     map_stacks_exec(NULL);
 
+    dbg("resolving ifuncs");
+    if (resolve_objects_ifunc(obj_main,
+      ld_bind_now != NULL && *ld_bind_now != '\0', NULL) == -1)
+       die();
+
     /*
      * Do NOT call the initlist here, give libc a chance to set up
      * the initial TLS segment.  crt1 will then call _rtld_call_init().
@@ -693,6 +700,17 @@ _rtld_call_init(void)
     lock_release(rtld_bind_lock, &lockstate);
 }
 
+void *
+rtld_resolve_ifunc(const Obj_Entry *obj, const Elf_Sym *def)
+{
+       void *ptr;
+       Elf_Addr target;
+
+       ptr = (void *)make_function_pointer(def, obj);
+       target = ((Elf_Addr (*)(void))ptr)();
+       return ((void *)target);
+}
+
 Elf_Addr
 _rtld_bind(Obj_Entry *obj, Elf_Size reloff, void *stack)
 {
@@ -716,8 +734,10 @@ _rtld_bind(Obj_Entry *obj, Elf_Size reloff, void *stack)
        &lockstate);
     if (def == NULL)
        die();
-
-    target = (Elf_Addr)(defobj->relocbase + def->st_value);
+    if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC)
+       target = (Elf_Addr)rtld_resolve_ifunc(defobj, def);
+    else
+        target = (Elf_Addr)(defobj->relocbase + def->st_value);
 
     dbg("\"%s\" in \"%s\" ==> %p in \"%s\"",
       defobj->strtab + def->st_name, basename(obj->path),
@@ -2110,6 +2130,10 @@ relocate_objects(Obj_Entry *first, bool bind_now, Obj_Entry *rtldobj,
            }
        }
 
+
+       /* Set the special PLT or GOT entries. */
+       init_pltgot(obj);
+
        /* Process the PLT relocations. */
        if (reloc_plt(obj) == -1)
            return -1;
@@ -2118,9 +2142,6 @@ relocate_objects(Obj_Entry *first, bool bind_now, Obj_Entry *rtldobj,
            if (reloc_jmpslots(obj, lockstate) == -1)
                return -1;
 
-       /* Set the special PLT or GOT entries. */
-       init_pltgot(obj);
-
        /*
         * Set up the magic number and version in the Obj_Entry.  These
         * were checked in the crt1.o from the original ElfKit, so we
@@ -2145,6 +2166,52 @@ relocate_objects(Obj_Entry *first, bool bind_now, Obj_Entry *rtldobj,
     return (0);
 }
 
+/*
+ * The handling of R_MACHINE_IRELATIVE relocations and jumpslots
+ * referencing STT_GNU_IFUNC symbols is postponed till the other
+ * relocations are done.  The indirect functions specified as
+ * ifunc are allowed to call other symbols, so we need to have
+ * objects relocated before asking for resolution from indirects.
+ *
+ * The R_MACHINE_IRELATIVE slots are resolved in greedy fashion,
+ * instead of the usual lazy handling of PLT slots.  It is
+ * consistent with how GNU does it.
+ */
+static int
+resolve_object_ifunc(Obj_Entry *obj, bool bind_now, RtldLockState *lockstate)
+{
+       if (obj->irelative && reloc_iresolve(obj, lockstate) == -1)
+               return (-1);
+       if ((obj->bind_now || bind_now) && obj->gnu_ifunc &&
+           reloc_gnu_ifunc(obj, lockstate) == -1)
+               return (-1);
+       return (0);
+}
+
+static int
+resolve_objects_ifunc(Obj_Entry *first, bool bind_now, RtldLockState *lockstate)
+{
+       Obj_Entry *obj;
+
+       for (obj = first;  obj != NULL;  obj = obj->next) {
+               if (resolve_object_ifunc(obj, bind_now, lockstate) == -1)
+                       return (-1);
+       }
+       return (0);
+}
+
+static int
+initlist_objects_ifunc(Objlist *list, bool bind_now, RtldLockState *lockstate)
+{
+       Objlist_Entry *elm;
+
+       STAILQ_FOREACH(elm, list, link) {
+               if (resolve_object_ifunc(elm->obj, bind_now, lockstate) == -1)
+                       return (-1);
+       }
+       return (0);
+}
+
 /*
  * Cleanup procedure.  It will be called (by the atexit mechanism) just
  * before the process exits.
@@ -2316,6 +2383,16 @@ dlopen(const char *name, int mode)
       mode & (RTLD_MODEMASK | RTLD_GLOBAL)));
 }
 
+static void
+dlopen_cleanup(Obj_Entry *obj)
+{
+
+       obj->dl_refcount--;
+       unref_dag(obj);
+       if (obj->refcount == 0)
+               unload_object(obj);
+}
+
 static Obj_Entry *
 dlopen_object(const char *name, Obj_Entry *refobj, int lo_flags, int mode)
 {
@@ -2354,10 +2431,7 @@ dlopen_object(const char *name, Obj_Entry *refobj, int lo_flags, int mode)
                goto trace;
            if (result == -1 || (relocate_objects(obj, (mode & RTLD_MODEMASK)
              == RTLD_NOW, &obj_rtld, &lockstate)) == -1) {
-               obj->dl_refcount--;
-               unref_dag(obj);
-               if (obj->refcount == 0)
-                   unload_object(obj);
+               dlopen_cleanup(obj);
                obj = NULL;
            } else {
                /* Make list of init functions to call. */
@@ -2391,6 +2465,14 @@ dlopen_object(const char *name, Obj_Entry *refobj, int lo_flags, int mode)
 
     map_stacks_exec(&lockstate);
 
+    if (initlist_objects_ifunc(&initlist, (mode & RTLD_MODEMASK) == RTLD_NOW,
+      &lockstate) == -1) {
+       objlist_clear(&initlist);
+       dlopen_cleanup(obj);
+       lock_release(rtld_bind_lock, &lockstate);
+       return (NULL);
+    }
+
     /* Call the init functions. */
     objlist_call_init(&initlist, &lockstate);
     objlist_clear(&initlist);
@@ -2523,6 +2605,8 @@ do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve,
         */
        if (ELF_ST_TYPE(def->st_info) == STT_FUNC)
            return (make_function_pointer(def, defobj));
+       else if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC)
+           return (rtld_resolve_ifunc(defobj, def));
        else
            return (defobj->relocbase + def->st_value);
     }
@@ -2968,6 +3052,8 @@ get_program_var_addr(const char *name, RtldLockState *lockstate)
     if (ELF_ST_TYPE(req.sym_out->st_info) == STT_FUNC)
        return ((const void **)make_function_pointer(req.sym_out,
          req.defobj_out));
+    else if (ELF_ST_TYPE(req.sym_out->st_info) == STT_GNU_IFUNC)
+       return ((const void **)rtld_resolve_ifunc(req.defobj_out, req.sym_out));
     else
            return ((const void **)(req.defobj_out->relocbase +
              req.sym_out->st_value));
@@ -3265,6 +3351,7 @@ symlook_obj1(SymLook *req, const Obj_Entry *obj)
        case STT_FUNC:
        case STT_NOTYPE:
        case STT_OBJECT:
+       case STT_GNU_IFUNC:
            if (symp->st_value == 0)
                continue;
                /* fallthrough */
index 7101a4f..a80702d 100644 (file)
@@ -215,6 +215,8 @@ typedef struct Struct_Obj_Entry {
     bool on_fini_list: 1;      /* Object is already on fini list. */
     bool dag_inited : 1;       /* Object has its DAG initialized. */
     bool filtees_loaded : 1;   /* Filtees loaded */
+    bool irelative : 1;                /* Object has R_MACHDEP_IRELATIVE relocs */
+    bool gnu_ifunc : 1;                /* Object has references to STT_GNU_IFUNC */
 
     struct link_map linkmap;   /* For GDB and dlinfo() */
     Objlist dldags;            /* Object belongs to these dlopened DAGs (%) */
@@ -302,6 +304,7 @@ void lockdflt_init(void);
 void obj_free(Obj_Entry *);
 Obj_Entry *obj_new(void);
 void _rtld_bind_start(void);
+void *rtld_resolve_ifunc(const Obj_Entry *obj, const Elf_Sym *def);
 void symlook_init(SymLook *, const char *);
 int symlook_obj(SymLook *, const Obj_Entry *);
 void *tls_get_addr_common(Elf_Addr** dtvp, int index, size_t offset);
@@ -319,6 +322,8 @@ int do_copy_relocations(Obj_Entry *);
 int reloc_non_plt(Obj_Entry *, Obj_Entry *, struct Struct_RtldLockState *);
 int reloc_plt(Obj_Entry *);
 int reloc_jmpslots(Obj_Entry *, struct Struct_RtldLockState *);
+int reloc_iresolve(Obj_Entry *, struct Struct_RtldLockState *);
+int reloc_gnu_ifunc(Obj_Entry *, struct Struct_RtldLockState *);
 void allocate_initial_tls(Obj_Entry *);
 
 #endif /* } */
index d82e0d6..36a9982 100644 (file)
@@ -354,6 +354,10 @@ reloc_plt(Obj_Entry *obj)
          *where += (Elf_Addr)obj->relocbase;
          break;
 
+       case R_X86_64_IRELATIVE:
+         obj->irelative = true;
+         break;
+
        default:
          _rtld_error("Unknown relocation type %x in PLT",
            (unsigned int)ELF_R_TYPE(rela->r_info));
@@ -385,10 +389,17 @@ reloc_jmpslots(Obj_Entry *obj, RtldLockState *lockstate)
              lockstate);
          if (def == NULL)
              return (-1);
+         if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) {
+             obj->gnu_ifunc = true;
+             continue;
+         }
          target = (Elf_Addr)(defobj->relocbase + def->st_value + rela->r_addend);
          reloc_jmpslot(where, target, defobj, obj, (const Elf_Rel *)rela);
          break;
 
+       case R_X86_64_IRELATIVE:
+         break;
+
        default:
          _rtld_error("Unknown relocation type %x in PLT",
            (unsigned int)ELF_R_TYPE(rela->r_info));
@@ -399,6 +410,70 @@ reloc_jmpslots(Obj_Entry *obj, RtldLockState *lockstate)
     return 0;
 }
 
+int
+reloc_iresolve(Obj_Entry *obj, RtldLockState *lockstate)
+{
+    const Elf_Rela *relalim;
+    const Elf_Rela *rela;
+
+    if (!obj->irelative)
+       return (0);
+    relalim = (const Elf_Rela *)((char *)obj->pltrela + obj->pltrelasize);
+    for (rela = obj->pltrela;  rela < relalim;  rela++) {
+       Elf_Addr *where, target, *ptr;
+
+       switch (ELF_R_TYPE(rela->r_info)) {
+       case R_X86_64_JMP_SLOT:
+         break;
+
+       case R_X86_64_IRELATIVE:
+         ptr = (Elf_Addr *)(obj->relocbase + rela->r_addend);
+         where = (Elf_Addr *)(obj->relocbase + rela->r_offset);
+         lock_release(rtld_bind_lock, lockstate);
+         target = ((Elf_Addr (*)(void))ptr)();
+         wlock_acquire(rtld_bind_lock, lockstate);
+         *where = target;
+         break;
+       }
+    }
+    obj->irelative = false;
+    return (0);
+}
+
+int
+reloc_gnu_ifunc(Obj_Entry *obj, RtldLockState *lockstate)
+{
+    const Elf_Rela *relalim;
+    const Elf_Rela *rela;
+
+    if (!obj->gnu_ifunc)
+       return (0);
+    relalim = (const Elf_Rela *)((char *)obj->pltrela + obj->pltrelasize);
+    for (rela = obj->pltrela;  rela < relalim;  rela++) {
+       Elf_Addr *where, target;
+       const Elf_Sym *def;
+       const Obj_Entry *defobj;
+
+       switch (ELF_R_TYPE(rela->r_info)) {
+       case R_X86_64_JMP_SLOT:
+         where = (Elf_Addr *)(obj->relocbase + rela->r_offset);
+         def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, true, NULL,
+             lockstate);
+         if (def == NULL)
+             return (-1);
+         if (ELF_ST_TYPE(def->st_info) != STT_GNU_IFUNC)
+             continue;
+         lock_release(rtld_bind_lock, lockstate);
+         target = (Elf_Addr)rtld_resolve_ifunc(defobj, def);
+         wlock_acquire(rtld_bind_lock, lockstate);
+         reloc_jmpslot(where, target, defobj, obj, (const Elf_Rel *)rela);
+         break;
+       }
+    }
+    obj->gnu_ifunc = false;
+    return (0);
+}
+
 void *__tls_get_addr(tls_index *ti)
 {
     struct tls_tcb *tcb;
index 38ffaeb..7a59323 100644 (file)
@@ -149,8 +149,10 @@ __ElfType(Auxinfo);
 #define R_386_TLS_DTPMOD32 35  /* GOT entry containing TLS index */
 #define R_386_TLS_DTPOFF32 36  /* GOT entry containing TLS offset */
 #define R_386_TLS_TPOFF32 37   /* GOT entry of -ve static TLS offset */
+#define        R_386_IRELATIVE 42      /* PLT entry resolved indirectly at runtime */
 
-#define R_386_COUNT    38      /* Count of defined relocation types. */
+
+#define R_386_COUNT    43      /* Count of defined relocation types. */
 
 /* Define "machine" characteristics */
 #define ELF_TARG_CLASS ELFCLASS32
index 1845539..6ee31d2 100644 (file)
@@ -141,8 +141,9 @@ __ElfType(Auxinfo);
 #define        R_X86_64_DTPOFF32 21    /* Offset in TLS block */
 #define        R_X86_64_GOTTPOFF 22    /* PC relative offset to IE GOT entry */
 #define        R_X86_64_TPOFF32 23     /* Offset in static TLS block */
+#define        R_X86_64_IRELATIVE 37
 
-#define        R_X86_64_COUNT  24      /* Count of defined relocation types. */
+#define        R_X86_64_COUNT  38      /* Count of defined relocation types. */
 
 /* Define "machine" characteristics */
 #if __ELF_WORD_SIZE == 32
index b7ecb7b..625cd02 100644 (file)
@@ -52,7 +52,7 @@ typedef struct {
        u_int32_t       n_type;         /* Type of this note. */
 } Elf_Note;
 
-/* Indexes into the e_ident array.  Keep synced with 
+/* Indexes into the e_ident array.  Keep synced with
    http://www.sco.com/developers/gabi/latest/ch4.eheader.html */
 #define EI_MAG0                0       /* Magic number, byte 0. */
 #define EI_MAG1                1       /* Magic number, byte 1. */
@@ -467,6 +467,7 @@ typedef struct {
 #define STT_TLS                6       /* TLS object. */
 #define STT_NUM                7
 #define STT_LOOS       10      /* Reserved range for operating system */
+#define STT_GNU_IFUNC  10
 #define STT_HIOS       12      /*   specific semantics. */
 #define STT_LOPROC     13      /* reserved range for processor */
 #define STT_HIPROC     15      /*   specific semantics. */