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
parent 4879681b7e
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.
*/
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];
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) {
elf_word ptr_offset = *(elf_word *)(text + got_offset);
LOG_DBG("relocation type %u offset %#x value %#x",
type, got_offset, ptr_offset);
/* Relocate a local symbol: Xtensa specific */
*(elf_word *)(text + got_offset) = (elf_word)(text + ptr_offset -
ldr->sects[LLEXT_MEM_TEXT].sh_addr);
if (ELF_ST_TYPE(sym->st_info) == STT_SECTION) {
elf_shdr_t *shdr = llext_peek(ldr, ldr->hdr.e_shoff +
sym->st_shndx * ldr->hdr.e_shentsize);
sh_addr = shdr->sh_addr;
} else {
sh_addr = 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] ext Extension to call function in
* @param[in] rel Relocation data provided by elf
* @param[in] sym Corresponding symbol table entry
* @param[in] got_offset Offset within a relocation table
*/
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);
/** Total calculated .data size for relocatable extensions */
size_t prog_data_size;
/** @cond ignore */
elf_ehdr_t hdr;
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);
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;
}