1070 lines
26 KiB
C
1070 lines
26 KiB
C
/*
|
|
* Copyright (c) 2023 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
*/
|
|
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/llext/elf.h>
|
|
#include <zephyr/llext/loader.h>
|
|
#include <zephyr/llext/llext.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/cache.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL);
|
|
|
|
#include <string.h>
|
|
|
|
#ifdef CONFIG_MMU_PAGE_SIZE
|
|
#define LLEXT_PAGE_SIZE CONFIG_MMU_PAGE_SIZE
|
|
#else
|
|
/* Arm's MPU wants a 32 byte minimum mpu region */
|
|
#define LLEXT_PAGE_SIZE 32
|
|
#endif
|
|
|
|
K_HEAP_DEFINE(llext_heap, CONFIG_LLEXT_HEAP_SIZE * 1024);
|
|
|
|
static const char ELF_MAGIC[] = {0x7f, 'E', 'L', 'F'};
|
|
|
|
static sys_slist_t _llext_list = SYS_SLIST_STATIC_INIT(&_llext_list);
|
|
|
|
static struct k_mutex llext_lock = Z_MUTEX_INITIALIZER(llext_lock);
|
|
|
|
ssize_t llext_find_section(struct llext_loader *ldr, const char *search_name)
|
|
{
|
|
elf_shdr_t *shdr;
|
|
unsigned int i;
|
|
size_t pos;
|
|
|
|
for (i = 0, pos = ldr->hdr.e_shoff;
|
|
i < ldr->hdr.e_shnum;
|
|
i++, pos += ldr->hdr.e_shentsize) {
|
|
shdr = llext_peek(ldr, pos);
|
|
if (!shdr) {
|
|
/* The peek() method isn't supported */
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
const char *name = llext_peek(ldr,
|
|
ldr->sects[LLEXT_MEM_SHSTRTAB].sh_offset +
|
|
shdr->sh_name);
|
|
|
|
if (!strcmp(name, search_name)) {
|
|
return shdr->sh_offset;
|
|
}
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
/*
|
|
* Note, that while we protect the global llext list while searching, we release
|
|
* the lock before returning the found extension to the caller. Therefore it's
|
|
* a responsibility of the caller to protect against races with a freeing
|
|
* context when calling this function.
|
|
*/
|
|
struct llext *llext_by_name(const char *name)
|
|
{
|
|
k_mutex_lock(&llext_lock, K_FOREVER);
|
|
|
|
for (sys_snode_t *node = sys_slist_peek_head(&_llext_list);
|
|
node != NULL;
|
|
node = sys_slist_peek_next(node)) {
|
|
struct llext *ext = CONTAINER_OF(node, struct llext, _llext_list);
|
|
|
|
if (strncmp(ext->name, name, sizeof(ext->name)) == 0) {
|
|
k_mutex_unlock(&llext_lock);
|
|
return ext;
|
|
}
|
|
}
|
|
|
|
k_mutex_unlock(&llext_lock);
|
|
return NULL;
|
|
}
|
|
|
|
int llext_iterate(int (*fn)(struct llext *ext, void *arg), void *arg)
|
|
{
|
|
sys_snode_t *node;
|
|
unsigned int i;
|
|
int ret = 0;
|
|
|
|
k_mutex_lock(&llext_lock, K_FOREVER);
|
|
|
|
for (node = sys_slist_peek_head(&_llext_list), i = 0;
|
|
node;
|
|
node = sys_slist_peek_next(node), i++) {
|
|
struct llext *ext = CONTAINER_OF(node, struct llext, _llext_list);
|
|
|
|
ret = fn(ext, arg);
|
|
if (ret) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
k_mutex_unlock(&llext_lock);
|
|
return ret;
|
|
}
|
|
|
|
const void * const llext_find_sym(const struct llext_symtable *sym_table, const char *sym_name)
|
|
{
|
|
if (sym_table == NULL) {
|
|
/* Built-in symbol table */
|
|
STRUCT_SECTION_FOREACH(llext_const_symbol, sym) {
|
|
if (strcmp(sym->name, sym_name) == 0) {
|
|
return sym->addr;
|
|
}
|
|
}
|
|
} else {
|
|
/* find symbols in module */
|
|
for (size_t i = 0; i < sym_table->sym_cnt; i++) {
|
|
if (strcmp(sym_table->syms[i].name, sym_name) == 0) {
|
|
return sym_table->syms[i].addr;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Find all relevant string and symbol tables
|
|
*/
|
|
static int llext_find_tables(struct llext_loader *ldr)
|
|
{
|
|
int sect_cnt, i, ret;
|
|
size_t pos;
|
|
elf_shdr_t shdr;
|
|
|
|
ldr->sects[LLEXT_MEM_SHSTRTAB] =
|
|
ldr->sects[LLEXT_MEM_STRTAB] =
|
|
ldr->sects[LLEXT_MEM_SYMTAB] = (elf_shdr_t){0};
|
|
|
|
/* Find symbol and string tables */
|
|
for (i = 0, sect_cnt = 0, pos = ldr->hdr.e_shoff;
|
|
i < ldr->hdr.e_shnum && sect_cnt < 3;
|
|
i++, pos += ldr->hdr.e_shentsize) {
|
|
ret = llext_seek(ldr, pos);
|
|
if (ret != 0) {
|
|
LOG_ERR("failed seeking to position %u\n", pos);
|
|
return ret;
|
|
}
|
|
|
|
ret = llext_read(ldr, &shdr, sizeof(elf_shdr_t));
|
|
if (ret != 0) {
|
|
LOG_ERR("failed reading section header at position %u\n", pos);
|
|
return ret;
|
|
}
|
|
|
|
LOG_DBG("section %d at %x: name %d, type %d, flags %x, addr %x, size %d",
|
|
i,
|
|
ldr->hdr.e_shoff + i * ldr->hdr.e_shentsize,
|
|
shdr.sh_name,
|
|
shdr.sh_type,
|
|
shdr.sh_flags,
|
|
shdr.sh_addr,
|
|
shdr.sh_size);
|
|
|
|
switch (shdr.sh_type) {
|
|
case SHT_SYMTAB:
|
|
case SHT_DYNSYM:
|
|
LOG_DBG("symtab at %d", i);
|
|
ldr->sects[LLEXT_MEM_SYMTAB] = shdr;
|
|
ldr->sect_map[i] = LLEXT_MEM_SYMTAB;
|
|
sect_cnt++;
|
|
break;
|
|
case SHT_STRTAB:
|
|
if (ldr->hdr.e_shstrndx == i) {
|
|
LOG_DBG("shstrtab at %d", i);
|
|
ldr->sects[LLEXT_MEM_SHSTRTAB] = shdr;
|
|
ldr->sect_map[i] = LLEXT_MEM_SHSTRTAB;
|
|
} else {
|
|
LOG_DBG("strtab at %d", i);
|
|
ldr->sects[LLEXT_MEM_STRTAB] = shdr;
|
|
ldr->sect_map[i] = LLEXT_MEM_STRTAB;
|
|
}
|
|
sect_cnt++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ldr->sects[LLEXT_MEM_SHSTRTAB].sh_type ||
|
|
!ldr->sects[LLEXT_MEM_STRTAB].sh_type ||
|
|
!ldr->sects[LLEXT_MEM_SYMTAB].sh_type) {
|
|
LOG_ERR("Some sections are missing or present multiple times!");
|
|
return -ENOENT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char *llext_string(struct llext_loader *ldr, struct llext *ext,
|
|
enum llext_mem mem_idx, unsigned int idx)
|
|
{
|
|
return (char *)ext->mem[mem_idx] + idx;
|
|
}
|
|
|
|
/*
|
|
* Maps the section indexes and copies special section headers for easier use
|
|
*/
|
|
static int llext_map_sections(struct llext_loader *ldr, struct llext *ext)
|
|
{
|
|
int i, ret;
|
|
size_t pos;
|
|
elf_shdr_t shdr;
|
|
const char *name;
|
|
|
|
for (i = 0, pos = ldr->hdr.e_shoff;
|
|
i < ldr->hdr.e_shnum;
|
|
i++, pos += ldr->hdr.e_shentsize) {
|
|
ret = llext_seek(ldr, pos);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = llext_read(ldr, &shdr, sizeof(elf_shdr_t));
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr.sh_name);
|
|
|
|
LOG_DBG("section %d name %s", i, name);
|
|
|
|
enum llext_mem mem_idx;
|
|
|
|
if (strcmp(name, ".text") == 0) {
|
|
mem_idx = LLEXT_MEM_TEXT;
|
|
} else if (strcmp(name, ".data") == 0) {
|
|
mem_idx = LLEXT_MEM_DATA;
|
|
} else if (strcmp(name, ".rodata") == 0) {
|
|
mem_idx = LLEXT_MEM_RODATA;
|
|
} else if (strcmp(name, ".bss") == 0) {
|
|
mem_idx = LLEXT_MEM_BSS;
|
|
} else if (strcmp(name, ".exported_sym") == 0) {
|
|
mem_idx = LLEXT_MEM_EXPORT;
|
|
} else {
|
|
LOG_DBG("Not copied section %s", name);
|
|
continue;
|
|
}
|
|
|
|
ldr->sects[mem_idx] = shdr;
|
|
ldr->sect_map[i] = mem_idx;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialize the memory partition associated with the extension memory
|
|
*/
|
|
static void llext_init_mem_part(struct llext *ext, enum llext_mem mem_idx,
|
|
uintptr_t start, size_t len)
|
|
{
|
|
#ifdef CONFIG_USERSPACE
|
|
if (mem_idx < LLEXT_MEM_PARTITIONS) {
|
|
ext->mem_parts[mem_idx].start = start;
|
|
ext->mem_parts[mem_idx].size = len;
|
|
|
|
switch (mem_idx) {
|
|
case LLEXT_MEM_TEXT:
|
|
ext->mem_parts[mem_idx].attr = K_MEM_PARTITION_P_RX_U_RX;
|
|
break;
|
|
case LLEXT_MEM_DATA:
|
|
case LLEXT_MEM_BSS:
|
|
ext->mem_parts[mem_idx].attr = K_MEM_PARTITION_P_RW_U_RW;
|
|
break;
|
|
case LLEXT_MEM_RODATA:
|
|
ext->mem_parts[mem_idx].attr = K_MEM_PARTITION_P_RO_U_RO;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
LOG_DBG("mem partition %d start 0x%lx, size %d", mem_idx,
|
|
ext->mem_parts[mem_idx].start,
|
|
ext->mem_parts[mem_idx].size);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int llext_copy_section(struct llext_loader *ldr, struct llext *ext,
|
|
enum llext_mem mem_idx)
|
|
{
|
|
int ret;
|
|
|
|
if (!ldr->sects[mem_idx].sh_size) {
|
|
return 0;
|
|
}
|
|
ext->mem_size[mem_idx] = ldr->sects[mem_idx].sh_size;
|
|
|
|
if (ldr->sects[mem_idx].sh_type != SHT_NOBITS &&
|
|
IS_ENABLED(CONFIG_LLEXT_STORAGE_WRITABLE)) {
|
|
ext->mem[mem_idx] = llext_peek(ldr, ldr->sects[mem_idx].sh_offset);
|
|
if (ext->mem[mem_idx]) {
|
|
llext_init_mem_part(ext, mem_idx, (uintptr_t)ext->mem[mem_idx],
|
|
ldr->sects[mem_idx].sh_size);
|
|
ext->mem_on_heap[mem_idx] = false;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* On ARM with an MPU a pow(2, N)*32 sized and aligned region is needed,
|
|
* otherwise its typically an mmu page (sized and aligned memory region)
|
|
* we are after that we can assign memory permission bits on.
|
|
*/
|
|
#ifndef CONFIG_ARM_MPU
|
|
const uintptr_t sect_alloc = ROUND_UP(ldr->sects[mem_idx].sh_size, LLEXT_PAGE_SIZE);
|
|
const uintptr_t sect_align = LLEXT_PAGE_SIZE;
|
|
#else
|
|
uintptr_t sect_alloc = LLEXT_PAGE_SIZE;
|
|
|
|
while (sect_alloc < ldr->sects[mem_idx].sh_size) {
|
|
sect_alloc *= 2;
|
|
}
|
|
uintptr_t sect_align = sect_alloc;
|
|
#endif
|
|
|
|
ext->mem[mem_idx] = k_heap_aligned_alloc(&llext_heap, sect_align,
|
|
sect_alloc,
|
|
K_NO_WAIT);
|
|
|
|
if (!ext->mem[mem_idx]) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ext->alloc_size += sect_alloc;
|
|
|
|
llext_init_mem_part(ext, mem_idx, (uintptr_t)ext->mem[mem_idx],
|
|
sect_alloc);
|
|
|
|
if (ldr->sects[mem_idx].sh_type == SHT_NOBITS) {
|
|
memset(ext->mem[mem_idx], 0, ldr->sects[mem_idx].sh_size);
|
|
} else {
|
|
ret = llext_seek(ldr, ldr->sects[mem_idx].sh_offset);
|
|
if (ret != 0) {
|
|
goto err;
|
|
}
|
|
|
|
ret = llext_read(ldr, ext->mem[mem_idx], ldr->sects[mem_idx].sh_size);
|
|
if (ret != 0) {
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
ext->mem_on_heap[mem_idx] = true;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
k_heap_free(&llext_heap, ext->mem[mem_idx]);
|
|
return ret;
|
|
}
|
|
|
|
static int llext_copy_strings(struct llext_loader *ldr, struct llext *ext)
|
|
{
|
|
int ret = llext_copy_section(ldr, ext, LLEXT_MEM_SHSTRTAB);
|
|
|
|
if (!ret) {
|
|
ret = llext_copy_section(ldr, ext, LLEXT_MEM_STRTAB);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int llext_copy_sections(struct llext_loader *ldr, struct llext *ext)
|
|
{
|
|
for (enum llext_mem mem_idx = 0; mem_idx < LLEXT_MEM_COUNT; mem_idx++) {
|
|
/* strings have already been copied */
|
|
if (ext->mem[mem_idx]) {
|
|
continue;
|
|
}
|
|
|
|
int ret = llext_copy_section(ldr, ext, mem_idx);
|
|
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int llext_count_export_syms(struct llext_loader *ldr, struct llext *ext)
|
|
{
|
|
size_t ent_size = ldr->sects[LLEXT_MEM_SYMTAB].sh_entsize;
|
|
size_t syms_size = ldr->sects[LLEXT_MEM_SYMTAB].sh_size;
|
|
int sym_cnt = syms_size / sizeof(elf_sym_t);
|
|
const char *name;
|
|
elf_sym_t sym;
|
|
int i, ret;
|
|
size_t pos;
|
|
|
|
LOG_DBG("symbol count %u", sym_cnt);
|
|
|
|
for (i = 0, pos = ldr->sects[LLEXT_MEM_SYMTAB].sh_offset;
|
|
i < sym_cnt;
|
|
i++, pos += ent_size) {
|
|
if (!i) {
|
|
/* A dummy entry */
|
|
continue;
|
|
}
|
|
|
|
ret = llext_seek(ldr, pos);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = llext_read(ldr, &sym, ent_size);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
uint32_t stt = ELF_ST_TYPE(sym.st_info);
|
|
uint32_t stb = ELF_ST_BIND(sym.st_info);
|
|
uint32_t sect = sym.st_shndx;
|
|
|
|
name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym.st_name);
|
|
|
|
if (stt == STT_FUNC && stb == STB_GLOBAL) {
|
|
LOG_DBG("function symbol %d, name %s, type tag %d, bind %d, sect %d",
|
|
i, name, stt, stb, sect);
|
|
ext->sym_tab.sym_cnt++;
|
|
} else {
|
|
LOG_DBG("unhandled symbol %d, name %s, type tag %d, bind %d, sect %d",
|
|
i, name, stt, stb, sect);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int llext_allocate_symtab(struct llext_loader *ldr, struct llext *ext)
|
|
{
|
|
struct llext_symtable *sym_tab = &ext->sym_tab;
|
|
size_t syms_size = sym_tab->sym_cnt * sizeof(struct llext_symbol);
|
|
|
|
sym_tab->syms = k_heap_alloc(&llext_heap, syms_size, K_NO_WAIT);
|
|
if (!sym_tab->syms) {
|
|
return -ENOMEM;
|
|
}
|
|
memset(sym_tab->syms, 0, syms_size);
|
|
ext->alloc_size += syms_size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int llext_export_symbols(struct llext_loader *ldr, struct llext *ext)
|
|
{
|
|
elf_shdr_t *shdr = ldr->sects + LLEXT_MEM_EXPORT;
|
|
struct llext_symbol *sym;
|
|
unsigned int i;
|
|
|
|
if (shdr->sh_size < sizeof(struct llext_symbol)) {
|
|
/* Not found, no symbols exported */
|
|
return 0;
|
|
}
|
|
|
|
struct llext_symtable *exp_tab = &ext->exp_tab;
|
|
|
|
exp_tab->sym_cnt = shdr->sh_size / sizeof(struct llext_symbol);
|
|
exp_tab->syms = k_heap_alloc(&llext_heap, exp_tab->sym_cnt * sizeof(struct llext_symbol),
|
|
K_NO_WAIT);
|
|
if (!exp_tab->syms) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0, sym = ext->mem[LLEXT_MEM_EXPORT];
|
|
i < exp_tab->sym_cnt;
|
|
i++, sym++) {
|
|
exp_tab->syms[i].name = sym->name;
|
|
exp_tab->syms[i].addr = sym->addr;
|
|
LOG_DBG("sym %p name %s in %p", sym->addr, sym->name, exp_tab->syms + i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int llext_copy_symbols(struct llext_loader *ldr, struct llext *ext)
|
|
{
|
|
size_t ent_size = ldr->sects[LLEXT_MEM_SYMTAB].sh_entsize;
|
|
size_t syms_size = ldr->sects[LLEXT_MEM_SYMTAB].sh_size;
|
|
int sym_cnt = syms_size / sizeof(elf_sym_t);
|
|
struct llext_symtable *sym_tab = &ext->sym_tab;
|
|
elf_sym_t sym;
|
|
int i, j, ret;
|
|
size_t pos;
|
|
|
|
for (i = 0, pos = ldr->sects[LLEXT_MEM_SYMTAB].sh_offset, j = 0;
|
|
i < sym_cnt;
|
|
i++, pos += ent_size) {
|
|
if (!i) {
|
|
/* A dummy entry */
|
|
continue;
|
|
}
|
|
|
|
ret = llext_seek(ldr, pos);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = llext_read(ldr, &sym, ent_size);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
uint32_t stt = ELF_ST_TYPE(sym.st_info);
|
|
uint32_t stb = ELF_ST_BIND(sym.st_info);
|
|
unsigned int sect = sym.st_shndx;
|
|
|
|
if (stt == STT_FUNC && stb == STB_GLOBAL && sect != SHN_UNDEF) {
|
|
enum llext_mem mem_idx = ldr->sect_map[sect];
|
|
const char *name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym.st_name);
|
|
|
|
__ASSERT(j <= sym_tab->sym_cnt, "Miscalculated symbol number %u\n", j);
|
|
|
|
sym_tab->syms[j].name = name;
|
|
sym_tab->syms[j].addr = (void *)((uintptr_t)ext->mem[mem_idx] +
|
|
sym.st_value -
|
|
(ldr->hdr.e_type == ET_REL ? 0 :
|
|
ldr->sects[mem_idx].sh_addr));
|
|
LOG_DBG("function symbol %d name %s addr %p",
|
|
j, name, sym_tab->syms[j].addr);
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Find the section, containing the supplied offset and return file offset for
|
|
* that value
|
|
*/
|
|
static size_t llext_file_offset(struct llext_loader *ldr, size_t offset)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < LLEXT_MEM_COUNT; i++)
|
|
if (ldr->sects[i].sh_addr <= offset &&
|
|
ldr->sects[i].sh_addr + ldr->sects[i].sh_size > offset)
|
|
return offset - ldr->sects[i].sh_addr + ldr->sects[i].sh_offset;
|
|
|
|
return offset;
|
|
}
|
|
|
|
__weak void arch_elf_relocate_local(struct llext_loader *ldr, struct llext *ext,
|
|
elf_rela_t *rel, size_t got_offset)
|
|
{
|
|
}
|
|
|
|
static void llext_link_plt(struct llext_loader *ldr, struct llext *ext,
|
|
elf_shdr_t *shdr, bool do_local)
|
|
{
|
|
unsigned int sh_cnt = shdr->sh_size / shdr->sh_entsize;
|
|
/*
|
|
* CPU address where the .text section is stored, we use .text just as a
|
|
* reference point
|
|
*/
|
|
uint8_t *text = ext->mem[LLEXT_MEM_TEXT];
|
|
|
|
LOG_DBG("Found %p in PLT %u size %u cnt %u text %p",
|
|
(void *)llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr->sh_name),
|
|
shdr->sh_type, shdr->sh_entsize, sh_cnt, (void *)text);
|
|
|
|
const elf_shdr_t *sym_shdr = ldr->sects + LLEXT_MEM_SYMTAB;
|
|
unsigned int sym_cnt = sym_shdr->sh_size / sym_shdr->sh_entsize;
|
|
|
|
for (unsigned int i = 0; i < sh_cnt; i++) {
|
|
elf_rela_t rela;
|
|
|
|
int ret = llext_seek(ldr, shdr->sh_offset + i * shdr->sh_entsize);
|
|
|
|
if (!ret) {
|
|
ret = llext_read(ldr, &rela, sizeof(rela));
|
|
}
|
|
|
|
if (ret < 0) {
|
|
LOG_ERR("PLT: failed to read RELA #%u, trying to continue", i);
|
|
continue;
|
|
}
|
|
|
|
/* Index in the symbol table */
|
|
unsigned int j = ELF32_R_SYM(rela.r_info);
|
|
|
|
if (j >= sym_cnt) {
|
|
LOG_WRN("PLT: idx %u >= %u", j, sym_cnt);
|
|
continue;
|
|
}
|
|
|
|
elf_sym_t sym_tbl;
|
|
|
|
ret = llext_seek(ldr, sym_shdr->sh_offset + j * sizeof(elf_sym_t));
|
|
if (!ret) {
|
|
ret = llext_read(ldr, &sym_tbl, sizeof(sym_tbl));
|
|
}
|
|
|
|
if (ret < 0) {
|
|
LOG_ERR("PLT: failed to read symbol table #%u RELA #%u, trying to continue",
|
|
j, i);
|
|
continue;
|
|
}
|
|
|
|
uint32_t stt = ELF_ST_TYPE(sym_tbl.st_info);
|
|
uint32_t stb = ELF_ST_BIND(sym_tbl.st_info);
|
|
|
|
if (stt != STT_FUNC && (stt != STT_NOTYPE || sym_tbl.st_shndx != SHN_UNDEF)) {
|
|
continue;
|
|
}
|
|
|
|
const char *name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym_tbl.st_name);
|
|
/*
|
|
* Both r_offset and sh_addr are addresses for which the extension
|
|
* has been built.
|
|
*/
|
|
size_t got_offset = llext_file_offset(ldr, rela.r_offset) -
|
|
ldr->sects[LLEXT_MEM_TEXT].sh_offset;
|
|
|
|
const void *link_addr;
|
|
|
|
switch (stb) {
|
|
case STB_GLOBAL:
|
|
link_addr = llext_find_sym(NULL, name);
|
|
if (!link_addr)
|
|
link_addr = llext_find_sym(&ext->sym_tab, name);
|
|
|
|
if (!link_addr) {
|
|
LOG_WRN("PLT: cannot find idx %u name %s", j, name);
|
|
continue;
|
|
}
|
|
|
|
if (!rela.r_offset) {
|
|
LOG_WRN("PLT: zero offset idx %u name %s", j, name);
|
|
continue;
|
|
}
|
|
|
|
/* Resolve the symbol */
|
|
*(const void **)(text + got_offset) = link_addr;
|
|
break;
|
|
case STB_LOCAL:
|
|
if (do_local) {
|
|
arch_elf_relocate_local(ldr, ext, &rela, got_offset);
|
|
}
|
|
}
|
|
|
|
LOG_DBG("symbol %s offset %#x r-offset %#x .text offset %#x stb %u",
|
|
name, got_offset,
|
|
rela.r_offset, ldr->sects[LLEXT_MEM_TEXT].sh_offset, stb);
|
|
}
|
|
}
|
|
|
|
__weak void arch_elf_relocate(elf_rela_t *rel, uintptr_t opaddr, uintptr_t opval)
|
|
{
|
|
}
|
|
|
|
static int llext_link(struct llext_loader *ldr, struct llext *ext, bool do_local)
|
|
{
|
|
uintptr_t loc = 0;
|
|
elf_shdr_t shdr;
|
|
elf_rela_t rel;
|
|
elf_sym_t sym;
|
|
elf_word rel_cnt = 0;
|
|
const char *name;
|
|
int i, ret;
|
|
size_t pos;
|
|
|
|
for (i = 0, pos = ldr->hdr.e_shoff;
|
|
i < ldr->hdr.e_shnum - 1;
|
|
i++, pos += ldr->hdr.e_shentsize) {
|
|
ret = llext_seek(ldr, pos);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = llext_read(ldr, &shdr, sizeof(elf_shdr_t));
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* find relocation sections */
|
|
if (shdr.sh_type != SHT_REL && shdr.sh_type != SHT_RELA) {
|
|
continue;
|
|
}
|
|
|
|
rel_cnt = shdr.sh_size / shdr.sh_entsize;
|
|
|
|
name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr.sh_name);
|
|
|
|
if (strcmp(name, ".rel.text") == 0 ||
|
|
strcmp(name, ".rela.text") == 0) {
|
|
loc = (uintptr_t)ext->mem[LLEXT_MEM_TEXT];
|
|
} else if (strcmp(name, ".rel.bss") == 0 ||
|
|
strcmp(name, ".rela.bss") == 0) {
|
|
loc = (uintptr_t)ext->mem[LLEXT_MEM_BSS];
|
|
} else if (strcmp(name, ".rel.rodata") == 0 ||
|
|
strcmp(name, ".rela.rodata") == 0) {
|
|
loc = (uintptr_t)ext->mem[LLEXT_MEM_RODATA];
|
|
} else if (strcmp(name, ".rel.data") == 0 ||
|
|
strcmp(name, ".rela.data") == 0) {
|
|
loc = (uintptr_t)ext->mem[LLEXT_MEM_DATA];
|
|
} else if (strcmp(name, ".rel.exported_sym") == 0) {
|
|
loc = (uintptr_t)ext->mem[LLEXT_MEM_EXPORT];
|
|
} else if (strcmp(name, ".rela.plt") == 0 ||
|
|
strcmp(name, ".rela.dyn") == 0) {
|
|
llext_link_plt(ldr, ext, &shdr, do_local);
|
|
continue;
|
|
}
|
|
|
|
LOG_DBG("relocation section %s (%d) linked to section %d has %d relocations",
|
|
name, i, shdr.sh_link, rel_cnt);
|
|
|
|
for (int j = 0; j < rel_cnt; j++) {
|
|
/* get each relocation entry */
|
|
ret = llext_seek(ldr, shdr.sh_offset + j * shdr.sh_entsize);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = llext_read(ldr, &rel, shdr.sh_entsize);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* get corresponding symbol */
|
|
ret = llext_seek(ldr, ldr->sects[LLEXT_MEM_SYMTAB].sh_offset
|
|
+ ELF_R_SYM(rel.r_info) * sizeof(elf_sym_t));
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = llext_read(ldr, &sym, sizeof(elf_sym_t));
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym.st_name);
|
|
|
|
LOG_DBG("relocation %d:%d info %x (type %d, sym %d) offset %d sym_name "
|
|
"%s sym_type %d sym_bind %d sym_ndx %d",
|
|
i, j, rel.r_info, ELF_R_TYPE(rel.r_info), ELF_R_SYM(rel.r_info),
|
|
rel.r_offset, name, ELF_ST_TYPE(sym.st_info),
|
|
ELF_ST_BIND(sym.st_info), sym.st_shndx);
|
|
|
|
uintptr_t link_addr, op_loc;
|
|
|
|
op_loc = loc + rel.r_offset;
|
|
|
|
/* If symbol is undefined, then we need to look it up */
|
|
if (sym.st_shndx == SHN_UNDEF) {
|
|
link_addr = (uintptr_t)llext_find_sym(NULL, name);
|
|
|
|
if (link_addr == 0) {
|
|
LOG_ERR("Undefined symbol with no entry in "
|
|
"symbol table %s, offset %d, link section %d",
|
|
name, rel.r_offset, shdr.sh_link);
|
|
return -ENODATA;
|
|
}
|
|
} else if (ELF_ST_TYPE(sym.st_info) == STT_SECTION ||
|
|
ELF_ST_TYPE(sym.st_info) == STT_FUNC) {
|
|
/* Current relocation location holds an offset into the section */
|
|
link_addr = (uintptr_t)ext->mem[ldr->sect_map[sym.st_shndx]]
|
|
+ sym.st_value
|
|
+ *((uintptr_t *)op_loc);
|
|
|
|
LOG_INF("found section symbol %s addr 0x%lx", name, link_addr);
|
|
} else {
|
|
/* Nothing to relocate here */
|
|
continue;
|
|
}
|
|
|
|
LOG_INF("relocating (linking) symbol %s type %d binding %d ndx %d offset "
|
|
"%d link section %d",
|
|
name, ELF_ST_TYPE(sym.st_info), ELF_ST_BIND(sym.st_info),
|
|
sym.st_shndx, rel.r_offset, shdr.sh_link);
|
|
|
|
LOG_INF("writing relocation symbol %s type %d sym %d at addr 0x%lx "
|
|
"addr 0x%lx",
|
|
name, ELF_R_TYPE(rel.r_info), ELF_R_SYM(rel.r_info),
|
|
op_loc, link_addr);
|
|
|
|
/* relocation */
|
|
arch_elf_relocate(&rel, op_loc, link_addr);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_CACHE_MANAGEMENT
|
|
/* Make sure changes to ext sections are flushed to RAM */
|
|
for (i = 0; i < LLEXT_MEM_COUNT; ++i) {
|
|
if (ext->mem[i]) {
|
|
sys_cache_data_flush_range(ext->mem[i], ext->mem_size[i]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Load a valid ELF as an extension
|
|
*/
|
|
static int do_llext_load(struct llext_loader *ldr, struct llext *ext,
|
|
struct llext_load_param *ldr_parm)
|
|
{
|
|
int ret = 0;
|
|
|
|
memset(ldr->sects, 0, sizeof(ldr->sects));
|
|
ldr->sect_cnt = 0;
|
|
ext->sym_tab.sym_cnt = 0;
|
|
|
|
size_t sect_map_sz = ldr->hdr.e_shnum * sizeof(ldr->sect_map[0]);
|
|
|
|
ldr->sect_map = k_heap_alloc(&llext_heap, sect_map_sz, K_NO_WAIT);
|
|
if (!ldr->sect_map) {
|
|
LOG_ERR("Failed to allocate memory for section map, size %u", sect_map_sz);
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
memset(ldr->sect_map, 0, sect_map_sz);
|
|
|
|
ldr->sect_cnt = ldr->hdr.e_shnum;
|
|
ext->alloc_size += sect_map_sz;
|
|
|
|
#ifdef CONFIG_USERSPACE
|
|
ret = k_mem_domain_init(&ext->mem_domain, 0, NULL);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to initialize extenion memory domain %d", ret);
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
LOG_DBG("Finding ELF tables...");
|
|
ret = llext_find_tables(ldr);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to find important ELF tables, ret %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
LOG_DBG("Allocate and copy strings...");
|
|
ret = llext_copy_strings(ldr, ext);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to copy ELF string sections, ret %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
LOG_DBG("Mapping ELF sections...");
|
|
ret = llext_map_sections(ldr, ext);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to map ELF sections, ret %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
LOG_DBG("Allocate and copy sections...");
|
|
ret = llext_copy_sections(ldr, ext);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to copy ELF sections, ret %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
LOG_DBG("Counting exported symbols...");
|
|
ret = llext_count_export_syms(ldr, ext);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to count exported ELF symbols, ret %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
LOG_DBG("Allocating memory for symbol table...");
|
|
ret = llext_allocate_symtab(ldr, ext);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to allocate extension symbol table, ret %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
LOG_DBG("Copying symbols...");
|
|
ret = llext_copy_symbols(ldr, ext);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to copy symbols, ret %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
LOG_DBG("Linking ELF...");
|
|
ret = llext_link(ldr, ext, ldr_parm ? ldr_parm->relocate_local : true);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to link, ret %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = llext_export_symbols(ldr, ext);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to export, ret %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
k_heap_free(&llext_heap, ldr->sect_map);
|
|
|
|
if (ret != 0) {
|
|
LOG_DBG("Failed to load extension, freeing memory...");
|
|
for (enum llext_mem mem_idx = 0; mem_idx < LLEXT_MEM_COUNT; mem_idx++) {
|
|
if (ext->mem_on_heap[mem_idx]) {
|
|
k_heap_free(&llext_heap, ext->mem[mem_idx]);
|
|
}
|
|
}
|
|
k_heap_free(&llext_heap, ext->exp_tab.syms);
|
|
} else {
|
|
LOG_DBG("loaded module, .text at %p, .rodata at %p", ext->mem[LLEXT_MEM_TEXT],
|
|
ext->mem[LLEXT_MEM_RODATA]);
|
|
}
|
|
|
|
ext->sym_tab.sym_cnt = 0;
|
|
k_heap_free(&llext_heap, ext->sym_tab.syms);
|
|
ext->sym_tab.syms = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int llext_load(struct llext_loader *ldr, const char *name, struct llext **ext,
|
|
struct llext_load_param *ldr_parm)
|
|
{
|
|
int ret;
|
|
elf_ehdr_t ehdr;
|
|
|
|
*ext = llext_by_name(name);
|
|
|
|
k_mutex_lock(&llext_lock, K_FOREVER);
|
|
|
|
if (*ext) {
|
|
/* The use count is at least 1 */
|
|
ret = (*ext)->use_count++;
|
|
goto out;
|
|
}
|
|
|
|
ret = llext_seek(ldr, 0);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to seek for ELF header");
|
|
goto out;
|
|
}
|
|
|
|
ret = llext_read(ldr, &ehdr, sizeof(ehdr));
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to read ELF header");
|
|
goto out;
|
|
}
|
|
|
|
/* check whether this is an valid elf file */
|
|
if (memcmp(ehdr.e_ident, ELF_MAGIC, sizeof(ELF_MAGIC)) != 0) {
|
|
LOG_HEXDUMP_ERR(ehdr.e_ident, 16, "Invalid ELF, magic does not match");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
switch (ehdr.e_type) {
|
|
case ET_REL:
|
|
case ET_DYN:
|
|
LOG_DBG("Loading relocatable or shared elf");
|
|
*ext = k_heap_alloc(&llext_heap, sizeof(struct llext), K_NO_WAIT);
|
|
if (*ext == NULL) {
|
|
LOG_ERR("Not enough memory for extension metadata");
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
memset(*ext, 0, sizeof(struct llext));
|
|
|
|
ldr->hdr = ehdr;
|
|
ret = do_llext_load(ldr, *ext, ldr_parm);
|
|
if (ret < 0) {
|
|
k_heap_free(&llext_heap, *ext);
|
|
*ext = NULL;
|
|
goto out;
|
|
}
|
|
|
|
strncpy((*ext)->name, name, sizeof((*ext)->name));
|
|
(*ext)->name[sizeof((*ext)->name) - 1] = '\0';
|
|
(*ext)->use_count++;
|
|
|
|
sys_slist_append(&_llext_list, &(*ext)->_llext_list);
|
|
LOG_INF("Loaded extension %s", (*ext)->name);
|
|
|
|
break;
|
|
default:
|
|
LOG_ERR("Unsupported elf file type %x", ehdr.e_type);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
out:
|
|
k_mutex_unlock(&llext_lock);
|
|
return ret;
|
|
}
|
|
|
|
int llext_unload(struct llext **ext)
|
|
{
|
|
__ASSERT(*ext, "Expected non-null extension");
|
|
struct llext *tmp = *ext;
|
|
|
|
k_mutex_lock(&llext_lock, K_FOREVER);
|
|
__ASSERT(tmp->use_count, "A valid LLEXT cannot have a zero use-count!");
|
|
|
|
if (tmp->use_count-- != 1) {
|
|
unsigned int ret = tmp->use_count;
|
|
|
|
k_mutex_unlock(&llext_lock);
|
|
return ret;
|
|
}
|
|
|
|
/* FIXME: protect the global list */
|
|
sys_slist_find_and_remove(&_llext_list, &tmp->_llext_list);
|
|
|
|
*ext = NULL;
|
|
k_mutex_unlock(&llext_lock);
|
|
|
|
for (int i = 0; i < LLEXT_MEM_COUNT; i++) {
|
|
if (tmp->mem_on_heap[i]) {
|
|
LOG_DBG("freeing memory region %d", i);
|
|
k_heap_free(&llext_heap, tmp->mem[i]);
|
|
tmp->mem[i] = NULL;
|
|
}
|
|
}
|
|
|
|
k_heap_free(&llext_heap, tmp->exp_tab.syms);
|
|
k_heap_free(&llext_heap, tmp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int llext_call_fn(struct llext *ext, const char *sym_name)
|
|
{
|
|
void (*fn)(void);
|
|
|
|
fn = llext_find_sym(&ext->exp_tab, sym_name);
|
|
if (fn == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
fn();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int llext_add_domain(struct llext *ext, struct k_mem_domain *domain)
|
|
{
|
|
#ifdef CONFIG_USERSPACE
|
|
int ret = 0;
|
|
|
|
for (int i = 0; i < LLEXT_MEM_PARTITIONS; i++) {
|
|
if (ext->mem_size[i] == 0) {
|
|
continue;
|
|
}
|
|
ret = k_mem_domain_add_partition(domain, &ext->mem_parts[i]);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed adding memory partition %d to domain %p",
|
|
i, domain);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
#else
|
|
return -ENOSYS;
|
|
#endif
|
|
}
|