git: 729d2b16b74f - main - rtld-elf: Support IFUNCs on riscv

From: Jessica Clarke <jrtc27_at_FreeBSD.org>
Date: Thu, 22 Aug 2024 19:36:59 UTC
The branch main has been updated by jrtc27:

URL: https://cgit.FreeBSD.org/src/commit/?id=729d2b16b74fa5207a12aa1de190bd930432810e

commit 729d2b16b74fa5207a12aa1de190bd930432810e
Author:     Jessica Clarke <jrtc27@FreeBSD.org>
AuthorDate: 2024-08-22 19:36:44 +0000
Commit:     Jessica Clarke <jrtc27@FreeBSD.org>
CommitDate: 2024-08-22 19:36:44 +0000

    rtld-elf: Support IFUNCs on riscv
    
    GNU/Linux has historically had the following two resolver prototypes:
    
      1. Elf_Addr(uint64_t, void *)
      2. Elf_Addr(uint64_t, void *, void *)
    
    For the former, AT_HWCAP is passed in the first argument, and NULL in
    the second. For the latter, AT_HWCAP is still passed, and the second
    argument is a pointer to their home-grown __riscv_hwprobe function.
    Should they want to use the third argument in future, they'll have to
    introduce yet another prototype to allow for later expansion, and then
    all users will have to check whether the second argument is NULL to know
    if the third argument really exists. This is all rather silly and will
    surely prove fun in the face of type-checking CFI.
    
    Instead, be like arm64 and just define all 8 possible general purpose
    register arguments up front. To naive source code that forgets non-Linux
    OSes exist this will be compatible with prototype 1 above, since the
    second argument will be 0 and it won't look further (though should we
    start using the second argument for something that wouldn't be true any
    more and it might think it's __riscv_hwprobe, but that incompatibility
    is one we can defer committing to, and can choose to never adopt).
    
    Until the standard interface for querying extension information[1] is
    settled and implemented in FreeBSD there's not much you can do in a
    resolver other than use HWCAP_ISA_B, but this gets the infrastructure in
    place for when that day comes.
    
    [1] https://github.com/riscv-non-isa/riscv-c-api-doc/pull/74
    
    Reviewed by:    kib, mhorne
    MFC after:      1 month
    Differential Revision:  https://reviews.freebsd.org/D46278
---
 libexec/rtld-elf/riscv/reloc.c        | 140 ++++++++++++++++++++++++++++------
 libexec/rtld-elf/riscv/rtld_machdep.h |   5 +-
 2 files changed, 122 insertions(+), 23 deletions(-)

diff --git a/libexec/rtld-elf/riscv/reloc.c b/libexec/rtld-elf/riscv/reloc.c
index 5ea005a813cb..aa2cc97ae769 100644
--- a/libexec/rtld-elf/riscv/reloc.c
+++ b/libexec/rtld-elf/riscv/reloc.c
@@ -152,10 +152,20 @@ reloc_plt(Obj_Entry *obj, int flags __unused, RtldLockState *lockstate __unused)
 	for (rela = obj->pltrela; rela < relalim; rela++) {
 		Elf_Addr *where;
 
-		assert(ELF_R_TYPE(rela->r_info) == R_RISCV_JUMP_SLOT);
-
 		where = (Elf_Addr *)(obj->relocbase + rela->r_offset);
-		*where += (Elf_Addr)obj->relocbase;
+
+		switch (ELF_R_TYPE(rela->r_info)) {
+		case R_RISCV_JUMP_SLOT:
+			*where += (Elf_Addr)obj->relocbase;
+			break;
+		case R_RISCV_IRELATIVE:
+			obj->irelative = true;
+			break;
+		default:
+			_rtld_error("Unknown relocation type %u in PLT",
+			    (unsigned int)ELF_R_TYPE(rela->r_info));
+			return (-1);
+		}
 	}
 
 	return (0);
@@ -187,6 +197,11 @@ reloc_jmpslots(Obj_Entry *obj, int flags, RtldLockState *lockstate)
 				return (-1);
 			}
 
+			if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) {
+				obj->gnu_ifunc = true;
+				continue;
+			}
+
 			*where = (Elf_Addr)(defobj->relocbase + def->st_value);
 			break;
 		default:
@@ -199,30 +214,89 @@ reloc_jmpslots(Obj_Entry *obj, int flags, RtldLockState *lockstate)
 	return (0);
 }
 
+static void
+reloc_iresolve_one(Obj_Entry *obj, const Elf_Rela *rela,
+    RtldLockState *lockstate)
+{
+	Elf_Addr *where, target, *ptr;
+
+	ptr = (Elf_Addr *)(obj->relocbase + rela->r_addend);
+	where = (Elf_Addr *)(obj->relocbase + rela->r_offset);
+	lock_release(rtld_bind_lock, lockstate);
+	target = call_ifunc_resolver(ptr);
+	wlock_acquire(rtld_bind_lock, lockstate);
+	*where = target;
+}
+
 int
-reloc_iresolve(Obj_Entry *obj __unused,
-    struct Struct_RtldLockState *lockstate __unused)
+reloc_iresolve(Obj_Entry *obj, struct Struct_RtldLockState *lockstate)
 {
+	const Elf_Rela *relalim;
+	const Elf_Rela *rela;
+
+	if (!obj->irelative)
+		return (0);
 
-	/* XXX not implemented */
+	obj->irelative = false;
+	relalim = (const Elf_Rela *)((const char *)obj->pltrela +
+	    obj->pltrelasize);
+	for (rela = obj->pltrela; rela < relalim; rela++) {
+		if (ELF_R_TYPE(rela->r_info) == R_RISCV_IRELATIVE)
+			reloc_iresolve_one(obj, rela, lockstate);
+	}
 	return (0);
 }
 
 int
-reloc_iresolve_nonplt(Obj_Entry *obj __unused,
-    struct Struct_RtldLockState *lockstate __unused)
+reloc_iresolve_nonplt(Obj_Entry *obj, struct Struct_RtldLockState *lockstate)
 {
+	const Elf_Rela *relalim;
+	const Elf_Rela *rela;
 
-	/* XXX not implemented */
+	if (!obj->irelative_nonplt)
+		return (0);
+
+	obj->irelative_nonplt = false;
+	relalim = (const Elf_Rela *)((const char *)obj->rela + obj->relasize);
+	for (rela = obj->rela; rela < relalim; rela++) {
+		if (ELF_R_TYPE(rela->r_info) == R_RISCV_IRELATIVE)
+			reloc_iresolve_one(obj, rela, lockstate);
+	}
 	return (0);
 }
 
 int
-reloc_gnu_ifunc(Obj_Entry *obj __unused, int flags __unused,
-   struct Struct_RtldLockState *lockstate __unused)
+reloc_gnu_ifunc(Obj_Entry *obj, int flags,
+   struct Struct_RtldLockState *lockstate)
 {
+	const Elf_Rela *relalim;
+	const Elf_Rela *rela;
+	Elf_Addr *where, target;
+	const Elf_Sym *def;
+	const Obj_Entry *defobj;
+
+	if (!obj->gnu_ifunc)
+		return (0);
 
-	/* XXX not implemented */
+	relalim = (const Elf_Rela *)((const char *)obj->pltrela + obj->pltrelasize);
+	for (rela = obj->pltrela; rela < relalim; rela++) {
+		if (ELF_R_TYPE(rela->r_info) == R_RISCV_JUMP_SLOT) {
+			where = (Elf_Addr *)(obj->relocbase + rela->r_offset);
+			def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj,
+			    SYMLOOK_IN_PLT | flags, 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);
+		}
+	}
+	obj->gnu_ifunc = false;
 	return (0);
 }
 
@@ -232,7 +306,8 @@ reloc_jmpslot(Elf_Addr *where, Elf_Addr target,
     const Elf_Rel *rel)
 {
 
-	assert(ELF_R_TYPE(rel->r_info) == R_RISCV_JUMP_SLOT);
+	assert(ELF_R_TYPE(rel->r_info) == R_RISCV_JUMP_SLOT ||
+	    ELF_R_TYPE(rel->r_info) == R_RISCV_IRELATIVE);
 
 	if (*where != target && !ld_bind_not)
 		*where = target;
@@ -251,13 +326,9 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
 	const Elf_Rela *rela;
 	const Elf_Sym *def;
 	SymCache *cache;
-	Elf_Addr *where;
+	Elf_Addr *where, symval;
 	unsigned long symnum;
 
-	if ((flags & SYMLOOK_IFUNC) != 0)
-		/* XXX not implemented */
-		return (0);
-
 	/*
 	 * The dynamic loader may be called from a thread, we have
 	 * limited amounts of stack available so we cannot use alloca().
@@ -285,8 +356,27 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
 			if (def == NULL)
 				return (-1);
 
-			*where = (Elf_Addr)(defobj->relocbase + def->st_value +
-			    rela->r_addend);
+			/*
+			 * If symbol is IFUNC, only perform relocation
+			 * when caller allowed it by passing
+			 * SYMLOOK_IFUNC flag.  Skip the relocations
+			 * otherwise.
+			 */
+			if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) {
+				if ((flags & SYMLOOK_IFUNC) == 0) {
+					obj->non_plt_gnu_ifunc = true;
+					continue;
+				}
+				symval = (Elf_Addr)rtld_resolve_ifunc(defobj,
+				    def);
+			} else {
+				if ((flags & SYMLOOK_IFUNC) != 0)
+					continue;
+				symval = (Elf_Addr)(defobj->relocbase +
+				    def->st_value);
+			}
+
+			*where = symval + rela->r_addend;
 			break;
 		case R_RISCV_TLS_DTPMOD64:
 			def = find_symdef(symnum, obj, &defobj, flags, cache,
@@ -365,6 +455,9 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
 		case R_RISCV_RELATIVE:
 			*where = (Elf_Addr)(obj->relocbase + rela->r_addend);
 			break;
+		case R_RISCV_IRELATIVE:
+			obj->irelative_nonplt = true;
+			break;
 		default:
 			rtld_printf("%s: Unhandled relocation %lu\n",
 			    obj->path, ELF_R_TYPE(rela->r_info));
@@ -375,10 +468,13 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
 	return (0);
 }
 
+unsigned long elf_hwcap;
+
 void
-ifunc_init(Elf_Auxinfo *aux_info[__min_size(AT_COUNT)] __unused)
+ifunc_init(Elf_Auxinfo *aux_info[__min_size(AT_COUNT)])
 {
-
+	if (aux_info[AT_HWCAP] != NULL)
+		elf_hwcap = aux_info[AT_HWCAP]->a_un.a_val;
 }
 
 void
diff --git a/libexec/rtld-elf/riscv/rtld_machdep.h b/libexec/rtld-elf/riscv/rtld_machdep.h
index fb5f5643efc6..c6600b583612 100644
--- a/libexec/rtld-elf/riscv/rtld_machdep.h
+++ b/libexec/rtld-elf/riscv/rtld_machdep.h
@@ -83,8 +83,11 @@ Elf_Addr reloc_jmpslot(Elf_Addr *where, Elf_Addr target,
 	__asm __volatile("mv    gp, %0" :: "r"(old1));			\
 })
 
+extern unsigned long elf_hwcap;
 #define	call_ifunc_resolver(ptr) \
-	(((Elf_Addr (*)(void))ptr)())
+	(((Elf_Addr (*)(unsigned long, unsigned long, unsigned long,	\
+	    unsigned long, unsigned long, unsigned long, unsigned long,	\
+	    unsigned long))ptr)(elf_hwcap, 0, 0, 0, 0, 0, 0, 0))
 
 /*
  * TLS