| /* | |
| * Copyright 2019, Data61 | |
| * Commonwealth Scientific and Industrial Research Organisation (CSIRO) | |
| * ABN 41 687 119 230. | |
| * | |
| * This software may be distributed and modified according to the terms of | |
| * the BSD 2-Clause license. Note that NO WARRANTY is provided. | |
| * See "LICENSE_BSD2.txt" for details. | |
| * | |
| * @TAG(DATA61_BSD) | |
| */ | |
| #include <sel4runtime.h> | |
| #include <sel4runtime/auxv.h> | |
| #include <sel4runtime/mode/elf.h> | |
| #include <sel4runtime/start.h> | |
| #include <sel4/sel4.h> | |
| #include <sel4runtime/gen_config.h> | |
| #include <autoconf.h> | |
| #include "init.h" | |
| #include "util.h" | |
| // Minimum alignment across all platforms. | |
| #define MIN_ALIGN_BYTES 16 | |
| #define MIN_ALIGNED __attribute__((aligned (MIN_ALIGN_BYTES))) | |
| // Global vsyscall handler (defined in musllibc) | |
| extern sel4runtime_size_t __sysinfo; | |
| // Static TLS for initial thread. | |
| static char static_tls[CONFIG_SEL4RUNTIME_STATIC_TLS] MIN_ALIGNED = {}; | |
| // Thread lookup pointers. | |
| typedef struct { | |
| sel4runtime_uintptr_t tls_base; | |
| } thread_lookup_t; | |
| // The seL4 runtime environment. | |
| static struct { | |
| char const *process_name; | |
| // optional bootinfo pointer. | |
| seL4_BootInfo *bootinfo; | |
| /* | |
| * The initial thread object is initially set to a static thread | |
| * object. It is only used until a TLS is set up for the first | |
| * thread. | |
| * | |
| * Once the TLS has been initialised for the first thread, this is | |
| * then set to NULL and the thread local reference should be used. | |
| */ | |
| sel4runtime_uintptr_t initial_thread_tls_base; | |
| seL4_CPtr initial_thread_tcb; | |
| seL4_IPCBuffer *initial_thread_ipc_buffer; | |
| // ELF Headers | |
| struct { | |
| sel4runtime_size_t count; | |
| sel4runtime_size_t size; | |
| Elf_Phdr *headers; | |
| } program_header; | |
| // TLS images | |
| struct { | |
| // The location of the initial image in memory. | |
| void *image; | |
| // The size of the initial image in memory. | |
| sel4runtime_size_t image_size; | |
| // The size needed to store the full TLS. | |
| sel4runtime_size_t memory_size; | |
| // The size needed to store the TLS and the thread structure. | |
| sel4runtime_size_t region_size; | |
| // Alignment needed for the TLS data. | |
| sel4runtime_size_t align; | |
| // Offset of the TLS data from the thread pointer. | |
| sel4runtime_size_t offset; | |
| } tls; | |
| // Argument vector | |
| char const *const *argv; | |
| int argc; | |
| // Auxiliary vector | |
| auxv_t const *auxv; | |
| // Environment vector | |
| char const *const *envp; | |
| // Exit callbacks | |
| sel4runtime_exit_cb *exit_cb; | |
| sel4runtime_pre_exit_cb *pre_exit_cb; | |
| } env = { | |
| /* | |
| * Initialise the initial thread as referring to the global thread | |
| * object. | |
| */ | |
| .initial_thread_tls_base = (sel4runtime_uintptr_t)SEL4RUNTIME_NULL, | |
| }; | |
| static void name_process(char const *name); | |
| static void parse_auxv(auxv_t const auxv[]); | |
| static void parse_phdrs(void); | |
| static void load_tls_data(Elf_Phdr *header); | |
| static void try_init_static_tls(void); | |
| static void copy_tls_data(unsigned char *tls); | |
| static sel4runtime_uintptr_t tls_base_from_tls_region(unsigned char *tls_region); | |
| static unsigned char *tls_from_tls_base(sel4runtime_uintptr_t tls_base); | |
| static unsigned char *tls_from_tls_region(unsigned char *tls_region); | |
| static thread_lookup_t *thread_lookup_from_tls_region(unsigned char *tls_region); | |
| static const sel4runtime_size_t tls_region_size(sel4runtime_size_t mem_size, sel4runtime_size_t align); | |
| static void empty_tls(void); | |
| static int is_initial_thread(void); | |
| char const *sel4runtime_process_name(void) | |
| { | |
| return env.process_name; | |
| } | |
| char const *const *sel4runtime_argv(void) | |
| { | |
| return env.argv; | |
| } | |
| int sel4runtime_argc(void) | |
| { | |
| return env.argc; | |
| } | |
| char const *const *sel4runtime_envp(void) | |
| { | |
| return env.envp; | |
| } | |
| auxv_t const *sel4runtime_auxv(void) | |
| { | |
| return env.auxv; | |
| } | |
| seL4_BootInfo *sel4runtime_bootinfo(void) | |
| { | |
| return env.bootinfo; | |
| } | |
| sel4runtime_size_t sel4runtime_get_tls_size(void) | |
| { | |
| return env.tls.region_size; | |
| } | |
| int sel4runtime_initial_tls_enabled(void) | |
| { | |
| /* | |
| * If the TLS for the initial process has been activated, the thread | |
| * object in the TLS will be used rather than the static thread | |
| * object. | |
| */ | |
| return env.initial_thread_tls_base != (sel4runtime_uintptr_t)SEL4RUNTIME_NULL; | |
| } | |
| sel4runtime_uintptr_t sel4runtime_write_tls_image(void *tls_memory) | |
| { | |
| if (tls_memory == SEL4RUNTIME_NULL) { | |
| return (sel4runtime_uintptr_t)SEL4RUNTIME_NULL; | |
| } | |
| copy_tls_data(tls_memory); | |
| return tls_base_from_tls_region(tls_memory); | |
| } | |
| sel4runtime_uintptr_t sel4runtime_move_initial_tls(void *tls_memory) | |
| { | |
| if (tls_memory == SEL4RUNTIME_NULL) { | |
| return (sel4runtime_uintptr_t)SEL4RUNTIME_NULL; | |
| } | |
| sel4runtime_uintptr_t tls_base = sel4runtime_write_tls_image(tls_memory); | |
| if (tls_base == (sel4runtime_uintptr_t)SEL4RUNTIME_NULL) { | |
| return (sel4runtime_uintptr_t)SEL4RUNTIME_NULL; | |
| } | |
| sel4runtime_set_tls_base(tls_base); | |
| if (env.initial_thread_ipc_buffer != SEL4RUNTIME_NULL) { | |
| __sel4_ipc_buffer = env.initial_thread_ipc_buffer; | |
| } | |
| env.initial_thread_tls_base = tls_base; | |
| #if defined(CONFIG_DEBUG_BUILD) | |
| if (env.initial_thread_tcb && env.initial_thread_ipc_buffer && env.process_name) { | |
| // The thread can only be named after the TLS is initialised | |
| // and if an IPC buffer is present. | |
| seL4_DebugNameThread(env.initial_thread_tcb, env.process_name); | |
| } | |
| #endif | |
| return env.initial_thread_tls_base; | |
| } | |
| sel4runtime_exit_cb *sel4runtime_set_exit(sel4runtime_exit_cb *cb) | |
| { | |
| sel4runtime_exit_cb *old = env.exit_cb; | |
| env.exit_cb = cb; | |
| return old; | |
| } | |
| sel4runtime_pre_exit_cb *sel4runtime_set_pre_exit(sel4runtime_pre_exit_cb *cb) | |
| { | |
| sel4runtime_pre_exit_cb *old = env.pre_exit_cb; | |
| env.pre_exit_cb = cb; | |
| return old; | |
| } | |
| void sel4runtime_exit(int code) | |
| { | |
| if (env.pre_exit_cb != SEL4RUNTIME_NULL) { | |
| code = env.pre_exit_cb(code); | |
| } | |
| __sel4runtime_run_destructors(); | |
| /* If the exit is never set this will try and call a NULL function | |
| * pointer which should result in a fault. This is as good a way as | |
| * any to exit the process if we don't know anything better about | |
| * the environment. */ | |
| env.exit_cb(code); | |
| } | |
| int __sel4runtime_write_tls_variable( | |
| sel4runtime_uintptr_t dest_tls_base, | |
| unsigned char *local_tls_dest, | |
| unsigned char *src, | |
| sel4runtime_size_t bytes | |
| ) | |
| { | |
| sel4runtime_uintptr_t local_tls_base = sel4runtime_get_tls_base(); | |
| unsigned char *local_tls = tls_from_tls_base(local_tls_base); | |
| sel4runtime_size_t offset = local_tls_dest - local_tls; | |
| sel4runtime_size_t tls_size = env.tls.memory_size; | |
| // Write must not go past end of TLS. | |
| if (offset > tls_size || offset + bytes > tls_size) { | |
| return -1; | |
| } | |
| unsigned char *dest_tls = tls_from_tls_base(dest_tls_base); | |
| unsigned char *dest_addr = dest_tls + offset; | |
| __sel4runtime_memcpy(dest_addr, src, bytes); | |
| return 0; | |
| } | |
| void __sel4runtime_load_env( | |
| int argc, | |
| char const *const *argv, | |
| char const *const *envp, | |
| auxv_t const auxv[] | |
| ) | |
| { | |
| empty_tls(); | |
| parse_auxv(auxv); | |
| parse_phdrs(); | |
| if (argc > 0) { | |
| name_process(argv[0]); | |
| } | |
| try_init_static_tls(); | |
| env.argc = argc; | |
| env.argv = argv; | |
| env.auxv = auxv; | |
| env.envp = envp; | |
| __sel4runtime_run_constructors(); | |
| } | |
| static void name_process(char const *name) | |
| { | |
| env.process_name = name; | |
| while (name && *name != '\0') { | |
| if (*name == '/') { | |
| env.process_name = name + 1; | |
| } | |
| name++; | |
| } | |
| } | |
| static void parse_auxv(auxv_t const auxv[]) | |
| { | |
| for (int i = 0; auxv[i].a_type != AT_NULL; i++) { | |
| switch (auxv[i].a_type) { | |
| case AT_PHENT: { | |
| env.program_header.size = auxv[i].a_un.a_val; | |
| break; | |
| } | |
| case AT_PHNUM: { | |
| env.program_header.count = auxv[i].a_un.a_val; | |
| break; | |
| } | |
| case AT_PHDR: { | |
| env.program_header.headers = auxv[i].a_un.a_ptr; | |
| break; | |
| } | |
| case AT_SYSINFO: { | |
| __sysinfo = (sel4runtime_size_t)(auxv[i].a_un.a_ptr); | |
| break; | |
| } | |
| case AT_SEL4_BOOT_INFO: { | |
| seL4_BootInfo *bootinfo = auxv[i].a_un.a_ptr; | |
| if (bootinfo == SEL4RUNTIME_NULL) { | |
| break; | |
| } | |
| env.bootinfo = bootinfo; | |
| env.initial_thread_ipc_buffer = bootinfo->ipcBuffer; | |
| env.initial_thread_tcb = seL4_CapInitThreadTCB; | |
| break; | |
| } | |
| case AT_SEL4_IPC_BUFFER_PTR: { | |
| env.initial_thread_ipc_buffer = auxv[i].a_un.a_ptr; | |
| break; | |
| } | |
| case AT_SEL4_TCB: { | |
| env.initial_thread_tcb = auxv[i].a_un.a_val; | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| static void parse_phdrs(void) | |
| { | |
| for (sel4runtime_size_t h = 0; h < env.program_header.count; h++) { | |
| Elf_Phdr *header = &env.program_header.headers[h]; | |
| switch (header->p_type) { | |
| case PT_TLS: | |
| load_tls_data(header); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| static void load_tls_data(Elf_Phdr *header) | |
| { | |
| env.tls.image = (void *) header->p_vaddr; | |
| if (header->p_align > MIN_ALIGN_BYTES) { | |
| env.tls.align = header->p_align; | |
| } else { | |
| env.tls.align = MIN_ALIGN_BYTES; | |
| } | |
| env.tls.image_size = header->p_filesz; | |
| env.tls.memory_size = ROUND_UP(header->p_memsz, header->p_align); | |
| env.tls.region_size = tls_region_size( | |
| env.tls.memory_size, | |
| env.tls.align | |
| ); | |
| } | |
| static void try_init_static_tls(void) | |
| { | |
| if (env.tls.region_size <= sizeof(static_tls)) { | |
| sel4runtime_move_initial_tls(static_tls); | |
| } | |
| } | |
| static void copy_tls_data(unsigned char *tls_region) | |
| { | |
| unsigned char *tls = tls_from_tls_region(tls_region); | |
| __sel4runtime_memcpy(tls, env.tls.image, env.tls.image_size); | |
| unsigned char *tbss = &tls[env.tls.image_size]; | |
| __sel4runtime_memset(tbss, 0, env.tls.memory_size - env.tls.image_size); | |
| thread_lookup_t *lookup = thread_lookup_from_tls_region(tls_region); | |
| if (lookup != SEL4RUNTIME_NULL) { | |
| lookup->tls_base = tls_base_from_tls_region(tls_region); | |
| } | |
| } | |
| static sel4runtime_uintptr_t tls_base_from_tls_region(unsigned char *tls_region) | |
| { | |
| sel4runtime_uintptr_t tls_base = (sel4runtime_uintptr_t)tls_region; | |
| #if !defined(TLS_ABOVE_TP) | |
| tls_base += env.tls.memory_size; | |
| #endif | |
| return ROUND_UP(tls_base, env.tls.align); | |
| } | |
| static unsigned char *tls_from_tls_base(sel4runtime_uintptr_t tls_base) | |
| { | |
| sel4runtime_uintptr_t tls_addr = tls_base; | |
| #if !defined(TLS_ABOVE_TP) | |
| tls_addr -= env.tls.memory_size; | |
| #endif | |
| #if defined(GAP_ABOVE_TP) | |
| tls_addr += GAP_ABOVE_TP; | |
| #endif | |
| return (unsigned char *)tls_addr; | |
| } | |
| static unsigned char *tls_from_tls_region(unsigned char *tls_region) | |
| { | |
| return tls_from_tls_base(tls_base_from_tls_region(tls_region)); | |
| } | |
| static thread_lookup_t *thread_lookup_from_tls_region( | |
| unsigned char *tls_region | |
| ) | |
| { | |
| #if !defined(TLS_ABOVE_TP) | |
| return (thread_lookup_t *)tls_base_from_tls_region(tls_region); | |
| #else | |
| return SEL4RUNTIME_NULL; | |
| #endif | |
| } | |
| static const sel4runtime_size_t tls_region_size(sel4runtime_size_t mem_size, sel4runtime_size_t align) | |
| { | |
| return align | |
| + ROUND_UP(sizeof(thread_lookup_t), align) | |
| #if defined(GAP_ABOVE_TP) | |
| + ROUND_UP(GAP_ABOVE_TP, align) | |
| #endif | |
| + ROUND_UP(mem_size, align); | |
| } | |
| static void empty_tls(void) | |
| { | |
| env.tls.image = SEL4RUNTIME_NULL; | |
| env.tls.align = MIN_ALIGN_BYTES; | |
| env.tls.image_size = 0; | |
| env.tls.memory_size = 0; | |
| env.tls.region_size = tls_region_size( | |
| env.tls.memory_size, | |
| env.tls.align | |
| ); | |
| } | |
| /* | |
| * Check if the executing thread is the inital thread of the process. | |
| * | |
| * This will optimistically assume that the current thread is the | |
| * initial thread of no thread ever had TLS configured. | |
| */ | |
| static int is_initial_thread(void) | |
| { | |
| return env.initial_thread_tls_base == (sel4runtime_uintptr_t)SEL4RUNTIME_NULL | |
| || sel4runtime_get_tls_base() == env.initial_thread_tls_base; | |
| } |