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

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 5 дней назад
Родитель
Сommit
cbf69df781

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

@@ -78,6 +78,9 @@ __asm__ (
 "        addi r10, r10, 8\n"
 "        addi r10, r10, 8\n"
 "        bne r11, zero, 4b\n"
 "        bne r11, zero, 4b\n"
 "\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"
 "        /* Jump to the user's entry point.  */\n"
 "        jmp r16\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,
 elf_machine_relative (Elf32_Addr load_off, const Elf32_Addr rel_addr,
 		      Elf32_Word relative_count)
 		      Elf32_Word relative_count)
 {
 {
-	Elf32_Rel * rpnt = (void *) rel_addr;
+	Elf32_Rela * rpnt = (void *) rel_addr;
+	--rpnt;
 	do {
 	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);
 	} while (--relative_count);
 }
 }
 
 

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

@@ -30,6 +30,10 @@
 
 
 #include "ldso.h"
 #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.
 /* Program to load an ELF binary on a linux system, and run it.
    References to symbols in sharable libraries can be resolved by either
    References to symbols in sharable libraries can be resolved by either
    an ELF sharable library or a linux style of shared library. */
    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;
 			*reloc_addr = (unsigned long)tpnt->loadaddr + rpnt->r_addend;
 			break;
 			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:
 		case R_NIOS2_COPY:
 			if (symbol_addr) {
 			if (symbol_addr) {
 #if defined (__SUPPORT_LD_DEBUG__)
 #if defined (__SUPPORT_LD_DEBUG__)

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

@@ -66,6 +66,14 @@ _start:
 	/* Push rtld_fini */
 	/* Push rtld_fini */
 	stw	r4, 4(sp)
 	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.  */
 	/* Set up the GOT pointer.  */
 	nextpc	r22
 	nextpc	r22
 1:	movhi	r2, %hiadj(_gp_got - 1b)
 1:	movhi	r2, %hiadj(_gp_got - 1b)

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

@@ -5,8 +5,15 @@
    .type   _init, @function
    .type   _init, @function
 _init:
 _init:
     addi sp, sp, -8
     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
    .balign 4
    
    
@@ -17,6 +24,13 @@ _init:
    .type   _fini, @function
    .type   _fini, @function
 _fini:
 _fini:
     addi sp, sp, -8
     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
   .balign 4

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

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

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

@@ -16,7 +16,8 @@
    <http://www.gnu.org/licenses/>.  */
    <http://www.gnu.org/licenses/>.  */
 
 
 /* Value passed to 'clone' for initialization of the thread register.  */
 /* 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.	 */
 /* Get the real implementation.	 */
 #include <sysdeps/pthread/createthread.c>
 #include <sysdeps/pthread/createthread.c>