kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 }