toaruos/linker/linker.c

985 lines
26 KiB
C
Raw Normal View History

2021-05-31 04:47:02 +03:00
/**
* @file linker/linker.c
* @brief ELF Dynamic Linker/Loader
2018-08-14 09:40:32 +03:00
*
* Loads ELF executables and links them at runtime to their
* shared library dependencies.
*
* As of writing, this is a simplistic and not-fully-compliant
* implementation of ELF dynamic linking. It suffers from a number
* of issues, including not actually sharing libraries (there
* isn't a sufficient mechanism in the kernel at the moment for
* doing that - we need something with copy-on-write, preferably
* an mmap-file mechanism), as well as not handling symbol
* resolution correctly.
*
* However, it's sufficient for our purposes, and works well enough
* to load Python C modules.
2021-05-31 04:47:02 +03:00
*
* @copyright
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2016-2021 K. Lange
2018-02-25 08:13:54 +03:00
*/
#include <stdlib.h>
#include <stdint.h>
#include <alloca.h>
#include <stdio.h>
#include <string.h>
2018-08-14 09:40:32 +03:00
#include <unistd.h>
2018-02-25 08:13:54 +03:00
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sysfunc.h>
2018-02-25 08:13:54 +03:00
2018-08-14 09:40:32 +03:00
#include <kernel/elf.h>
2018-02-25 08:13:54 +03:00
void * (*_malloc)(size_t size) = malloc;
void (*_free)(void * ptr) = free;
#undef malloc
#undef free
#define malloc ld_x_malloc
#define free ld_x_free
uintptr_t _malloc_minimum = 0;
static void * malloc(size_t size) {
return _malloc(size);
}
static void free(void * ptr) {
if ((uintptr_t)ptr < _malloc_minimum) return;
_free(ptr);
}
2018-08-14 09:40:32 +03:00
/*
* When the LD_DEBUG environment variable is set, TRACE_LD messages
* will be printed to stderr
*/
2018-02-25 08:13:54 +03:00
#define TRACE_APP_NAME "ld.so"
2018-02-25 11:14:43 +03:00
#define TRACE_LD(...) do { if (__trace_ld) { TRACE(__VA_ARGS__); } } while (0)
2018-02-25 08:13:54 +03:00
static int __trace_ld = 0;
2018-03-19 05:38:11 +03:00
#include <toaru/trace.h>
2018-02-25 11:14:43 +03:00
2018-08-14 09:40:32 +03:00
/*
* This libraries are included in source form to avoid having
* to build separate objects for them and complicate linking,
* since ld is specially built as a static object.
*/
2018-02-25 08:13:54 +03:00
#include "../lib/list.c"
#include "../lib/hashmap.c"
typedef int (*entry_point_t)(int, char *[], char**);
2018-08-14 09:40:32 +03:00
/* Global linking state */
2018-02-25 08:13:54 +03:00
static hashmap_t * dumb_symbol_table;
static hashmap_t * glob_dat;
static hashmap_t * objects_map;
static hashmap_t * tls_map;
static size_t current_tls_offset = 0;
2018-02-25 08:13:54 +03:00
2018-08-14 09:40:32 +03:00
/* Used for dlerror */
2018-02-25 08:13:54 +03:00
static char * last_error = NULL;
static int _target_is_suid = 0;
2018-02-25 08:13:54 +03:00
typedef struct elf_object {
FILE * file;
/* Full copy of the header. */
2021-05-31 04:47:02 +03:00
Elf64_Header header;
2018-02-25 08:13:54 +03:00
char * dyn_string_table;
size_t dyn_string_table_size;
2021-05-31 04:47:02 +03:00
Elf64_Sym * dyn_symbol_table;
2018-02-25 08:13:54 +03:00
size_t dyn_symbol_table_size;
2021-05-31 04:47:02 +03:00
Elf64_Dyn * dynamic;
Elf64_Word * dyn_hash;
2018-02-25 08:13:54 +03:00
void (*init)(void);
void (**init_array)(void);
size_t init_array_size;
uintptr_t base;
list_t * dependencies;
int loaded;
} elf_t;
static elf_t * _main_obj = NULL;
2018-08-14 09:40:32 +03:00
/* Locate library for LD_LIBRARY PATH */
2018-02-25 08:13:54 +03:00
static char * find_lib(const char * file) {
2018-08-14 09:40:32 +03:00
/* If it was an absolute path, there's no need to find it. */
2018-02-25 08:13:54 +03:00
if (strchr(file, '/')) return strdup(file);
2018-08-14 09:40:32 +03:00
/* Collect the environment variable. */
char * path = _target_is_suid ? NULL : getenv("LD_LIBRARY_PATH");
2018-02-25 08:13:54 +03:00
if (!path) {
2018-08-14 09:40:32 +03:00
/* Not set - this is the default state. Should probably read from config file? */
path = "/lib:/usr/lib";
2018-02-25 08:13:54 +03:00
}
2018-08-14 09:40:32 +03:00
/* Duplicate so we can tokenize without editing */
2018-02-25 08:13:54 +03:00
char * xpath = strdup(path);
2018-04-25 08:03:29 +03:00
char * p, * last;
2018-02-25 08:13:54 +03:00
for ((p = strtok_r(xpath, ":", &last)); p; p = strtok_r(NULL, ":", &last)) {
2018-08-14 09:40:32 +03:00
/* Go through each LD_LIBRARY_PATH entry */
2018-02-25 08:13:54 +03:00
int r;
struct stat stat_buf;
2018-08-14 09:40:32 +03:00
/* Append the requested file to that path */
2018-02-25 08:13:54 +03:00
char * exe = malloc(strlen(p) + strlen(file) + 2);
2018-08-14 09:40:32 +03:00
*exe = '\0';
strcat(exe, p);
2018-02-25 08:13:54 +03:00
strcat(exe, "/");
strcat(exe, file);
2018-08-14 09:40:32 +03:00
/* See if it exists */
2018-02-25 08:13:54 +03:00
r = stat(exe, &stat_buf);
if (r != 0) {
2018-08-14 09:40:32 +03:00
/* Nope. */
2018-02-25 08:13:54 +03:00
free(exe);
continue;
}
2018-08-14 09:40:32 +03:00
/* It exists, so this is what we want. */
2018-02-25 08:13:54 +03:00
return exe;
}
free(xpath);
2018-08-14 09:40:32 +03:00
/* No match found. */
2018-02-25 08:13:54 +03:00
return NULL;
}
2018-08-14 09:40:32 +03:00
/* Open an object file */
2018-02-25 08:13:54 +03:00
static elf_t * open_object(const char * path) {
2018-08-14 09:40:32 +03:00
/* If no path (eg. dlopen(NULL)), return the main object (the executable). */
2018-02-25 08:13:54 +03:00
if (!path) {
return _main_obj;
}
2018-08-14 09:40:32 +03:00
/* If we've already opened a file with this name, return it - don't load things twice. */
2018-02-25 08:13:54 +03:00
if (hashmap_has(objects_map, (void*)path)) {
elf_t * object = hashmap_get(objects_map, (void*)path);
return object;
}
2018-08-14 09:40:32 +03:00
/* Locate the library */
2018-02-25 08:13:54 +03:00
char * file = find_lib(path);
if (!file) {
last_error = "Could not find library.";
return NULL;
}
2018-08-14 09:40:32 +03:00
/* Open the library. */
2018-02-25 08:13:54 +03:00
FILE * f = fopen(file, "r");
2018-08-14 09:40:32 +03:00
/* Free the expanded path, we don't need it anymore. */
2018-02-25 08:13:54 +03:00
free(file);
2018-08-14 09:40:32 +03:00
/* Failed to open? Unlikely, but could mean permissions problems. */
2018-02-25 08:13:54 +03:00
if (!f) {
last_error = "Could not open library.";
return NULL;
}
2018-08-14 09:40:32 +03:00
/* Initialize a fresh object object. */
elf_t * object = malloc(sizeof(elf_t));
memset(object, 0, sizeof(elf_t));
2018-02-25 08:13:54 +03:00
hashmap_set(objects_map, (void*)path, object);
2018-08-14 09:40:32 +03:00
/* Really unlikely... */
2018-02-25 08:13:54 +03:00
if (!object) {
last_error = "Could not allocate space.";
return NULL;
}
object->file = f;
2018-08-14 09:40:32 +03:00
/* Read the header */
2021-05-31 04:47:02 +03:00
size_t r = fread(&object->header, sizeof(Elf64_Header), 1, object->file);
2018-02-25 08:13:54 +03:00
2018-08-14 09:40:32 +03:00
/* Header failed to read? */
2018-02-25 08:13:54 +03:00
if (!r) {
last_error = "Failed to read object header.";
free(object);
return NULL;
}
2018-08-14 09:40:32 +03:00
/* Is this actually an ELF object? */
2018-02-25 08:13:54 +03:00
if (object->header.e_ident[0] != ELFMAG0 ||
object->header.e_ident[1] != ELFMAG1 ||
object->header.e_ident[2] != ELFMAG2 ||
object->header.e_ident[3] != ELFMAG3) {
last_error = "Not an ELF object.";
free(object);
return NULL;
}
2018-08-14 09:40:32 +03:00
/* Prepare a list for tracking dependencies. */
2018-02-25 08:13:54 +03:00
object->dependencies = list_create();
return object;
}
2018-08-14 09:40:32 +03:00
/* Calculate the size of an object file by examining its phdrs */
2018-02-25 08:13:54 +03:00
static size_t object_calculate_size(elf_t * object) {
2021-05-31 04:47:02 +03:00
uintptr_t base_addr = (uintptr_t)-1;
2018-02-25 08:13:54 +03:00
uintptr_t end_addr = 0x0;
2018-08-14 09:40:32 +03:00
size_t headers = 0;
while (headers < object->header.e_phnum) {
2021-05-31 04:47:02 +03:00
Elf64_Phdr phdr;
2018-08-14 09:40:32 +03:00
/* Read the phdr */
fseek(object->file, object->header.e_phoff + object->header.e_phentsize * headers, SEEK_SET);
fread(&phdr, object->header.e_phentsize, 1, object->file);
switch (phdr.p_type) {
case PT_LOAD:
{
/* If this loads lower than our current base... */
if (phdr.p_vaddr < base_addr) {
base_addr = phdr.p_vaddr;
2018-02-25 08:13:54 +03:00
}
2018-08-14 09:40:32 +03:00
/* Or higher than our current end address... */
if (phdr.p_memsz + phdr.p_vaddr > end_addr) {
end_addr = phdr.p_memsz + phdr.p_vaddr;
}
}
break;
/* TODO: Do we care about other PHDR types here? */
default:
break;
2018-02-25 08:13:54 +03:00
}
2018-08-14 09:40:32 +03:00
headers++;
2018-02-25 08:13:54 +03:00
}
2018-08-14 09:40:32 +03:00
/* If base_addr is still -1, then no valid phdrs were found, and the object has no loaded size. */
2021-05-31 04:47:02 +03:00
if (base_addr == (uintptr_t)-1) return 0;
2018-02-25 08:13:54 +03:00
return end_addr - base_addr;
}
2018-08-14 09:40:32 +03:00
/* Load an object into memory */
2018-02-25 08:13:54 +03:00
static uintptr_t object_load(elf_t * object, uintptr_t base) {
uintptr_t end_addr = 0x0;
object->base = base;
2018-08-14 09:40:32 +03:00
size_t headers = 0;
while (headers < object->header.e_phnum) {
2021-05-31 04:47:02 +03:00
Elf64_Phdr phdr;
2018-08-14 09:40:32 +03:00
/* Read the phdr */
fseek(object->file, object->header.e_phoff + object->header.e_phentsize * headers, SEEK_SET);
fread(&phdr, object->header.e_phentsize, 1, object->file);
switch (phdr.p_type) {
case PT_LOAD:
{
/* Request memory to load this PHDR into */
char * args[] = {(char *)(base + phdr.p_vaddr), (char *)phdr.p_memsz};
sysfunc(TOARU_SYS_FUNC_MMAP, args);
2018-08-14 09:40:32 +03:00
/* Copy the code into memory */
fseek(object->file, phdr.p_offset, SEEK_SET);
fread((void *)(base + phdr.p_vaddr), phdr.p_filesz, 1, object->file);
/* Zero the remaining area */
size_t r = phdr.p_filesz;
while (r < phdr.p_memsz) {
*(char *)(phdr.p_vaddr + base + r) = 0;
r++;
2018-02-25 08:13:54 +03:00
}
2018-08-14 09:40:32 +03:00
/* If this expands our end address, be sure to update it */
if (end_addr < phdr.p_vaddr + base + phdr.p_memsz) {
end_addr = phdr.p_vaddr + base + phdr.p_memsz;
}
}
break;
case PT_DYNAMIC:
{
/* Keep a reference to the dynamic section, which is actually loaded by a PT_LOAD normally. */
2021-05-31 04:47:02 +03:00
object->dynamic = (Elf64_Dyn *)(base + phdr.p_vaddr);
2018-08-14 09:40:32 +03:00
}
break;
default:
break;
2018-02-25 08:13:54 +03:00
}
2018-08-14 09:40:32 +03:00
headers++;
2018-02-25 08:13:54 +03:00
}
return end_addr;
}
2018-08-14 09:40:32 +03:00
/* Perform cleanup after loading */
2018-02-25 08:13:54 +03:00
static int object_postload(elf_t * object) {
2018-08-14 09:40:32 +03:00
/* If there is a dynamic table, parse it. */
2018-02-25 08:13:54 +03:00
if (object->dynamic) {
2021-05-31 04:47:02 +03:00
Elf64_Dyn * table;
2018-02-25 08:13:54 +03:00
2018-08-14 09:40:32 +03:00
/* Locate string tables */
2018-02-25 08:13:54 +03:00
table = object->dynamic;
while (table->d_tag) {
switch (table->d_tag) {
2021-05-31 04:47:02 +03:00
case DT_HASH:
object->dyn_hash = (Elf64_Word *)(object->base + table->d_un.d_ptr);
2018-02-25 08:13:54 +03:00
object->dyn_symbol_table_size = object->dyn_hash[1];
break;
2021-05-31 04:47:02 +03:00
case DT_STRTAB:
2018-02-25 08:13:54 +03:00
object->dyn_string_table = (char *)(object->base + table->d_un.d_ptr);
break;
2021-05-31 04:47:02 +03:00
case DT_SYMTAB:
object->dyn_symbol_table = (Elf64_Sym *)(object->base + table->d_un.d_ptr);
2018-02-25 08:13:54 +03:00
break;
2021-05-31 04:47:02 +03:00
case DT_STRSZ: /* Size of string table */
2018-02-25 08:13:54 +03:00
object->dyn_string_table_size = table->d_un.d_val;
break;
2021-05-31 04:47:02 +03:00
case DT_INIT: /* DT_INIT - initialization function */
2018-02-25 08:13:54 +03:00
object->init = (void (*)(void))(table->d_un.d_ptr + object->base);
break;
2021-05-31 04:47:02 +03:00
case DT_INIT_ARRAY: /* DT_INIT_ARRAY - array of constructors */
object->init_array = (void (**)(void))(table->d_un.d_ptr + object->base);
break;
2021-05-31 04:47:02 +03:00
case DT_INIT_ARRAYSZ: /* DT_INIT_ARRAYSZ - size of the table of constructors */
object->init_array_size = table->d_un.d_val / sizeof(uintptr_t);
break;
2018-02-25 08:13:54 +03:00
}
table++;
}
2018-08-14 09:40:32 +03:00
/*
* Read through dependencies
* We have to do this separately from the above to make sure
* we have the dynamic string tables loaded first, as they
* are needed for the dependency names.
*/
2018-02-25 08:13:54 +03:00
table = object->dynamic;
while (table->d_tag) {
switch (table->d_tag) {
case 1:
list_insert(object->dependencies, object->dyn_string_table + table->d_un.d_val);
break;
}
table++;
}
}
return 0;
}
2018-08-14 09:40:32 +03:00
/* Whether symbol addresses is needed for a relocation type */
2018-02-25 08:13:54 +03:00
static int need_symbol_for_type(unsigned char type) {
switch(type) {
case 1:
case 2:
case 5:
case 6:
case 7:
case 14:
2021-05-31 04:47:02 +03:00
case R_X86_64_TPOFF64:
2018-02-25 08:13:54 +03:00
return 1;
default:
return 0;
}
}
2018-08-14 09:40:32 +03:00
/* Apply ELF relocations */
2018-02-25 08:13:54 +03:00
static int object_relocate(elf_t * object) {
2018-08-14 09:40:32 +03:00
/* If there is a dynamic symbol table, load symbols */
2018-02-25 08:13:54 +03:00
if (object->dyn_symbol_table) {
2021-05-31 04:47:02 +03:00
Elf64_Sym * table = object->dyn_symbol_table;
2018-02-25 08:13:54 +03:00
size_t i = 0;
while (i < object->dyn_symbol_table_size) {
char * symname = (char *)((uintptr_t)object->dyn_string_table + table->st_name);
2018-08-14 09:40:32 +03:00
/* If we haven't added this symbol to our symbol table, do so now. */
2018-02-25 08:13:54 +03:00
if (!hashmap_has(dumb_symbol_table, symname)) {
if (table->st_shndx) {
hashmap_set(dumb_symbol_table, symname, (void*)(table->st_value + object->base));
}
}
2018-08-14 09:40:32 +03:00
2018-02-25 08:13:54 +03:00
table++;
i++;
}
}
2018-08-14 09:40:32 +03:00
/* Find relocation table */
2018-02-25 08:13:54 +03:00
for (uintptr_t x = 0; x < object->header.e_shentsize * object->header.e_shnum; x += object->header.e_shentsize) {
2021-05-31 04:47:02 +03:00
Elf64_Shdr shdr;
2018-08-14 09:40:32 +03:00
/* Load section header */
2018-02-25 08:13:54 +03:00
fseek(object->file, object->header.e_shoff + x, SEEK_SET);
fread(&shdr, object->header.e_shentsize, 1, object->file);
2018-08-14 09:40:32 +03:00
/* Relocation table found */
2021-05-31 04:47:02 +03:00
if (shdr.sh_type == SHT_REL) {
TRACE_LD("Found a REL section, this is not handled.");
} else if (shdr.sh_type == SHT_RELA) {
Elf64_Rela * table = (Elf64_Rela *)(shdr.sh_addr + object->base);
2018-02-25 08:13:54 +03:00
while ((uintptr_t)table - ((uintptr_t)shdr.sh_addr + object->base) < shdr.sh_size) {
2021-05-31 04:47:02 +03:00
unsigned int symbol = ELF64_R_SYM(table->r_info);
unsigned int type = ELF64_R_TYPE(table->r_info);
Elf64_Sym * sym = &object->dyn_symbol_table[symbol];
2018-02-25 08:13:54 +03:00
2018-08-14 09:40:32 +03:00
/* If we need symbol for this, get it. */
2018-02-25 08:13:54 +03:00
char * symname = NULL;
uintptr_t x = sym->st_value + object->base;
if (need_symbol_for_type(type) || (type == 5)) {
symname = (char *)((uintptr_t)object->dyn_string_table + sym->st_name);
if (symname && hashmap_has(dumb_symbol_table, symname)) {
x = (uintptr_t)hashmap_get(dumb_symbol_table, symname);
sym->st_value = x;
2018-02-25 08:13:54 +03:00
} else {
2018-08-14 09:40:32 +03:00
/* This isn't fatal, but do log a message if debugging is enabled. */
2018-02-25 08:13:54 +03:00
TRACE_LD("Symbol not found: %s", symname);
x = 0x0;
}
}
/* Relocations, symbol lookups, etc. */
switch (type) {
2021-05-31 04:47:02 +03:00
case R_X86_64_GLOB_DAT: /* 6 */
if (symname && hashmap_has(glob_dat, symname)) {
x = (uintptr_t)hashmap_get(glob_dat, symname);
} /* fallthrough */
case R_X86_64_JUMP_SLOT: /* 7 */
memcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t));
break;
case R_X86_64_RELATIVE: /* 8*/
x = object->base;
x += table->r_addend;
//*((ssize_t *)(table->r_offset + object->base));
memcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t));
break;
case R_X86_64_64: /* 1 */
x += table->r_addend;
memcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t));
break;
case R_X86_64_COPY: /* 5 */
memcpy((void *)(table->r_offset + object->base), (void *)x, sym->st_size);
break;
case R_X86_64_TPOFF64:
x = *((ssize_t *)(table->r_offset + object->base));
if (!hashmap_has(tls_map, symname)) {
if (!sym->st_size) {
fprintf(stderr, "Haven't placed %s in static TLS yet but don't know its size?\n", symname);
}
current_tls_offset += sym->st_size; /* TODO alignment restrictions */
hashmap_set(tls_map, symname, (void*)(current_tls_offset));
x -= current_tls_offset;
} else {
x -= (size_t)hashmap_get(tls_map, symname);
}
memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));
break;
#if 0
2018-02-25 08:13:54 +03:00
case 6: /* GLOB_DAT */
if (symname && hashmap_has(glob_dat, symname)) {
x = (uintptr_t)hashmap_get(glob_dat, symname);
}
case 7: /* JUMP_SLOT */
memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));
break;
case 1: /* 32 */
x += *((ssize_t *)(table->r_offset + object->base));
memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));
break;
case 2: /* PC32 */
x += *((ssize_t *)(table->r_offset + object->base));
x -= (table->r_offset + object->base);
memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));
break;
case 8: /* RELATIVE */
x = object->base;
x += *((ssize_t *)(table->r_offset + object->base));
memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));
break;
case 5: /* COPY */
memcpy((void *)(table->r_offset + object->base), (void *)x, sym->st_size);
break;
case 14: /* TLS_TPOFF */
x = *((ssize_t *)(table->r_offset + object->base));
if (!hashmap_has(tls_map, symname)) {
if (!sym->st_size) {
fprintf(stderr, "Haven't placed %s in static TLS yet but don't know its size?\n", symname);
}
current_tls_offset += sym->st_size; /* TODO alignment restrictions */
hashmap_set(tls_map, symname, (void*)(current_tls_offset));
x -= current_tls_offset;
} else {
x -= (size_t)hashmap_get(tls_map, symname);
}
memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));
break;
2021-05-31 04:47:02 +03:00
#endif
2018-02-25 08:13:54 +03:00
default:
TRACE_LD("Unknown relocation type: %d", type);
}
table++;
}
}
}
return 0;
}
2018-08-14 09:40:32 +03:00
/* Copy relocations are special and need to be located before other relocations. */
2018-02-25 08:13:54 +03:00
static void object_find_copy_relocations(elf_t * object) {
2018-08-14 09:40:32 +03:00
2018-02-25 08:13:54 +03:00
for (uintptr_t x = 0; x < object->header.e_shentsize * object->header.e_shnum; x += object->header.e_shentsize) {
2021-05-31 04:47:02 +03:00
Elf64_Shdr shdr;
2018-02-25 08:13:54 +03:00
fseek(object->file, object->header.e_shoff + x, SEEK_SET);
fread(&shdr, object->header.e_shentsize, 1, object->file);
2018-08-14 09:40:32 +03:00
/* Relocation table found */
2021-05-31 04:47:02 +03:00
if (shdr.sh_type == SHT_REL) {
Elf64_Rel * table = (Elf64_Rel *)(shdr.sh_addr + object->base);
2018-02-25 08:13:54 +03:00
while ((uintptr_t)table - ((uintptr_t)shdr.sh_addr + object->base) < shdr.sh_size) {
2021-05-31 04:47:02 +03:00
unsigned int type = ELF64_R_TYPE(table->r_info);
if (type == R_X86_64_COPY) {
unsigned int symbol = ELF64_R_SYM(table->r_info);
Elf64_Sym * sym = &object->dyn_symbol_table[symbol];
char * symname = (char *)((uintptr_t)object->dyn_string_table + sym->st_name);
hashmap_set(glob_dat, symname, (void *)table->r_offset);
}
table++;
}
}
if (shdr.sh_type == SHT_RELA) {
Elf64_Rela * table = (Elf64_Rela *)(shdr.sh_addr + object->base);
while ((uintptr_t)table - ((uintptr_t)shdr.sh_addr + object->base) < shdr.sh_size) {
unsigned int type = ELF64_R_TYPE(table->r_info);
if (type == R_X86_64_COPY) {
unsigned int symbol = ELF64_R_SYM(table->r_info);
Elf64_Sym * sym = &object->dyn_symbol_table[symbol];
2018-02-25 08:13:54 +03:00
char * symname = (char *)((uintptr_t)object->dyn_string_table + sym->st_name);
hashmap_set(glob_dat, symname, (void *)table->r_offset);
}
table++;
}
}
}
}
2018-08-14 09:40:32 +03:00
/* Find a symbol in a specific object. */
2018-02-25 08:13:54 +03:00
static void * object_find_symbol(elf_t * object, const char * symbol_name) {
2018-08-14 09:40:32 +03:00
2018-02-25 08:13:54 +03:00
if (!object->dyn_symbol_table) {
last_error = "lib does not have a symbol table";
return NULL;
}
2021-05-31 04:47:02 +03:00
Elf64_Sym * table = object->dyn_symbol_table;
2018-02-25 08:13:54 +03:00
size_t i = 0;
while (i < object->dyn_symbol_table_size) {
if (!strcmp(symbol_name, (char *)((uintptr_t)object->dyn_string_table + table->st_name))) {
return (void *)(table->st_value + object->base);
}
table++;
i++;
}
last_error = "symbol not found in library";
return NULL;
}
2018-08-14 09:40:32 +03:00
/* Fully load an object. */
2018-02-25 08:13:54 +03:00
static void * do_actual_load(const char * filename, elf_t * lib, int flags) {
(void)flags;
if (!lib) {
last_error = "could not open library (not found, or other failure)";
2019-01-02 15:40:27 +03:00
TRACE_LD("could not open library");
2018-02-25 08:13:54 +03:00
return NULL;
}
size_t lib_size = object_calculate_size(lib);
2018-08-14 09:40:32 +03:00
/* Needs to be at least a page. */
2018-02-25 08:13:54 +03:00
if (lib_size < 4096) {
lib_size = 4096;
}
2018-08-14 09:40:32 +03:00
/*
* Allocate space to load the library
* This is where we should really be loading things into COW
* but we don't have the functionality available.
*/
2018-02-25 08:13:54 +03:00
uintptr_t load_addr = (uintptr_t)malloc(lib_size);
object_load(lib, load_addr);
2018-08-14 09:40:32 +03:00
/* Perform cleanup steps */
2018-02-25 08:13:54 +03:00
object_postload(lib);
2018-08-14 09:40:32 +03:00
/* Ensure dependencies are available */
2018-02-25 08:13:54 +03:00
node_t * item;
2018-04-25 08:03:29 +03:00
while ((item = list_pop(lib->dependencies))) {
2018-02-25 08:13:54 +03:00
elf_t * _lib = open_object(item->value);
2018-02-25 08:13:54 +03:00
if (!_lib) {
2018-08-14 09:40:32 +03:00
/* Missing dependencies are fatal to this process, but
* not to the entire application. */
2018-02-25 08:13:54 +03:00
free((void *)load_addr);
last_error = "Failed to load a dependency.";
lib->loaded = 0;
2019-01-02 15:40:27 +03:00
TRACE_LD("Failed to load object: %s", item->value);
2018-02-25 08:13:54 +03:00
return NULL;
}
if (!_lib->loaded) {
do_actual_load(item->value, _lib, 0);
2018-02-25 08:13:54 +03:00
TRACE_LD("Loaded %s at 0x%x", item->value, lib->base);
}
}
2018-08-14 09:40:32 +03:00
/* Perform relocations */
2018-02-25 08:13:54 +03:00
TRACE_LD("Relocating %s", filename);
object_relocate(lib);
2018-08-14 09:40:32 +03:00
/* We're done with the file. */
2018-02-25 08:13:54 +03:00
fclose(lib->file);
2018-08-14 09:40:32 +03:00
/* If there was an init_array, call everything in it */
2018-02-25 08:13:54 +03:00
if (lib->init_array) {
for (size_t i = 0; i < lib->init_array_size; i++) {
TRACE_LD(" 0x%x()", lib->init_array[i]);
lib->init_array[i]();
}
}
2018-08-14 09:40:32 +03:00
/* If the library has an init function, call that last. */
2018-02-25 08:13:54 +03:00
if (lib->init) {
lib->init();
}
lib->loaded = 1;
2018-08-14 09:40:32 +03:00
/* And return an object for the loaded library */
2018-02-25 08:13:54 +03:00
return (void *)lib;
}
static uintptr_t end_addr = 0;
/**
* Half loads an object using the dumb allocator and performs dependency
* resolution. This is a separate process from do_actual_load and dlopen_ld
* to avoid problems with malloc and library functions while loading.
* Preloaded objects will be fully loaded everything is relocated.
*/
static elf_t * preload(hashmap_t * libs, list_t * load_libs, char * lib_name) {
/* Find and open the library */
elf_t * lib = open_object(lib_name);
if (!lib) {
fprintf(stderr, "Failed to load dependency '%s'.\n", lib_name);
return NULL;
}
/* Skip already loaded libraries */
if (lib->loaded) return lib;
/* Mark this library available */
hashmap_set(libs, lib_name, lib);
TRACE_LD("Loading %s at 0x%x", lib_name, end_addr);
/* Adjust dumb allocator */
while (end_addr & 0xFFF) {
end_addr++;
}
/* Load PHDRs */
end_addr = object_load(lib, end_addr);
/* Extract information */
object_postload(lib);
/* Mark loaded */
lib->loaded = 1;
/* Verify dependencies are loaded before we relocate */
foreach(node, lib->dependencies) {
if (!hashmap_has(libs, node->value)) {
TRACE_LD("Need unloaded dependency %s", node->value);
preload(libs, load_libs, node->value);
}
}
/* Add this to the (forward scan) list of libraries to finish loading */
list_insert(load_libs, lib);
return lib;
}
2018-08-14 09:40:32 +03:00
/* exposed dlopen() method */
2018-02-25 08:13:54 +03:00
static void * dlopen_ld(const char * filename, int flags) {
TRACE_LD("dlopen(%s,0x%x)", filename, flags);
elf_t * lib = open_object(filename);
if (!lib) {
return NULL;
}
if (lib->loaded) {
return lib;
}
void * ret = do_actual_load(filename, lib, flags);
if (!ret) {
/* Dependency load failure, remove us from hash */
2019-01-02 15:40:27 +03:00
TRACE_LD("Dependency load failure");
hashmap_remove(objects_map, (void*)filename);
}
2018-02-25 08:13:54 +03:00
TRACE_LD("Loaded %s at 0x%x", filename, lib->base);
return ret;
}
2018-08-14 09:40:32 +03:00
/* exposed dlclose() method - XXX not fully implemented */
2018-02-25 08:13:54 +03:00
static int dlclose_ld(elf_t * lib) {
/* TODO close dependencies? Make sure nothing references this. */
free((void *)lib->base);
return 0;
}
2018-08-14 09:40:32 +03:00
/* exposed dlerror() method */
2018-02-25 08:13:54 +03:00
static char * dlerror_ld(void) {
char * this_error = last_error;
last_error = NULL;
return this_error;
}
2018-08-14 09:40:32 +03:00
/* Specially used by libc */
static void * _argv_value = NULL;
static char * argv_value(void) {
return _argv_value;
}
static uintptr_t dl_symbol_table_ptr_addr(void) {
return (uintptr_t)&dumb_symbol_table;
}
static uintptr_t dl_objects_table_ptr_addr(void) {
return (uintptr_t)&objects_map;
}
2018-08-14 09:40:32 +03:00
/* Exported methods (dlfcn) */
2018-02-25 08:13:54 +03:00
typedef struct {
char * name;
void * symbol;
} ld_exports_t;
ld_exports_t ld_builtin_exports[] = {
{"dlopen", dlopen_ld},
{"dlsym", object_find_symbol},
{"dlclose", dlclose_ld},
{"dlerror", dlerror_ld},
{"__get_argv", argv_value},
{"__ld_symbol_table", dl_symbol_table_ptr_addr},
{"__ld_objects_table", dl_objects_table_ptr_addr},
2018-02-25 08:13:54 +03:00
{NULL, NULL},
};
int main(int argc, char * argv[]) {
if (argc < 2) {
fprintf(stderr,
"ld.so - dynamic binary loader\n"
"\n"
"usage: %s [-e] [EXECUTABLE PATH]\n"
"\n"
" -e \033[3mAdjust argument offset\033[0m\n"
"\n", argv[0]);
return -1;
}
2018-02-25 08:13:54 +03:00
char * file = argv[1];
size_t arg_offset = 1;
if (!strcmp(argv[1], "-e")) {
arg_offset = 3;
file = argv[2];
}
_argv_value = argv+arg_offset;
2018-08-14 09:40:32 +03:00
/* Enable tracing if requested */
2018-02-25 08:13:54 +03:00
char * trace_ld_env = getenv("LD_DEBUG");
2021-05-31 04:47:02 +03:00
if (trace_ld_env && (!strcmp(trace_ld_env,"1") || !strcmp(trace_ld_env,"yes"))) {
2018-02-25 08:13:54 +03:00
__trace_ld = 1;
}
2018-08-14 09:40:32 +03:00
/* Initialize hashmaps for symbols, GLOB_DATs, and objects */
dumb_symbol_table = hashmap_create(100);
2018-02-25 08:13:54 +03:00
glob_dat = hashmap_create(10);
objects_map = hashmap_create(10);
tls_map = hashmap_create(10);
2018-02-25 08:13:54 +03:00
2018-08-14 09:40:32 +03:00
/* Setup symbols for built-in exports */
2018-02-25 08:13:54 +03:00
ld_exports_t * ex = ld_builtin_exports;
while (ex->name) {
hashmap_set(dumb_symbol_table, ex->name, ex->symbol);
ex++;
}
/* Technically there's a potential time-of-use probably if we check like this but
* this is a toy linker for a toy OS so the fact that we even need to check suid
* bits at all is outrageous
*/
struct stat buf;
if (stat(file, &buf)) {
fprintf(stderr, "%s: target binary '%s' not available\n", argv[0], file);
}
/* Technically there's a way to know we're running suid, but let's check the actual file */
if (buf.st_mode & S_ISUID) {
_target_is_suid = 1;
}
2018-08-14 09:40:32 +03:00
/* Open the requested main object */
2018-02-25 08:13:54 +03:00
elf_t * main_obj = open_object(file);
_main_obj = main_obj;
if (!main_obj) {
2018-08-14 09:40:32 +03:00
fprintf(stderr, "%s: error: failed to open object '%s'.\n", argv[0], file);
2018-02-25 08:13:54 +03:00
return 1;
}
2018-08-14 09:40:32 +03:00
/* Load the main object */
end_addr = object_load(main_obj, 0x0);
2018-02-25 08:13:54 +03:00
object_postload(main_obj);
object_find_copy_relocations(main_obj);
2018-08-14 09:40:32 +03:00
/* Load library dependencies */
2018-02-25 08:13:54 +03:00
hashmap_t * libs = hashmap_create(10);
while (end_addr & 0xFFF) {
end_addr++;
}
/* Load dependent libraries, recursively. */
2018-02-25 08:13:54 +03:00
TRACE_LD("Loading dependencies.");
list_t * load_libs = list_create();
2018-02-25 08:13:54 +03:00
node_t * item;
2018-04-25 08:03:29 +03:00
while ((item = list_pop(main_obj->dependencies))) {
2018-02-25 08:13:54 +03:00
char * lib_name = item->value;
/* Skip libg.so which is a fake library that doesn't really exist.
* XXX: Only binaries should depend on this I think? */
2018-02-25 08:13:54 +03:00
if (!strcmp(lib_name, "libg.so")) goto nope;
2018-08-14 09:40:32 +03:00
/* Preload library */
elf_t * lib = preload(libs, load_libs, lib_name);
2018-02-25 08:13:54 +03:00
/* Failed to load */
if (!lib) return 1;
nope:
free(item);
}
list_t * ctor_libs = list_create();
list_t * init_libs = list_create();
while ((item = list_dequeue(load_libs))) {
elf_t * lib = item->value;
/* Complete relocation */
2018-02-25 08:13:54 +03:00
object_relocate(lib);
/* Close the underlying file */
2018-02-25 08:13:54 +03:00
fclose(lib->file);
2018-08-14 09:40:32 +03:00
/* Store constructors for later execution */
if (lib->init_array) {
2018-02-25 08:13:54 +03:00
list_insert(ctor_libs, lib);
}
if (lib->init) {
list_insert(init_libs, lib);
}
free(item);
}
2018-08-14 09:40:32 +03:00
/* Relocate the main object */
2018-02-25 08:13:54 +03:00
TRACE_LD("Relocating main object");
object_relocate(main_obj);
fclose(main_obj->file);
2018-02-25 08:13:54 +03:00
TRACE_LD("Placing heap at end");
while (end_addr & 0xFFF) {
end_addr++;
}
/* Move heap start (kind of like a weird sbrk) */
{
char * args[] = {(char*)end_addr};
sysfunc(TOARU_SYS_FUNC_SETHEAP, args);
}
2018-08-14 09:40:32 +03:00
/* Call constructors for loaded dependencies */
2018-02-25 08:13:54 +03:00
char * ld_no_ctors = getenv("LD_DISABLE_CTORS");
if (ld_no_ctors && (!strcmp(ld_no_ctors,"1") || !strcmp(ld_no_ctors,"yes"))) {
TRACE_LD("skipping ctors because LD_DISABLE_CTORS was set");
} else {
foreach(node, ctor_libs) {
elf_t * lib = node->value;
if (lib->init_array) {
TRACE_LD("Executing init_array...");
for (size_t i = 0; i < lib->init_array_size; i++) {
TRACE_LD(" 0x%x()", lib->init_array[i]);
lib->init_array[i]();
}
}
}
}
foreach(node, init_libs) {
elf_t * lib = node->value;
lib->init();
}
2018-08-14 09:40:32 +03:00
/* If main object had constructors, call them. */
2018-02-25 08:13:54 +03:00
if (main_obj->init_array) {
for (size_t i = 0; i < main_obj->init_array_size; i++) {
TRACE_LD(" 0x%x()", main_obj->init_array[i]);
main_obj->init_array[i]();
}
}
if (main_obj->init) {
main_obj->init();
}
main_obj->loaded = 1;
/* Set heap functions for later usage */
if (hashmap_has(dumb_symbol_table, "malloc")) _malloc = hashmap_get(dumb_symbol_table, "malloc");
if (hashmap_has(dumb_symbol_table, "free")) _free = hashmap_get(dumb_symbol_table, "free");
_malloc_minimum = 0x40000000;
2018-08-14 09:40:32 +03:00
/* Jump to the entry for the main object */
2021-05-31 04:47:02 +03:00
TRACE_LD("Jumping to entry point 0x%lx", main_obj->header.e_entry);
2018-02-25 08:13:54 +03:00
entry_point_t entry = (entry_point_t)main_obj->header.e_entry;
entry(argc-arg_offset,argv+arg_offset,environ);
return 0;
}