start.c (7842B)
1 /* Freestanding _start for Path E (ELF exec) tests. 2 * 3 * Convention: 4 * test_main() — primary test body; returns 0 on pass. 5 * test_post_fini() — optional post-destructor check (weak default: 0). 6 * 7 * Lifecycle: TLS setup → ctors → test_main → dtors → test_post_fini → exit. 8 * 9 * kit-ld defines: 10 * __init_array_start/end, __fini_array_start/end — sorted ctor/dtor 11 * spans (synthesized around the corresponding SHT_*_ARRAY sections). 12 * __tdata_start, __tdata_end — .tdata template 13 * bytes; identical when no TLS (length 0). 14 * __tbss_size — SK_ABS, holds the 15 * .tbss byte count as its symbol value. 16 * 17 * The TLS prologue runs unconditionally: with no TLS in the image, the 18 * three boundary symbols all read as 0 and the loop is a no-op. */ 19 20 extern int test_main(void); 21 __attribute__((weak)) int test_post_fini(void) { return 0; } 22 23 typedef void (*VoidFn)(void); 24 extern VoidFn __preinit_array_start[]; 25 extern VoidFn __preinit_array_end[]; 26 extern VoidFn __init_array_start[]; 27 extern VoidFn __init_array_end[]; 28 extern VoidFn __fini_array_start[]; 29 extern VoidFn __fini_array_end[]; 30 31 extern char __tdata_start[]; 32 extern char __tdata_end[]; 33 extern char __tbss_size[]; /* SK_ABS: address-of yields the byte count */ 34 35 /* TLS-block prologue layout — per-arch ABI dictates whether the TCB sits 36 * before or after .tdata in the thread-pointer-relative image. AArch64 37 * keeps a 16-byte reserved TCB; SysV-x86_64 uses TLS variant II (negative 38 * offsets from the thread pointer, see below); RISC-V LP64 follows variant I 39 * but points the thread pointer at the TLS image itself. */ 40 #define AARCH64_TCB_SIZE 16 41 42 /* Per-thread TLS workspace; the test harness is single-threaded so a 43 * file-scope buffer is enough. Sized generously for any test we run here. */ 44 static char g_tls_block[4096] __attribute__((aligned(16))); 45 46 /* IFUNC startup init. Mirrors rt/lib/kit/ifunc_init.c — duplicated 47 * here so the test harness doesn't need libkit_rt.a on the link 48 * line. When the linker emits a static ET_EXEC and the image 49 * contains any STT_GNU_IFUNC, layout_iplt synthesizes a .init_array 50 * entry pointing at __kit_ifunc_init; the loop in _start below 51 * walks .init_array and calls each entry, so this fills every 52 * .igot.plt slot before test_main runs. */ 53 extern void* __start_iplt_pairs[] __attribute__((weak)); 54 extern void* __stop_iplt_pairs[] __attribute__((weak)); 55 void __kit_ifunc_init(void) { 56 void** p = __start_iplt_pairs; 57 void** end = __stop_iplt_pairs; 58 if (!p || !end) return; 59 for (; p < end; p += 2) { 60 void* (*r)(void) = (void* (*)(void))p[0]; 61 void** slot = (void**)p[1]; 62 *slot = r(); 63 } 64 } 65 66 #if defined(__APPLE__) 67 /* macOS doesn't expose a stable syscall ABI — all syscalls must go 68 * through libSystem.dylib. start.c on macOS therefore calls libc 69 * `exit` rather than emitting `svc #0x80` inline; the kit Mach-O 70 * exe linker resolves the import via LC_LOAD_DYLIB libSystem.B.dylib 71 * and the dyld bind info / chained-fixups stream. */ 72 extern void exit(int) __attribute__((noreturn)); 73 #endif 74 75 __attribute__((noreturn)) static void do_exit(int code) { 76 #if defined(__APPLE__) 77 exit(code); 78 __builtin_unreachable(); 79 #elif defined(__aarch64__) 80 register long x8 __asm__("x8") = 94; /* sys_exit_group */ 81 register long x0 __asm__("x0") = code; 82 __asm__ volatile("svc #0" ::"r"(x8), "r"(x0) : "memory"); 83 #elif defined(__x86_64__) 84 register long rax __asm__("rax") = 231; /* sys_exit_group */ 85 register long rdi __asm__("rdi") = code; 86 __asm__ volatile("syscall" ::"r"(rax), "r"(rdi) : "memory"); 87 #elif defined(__riscv) && __riscv_xlen == 64 88 register long a7 __asm__("a7") = 94; /* sys_exit_group */ 89 register long a0 __asm__("a0") = code; 90 __asm__ volatile("ecall" ::"r"(a7), "r"(a0) : "memory"); 91 #else 92 #error "start.c: unsupported architecture" 93 #endif 94 __builtin_unreachable(); 95 } 96 97 static void tls_init(void) { 98 #if defined(__APPLE__) 99 /* On Darwin, tpidr_el0 is owned by libsystem/dyld; freestanding 100 * tests don't synthesize TLS roots (31_tls_local_exec is N/A on 101 * Mach-O), so the prologue is a no-op. */ 102 return; 103 #else 104 unsigned long td_n = (unsigned long)(__tdata_end - __tdata_start); 105 unsigned long bs_n = (unsigned long)(unsigned long long)__tbss_size; 106 unsigned long i; 107 /* Launder bs_n past clang's "extern char[] has non-null address" 108 * assumption — without this the .tbss zero loop is peeled and 109 * unconditionally writes one byte at tls[td_n], which on the SysV 110 * x86_64 variant II layout (TCB sits at tls[td_n]) clobbers the 111 * thread-pointer self-pointer for any TLS image with bs_n == 0. */ 112 __asm__ volatile("" : "+r"(bs_n)); 113 #if defined(__aarch64__) 114 /* Variant I (TCB first): tp -> [TCB(16) | tdata | tbss] */ 115 char* dst = g_tls_block + AARCH64_TCB_SIZE; 116 for (i = 0; i < td_n; ++i) dst[i] = __tdata_start[i]; 117 for (i = 0; i < bs_n; ++i) dst[td_n + i] = 0; 118 __asm__ volatile("msr tpidr_el0, %0" ::"r"(g_tls_block) : "memory"); 119 #elif defined(__x86_64__) 120 /* SysV TLS variant II: TLS bytes at *negative* offsets from the 121 * thread pointer (fs base). Lay out [tdata | tbss | TCB] where the 122 * TCB self-pointer sits at offset 0. The first slot of the TCB 123 * must be the thread pointer (self) per ELF ABI. */ 124 char* tcb = g_tls_block + sizeof(g_tls_block) - 64; 125 *(void**)tcb = tcb; 126 char* tls = tcb - (td_n + bs_n); 127 for (i = 0; i < td_n; ++i) tls[i] = __tdata_start[i]; 128 for (i = 0; i < bs_n; ++i) tls[td_n + i] = 0; 129 /* arch_prctl(ARCH_SET_FS, tcb): syscall 158, code 0x1002. */ 130 register long rax __asm__("rax") = 158; 131 register long rdi __asm__("rdi") = 0x1002; 132 register long rsi __asm__("rsi") = (long)tcb; 133 __asm__ volatile("syscall" 134 : "+r"(rax) 135 : "r"(rdi), "r"(rsi) 136 : "rcx", "r11", "memory"); 137 #elif defined(__riscv) && __riscv_xlen == 64 138 /* RISC-V variant I: tp points at the TLS image start and TPREL offsets are 139 * relative to that address. Keep a little guard space ahead of the image, 140 * but point tp at the same byte where the .tdata copy begins. */ 141 char* dst = g_tls_block + 16; 142 for (i = 0; i < td_n; ++i) dst[i] = __tdata_start[i]; 143 for (i = 0; i < bs_n; ++i) dst[td_n + i] = 0; 144 __asm__ volatile("mv tp, %0" ::"r"(dst) : "memory"); 145 #else 146 #error "start.c: unsupported architecture" 147 #endif 148 #endif /* !__APPLE__ */ 149 } 150 151 /* On x86_64 the kernel hands _start an rsp that is 16-aligned (so argc 152 * lands on a 16-byte boundary), but clang compiles _start as an ordinary 153 * function assuming the standard SysV contract of rsp ≡ 8 (mod 16) on 154 * entry — off by 8. force_align_arg_pointer makes the prologue realign 155 * rsp itself so every `call` downstream lands at the canonical 156 * rsp ≡ 8 (mod 16). aarch64/rv64 ABIs keep SP 16-aligned at all times, 157 * so no analogue is needed there. */ 158 #if defined(__x86_64__) 159 __attribute__((force_align_arg_pointer)) 160 #endif 161 void _start(void) { 162 VoidFn* p; 163 int result; 164 165 tls_init(); 166 167 #if defined(__APPLE__) 168 /* Mach-O: dyld walks __DATA,__mod_init_func before _start runs, so 169 * the harness must NOT walk __init_array_start/end — the boundary 170 * symbols are synthesized into the __got region (no real init array 171 * on Mach-O) and dereferencing them faults. */ 172 (void)p; 173 #else 174 /* SHT_PREINIT_ARRAY runs strictly before .init_array. kit-ld 175 * lands its synthetic __kit_ifunc_init entry here so IFUNC 176 * slots are filled before any user ctor or test_main runs. */ 177 for (p = __preinit_array_start; p != __preinit_array_end; ++p) (*p)(); 178 for (p = __init_array_start; p != __init_array_end; ++p) (*p)(); 179 #endif 180 181 result = test_main(); 182 183 #if !defined(__APPLE__) 184 for (p = __fini_array_end; p-- != __fini_array_start;) (*p)(); 185 #endif 186 187 if (result == 0) result = test_post_fini(); 188 189 do_exit(result); 190 }