llext: Linkable loadable extensions
Adds the linkable loadable extensions (llext) subsystem which provides functionality for reading, parsing, and linking ELF encoded executable code into a managed extension to the running elf base image. A loader interface, and default buffer loader implementation, make available to the llext subsystem the elf data. A simple management API provide the ability to load and unload extensions as needed. A shell interface for extension loading and unloading makes it easy to try. Adds initial support for armv7 thumb built elfs with very specific compiler flags. Signed-off-by: Tom Burdick <thomas.burdick@intel.com> Co-authored-by: Chen Peng1 <peng1.chen@intel.com> Co-authored-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
This commit is contained in:
parent
34e16225eb
commit
41e0a4a371
16 changed files with 1927 additions and 0 deletions
757
subsys/llext/llext.c
Normal file
757
subsys/llext/llext.c
Normal file
|
@ -0,0 +1,757 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
#include "zephyr/sys/__assert.h"
|
||||
#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/logging/log.h>
|
||||
LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL);
|
||||
|
||||
#include <string.h>
|
||||
|
||||
K_HEAP_DEFINE(llext_heap, CONFIG_LLEXT_HEAP_SIZE * 1024);
|
||||
|
||||
static const char ELF_MAGIC[] = {0x7f, 'E', 'L', 'F'};
|
||||
|
||||
static inline int llext_read(struct llext_loader *l, void *buf, size_t len)
|
||||
{
|
||||
return l->read(l, buf, len);
|
||||
}
|
||||
|
||||
static inline int llext_seek(struct llext_loader *l, size_t pos)
|
||||
{
|
||||
return l->seek(l, pos);
|
||||
}
|
||||
|
||||
static sys_slist_t _llext_list = SYS_SLIST_STATIC_INIT(&_llext_list);
|
||||
|
||||
sys_slist_t *llext_list(void)
|
||||
{
|
||||
return &_llext_list;
|
||||
}
|
||||
|
||||
struct llext *llext_by_name(const char *name)
|
||||
{
|
||||
sys_slist_t *mlist = llext_list();
|
||||
sys_snode_t *node = sys_slist_peek_head(mlist);
|
||||
struct llext *ext = CONTAINER_OF(node, struct llext, _llext_list);
|
||||
|
||||
while (node != NULL) {
|
||||
if (strncmp(ext->name, name, sizeof(ext->name)) == 0) {
|
||||
return ext;
|
||||
}
|
||||
node = sys_slist_peek_next(node);
|
||||
ext = CONTAINER_OF(node, struct llext, _llext_list);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const void * const llext_find_sym(const struct llext_symtable *sym_table, const char *sym_name)
|
||||
{
|
||||
if (sym_table == NULL) {
|
||||
/* Buildin 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 ret = 0;
|
||||
size_t pos = ldr->hdr.e_shoff;
|
||||
elf_shdr_t shdr;
|
||||
|
||||
ldr->sects[LLEXT_SECT_SHSTRTAB] =
|
||||
ldr->sects[LLEXT_SECT_STRTAB] =
|
||||
ldr->sects[LLEXT_SECT_SYMTAB] = (elf_shdr_t){0};
|
||||
|
||||
/* Find symbol and string tables */
|
||||
for (int i = 0, str_cnt = 0; i < ldr->hdr.e_shnum && str_cnt < 3; i++) {
|
||||
ret = llext_seek(ldr, pos);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("failed seeking to position %u\n", pos);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, &shdr, sizeof(elf_shdr_t));
|
||||
if (ret != 0) {
|
||||
LOG_ERR("failed reading section header at position %u\n", pos);
|
||||
goto out;
|
||||
}
|
||||
|
||||
pos += ldr->hdr.e_shentsize;
|
||||
|
||||
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_SECT_SYMTAB] = shdr;
|
||||
ldr->sect_map[i] = LLEXT_SECT_SYMTAB;
|
||||
str_cnt++;
|
||||
break;
|
||||
case SHT_STRTAB:
|
||||
if (ldr->hdr.e_shstrndx == i) {
|
||||
LOG_DBG("shstrtab at %d", i);
|
||||
ldr->sects[LLEXT_SECT_SHSTRTAB] = shdr;
|
||||
ldr->sect_map[i] = LLEXT_SECT_SHSTRTAB;
|
||||
} else {
|
||||
LOG_DBG("strtab at %d", i);
|
||||
ldr->sects[LLEXT_SECT_STRTAB] = shdr;
|
||||
ldr->sect_map[i] = LLEXT_SECT_STRTAB;
|
||||
}
|
||||
str_cnt++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ldr->sects[LLEXT_SECT_SHSTRTAB].sh_type ||
|
||||
!ldr->sects[LLEXT_SECT_STRTAB].sh_type ||
|
||||
!ldr->sects[LLEXT_SECT_SYMTAB].sh_type) {
|
||||
LOG_ERR("Some sections are missing or present multiple times!");
|
||||
ret = -ENOENT;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Maps the section indexes and copies special section headers for easier use
|
||||
*/
|
||||
static int llext_map_sections(struct llext_loader *ldr)
|
||||
{
|
||||
int ret = 0;
|
||||
size_t pos = ldr->hdr.e_shoff;
|
||||
elf_shdr_t shdr;
|
||||
char name[32];
|
||||
|
||||
for (int i = 0; i < ldr->hdr.e_shnum; i++) {
|
||||
ret = llext_seek(ldr, pos);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, &shdr, sizeof(elf_shdr_t));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
pos += ldr->hdr.e_shentsize;
|
||||
|
||||
elf_word str_idx = shdr.sh_name;
|
||||
|
||||
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_SHSTRTAB].sh_offset + str_idx);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, name, sizeof(name));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
name[sizeof(name) - 1] = '\0';
|
||||
|
||||
LOG_DBG("section %d name %s", i, name);
|
||||
|
||||
enum llext_section sect_idx;
|
||||
|
||||
if (strncmp(name, ".text", sizeof(name)) == 0) {
|
||||
sect_idx = LLEXT_SECT_TEXT;
|
||||
} else if (strncmp(name, ".data", sizeof(name)) == 0) {
|
||||
sect_idx = LLEXT_SECT_DATA;
|
||||
} else if (strncmp(name, ".rodata", sizeof(name)) == 0) {
|
||||
sect_idx = LLEXT_SECT_RODATA;
|
||||
} else if (strncmp(name, ".bss", sizeof(name)) == 0) {
|
||||
sect_idx = LLEXT_SECT_BSS;
|
||||
} else {
|
||||
LOG_DBG("Not copied section %s", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
ldr->sects[sect_idx] = shdr;
|
||||
ldr->sect_map[i] = sect_idx;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline enum llext_section llext_sect_from_mem(enum llext_mem m)
|
||||
{
|
||||
enum llext_section s;
|
||||
|
||||
switch (m) {
|
||||
case LLEXT_MEM_BSS:
|
||||
s = LLEXT_SECT_BSS;
|
||||
break;
|
||||
case LLEXT_MEM_DATA:
|
||||
s = LLEXT_SECT_DATA;
|
||||
break;
|
||||
case LLEXT_MEM_RODATA:
|
||||
s = LLEXT_SECT_RODATA;
|
||||
break;
|
||||
case LLEXT_MEM_TEXT:
|
||||
s = LLEXT_SECT_TEXT;
|
||||
break;
|
||||
default:
|
||||
CODE_UNREACHABLE;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static int llext_allocate_mem(struct llext_loader *ldr, struct llext *ext)
|
||||
{
|
||||
int ret = 0;
|
||||
enum llext_section sect_idx;
|
||||
|
||||
for (enum llext_mem mem_idx = 0; mem_idx < LLEXT_MEM_COUNT; mem_idx++) {
|
||||
sect_idx = llext_sect_from_mem(mem_idx);
|
||||
|
||||
if (ldr->sects[sect_idx].sh_size > 0) {
|
||||
ext->mem[mem_idx] =
|
||||
k_heap_aligned_alloc(&llext_heap, sizeof(uintptr_t),
|
||||
ldr->sects[sect_idx].sh_size,
|
||||
K_NO_WAIT);
|
||||
|
||||
if (ext->mem[mem_idx] == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int llext_copy_sections(struct llext_loader *ldr, struct llext *ext)
|
||||
{
|
||||
int ret = 0;
|
||||
enum llext_section sect_idx;
|
||||
|
||||
for (enum llext_mem mem_idx = 0; mem_idx < LLEXT_MEM_COUNT; mem_idx++) {
|
||||
sect_idx = llext_sect_from_mem(mem_idx);
|
||||
|
||||
if (ldr->sects[sect_idx].sh_size > 0) {
|
||||
ret = llext_seek(ldr, ldr->sects[sect_idx].sh_offset);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, ext->mem[mem_idx], ldr->sects[sect_idx].sh_size);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int llext_count_export_syms(struct llext_loader *ldr)
|
||||
{
|
||||
int ret = 0;
|
||||
elf_sym_t sym;
|
||||
size_t ent_size = ldr->sects[LLEXT_SECT_SYMTAB].sh_entsize;
|
||||
size_t syms_size = ldr->sects[LLEXT_SECT_SYMTAB].sh_size;
|
||||
size_t pos = ldr->sects[LLEXT_SECT_SYMTAB].sh_offset;
|
||||
size_t sym_cnt = syms_size / sizeof(elf_sym_t);
|
||||
char name[32];
|
||||
|
||||
LOG_DBG("symbol count %u", sym_cnt);
|
||||
|
||||
for (int i = 0; i < sym_cnt; i++) {
|
||||
ret = llext_seek(ldr, pos);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, &sym, ent_size);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
pos += ent_size;
|
||||
|
||||
uint32_t stt = ELF_ST_TYPE(sym.st_info);
|
||||
uint32_t stb = ELF_ST_BIND(sym.st_info);
|
||||
uint32_t sect = sym.st_shndx;
|
||||
|
||||
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_STRTAB].sh_offset + sym.st_name);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, name, sizeof(name));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
name[sizeof(name) - 1] = '\0';
|
||||
|
||||
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);
|
||||
ldr->sym_cnt++;
|
||||
} else {
|
||||
LOG_DBG("unhandled symbol %d, name %s, type tag %d, bind %d, sect %d",
|
||||
i, name, stt, stb, sect);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int llext_allocate_symtab(struct llext_loader *ldr, struct llext *ext)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ext->sym_tab.syms = k_heap_alloc(&llext_heap, ldr->sym_cnt * sizeof(struct llext_symbol),
|
||||
K_NO_WAIT);
|
||||
ext->sym_tab.sym_cnt = ldr->sym_cnt;
|
||||
memset(ext->sym_tab.syms, 0, ldr->sym_cnt * sizeof(struct llext_symbol));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int llext_copy_symbols(struct llext_loader *ldr, struct llext *ext)
|
||||
{
|
||||
int ret = 0;
|
||||
elf_sym_t sym;
|
||||
size_t ent_size = ldr->sects[LLEXT_SECT_SYMTAB].sh_entsize;
|
||||
size_t syms_size = ldr->sects[LLEXT_SECT_SYMTAB].sh_size;
|
||||
size_t pos = ldr->sects[LLEXT_SECT_SYMTAB].sh_offset;
|
||||
size_t sym_cnt = syms_size / sizeof(elf_sym_t);
|
||||
char name[32];
|
||||
int i, j = 0;
|
||||
|
||||
for (i = 0; i < sym_cnt; i++) {
|
||||
ret = llext_seek(ldr, pos);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, &sym, ent_size);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
pos += ent_size;
|
||||
|
||||
uint32_t stt = ELF_ST_TYPE(sym.st_info);
|
||||
uint32_t stb = ELF_ST_BIND(sym.st_info);
|
||||
uint32_t sect = sym.st_shndx;
|
||||
|
||||
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_STRTAB].sh_offset + sym.st_name);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
llext_read(ldr, name, sizeof(name));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (stt == STT_FUNC && stb == STB_GLOBAL && sect != SHN_UNDEF) {
|
||||
ext->sym_tab.syms[j].name = k_heap_alloc(&llext_heap,
|
||||
sizeof(name),
|
||||
K_NO_WAIT);
|
||||
strcpy(ext->sym_tab.syms[j].name, name);
|
||||
ext->sym_tab.syms[j].addr =
|
||||
(void *)((uintptr_t)ext->mem[ldr->sect_map[sym.st_shndx]]
|
||||
+ sym.st_value);
|
||||
LOG_DBG("function symbol %d name %s addr %p",
|
||||
j, name, ext->sym_tab.syms[j].addr);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int llext_link(struct llext_loader *ldr, struct llext *ext)
|
||||
{
|
||||
int ret = 0;
|
||||
uintptr_t loc = 0;
|
||||
elf_shdr_t shdr;
|
||||
elf_rel_t rel;
|
||||
elf_sym_t sym;
|
||||
size_t pos = ldr->hdr.e_shoff;
|
||||
elf_word rel_cnt = 0;
|
||||
char name[32];
|
||||
|
||||
for (int i = 0; i < ldr->hdr.e_shnum - 1; i++) {
|
||||
ret = llext_seek(ldr, pos);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, &shdr, sizeof(elf_shdr_t));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
pos += ldr->hdr.e_shentsize;
|
||||
|
||||
/* find relocation sections */
|
||||
if (shdr.sh_type != SHT_REL && shdr.sh_type != SHT_RELA) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rel_cnt = shdr.sh_size / sizeof(elf_rel_t);
|
||||
|
||||
|
||||
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_SHSTRTAB].sh_offset + shdr.sh_name);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, name, sizeof(name));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (strncmp(name, ".rel.text", sizeof(name)) == 0 ||
|
||||
strncmp(name, ".rela.text", sizeof(name)) == 0) {
|
||||
loc = (uintptr_t)ext->mem[LLEXT_MEM_TEXT];
|
||||
} else if (strncmp(name, ".rel.bss", sizeof(name)) == 0) {
|
||||
loc = (uintptr_t)ext->mem[LLEXT_MEM_BSS];
|
||||
} else if (strncmp(name, ".rel.rodata", sizeof(name)) == 0) {
|
||||
loc = (uintptr_t)ext->mem[LLEXT_MEM_RODATA];
|
||||
} else if (strncmp(name, ".rel.data", sizeof(name)) == 0) {
|
||||
loc = (uintptr_t)ext->mem[LLEXT_MEM_DATA];
|
||||
}
|
||||
|
||||
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 * sizeof(elf_rel_t));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, &rel, sizeof(elf_rel_t));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* get corresponding symbol */
|
||||
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_SYMTAB].sh_offset
|
||||
+ ELF_R_SYM(rel.r_info) * sizeof(elf_sym_t));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, &sym, sizeof(elf_sym_t));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_STRTAB].sh_offset +
|
||||
sym.st_name);
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = llext_read(ldr, name, sizeof(name));
|
||||
if (ret != 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
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_code;
|
||||
|
||||
/* 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);
|
||||
ret = -ENODATA;
|
||||
goto out;
|
||||
} else {
|
||||
op_code = (uintptr_t)(loc + rel.r_offset);
|
||||
|
||||
LOG_INF("found symbol %s at 0x%lx, updating op code 0x%lx",
|
||||
name, link_addr, op_code);
|
||||
}
|
||||
} else if (ELF_ST_TYPE(sym.st_info) == STT_SECTION) {
|
||||
link_addr = (uintptr_t)ext->mem[ldr->sect_map[sym.st_shndx]];
|
||||
|
||||
LOG_INF("found section symbol %s addr 0x%lx", name, link_addr);
|
||||
} else {
|
||||
/* Nothing to relocate here */
|
||||
continue;
|
||||
}
|
||||
|
||||
op_loc = loc + rel.r_offset;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load a valid ELF as an extension
|
||||
*/
|
||||
static int do_llext_load(struct llext_loader *ldr, struct llext *ext)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
memset(ldr->sects, 0, sizeof(ldr->sects));
|
||||
ldr->sect_cnt = 0;
|
||||
ldr->sym_cnt = 0;
|
||||
|
||||
size_t sect_map_sz = ldr->hdr.e_shnum * sizeof(uint32_t);
|
||||
|
||||
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, ldr->hdr.e_shnum*sizeof(uint32_t));
|
||||
ldr->sect_cnt = ldr->hdr.e_shnum;
|
||||
|
||||
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("Mapping ELF sections...");
|
||||
ret = llext_map_sections(ldr);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to map ELF sections, ret %d", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
LOG_DBG("Allocation memory for ELF sections...");
|
||||
ret = llext_allocate_mem(ldr, ext);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to map memory for ELF sections, ret %d", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
LOG_DBG("Copying 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);
|
||||
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);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to link, ret %d", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
if (ldr->sect_map != NULL) {
|
||||
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[mem_idx] != NULL) {
|
||||
k_heap_free(&llext_heap, ext->mem[mem_idx]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < ext->sym_tab.sym_cnt; i++) {
|
||||
if (ext->sym_tab.syms[i].name != NULL) {
|
||||
k_heap_free(&llext_heap, ext->sym_tab.syms[i].name);
|
||||
}
|
||||
}
|
||||
k_heap_free(&llext_heap, ext->sym_tab.syms);
|
||||
} else {
|
||||
LOG_DBG("loaded module, .text at %p, .rodata at %p", ext->mem[LLEXT_MEM_TEXT],
|
||||
ext->mem[LLEXT_MEM_RODATA]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int llext_load(struct llext_loader *ldr, const char *name, struct llext **ext)
|
||||
{
|
||||
int ret = 0;
|
||||
elf_ehdr_t ehdr;
|
||||
|
||||
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));
|
||||
|
||||
for (int i = 0; i < LLEXT_MEM_COUNT; i++) {
|
||||
(*ext)->mem[i] = NULL;
|
||||
}
|
||||
|
||||
ldr->hdr = ehdr;
|
||||
ret = do_llext_load(ldr, *ext);
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Unsupported elf file type %x", ehdr.e_type);
|
||||
*ext = NULL;
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
strncpy((*ext)->name, name, sizeof((*ext)->name));
|
||||
(*ext)->name[sizeof((*ext)->name) - 1] = '\0';
|
||||
sys_slist_append(&_llext_list, &(*ext)->_llext_list);
|
||||
LOG_INF("Loaded extension %s", (*ext)->name);
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void llext_unload(struct llext *ext)
|
||||
{
|
||||
__ASSERT(ext, "Expected non-null extension");
|
||||
|
||||
sys_slist_find_and_remove(&_llext_list, &ext->_llext_list);
|
||||
|
||||
for (int i = 0; i < LLEXT_MEM_COUNT; i++) {
|
||||
if (ext->mem[i] != NULL) {
|
||||
LOG_DBG("freeing memory region %d", i);
|
||||
k_heap_free(&llext_heap, ext->mem[i]);
|
||||
ext->mem[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (ext->sym_tab.syms != NULL) {
|
||||
for (int i = 0; i < ext->sym_tab.sym_cnt; i++) {
|
||||
k_heap_free(&llext_heap, ext->sym_tab.syms[i].name);
|
||||
}
|
||||
k_heap_free(&llext_heap, ext->sym_tab.syms);
|
||||
}
|
||||
|
||||
k_heap_free(&llext_heap, ext);
|
||||
}
|
||||
|
||||
int llext_call_fn(struct llext *ext, const char *sym_name)
|
||||
{
|
||||
void (*fn)(void);
|
||||
|
||||
fn = llext_find_sym(&ext->sym_tab, sym_name);
|
||||
if (fn == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
fn();
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue