blob: c92f49abd604ac38c999a13cc76b5380091a2385 [file] [log] [blame]
/*
* 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);
static elf_shdr_t *llext_section_by_name(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 NULL;
}
const char *name = llext_peek(ldr,
ldr->sects[LLEXT_MEM_SHSTRTAB].sh_offset +
shdr->sh_name);
if (!strcmp(name, search_name)) {
return shdr;
}
}
return NULL;
}
ssize_t llext_find_section(struct llext_loader *ldr, const char *search_name)
{
elf_shdr_t *shdr = llext_section_by_name(ldr, search_name);
return shdr ? shdr->sh_offset : -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 *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 %zu\n", pos);
return ret;
}
ret = llext_read(ldr, &shdr, sizeof(elf_shdr_t));
if (ret != 0) {
LOG_ERR("failed reading section header at position %zu\n", pos);
return ret;
}
LOG_DBG("section %d at %zx: name %d, type %d, flags %zx, addr %zx, size %zd",
i,
(size_t)ldr->hdr.e_shoff + i * ldr->hdr.e_shentsize,
shdr.sh_name,
shdr.sh_type,
(size_t)shdr.sh_flags,
(size_t)shdr.sh_addr,
(size_t)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, rodata = {.sh_addr = ~0},
high_shdr = {.sh_offset = 0}, low_shdr = {.sh_offset = ~0};
const char *name;
ldr->sects[LLEXT_MEM_RODATA].sh_size = 0;
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;
}
/* Identify the lowest and the highest data sections */
if (!(shdr.sh_flags & SHF_EXECINSTR) &&
shdr.sh_type == SHT_PROGBITS) {
if (shdr.sh_offset > high_shdr.sh_offset) {
high_shdr = shdr;
}
if (shdr.sh_offset < low_shdr.sh_offset) {
low_shdr = shdr;
}
}
name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr.sh_name);
LOG_DBG("section %d name %s", i, name);
enum llext_mem mem_idx;
/*
* .rodata section is optional. If there isn't one, use the
* first read-only data section
*/
if (shdr.sh_addr && !(shdr.sh_flags & (SHF_WRITE | SHF_EXECINSTR)) &&
shdr.sh_addr < rodata.sh_addr) {
rodata = shdr;
LOG_DBG("rodata: select %#zx name %s", (size_t)shdr.sh_addr, name);
}
/*
* Keep in mind, that when using relocatable (partially linked)
* objects, ELF segments aren't created, so ldr->sect_map[] and
* ldr->sects[] don't contain all the sections
*/
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;
}
ldr->prog_data_size = high_shdr.sh_size + high_shdr.sh_offset - low_shdr.sh_offset;
/* No verbatim .rodata, use an automatically selected one */
if (!ldr->sects[LLEXT_MEM_RODATA].sh_size) {
ldr->sects[LLEXT_MEM_RODATA] = rodata;
}
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,
const elf_rela_t *rel, const elf_sym_t *sym, size_t got_offset)
{
}
static void llext_link_plt(struct llext_loader *ldr, struct llext *ext,
elf_shdr_t *shdr, bool do_local, elf_shdr_t *tgt)
{
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 %zu cnt %u text %p",
(void *)llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr->sh_name),
shdr->sh_type, (size_t)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);
if (stt != STT_FUNC &&
stt != STT_SECTION &&
(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;
if (tgt) {
got_offset = rela.r_offset + tgt->sh_offset -
ldr->sects[LLEXT_MEM_TEXT].sh_offset;
} else {
got_offset = llext_file_offset(ldr, rela.r_offset) -
ldr->sects[LLEXT_MEM_TEXT].sh_offset;
}
uint32_t stb = ELF_ST_BIND(sym_tbl.st_info);
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, &sym_tbl, got_offset);
}
}
LOG_DBG("symbol %s offset %#zx r-offset %#zx .text offset %#zx stb %u",
name, got_offset,
(size_t)rela.r_offset, (size_t)ldr->sects[LLEXT_MEM_TEXT].sh_offset, stb);
}
}
__weak int arch_elf_relocate(elf_rela_t *rel, uintptr_t loc,
uintptr_t sym_base_addr, const char *sym_name, uintptr_t load_bias)
{
return -EOPNOTSUPP;
}
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) {
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) {
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, NULL);
continue;
} else if (strncmp(name, ".rela", 5) == 0 && strlen(name) > 5) {
elf_shdr_t *tgt = llext_section_by_name(ldr, name + 5);
if (tgt)
llext_link_plt(ldr, ext, &shdr, do_local, tgt);
continue;
} else if (strcmp(name, ".rel.dyn") == 0) {
/* we assume that first load segment starts at MEM_TEXT */
loc = (uintptr_t)ext->mem[LLEXT_MEM_TEXT];
}
LOG_DBG("relocation section %s (%d) linked to section %d has %zd relocations",
name, i, shdr.sh_link, (size_t)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 %zx (type %zd, sym %zd) offset %zd sym_name "
"%s sym_type %d sym_bind %d sym_ndx %d",
i, j, (size_t)rel.r_info, (size_t)ELF_R_TYPE(rel.r_info),
(size_t)ELF_R_SYM(rel.r_info),
(size_t)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 (ELF_R_SYM(rel.r_info) == 0) {
/* no symbol ex: R_ARM_V4BX relocation, R_ARM_RELATIVE */
link_addr = 0;
} else if (sym.st_shndx == SHN_UNDEF) {
/* If symbol is undefined, then we need to look it up */
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 %zd, link section %d",
name, (size_t)rel.r_offset, shdr.sh_link);
return -ENODATA;
} else {
LOG_INF("found symbol %s at 0x%lx", name, link_addr);
}
} else if (ELF_ST_TYPE(sym.st_info) == STT_SECTION ||
ELF_ST_TYPE(sym.st_info) == STT_FUNC ||
ELF_ST_TYPE(sym.st_info) == STT_OBJECT) {
/* Link address is relative to the start of the section */
link_addr = (uintptr_t)ext->mem[ldr->sect_map[sym.st_shndx]]
+ sym.st_value;
LOG_INF("found section symbol %s addr 0x%lx", name, link_addr);
} else {
/* Nothing to relocate here */
LOG_DBG("not relocated");
continue;
}
LOG_INF("writing relocation symbol %s type %zd sym %zd at addr 0x%lx "
"addr 0x%lx",
name, (size_t)ELF_R_TYPE(rel.r_info), (size_t)ELF_R_SYM(rel.r_info),
op_loc, link_addr);
/* relocation */
ret = arch_elf_relocate(&rel, op_loc, link_addr, name,
(uintptr_t)ext->mem[LLEXT_MEM_TEXT]);
if (ret != 0) {
return ret;
}
}
}
#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]);
sys_cache_instr_invd_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 %zu", 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
}