zephyr/subsys/llext/llext.c
Tom Burdick 84e883b611 llext: Support memory protection
Sets up memory partitions and allows for the partitions to be added to a
memory domain after loading an extension. This allows for applying
memory protection attributes to all of the needed memory regions an
extension requires to execute code correctly.

Currently only works when usermode is enabled as otherwise memory
protection APIs are unavailable.

Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
2024-02-02 13:06:28 -05:00

1069 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
}