Files
rosetta/sys/ld/elf.c

801 lines
16 KiB
C

#include "elf.h"
#include "resolve.h"
#include <errno.h>
#include <fcntl.h>
#include <mango/config.h>
#include <mango/handle.h>
#include <mango/log.h>
#include <mango/vm.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define NEEDS_NOTHING 0
#define NEEDS_VDSO 1
#define NEEDS_MORE 2
#define ACL (PF_R | PF_W | PF_X)
#define ACCESS(x) ((x) & ACL)
/* TODO in case we ever support ELF32 images */
#define elf_class_bits(x) (64)
#define PAGE_SIZE (image->e_page_size)
#define PAGE_MASK (image->e_page_size - 1)
#define PAGE_OFFSET(v) ((v) & (PAGE_SIZE - 1))
#define PAGE_ALIGN_DOWN(v) (v) &= ~(PAGE_SIZE - 1)
#define PAGE_ALIGN_UP(v) \
do { \
if ((v) & (PAGE_SIZE - 1)) { \
v &= ~(PAGE_SIZE - 1); \
v += PAGE_SIZE; \
} \
} while (0)
#undef DEBUG_LOG
const char *elf_image_status_to_string(enum elf_image_status status)
{
#define ENUM_STR(s) \
case s: \
return #s
switch (status) {
ENUM_STR(ELF_IMAGE_NONE);
ENUM_STR(ELF_IMAGE_OPEN);
ENUM_STR(ELF_IMAGE_PARSED);
ENUM_STR(ELF_IMAGE_LOADED);
ENUM_STR(ELF_IMAGE_LINKED);
default:
return "UNKNOWN";
}
#undef ENUM_STR
}
static bool elf_validate_ehdr(elf_ehdr_t *hdr)
{
if (hdr->e_ident[EI_MAG0] != ELF_MAG0) {
return false;
}
if (hdr->e_ident[EI_MAG1] != ELF_MAG1) {
return false;
}
if (hdr->e_ident[EI_MAG2] != ELF_MAG2) {
return false;
}
if (hdr->e_ident[EI_MAG3] != ELF_MAG3) {
return false;
}
if (hdr->e_ident[EI_CLASS] != ELFCLASS64) {
return false;
}
if (hdr->e_machine != EM_X86_64) {
return false;
}
if (hdr->e_ident[EI_DATA] != ELFDATA2LSB) {
return false;
}
if (hdr->e_ident[EI_VERSION] != EV_CURRENT) {
return false;
}
return true;
}
static int map_image(struct elf_image *image)
{
elf_phdr_t phdr;
size_t r = 0;
size_t data_offset = 0;
for (size_t i = 0; i < image->e_hdr.e_phnum; i++) {
off_t phdr_offset
= image->e_hdr.e_phoff + (i * image->e_hdr.e_phentsize);
lseek(image->e_fd, phdr_offset, SEEK_SET);
int r = read(image->e_fd, &phdr, sizeof phdr);
if (r < 0) {
return -r;
}
if (r != sizeof phdr) {
return ENOEXEC;
}
if (phdr.p_type != PT_LOAD) {
continue;
}
int prot = 0;
size_t offset = phdr.p_offset & ~PAGE_MASK;
phdr.p_flags &PF_R && (prot |= PROT_READ);
phdr.p_flags &PF_W && (prot |= PROT_WRITE);
phdr.p_flags &PF_X && (prot |= PROT_EXEC);
virt_addr_t vaddr = phdr.p_vaddr;
virt_addr_t vlimit = phdr.p_vaddr + phdr.p_memsz;
if (vaddr & PAGE_MASK) {
vaddr &= ~PAGE_MASK;
}
if (vlimit & PAGE_MASK) {
vlimit &= ~PAGE_MASK;
vlimit += PAGE_SIZE;
}
if (image->e_hdr.e_type == ET_DYN) {
vaddr += image->e_base;
vlimit += image->e_base;
}
int fd = image->e_fd;
int flags = MAP_SHARED | MAP_EXECUTABLE | MAP_FIXED;
if (phdr.p_flags & PF_W) {
fd = -1;
flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED;
offset = 0;
}
void *p
= mmap((void *)vaddr,
vlimit - vaddr,
prot,
flags,
fd,
offset);
if (p == MAP_FAILED) {
return EIO;
}
kern_tracef(
"mapped PHDR %u [%zx-%zx] at %p",
i,
phdr.p_vaddr,
phdr.p_vaddr + phdr.p_memsz,
p);
if (phdr.p_flags & PF_W) {
lseek(image->e_fd, phdr.p_offset, SEEK_SET);
void *dst = (void *)image->e_base + phdr.p_vaddr;
r = read(image->e_fd, dst, phdr.p_filesz);
if (r < 0) {
return -r;
}
}
}
return SUCCESS;
}
static int parse_phdr(struct elf_image *image)
{
elf_phdr_t phdr;
size_t r = 0;
image->e_length = 0;
image->e_data_length = 0;
off_t vaddr, vlimit;
for (size_t i = 0; i < image->e_hdr.e_phnum; i++) {
off_t phdr_offset
= image->e_hdr.e_phoff + (i * image->e_hdr.e_phentsize);
lseek(image->e_fd, phdr_offset, SEEK_SET);
int r = read(image->e_fd, &phdr, sizeof phdr);
if (r < 0) {
return -r;
}
if (r != sizeof phdr) {
return ENOEXEC;
}
vaddr = phdr.p_vaddr;
vlimit = phdr.p_vaddr + phdr.p_memsz;
if (vaddr & (PAGE_SIZE - 1)) {
vaddr &= ~(PAGE_SIZE - 1);
}
if (vlimit & (PAGE_SIZE - 1)) {
vlimit &= ~(PAGE_SIZE - 1);
vlimit += PAGE_SIZE;
}
switch (phdr.p_type) {
case PT_DYNAMIC:
image->e_dynamic = phdr;
break;
case PT_LOAD:
image->e_length = MAX(image->e_length, vlimit);
break;
#if 0
case PT_INTERP: {
size_t r = 0;
vm_object_read(
image->e_image,
image->e_interp,
phdr.p_offset,
MIN(sizeof image->e_interp - 1, phdr.p_filesz),
&r);
image->e_interp[r] = 0;
break;
}
#endif
default:
break;
}
if (phdr.p_flags & PF_W) {
image->e_data_length
= MAX(image->e_data_length, vlimit - vaddr);
}
}
return SUCCESS;
}
#if 1
static elf_sym_t *get_dynsym(struct elf_image *image, size_t index)
{
elf_sym_t *sym = (elf_sym_t *)(image->e_base + image->e_dynsym
+ (index * image->e_dynsym_entsize));
if (!sym->st_value) {
return NULL;
}
return sym;
}
static void resolve_symbol(unsigned int slot)
{
kern_tracef("request for symbol %u", slot);
}
static int do_rela(struct elf_image *image, elf_rela_t *rela, bool lazy)
{
kern_tracef(
"do_rela(%p, %d, %d, %d)",
image,
rela->r_info,
rela->r_addend,
rela->r_offset);
int type = ELF64_R_TYPE(rela->r_info);
elf_sym_t *sym = NULL;
switch (type) {
case R_X86_64_JUMP_SLOT:
*(uint64_t *)(image->e_base + rela->r_offset) += image->e_base;
kern_tracef(
"JUMP_SLOT: offset=%zx, symbol=%zu, addend=%zx",
rela->r_offset,
ELF64_R_SYM(rela->r_info),
rela->r_addend);
break;
case R_X86_64_RELATIVE:
*(uint64_t *)(image->e_base + rela->r_offset)
= image->e_base + rela->r_addend;
kern_tracef(
"RELATIVE: offset=%zx, addend=%zx",
rela->r_offset,
rela->r_addend);
break;
default:
kern_log("Unknown relocation type");
return ENOEXEC;
}
return SUCCESS;
}
static int relocate_pltrel(
struct elf_image *image,
off_t offset,
size_t size,
size_t entsize)
{
size_t entries = size / entsize;
elf_rela_t *rela = (elf_rela_t *)(image->e_base + offset);
int status = SUCCESS;
for (size_t i = 0; i < entries; i++) {
status = do_rela(image, rela, true);
if (status != SUCCESS) {
break;
}
rela = (elf_rela_t *)((char *)rela + entsize);
}
return status;
}
static int relocate_rela(
struct elf_image *image,
off_t offset,
size_t size,
size_t entsize)
{
size_t entries = size / entsize;
elf_rela_t *rela = (elf_rela_t *)(image->e_base + offset);
int status = SUCCESS;
for (size_t i = 0; i < entries; i++) {
status = do_rela(image, rela, false);
if (status != SUCCESS) {
break;
}
rela = (elf_rela_t *)((char *)rela + entsize);
}
return status;
}
static int relocate_rel(
struct elf_image *image,
off_t offset,
size_t size,
size_t entsize)
{
return ENOEXEC;
}
static int do_rel(
struct elf_image *image,
off_t offset,
size_t size,
size_t entsize)
{
kern_tracef("do_rel (unsupported)");
return ENOEXEC;
}
#endif
static int load_dependency(struct elf_image *image, const char *name)
{
kern_tracef("required library: %s", name);
return ENOEXEC;
}
static int parse_dynamic(struct elf_image *image)
{
if (image->e_dynamic.p_type != PT_DYNAMIC) {
return SUCCESS;
}
image->e_dyn = (elf_dyn_t *)(image->e_base + image->e_dynamic.p_vaddr);
int status = SUCCESS;
size_t nr_dyn = image->e_dynamic.p_filesz / sizeof *image->e_dyn;
for (size_t i = 0; i < nr_dyn; i++) {
if (image->e_dyn[i].d_tag == DT_NULL) {
break;
}
switch (image->e_dyn[i].d_tag) {
case DT_NEEDED:
image->e_nr_links++;
break;
case DT_STRTAB:
image->e_strtab = image->e_dyn[i].d_un.d_ptr;
break;
case DT_SYMTAB:
image->e_dynsym = image->e_dyn[i].d_un.d_ptr;
break;
case DT_SYMENT:
image->e_dynsym_entsize = image->e_dyn[i].d_un.d_val;
break;
case DT_PLTGOT:
image->e_got_plt = image->e_dyn[i].d_un.d_val;
break;
case DT_HASH:
image->e_hash_type = ELF_HASH_STANDARD;
image->e_hash_table = image->e_dyn[i].d_un.d_ptr;
break;
case DT_GNU_HASH:
image->e_hash_type = ELF_HASH_GNU;
image->e_hash_table = image->e_dyn[i].d_un.d_ptr;
break;
case DT_REL:
image->e_rel_offset[ELF_RT_REL]
= image->e_dyn[i].d_un.d_ptr;
break;
case DT_RELSZ:
image->e_rel_size[ELF_RT_REL]
= image->e_dyn[i].d_un.d_val;
break;
case DT_RELENT:
image->e_rel_entsize[ELF_RT_REL]
= image->e_dyn[i].d_un.d_val;
break;
case DT_RELA:
image->e_rel_offset[ELF_RT_RELA]
= image->e_dyn[i].d_un.d_ptr;
break;
case DT_RELASZ:
image->e_rel_size[ELF_RT_RELA]
= image->e_dyn[i].d_un.d_val;
break;
case DT_RELAENT:
image->e_rel_entsize[ELF_RT_RELA]
= image->e_dyn[i].d_un.d_val;
break;
case DT_PLTREL:
image->e_pltrel_type = image->e_dyn[i].d_un.d_val;
switch (image->e_pltrel_type) {
case DT_REL:
image->e_rel_entsize[ELF_RT_PLTREL] = 0;
break;
case DT_RELA:
image->e_rel_entsize[ELF_RT_PLTREL]
= sizeof(elf_rela_t);
break;
default:
break;
}
break;
case DT_JMPREL:
image->e_rel_offset[ELF_RT_PLTREL]
= image->e_dyn[i].d_un.d_ptr;
break;
case DT_PLTRELSZ:
image->e_rel_size[ELF_RT_PLTREL]
= image->e_dyn[i].d_un.d_val;
break;
default:
break;
}
image->e_dyn_count++;
}
return SUCCESS;
}
static int reserve_exec_region(struct elf_image *image)
{
void *base
= mmap(NULL,
image->e_length,
PROT_NONE,
MAP_ANONYMOUS | MAP_PRIVATE,
-1,
0);
if (base == MAP_FAILED) {
return ENOMEM;
}
image->e_base = (virt_addr_t)base;
return KERN_OK;
}
static int create_image_with_name(const char *name, struct elf_image **out)
{
struct elf_image *elf = malloc(sizeof *elf);
if (!elf) {
return ENOMEM;
}
memset(elf, 0x0, sizeof *elf);
snprintf(elf->e_leaf.l_name, sizeof elf->e_leaf.l_name, "%s", name);
kern_config_get(
KERN_CFG_PAGE_SIZE,
&elf->e_page_size,
sizeof elf->e_page_size);
*out = elf;
return SUCCESS;
}
int elf_image_open(const char *path, struct elf_image **out)
{
struct elf_image *elf = malloc(sizeof *elf);
if (!elf) {
return ENOMEM;
}
memset(elf, 0x0, sizeof *elf);
kern_config_get(
KERN_CFG_PAGE_SIZE,
&elf->e_page_size,
sizeof elf->e_page_size);
int fd = open(path, O_RDONLY);
if (fd < 0) {
elf_image_close(elf);
return -fd;
}
elf->e_status = ELF_IMAGE_OPEN;
elf->e_fd = fd;
*out = elf;
return SUCCESS;
}
int elf_image_parse(struct elf_image *img)
{
if (img->e_status != ELF_IMAGE_OPEN) {
return EINVAL;
}
int e = read(img->e_fd, &img->e_hdr, sizeof img->e_hdr);
if (e < 0) {
return -e;
}
if (e != sizeof img->e_hdr) {
return ENOEXEC;
}
if (!elf_validate_ehdr(&img->e_hdr)) {
return ENOEXEC;
}
e = parse_phdr(img);
if (e != SUCCESS) {
return e;
}
img->e_status = ELF_IMAGE_PARSED;
return SUCCESS;
}
int elf_image_load(struct elf_image *img)
{
if (img->e_status != ELF_IMAGE_PARSED) {
return EINVAL;
}
int e = reserve_exec_region(img);
if (e != SUCCESS) {
return e;
}
e = map_image(img);
if (e != SUCCESS) {
return e;
}
e = parse_dynamic(img);
if (e != SUCCESS) {
return e;
}
img->e_status = ELF_IMAGE_LOADED;
return SUCCESS;
}
int elf_image_link(struct elf_image *img)
{
if (img->e_status != ELF_IMAGE_LOADED) {
return EINVAL;
}
int status = SUCCESS;
if (img->e_rel_offset[ELF_RT_REL]) {
status = relocate_rel(
img,
img->e_rel_offset[ELF_RT_REL],
img->e_rel_size[ELF_RT_REL],
img->e_rel_entsize[ELF_RT_REL]);
if (status != SUCCESS) {
return status;
}
}
if (img->e_rel_offset[ELF_RT_RELA]) {
status = relocate_rela(
img,
img->e_rel_offset[ELF_RT_RELA],
img->e_rel_size[ELF_RT_RELA],
img->e_rel_entsize[ELF_RT_RELA]);
if (status != SUCCESS) {
return status;
}
}
#if 1
if (img->e_rel_offset[ELF_RT_PLTREL]) {
status = relocate_pltrel(
img,
img->e_rel_offset[ELF_RT_PLTREL],
img->e_rel_size[ELF_RT_PLTREL],
img->e_rel_entsize[ELF_RT_PLTREL]);
if (status != SUCCESS) {
return status;
}
}
#endif
*(uintptr_t *)(img->e_base + img->e_got_plt + 16)
= (uintptr_t)_dl_runtime_resolve;
*(uintptr_t *)(img->e_base + img->e_got_plt + 8) = (uintptr_t)img;
img->e_entry = (virt_addr_t)img->e_base + img->e_hdr.e_entry;
img->e_status = ELF_IMAGE_LINKED;
return SUCCESS;
}
extern int elf_image_collect_dependencies(
struct elf_image *img,
struct image_list *dest)
{
if (!img->e_nr_links || img->e_links) {
return SUCCESS;
}
int nr_added = 0;
img->e_links = calloc(img->e_nr_links, sizeof(struct elf_image *));
for (size_t i = 0; i < img->e_dyn_count; i++) {
if (img->e_dyn[i].d_tag != DT_NEEDED) {
continue;
}
const char *name = (const char *)img->e_base + img->e_strtab
+ img->e_dyn[i].d_un.d_val;
if (image_list_get(dest, name)) {
continue;
}
struct elf_image *dep = NULL;
int status = create_image_with_name(name, &dep);
if (status != SUCCESS) {
return -status;
}
image_list_put(dest, &dep->e_leaf);
img->e_links[nr_added] = dep;
nr_added++;
}
return nr_added;
}
void elf_image_close(struct elf_image *image)
{
if (image->e_fd) {
close(image->e_fd);
}
free(image);
}
static uint32_t std_hash(const char *name)
{
uint32_t h = 0, g;
for (; *name; name++) {
h = (h << 4) + *name;
if ((g = h & 0xf0000000)) {
h ^= g >> 24;
}
h &= ~g;
}
return h;
}
static uint32_t gnu_hash(const char *name)
{
uint32_t h = 5381;
for (; *name; name++) {
h = (h << 5) + h + *name;
}
return h;
}
static virt_addr_t find_symbol_stdhash(
struct elf_image *img,
const char *name,
uint32_t hash)
{
const uint32_t *hashtab
= (void *)((virt_addr_t)img->e_base + img->e_hash_table);
const char *strtab = (void *)((virt_addr_t)img->e_base + img->e_strtab);
const elf_sym_t *symtab
= (void *)((virt_addr_t)img->e_base + img->e_dynsym);
const uint32_t nbucket = hashtab[0];
const uint32_t nchain = hashtab[1];
const uint32_t *bucket = &hashtab[2];
const uint32_t *chain = &bucket[nbucket];
for (uint32_t i = bucket[hash % nbucket]; i; i = chain[i]) {
if (strcmp(name, strtab + symtab[i].st_name) == 0) {
return img->e_base + symtab[i].st_value;
}
}
return 0;
}
static virt_addr_t find_symbol_gnuhash(
struct elf_image *img,
const char *name,
uint32_t hash)
{
return 0;
}
static virt_addr_t find_symbol_slow(struct elf_image *img, const char *name)
{
return 0;
}
static virt_addr_t find_symbol(
struct elf_image *img,
const char *name,
uint32_t std_hash,
uint32_t gnu_hash)
{
switch (img->e_hash_type) {
case ELF_HASH_STANDARD:
return find_symbol_stdhash(img, name, std_hash);
case ELF_HASH_GNU:
return find_symbol_gnuhash(img, name, gnu_hash);
default:
return find_symbol_slow(img, name);
}
}
virt_addr_t elf_image_find_symbol(struct elf_image *img, const char *name)
{
uint32_t std_hash_val = std_hash(name);
uint32_t gnu_hash_val = gnu_hash(name);
return find_symbol(img, name, std_hash_val, gnu_hash_val);
}
virt_addr_t elf_image_find_linked_symbol(
struct elf_image *img,
const char *name)
{
uint32_t std_hash_val = std_hash(name);
uint32_t gnu_hash_val = gnu_hash(name);
virt_addr_t sym = 0;
for (size_t i = 0; i < img->e_nr_links; i++) {
sym = find_symbol(
img->e_links[i],
name,
std_hash_val,
gnu_hash_val);
if (sym) {
break;
}
}
return sym;
}