From 709b2e44bfdd159577cb2e15d17bc10b5c8dc783 Mon Sep 17 00:00:00 2001 From: Luca Burelli Date: Fri, 31 May 2024 10:54:48 +0200 Subject: [PATCH] llext: automatically merge sections by type This patch changes the way sections are mapped to memories. Instead of looking at the section name, each section in the ELF file is mapped to the llext_mem enum by looking at the section type and flags. This allows for a more generic mapping that works for both the ARM and Xtensa cases, and also allows for sections to be merged if they are contiguous and non-overlapping in the ELF file. This patch also fixes a number of corner cases, such as in the logging test where a section with read-only data was being ignored (not copied and not relinked). Signed-off-by: Luca Burelli --- include/zephyr/llext/elf.h | 1 + include/zephyr/llext/loader.h | 3 - subsys/llext/llext.c | 178 +++++++++++++++++++++++++--------- 3 files changed, 132 insertions(+), 50 deletions(-) diff --git a/include/zephyr/llext/elf.h b/include/zephyr/llext/elf.h index 7e8628b37d5..b39af854312 100644 --- a/include/zephyr/llext/elf.h +++ b/include/zephyr/llext/elf.h @@ -196,6 +196,7 @@ struct elf64_shdr { elf64_xword sh_entsize; }; +#define SHT_NULL 0x0 #define SHT_PROGBITS 0x1 #define SHT_SYMTAB 0x2 #define SHT_STRTAB 0x3 diff --git a/include/zephyr/llext/loader.h b/include/zephyr/llext/loader.h index b8477eb3003..7eb49d611be 100644 --- a/include/zephyr/llext/loader.h +++ b/include/zephyr/llext/loader.h @@ -68,9 +68,6 @@ 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 a647ac3bc27..ca5513b741f 100644 --- a/subsys/llext/llext.c +++ b/subsys/llext/llext.c @@ -186,11 +186,13 @@ static int llext_find_tables(struct llext_loader *ldr) return ret; } - LOG_DBG("section %d at %zx: name %d, type %d, flags %zx, addr %zx, size %zd", + LOG_DBG("section %d at %zx: name %d, type %d, flags %zx, " + "ofs %zx, addr %zx, size %zd", i, pos, shdr.sh_name, shdr.sh_type, (size_t)shdr.sh_flags, + (size_t)shdr.sh_offset, (size_t)shdr.sh_addr, (size_t)shdr.sh_size); @@ -240,14 +242,11 @@ static const char *llext_string(struct llext_loader *ldr, struct llext *ext, */ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext) { - int i, ret; + int i, j, ret; size_t pos; - elf_shdr_t shdr, rodata = {.sh_addr = ~0}, - high_shdr = {.sh_offset = 0}, low_shdr = {.sh_offset = ~0}; + elf_shdr_t shdr; 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) { @@ -261,62 +260,145 @@ 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; - } + if ((shdr.sh_type != SHT_PROGBITS && shdr.sh_type != SHT_NOBITS) || + !(shdr.sh_flags & SHF_ALLOC) || + shdr.sh_size == 0) { + continue; } name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr.sh_name); - LOG_DBG("section %d name %s", i, name); - + /* Identify the section type by its flags */ 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) { - mem_idx = LLEXT_MEM_DATA; - } else if (strcmp(name, ".rodata") == 0) { - mem_idx = LLEXT_MEM_RODATA; - } else if (strcmp(name, ".bss") == 0) { + switch (shdr.sh_type) { + case SHT_NOBITS: mem_idx = LLEXT_MEM_BSS; - } else if (strcmp(name, ".exported_sym") == 0) { - mem_idx = LLEXT_MEM_EXPORT; - } else { + break; + case SHT_PROGBITS: + if (shdr.sh_flags & SHF_EXECINSTR) { + mem_idx = LLEXT_MEM_TEXT; + } else if (shdr.sh_flags & SHF_WRITE) { + mem_idx = LLEXT_MEM_DATA; + } else { + mem_idx = LLEXT_MEM_RODATA; + } + break; + default: LOG_DBG("Not copied section %s", name); continue; } - ldr->sects[mem_idx] = shdr; + /* Special exception for .exported_sym */ + if (strcmp(name, ".exported_sym") == 0) { + mem_idx = LLEXT_MEM_EXPORT; + } + + LOG_DBG("section %d name %s maps to idx %d", i, name, mem_idx); + ldr->sect_map[i] = mem_idx; + elf_shdr_t *sect = ldr->sects + mem_idx; + + if (sect->sh_type == SHT_NULL) { + /* First section of this type, copy all info */ + *sect = shdr; + } else { + /* Make sure the sections are compatible before merging */ + if (shdr.sh_flags != sect->sh_flags) { + LOG_ERR("Unsupported section flags for %s (mem %d)", + name, mem_idx); + return -ENOEXEC; + } + + if (mem_idx == LLEXT_MEM_BSS) { + /* SHT_NOBITS sections cannot be merged properly: + * as they use no space in the file, the logic + * below does not work; they must be treated as + * independent entities. + */ + LOG_ERR("Multiple SHT_NOBITS sections are not supported"); + return -ENOEXEC; + } + + if (ldr->hdr.e_type == ET_DYN) { + /* In shared objects, sh_addr is the VMA. Before + * merging these sections, make sure the delta + * in VMAs matches that of file offsets. + */ + if (shdr.sh_addr - sect->sh_addr != + shdr.sh_offset - sect->sh_offset) { + LOG_ERR("Incompatible section addresses " + "for %s (mem %d)", name, mem_idx); + return -ENOEXEC; + } + } + + /* + * Extend the current section to include the new one + * (overlaps are detected later) + */ + size_t address = MIN(sect->sh_addr, shdr.sh_addr); + size_t bot_ofs = MIN(sect->sh_offset, shdr.sh_offset); + size_t top_ofs = MAX(sect->sh_offset + sect->sh_size, + shdr.sh_offset + shdr.sh_size); + + sect->sh_addr = address; + sect->sh_offset = bot_ofs; + sect->sh_size = top_ofs - bot_ofs; + } } - ldr->prog_data_size = high_shdr.sh_size + high_shdr.sh_offset - low_shdr.sh_offset; + /* + * Test that no computed range overlaps. This can happen if sections of + * different llext_mem type are interleaved in the ELF file or in VMAs. + */ + for (i = 0; i < LLEXT_MEM_COUNT; i++) { + for (j = i+1; j < LLEXT_MEM_COUNT; j++) { + elf_shdr_t *x = ldr->sects + i; + elf_shdr_t *y = ldr->sects + j; - /* No verbatim .rodata, use an automatically selected one */ - if (!ldr->sects[LLEXT_MEM_RODATA].sh_size) { - ldr->sects[LLEXT_MEM_RODATA] = rodata; + if (x->sh_type == SHT_NULL || x->sh_size == 0 || + y->sh_type == SHT_NULL || y->sh_size == 0) { + /* Skip empty sections */ + continue; + } + + if (ldr->hdr.e_type == ET_DYN) { + /* + * Test all merged VMA ranges for overlaps + */ + if ((x->sh_addr <= y->sh_addr && + x->sh_addr + x->sh_size > y->sh_addr) || + (y->sh_addr <= x->sh_addr && + y->sh_addr + y->sh_size > x->sh_addr)) { + LOG_ERR("VMA range %d (0x%zx +%zd) " + "overlaps with %d (0x%zx +%zd)", + i, (size_t)x->sh_addr, (size_t)x->sh_size, + j, (size_t)y->sh_addr, (size_t)y->sh_size); + return -ENOEXEC; + } + } + + /* + * Test file offsets. BSS sections store no + * data in the file and must not be included + * in checks to avoid false positives. + */ + if (i == LLEXT_MEM_BSS || j == LLEXT_MEM_BSS) { + continue; + } + + if ((x->sh_offset <= y->sh_offset && + x->sh_offset + x->sh_size > y->sh_offset) || + (y->sh_offset <= x->sh_offset && + y->sh_offset + y->sh_size > x->sh_offset)) { + LOG_ERR("ELF file range %d (0x%zx +%zd) " + "overlaps with %d (0x%zx +%zd)", + i, (size_t)x->sh_offset, (size_t)x->sh_size, + j, (size_t)y->sh_offset, (size_t)y->sh_size); + return -ENOEXEC; + } + } } return 0; @@ -352,6 +434,8 @@ static void llext_init_mem_part(struct llext *ext, enum llext_mem mem_idx, ext->mem_parts[mem_idx].size); } #endif + + LOG_DBG("mem idx %d: start 0x%zx, size %zd", mem_idx, (size_t)start, len); } static int llext_copy_section(struct llext_loader *ldr, struct llext *ext,