llext: add support for relocatable objects on Xtensa

Some toolchains cannot create shared objects for Xtensa, with them we
have to use relocatable objects. Add support for them to llext.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
This commit is contained in:
Guennadi Liakhovetski 2024-03-27 14:43:31 +01:00 committed by David Leach
commit 2ccf775396
4 changed files with 106 additions and 29 deletions

View file

@ -32,7 +32,7 @@ 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)
static elf_shdr_t *llext_section_by_name(struct llext_loader *ldr, const char *search_name)
{
elf_shdr_t *shdr;
unsigned int i;
@ -44,7 +44,7 @@ ssize_t llext_find_section(struct llext_loader *ldr, const char *search_name)
shdr = llext_peek(ldr, pos);
if (!shdr) {
/* The peek() method isn't supported */
return -EOPNOTSUPP;
return NULL;
}
const char *name = llext_peek(ldr,
@ -52,11 +52,18 @@ ssize_t llext_find_section(struct llext_loader *ldr, const char *search_name)
shdr->sh_name);
if (!strcmp(name, search_name)) {
return shdr->sh_offset;
return shdr;
}
}
return -ENOENT;
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;
}
/*
@ -214,9 +221,12 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext)
{
int i, ret;
size_t pos;
elf_shdr_t shdr;
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) {
@ -230,12 +240,38 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext)
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) {
@ -255,6 +291,13 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext)
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;
}
@ -556,12 +599,12 @@ static size_t llext_file_offset(struct llext_loader *ldr, size_t offset)
}
__weak void arch_elf_relocate_local(struct llext_loader *ldr, struct llext *ext,
elf_rela_t *rel, size_t got_offset)
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 *shdr, bool do_local, elf_shdr_t *tgt)
{
unsigned int sh_cnt = shdr->sh_size / shdr->sh_entsize;
/*
@ -613,25 +656,36 @@ static void llext_link_plt(struct llext_loader *ldr, struct llext *ext,
}
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)) {
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 = llext_file_offset(ldr, rela.r_offset) -
ldr->sects[LLEXT_MEM_TEXT].sh_offset;
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);
@ -648,9 +702,9 @@ static void llext_link_plt(struct llext_loader *ldr, struct llext *ext,
/* Resolve the symbol */
*(const void **)(text + got_offset) = link_addr;
break;
case STB_LOCAL:
case STB_LOCAL:
if (do_local) {
arch_elf_relocate_local(ldr, ext, &rela, got_offset);
arch_elf_relocate_local(ldr, ext, &rela, &sym_tbl, got_offset);
}
}
@ -697,8 +751,7 @@ static int llext_link(struct llext_loader *ldr, struct llext *ext, bool do_local
name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr.sh_name);
if (strcmp(name, ".rel.text") == 0 ||
strcmp(name, ".rela.text") == 0) {
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) {
@ -706,14 +759,19 @@ static int llext_link(struct llext_loader *ldr, struct llext *ext, bool do_local
} 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) {
} 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);
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;
}