From 0e63a289be0e5b68be98ed583e6cf069f47ef7fa Mon Sep 17 00:00:00 2001 From: John Marino Date: Sun, 22 Jan 2012 00:00:15 +0100 Subject: [PATCH] rtld: Add STT_GNU_IFUNC and R_MACHINE_IRELATIVE_GNU support 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 | 110 ++++++++++++++++++++++---------- libexec/rtld-elf/rtld.c | 105 +++++++++++++++++++++++++++--- libexec/rtld-elf/rtld.h | 5 ++ libexec/rtld-elf/x86_64/reloc.c | 75 ++++++++++++++++++++++ sys/cpu/i386/include/elf.h | 4 +- sys/cpu/x86_64/include/elf.h | 3 +- sys/sys/elf_common.h | 3 +- 7 files changed, 261 insertions(+), 44 deletions(-) diff --git a/libexec/rtld-elf/i386/reloc.c b/libexec/rtld-elf/i386/reloc.c index 5b0c68ae50..7fdcd1d378 100644 --- a/libexec/rtld-elf/i386/reloc.c +++ b/libexec/rtld-elf/i386/reloc.c @@ -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 * diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c index 49afbb41ee..b0c570c434 100644 --- a/libexec/rtld-elf/rtld.c +++ b/libexec/rtld-elf/rtld.c @@ -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 */ diff --git a/libexec/rtld-elf/rtld.h b/libexec/rtld-elf/rtld.h index 7101a4fc5c..a80702d224 100644 --- a/libexec/rtld-elf/rtld.h +++ b/libexec/rtld-elf/rtld.h @@ -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 /* } */ diff --git a/libexec/rtld-elf/x86_64/reloc.c b/libexec/rtld-elf/x86_64/reloc.c index d82e0d61ce..36a9982ad5 100644 --- a/libexec/rtld-elf/x86_64/reloc.c +++ b/libexec/rtld-elf/x86_64/reloc.c @@ -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; diff --git a/sys/cpu/i386/include/elf.h b/sys/cpu/i386/include/elf.h index 38ffaeba63..7a593235ec 100644 --- a/sys/cpu/i386/include/elf.h +++ b/sys/cpu/i386/include/elf.h @@ -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 diff --git a/sys/cpu/x86_64/include/elf.h b/sys/cpu/x86_64/include/elf.h index 1845539553..6ee31d24ae 100644 --- a/sys/cpu/x86_64/include/elf.h +++ b/sys/cpu/x86_64/include/elf.h @@ -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 diff --git a/sys/sys/elf_common.h b/sys/sys/elf_common.h index b7ecb7be62..625cd02f82 100644 --- a/sys/sys/elf_common.h +++ b/sys/sys/elf_common.h @@ -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. */ -- 2.41.0