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

@ -27,19 +27,34 @@ LOG_MODULE_DECLARE(llext);
* supporting modules must implement this. * supporting modules must implement this.
*/ */
void arch_elf_relocate_local(struct llext_loader *ldr, struct llext *ext, 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)
{ {
uint8_t *text = ext->mem[LLEXT_MEM_TEXT]; uint8_t *text = ext->mem[LLEXT_MEM_TEXT];
int type = ELF32_R_TYPE(rel->r_info); int type = ELF32_R_TYPE(rel->r_info);
elf_word *got_entry = (elf_word *)(text + got_offset);
uintptr_t sh_addr;
if (type == R_XTENSA_RELATIVE) { if (ELF_ST_TYPE(sym->st_info) == STT_SECTION) {
elf_word ptr_offset = *(elf_word *)(text + got_offset); elf_shdr_t *shdr = llext_peek(ldr, ldr->hdr.e_shoff +
sym->st_shndx * ldr->hdr.e_shentsize);
LOG_DBG("relocation type %u offset %#x value %#x", sh_addr = shdr->sh_addr;
type, got_offset, ptr_offset); } else {
sh_addr = ldr->sects[LLEXT_MEM_TEXT].sh_addr;
/* Relocate a local symbol: Xtensa specific */
*(elf_word *)(text + got_offset) = (elf_word)(text + ptr_offset -
ldr->sects[LLEXT_MEM_TEXT].sh_addr);
} }
switch (type) {
case R_XTENSA_RELATIVE:
/* Relocate a local symbol: Xtensa specific */
*got_entry += (uintptr_t)text - sh_addr;
break;
case R_XTENSA_32:
*got_entry += sh_addr;
break;
default:
LOG_DBG("unsupported relocation type %u", type);
return;
}
LOG_DBG("relocation to %#x type %u at %p", *got_entry, type, (void *)got_entry);
} }

View file

@ -222,10 +222,11 @@ ssize_t llext_find_section(struct llext_loader *loader, const char *search_name)
* @param[in] loader Extension loader data and context * @param[in] loader Extension loader data and context
* @param[in] ext Extension to call function in * @param[in] ext Extension to call function in
* @param[in] rel Relocation data provided by elf * @param[in] rel Relocation data provided by elf
* @param[in] sym Corresponding symbol table entry
* @param[in] got_offset Offset within a relocation table * @param[in] got_offset Offset within a relocation table
*/ */
void arch_elf_relocate_local(struct llext_loader *loader, struct llext *ext, void arch_elf_relocate_local(struct llext_loader *loader, 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);
/** /**
* @} * @}

View file

@ -68,6 +68,9 @@ struct llext_loader {
*/ */
void *(*peek)(struct llext_loader *ldr, size_t pos); void *(*peek)(struct llext_loader *ldr, size_t pos);
/** Total calculated .data size for relocatable extensions */
size_t prog_data_size;
/** @cond ignore */ /** @cond ignore */
elf_ehdr_t hdr; elf_ehdr_t hdr;
elf_shdr_t sects[LLEXT_MEM_COUNT]; elf_shdr_t sects[LLEXT_MEM_COUNT];

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); 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; elf_shdr_t *shdr;
unsigned int i; 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); shdr = llext_peek(ldr, pos);
if (!shdr) { if (!shdr) {
/* The peek() method isn't supported */ /* The peek() method isn't supported */
return -EOPNOTSUPP; return NULL;
} }
const char *name = llext_peek(ldr, 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); shdr->sh_name);
if (!strcmp(name, search_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; int i, ret;
size_t pos; 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; const char *name;
ldr->sects[LLEXT_MEM_RODATA].sh_size = 0;
for (i = 0, pos = ldr->hdr.e_shoff; for (i = 0, pos = ldr->hdr.e_shoff;
i < ldr->hdr.e_shnum; i < ldr->hdr.e_shnum;
i++, pos += ldr->hdr.e_shentsize) { i++, pos += ldr->hdr.e_shentsize) {
@ -230,12 +240,38 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext)
return ret; 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); name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr.sh_name);
LOG_DBG("section %d name %s", i, name); LOG_DBG("section %d name %s", i, name);
enum llext_mem mem_idx; 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) { if (strcmp(name, ".text") == 0) {
mem_idx = LLEXT_MEM_TEXT; mem_idx = LLEXT_MEM_TEXT;
} else if (strcmp(name, ".data") == 0) { } 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->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; 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, __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, 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; 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 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; continue;
} }
const char *name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym_tbl.st_name); 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 * Both r_offset and sh_addr are addresses for which the extension
* has been built. * has been built.
*/ */
size_t got_offset = llext_file_offset(ldr, rela.r_offset) - size_t got_offset;
ldr->sects[LLEXT_MEM_TEXT].sh_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; const void *link_addr;
switch (stb) { switch (stb) {
case STB_GLOBAL: case STB_GLOBAL:
link_addr = llext_find_sym(NULL, name); link_addr = llext_find_sym(NULL, name);
if (!link_addr) if (!link_addr)
link_addr = llext_find_sym(&ext->sym_tab, name); 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 */ /* Resolve the symbol */
*(const void **)(text + got_offset) = link_addr; *(const void **)(text + got_offset) = link_addr;
break; break;
case STB_LOCAL: case STB_LOCAL:
if (do_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); name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr.sh_name);
if (strcmp(name, ".rel.text") == 0 || if (strcmp(name, ".rel.text") == 0) {
strcmp(name, ".rela.text") == 0) {
loc = (uintptr_t)ext->mem[LLEXT_MEM_TEXT]; loc = (uintptr_t)ext->mem[LLEXT_MEM_TEXT];
} else if (strcmp(name, ".rel.bss") == 0 || } else if (strcmp(name, ".rel.bss") == 0 ||
strcmp(name, ".rela.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 || } else if (strcmp(name, ".rel.rodata") == 0 ||
strcmp(name, ".rela.rodata") == 0) { strcmp(name, ".rela.rodata") == 0) {
loc = (uintptr_t)ext->mem[LLEXT_MEM_RODATA]; loc = (uintptr_t)ext->mem[LLEXT_MEM_RODATA];
} else if (strcmp(name, ".rel.data") == 0 || } else if (strcmp(name, ".rel.data") == 0) {
strcmp(name, ".rela.data") == 0) {
loc = (uintptr_t)ext->mem[LLEXT_MEM_DATA]; loc = (uintptr_t)ext->mem[LLEXT_MEM_DATA];
} else if (strcmp(name, ".rel.exported_sym") == 0) { } else if (strcmp(name, ".rel.exported_sym") == 0) {
loc = (uintptr_t)ext->mem[LLEXT_MEM_EXPORT]; loc = (uintptr_t)ext->mem[LLEXT_MEM_EXPORT];
} else if (strcmp(name, ".rela.plt") == 0 || } else if (strcmp(name, ".rela.plt") == 0 ||
strcmp(name, ".rela.dyn") == 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; continue;
} }