Просмотр исходного кода

nios2: make dynamic linking work

ld.so support for nios2 has been marked experimental since b30bd74db
("nios2: allow to hack on experimenting ld.so support", 2017) and never
worked: a dynamically linked program did not survive startup.  Four
things had to be fixed, all of them nios2 specific.

  * elf_machine_relative() walked ld.so's own relative relocations as
    Elf32_Rel and added the load offset to the slot's contents.  nios2 is
    a RELA target: the linker leaves the slot zeroed and puts the
    link-time value in r_addend, so every entry resolved to the bare load
    address.

  * The startup code jumped to the application entry point without
    setting r4, where crt1.S expects the rtld_fini pointer, so whatever
    _dl_start had left in that register was registered as the destructor
    to run at exit.

  * R_NIOS2_TLS_DTPMOD, _DTPREL and _TPREL fell through to the default
    case, which exits the loader, making any object with thread-local
    storage unloadable.  The offsets follow the psABI (0x7000 for the
    thread pointer, 0x8000 for the DTV) and TPREL needs the static TLS
    check so a dlopen()ed module cannot silently claim surplus static
    TLS.

  * _start left the app_init and app_fini arguments of __uClibc_main()
    untouched.  After execve the kernel hands over zeroed registers, so
    static binaries did not care, but ld.so does not zero them and its
    leftovers were called as the legacy .init/.fini functions.

crti.S needed the same treatment as glibc's: the code gcc places between
crti and crtn reaches the GOT through r22, which is caller-saved -- and
ld.so calls _init directly, so nobody set it up.  Compute it from nextpc
and save and restore it around the body.

Finally, TLS_VALUE handed clone() the address just past the thread
descriptor.  nios2 is a variant I target: the thread register points at
TLS_TCB_OFFSET past the descriptor including the pre-TCB area, so new
threads saw a thread pointer off by the size of the pthread struct and
every thread-local access in them landed outside the block.

Signed-off-by: Ramin Moussavi <lordrasmus@gmail.com>
ramin 3 дней назад
Родитель
Сommit
cbf69df781

+ 3 - 0
ldso/ldso/nios2/dl-startup.h

@@ -78,6 +78,9 @@ __asm__ (
 "        addi r10, r10, 8\n"
 "        bne r11, zero, 4b\n"
 "\n"
+"        /* Pass our FINI ptr() to the user in r4, as crt1.S expects.  */\n"
+"        ldw r4, %call(_dl_fini)(r22)\n"
+"\n"
 "        /* Jump to the user's entry point.  */\n"
 "        jmp r16\n"
 );

+ 7 - 3
ldso/ldso/nios2/dl-sysdep.h

@@ -64,11 +64,15 @@ static __always_inline void
 elf_machine_relative (Elf32_Addr load_off, const Elf32_Addr rel_addr,
 		      Elf32_Word relative_count)
 {
-	Elf32_Rel * rpnt = (void *) rel_addr;
+	Elf32_Rela * rpnt = (void *) rel_addr;
+	--rpnt;
 	do {
-		Elf32_Addr *const reloc_addr = (void *) (load_off + (rpnt)->r_offset);
+		Elf32_Addr *const reloc_addr = (void *) (load_off +
+							 (++rpnt)->r_offset);
 
-		*reloc_addr += load_off;
+		/* The linker leaves the slot zeroed; the addend carries the
+		   link-time value.  */
+		*reloc_addr = load_off + rpnt->r_addend;
 	} while (--relative_count);
 }
 

+ 20 - 0
ldso/ldso/nios2/elfinterp.c

@@ -30,6 +30,10 @@
 
 #include "ldso.h"
 
+/* The nios2 psABI biases both TLS offsets, as powerpc does.  */
+#define TLS_DTV_OFFSET 0x8000
+#define TLS_TP_OFFSET 0x7000
+
 /* Program to load an ELF binary on a linux system, and run it.
    References to symbols in sharable libraries can be resolved by either
    an ELF sharable library or a linux style of shared library. */
@@ -209,6 +213,22 @@ _dl_do_reloc(struct elf_resolve *tpnt, struct r_scope_elem *scope,
 			*reloc_addr = (unsigned long)tpnt->loadaddr + rpnt->r_addend;
 			break;
 
+#if defined USE_TLS && USE_TLS
+		case R_NIOS2_TLS_DTPMOD:
+			*reloc_addr = tls_tpnt->l_tls_modid;
+			break;
+
+		case R_NIOS2_TLS_DTPREL:
+			*reloc_addr = symbol_addr + rpnt->r_addend - TLS_DTV_OFFSET;
+			break;
+
+		case R_NIOS2_TLS_TPREL:
+			CHECK_STATIC_TLS ((struct link_map *) tls_tpnt);
+			*reloc_addr = tls_tpnt->l_tls_offset + symbol_addr
+				      + rpnt->r_addend - TLS_TP_OFFSET;
+			break;
+#endif
+
 		case R_NIOS2_COPY:
 			if (symbol_addr) {
 #if defined (__SUPPORT_LD_DEBUG__)

+ 8 - 0
libc/sysdeps/linux/nios2/crt1.S

@@ -66,6 +66,14 @@ _start:
 	/* Push rtld_fini */
 	stw	r4, 4(sp)
 
+	/* Push app_fini, and pass app_init in r7.  __uClibc_main() runs the
+	   init arrays itself; these two are the legacy .init/.fini sections.
+	   They have to be set: after exec the kernel hands us zeroed
+	   registers, but ld.so does not, so leaving them alone means calling
+	   whatever the loader left behind.  */
+	stw	zero, 0(sp)
+	mov	r7, zero
+
 	/* Set up the GOT pointer.  */
 	nextpc	r22
 1:	movhi	r2, %hiadj(_gp_got - 1b)

+ 18 - 4
libc/sysdeps/linux/nios2/crti.S

@@ -5,8 +5,15 @@
    .type   _init, @function
 _init:
     addi sp, sp, -8
-    stw  ra, 0(sp)
-    stw  fp, 4(sp)
+    /* The code gcc puts between crti and crtn reaches the GOT through r22,
+       and r22 belongs to the caller here -- ld.so calls _init directly.  So
+       set it up ourselves, as glibc's crti.S does.  */
+    stw  r22, 0(sp)
+    nextpc r22
+1:  movhi r8, %hiadj(_gp_got - 1b)
+    addi  r8, r8, %lo(_gp_got - 1b)
+    add   r22, r22, r8
+    stw  ra, 4(sp)
 
    .balign 4
    
@@ -17,6 +24,13 @@ _init:
    .type   _fini, @function
 _fini:
     addi sp, sp, -8
-    stw  ra, 0(sp)
-    stw  fp, 4(sp)
+    /* The code gcc puts between crti and crtn reaches the GOT through r22,
+       and r22 belongs to the caller here -- ld.so calls _init directly.  So
+       set it up ourselves, as glibc's crti.S does.  */
+    stw  r22, 0(sp)
+    nextpc r22
+1:  movhi r8, %hiadj(_gp_got - 1b)
+    addi  r8, r8, %lo(_gp_got - 1b)
+    add   r22, r22, r8
+    stw  ra, 4(sp)
   .balign 4

+ 4 - 4
libc/sysdeps/linux/nios2/crtn.S

@@ -1,14 +1,14 @@
 
    .section .init
 
-    ldw ra, 0(sp)
-    ldw fp, 4(sp)
+    ldw ra, 4(sp)
+    ldw r22, 0(sp)
     addi sp, sp, 8
     ret
 
    .section .fini
 
-    ldw ra, 0(sp)
-    ldw fp, 4(sp)
+    ldw ra, 4(sp)
+    ldw r22, 0(sp)
     addi sp, sp, 8
     ret

+ 2 - 1
libpthread/nptl/sysdeps/unix/sysv/linux/nios2/createthread.c

@@ -16,7 +16,8 @@
    <http://www.gnu.org/licenses/>.  */
 
 /* Value passed to 'clone' for initialization of the thread register.  */
-#define TLS_VALUE (pd + 1)
+#define TLS_VALUE ((void *) (pd) \
+		   + TLS_TCB_OFFSET + TLS_PRE_TCB_SIZE)
 
 /* Get the real implementation.	 */
 #include <sysdeps/pthread/createthread.c>