blob: f00bf61cc0bf559be89e9c0c7bf66b59bbe0768d [file] [log] [blame]
/*
* 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)))
// 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_SEL4_BOOT_INFO: {
env.bootinfo = auxv[i].a_un.a_ptr;
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;
}