From 2ccf775396c225f4398db1014886397a91f6d42f Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 27 Mar 2024 14:43:31 +0100 Subject: [PATCH] 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 --- arch/xtensa/core/elf.c | 35 +++++++++---- include/zephyr/llext/llext.h | 3 +- include/zephyr/llext/loader.h | 3 ++ subsys/llext/llext.c | 94 ++++++++++++++++++++++++++++------- 4 files changed, 106 insertions(+), 29 deletions(-) diff --git a/arch/xtensa/core/elf.c b/arch/xtensa/core/elf.c index 976be9f794a..46da8eb908a 100644 --- a/arch/xtensa/core/elf.c +++ b/arch/xtensa/core/elf.c @@ -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); } diff --git a/include/zephyr/llext/llext.h b/include/zephyr/llext/llext.h index e09e21c14f6..754f0ea846d 100644 --- a/include/zephyr/llext/llext.h +++ b/include/zephyr/llext/llext.h @@ -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); /** * @} diff --git a/include/zephyr/llext/loader.h b/include/zephyr/llext/loader.h index 7eb49d611be..b8477eb3003 100644 --- a/include/zephyr/llext/loader.h +++ b/include/zephyr/llext/loader.h @@ -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]; diff --git a/subsys/llext/llext.c b/subsys/llext/llext.c index 3a5a39ed7ee..08d3719778a 100644 --- a/subsys/llext/llext.c +++ b/subsys/llext/llext.c @@ -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; }