| /* |
| * Copyright (c) 2023 Intel Corporation |
| * Copyright (c) 2024 Arduino SA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/sys/util.h> |
| #include <zephyr/llext/elf.h> |
| #include <zephyr/llext/loader.h> |
| #include <zephyr/llext/llext.h> |
| #include <zephyr/llext/llext_internal.h> |
| #include <zephyr/kernel.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL); |
| |
| #include <string.h> |
| |
| #include "llext_priv.h" |
| |
| /* |
| * NOTICE: Functions in this file do not clean up allocations in their error |
| * paths; instead, this is performed once and for all when leaving the parent |
| * `do_llext_load()` function. This approach consolidates memory management |
| * in a single place, simplifying error handling and reducing the risk of |
| * memory leaks. |
| * |
| * The following rationale applies: |
| * |
| * - The input `struct llext` and fields in `struct loader` are zero-filled |
| * at the beginning of the do_llext_load function, so that every pointer is |
| * set to NULL and every bool is false. |
| * - If some function called by do_llext_load allocates memory, it does so by |
| * immediately writing the pointer in the `ext` and `ldr` structures. |
| * - do_llext_load() will clean up the memory allocated by the functions it |
| * calls, taking into account if the load process was successful or not. |
| */ |
| |
| static const char ELF_MAGIC[] = {0x7f, 'E', 'L', 'F'}; |
| |
| const void *llext_loaded_sect_ptr(struct llext_loader *ldr, struct llext *ext, unsigned int sh_ndx) |
| { |
| enum llext_mem mem_idx = ldr->sect_map[sh_ndx].mem_idx; |
| |
| if (mem_idx == LLEXT_MEM_COUNT) { |
| return NULL; |
| } |
| |
| return (const uint8_t *)ext->mem[mem_idx] + ldr->sect_map[sh_ndx].offset; |
| } |
| |
| /* |
| * Load basic ELF file data |
| */ |
| |
| static int llext_load_elf_data(struct llext_loader *ldr, struct llext *ext) |
| { |
| int ret; |
| |
| /* read ELF header */ |
| |
| ret = llext_seek(ldr, 0); |
| if (ret != 0) { |
| LOG_ERR("Failed to seek for ELF header"); |
| return ret; |
| } |
| |
| ret = llext_read(ldr, &ldr->hdr, sizeof(ldr->hdr)); |
| if (ret != 0) { |
| LOG_ERR("Failed to read ELF header"); |
| return ret; |
| } |
| |
| /* check whether this is a valid ELF file */ |
| if (memcmp(ldr->hdr.e_ident, ELF_MAGIC, sizeof(ELF_MAGIC)) != 0) { |
| LOG_HEXDUMP_ERR(ldr->hdr.e_ident, 16, "Invalid ELF, magic does not match"); |
| return -ENOEXEC; |
| } |
| |
| switch (ldr->hdr.e_type) { |
| case ET_REL: |
| LOG_DBG("Loading relocatable ELF"); |
| break; |
| |
| case ET_DYN: |
| LOG_DBG("Loading shared ELF"); |
| break; |
| |
| default: |
| LOG_ERR("Unsupported ELF file type %x", ldr->hdr.e_type); |
| return -ENOEXEC; |
| } |
| |
| /* |
| * Read all ELF section headers and initialize maps. Buffers allocated |
| * below are freed when leaving do_llext_load(), so don't count them in |
| * alloc_size. |
| */ |
| |
| if (ldr->hdr.e_shentsize != sizeof(elf_shdr_t)) { |
| LOG_ERR("Invalid section header size %d", ldr->hdr.e_shentsize); |
| return -ENOEXEC; |
| } |
| |
| ext->sect_cnt = ldr->hdr.e_shnum; |
| |
| size_t sect_map_sz = ext->sect_cnt * sizeof(ldr->sect_map[0]); |
| |
| ldr->sect_map = llext_alloc(sect_map_sz); |
| if (!ldr->sect_map) { |
| LOG_ERR("Failed to allocate section map, size %zu", sect_map_sz); |
| return -ENOMEM; |
| } |
| for (int i = 0; i < ext->sect_cnt; i++) { |
| ldr->sect_map[i].mem_idx = LLEXT_MEM_COUNT; |
| ldr->sect_map[i].offset = 0; |
| } |
| |
| ext->sect_hdrs = (elf_shdr_t *)llext_peek(ldr, ldr->hdr.e_shoff); |
| if (ext->sect_hdrs) { |
| ext->sect_hdrs_on_heap = false; |
| } else { |
| size_t sect_hdrs_sz = ext->sect_cnt * sizeof(ext->sect_hdrs[0]); |
| |
| ext->sect_hdrs_on_heap = true; |
| ext->sect_hdrs = llext_alloc(sect_hdrs_sz); |
| if (!ext->sect_hdrs) { |
| LOG_ERR("Failed to allocate section headers, size %zu", sect_hdrs_sz); |
| return -ENOMEM; |
| } |
| |
| ret = llext_seek(ldr, ldr->hdr.e_shoff); |
| if (ret != 0) { |
| LOG_ERR("Failed to seek for section headers"); |
| return ret; |
| } |
| |
| ret = llext_read(ldr, ext->sect_hdrs, sect_hdrs_sz); |
| if (ret != 0) { |
| LOG_ERR("Failed to read section headers"); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Find all relevant string and symbol tables |
| */ |
| static int llext_find_tables(struct llext_loader *ldr, struct llext *ext) |
| { |
| int table_cnt, i; |
| |
| memset(ldr->sects, 0, sizeof(ldr->sects)); |
| |
| /* Find symbol and string tables */ |
| for (i = 0, table_cnt = 0; i < ext->sect_cnt && table_cnt < 3; ++i) { |
| elf_shdr_t *shdr = ext->sect_hdrs + i; |
| |
| LOG_DBG("section %d at 0x%zx: name %d, type %d, flags 0x%zx, " |
| "addr 0x%zx, size %zd, link %d, info %d", |
| i, |
| (size_t)shdr->sh_offset, |
| shdr->sh_name, |
| shdr->sh_type, |
| (size_t)shdr->sh_flags, |
| (size_t)shdr->sh_addr, |
| (size_t)shdr->sh_size, |
| shdr->sh_link, |
| shdr->sh_info); |
| |
| switch (shdr->sh_type) { |
| case SHT_SYMTAB: |
| case SHT_DYNSYM: |
| LOG_DBG("symtab at %d", i); |
| ldr->sects[LLEXT_MEM_SYMTAB] = *shdr; |
| ldr->sect_map[i].mem_idx = LLEXT_MEM_SYMTAB; |
| table_cnt++; |
| break; |
| case SHT_STRTAB: |
| if (ldr->hdr.e_shstrndx == i) { |
| LOG_DBG("shstrtab at %d", i); |
| ldr->sects[LLEXT_MEM_SHSTRTAB] = *shdr; |
| ldr->sect_map[i].mem_idx = LLEXT_MEM_SHSTRTAB; |
| } else { |
| LOG_DBG("strtab at %d", i); |
| ldr->sects[LLEXT_MEM_STRTAB] = *shdr; |
| ldr->sect_map[i].mem_idx = LLEXT_MEM_STRTAB; |
| } |
| table_cnt++; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (!ldr->sects[LLEXT_MEM_SHSTRTAB].sh_type || |
| !ldr->sects[LLEXT_MEM_STRTAB].sh_type || |
| !ldr->sects[LLEXT_MEM_SYMTAB].sh_type) { |
| LOG_ERR("Some sections are missing or present multiple times!"); |
| return -ENOEXEC; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Maps the ELF sections into regions according to their usage flags, |
| * calculating ldr->sects and ldr->sect_map. |
| */ |
| static int llext_map_sections(struct llext_loader *ldr, struct llext *ext, |
| const struct llext_load_param *ldr_parm) |
| { |
| int i, j; |
| const char *name; |
| |
| for (i = 0; i < ext->sect_cnt; ++i) { |
| elf_shdr_t *shdr = ext->sect_hdrs + i; |
| |
| name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr->sh_name); |
| |
| if (ldr->sect_map[i].mem_idx != LLEXT_MEM_COUNT) { |
| LOG_DBG("section %d name %s already mapped to region %d", |
| i, name, ldr->sect_map[i].mem_idx); |
| continue; |
| } |
| |
| /* Identify the section type by its flags */ |
| enum llext_mem mem_idx; |
| |
| switch (shdr->sh_type) { |
| case SHT_NOBITS: |
| mem_idx = LLEXT_MEM_BSS; |
| 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; |
| case SHT_PREINIT_ARRAY: |
| mem_idx = LLEXT_MEM_PREINIT; |
| break; |
| case SHT_INIT_ARRAY: |
| mem_idx = LLEXT_MEM_INIT; |
| break; |
| case SHT_FINI_ARRAY: |
| mem_idx = LLEXT_MEM_FINI; |
| break; |
| default: |
| mem_idx = LLEXT_MEM_COUNT; |
| break; |
| } |
| |
| /* Special exception for .exported_sym */ |
| if (strcmp(name, ".exported_sym") == 0) { |
| mem_idx = LLEXT_MEM_EXPORT; |
| } |
| |
| if (mem_idx == LLEXT_MEM_COUNT || |
| !(shdr->sh_flags & SHF_ALLOC) || |
| shdr->sh_size == 0) { |
| LOG_DBG("section %d name %s skipped", i, name); |
| continue; |
| } |
| |
| switch (mem_idx) { |
| case LLEXT_MEM_PREINIT: |
| case LLEXT_MEM_INIT: |
| case LLEXT_MEM_FINI: |
| if (shdr->sh_entsize != sizeof(void *) || |
| shdr->sh_size % shdr->sh_entsize != 0) { |
| LOG_ERR("Invalid %s array in section %d", name, i); |
| return -ENOEXEC; |
| } |
| default: |
| break; |
| } |
| |
| LOG_DBG("section %d name %s maps to region %d", i, name, mem_idx); |
| |
| ldr->sect_map[i].mem_idx = mem_idx; |
| elf_shdr_t *region = ldr->sects + mem_idx; |
| |
| /* |
| * ELF objects can have sections for memory regions, detached from |
| * other sections of the same type. E.g. executable sections that will be |
| * placed in slower memory. Don't merge such sections into main regions |
| */ |
| if (ldr_parm->section_detached && ldr_parm->section_detached(shdr)) { |
| continue; |
| } |
| |
| if (region->sh_type == SHT_NULL) { |
| /* First section of this type, copy all info to the |
| * region descriptor. |
| */ |
| memcpy(region, shdr, sizeof(*region)); |
| } else { |
| /* Make sure this section is compatible with the region */ |
| if ((shdr->sh_flags & SHF_BASIC_TYPE_MASK) != |
| (region->sh_flags & SHF_BASIC_TYPE_MASK)) { |
| LOG_ERR("Unsupported section flags %#x / %#x for %s (region %d)", |
| (uint32_t)shdr->sh_flags, (uint32_t)region->sh_flags, |
| name, mem_idx); |
| return -ENOEXEC; |
| } |
| |
| /* Check if this region type is extendable */ |
| switch (mem_idx) { |
| case 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 -ENOTSUP; |
| case LLEXT_MEM_PREINIT: |
| case LLEXT_MEM_INIT: |
| case LLEXT_MEM_FINI: |
| /* These regions are not extendable and must be |
| * referenced at most once in the ELF file. |
| */ |
| LOG_ERR("Region %d redefined", mem_idx); |
| return -ENOEXEC; |
| default: |
| break; |
| } |
| |
| if (ldr->hdr.e_type == ET_DYN) { |
| /* In shared objects, sh_addr is the VMA. |
| * Before merging this section in the region, |
| * make sure the delta in VMAs matches that of |
| * file offsets. |
| */ |
| if (shdr->sh_addr - region->sh_addr != |
| shdr->sh_offset - region->sh_offset) { |
| LOG_ERR("Incompatible section addresses " |
| "for %s (region %d)", name, mem_idx); |
| return -ENOEXEC; |
| } |
| } |
| |
| /* |
| * Extend the current region to include the new section |
| * (overlaps are detected later) |
| */ |
| size_t address = MIN(region->sh_addr, shdr->sh_addr); |
| size_t bot_ofs = MIN(region->sh_offset, shdr->sh_offset); |
| size_t top_ofs = MAX(region->sh_offset + region->sh_size, |
| shdr->sh_offset + shdr->sh_size); |
| |
| region->sh_addr = address; |
| region->sh_offset = bot_ofs; |
| region->sh_size = top_ofs - bot_ofs; |
| } |
| } |
| |
| /* |
| * Test that no computed region 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; |
| |
| if (x->sh_type == SHT_NULL || x->sh_size == 0 || |
| y->sh_type == SHT_NULL || y->sh_size == 0) { |
| /* Skip empty regions */ |
| continue; |
| } |
| |
| /* |
| * The export symbol table may be surrounded by |
| * other data sections. Ignore overlaps in that |
| * case. |
| */ |
| if ((i == LLEXT_MEM_DATA || i == LLEXT_MEM_RODATA) && |
| j == LLEXT_MEM_EXPORT) { |
| continue; |
| } |
| |
| /* |
| * Exported symbols region can also overlap |
| * with rodata. |
| */ |
| if (i == LLEXT_MEM_EXPORT || j == LLEXT_MEM_EXPORT) { |
| 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("Region %d VMA range (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("Region %d ELF file range (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; |
| } |
| } |
| } |
| |
| /* |
| * Calculate each ELF section's offset inside its memory region. This |
| * is done as a separate pass so the final regions are already defined. |
| */ |
| for (i = 0; i < ext->sect_cnt; ++i) { |
| elf_shdr_t *shdr = ext->sect_hdrs + i; |
| enum llext_mem mem_idx = ldr->sect_map[i].mem_idx; |
| |
| if (mem_idx != LLEXT_MEM_COUNT) { |
| ldr->sect_map[i].offset = shdr->sh_offset - ldr->sects[mem_idx].sh_offset; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int llext_count_export_syms(struct llext_loader *ldr, struct llext *ext) |
| { |
| size_t ent_size = ldr->sects[LLEXT_MEM_SYMTAB].sh_entsize; |
| size_t syms_size = ldr->sects[LLEXT_MEM_SYMTAB].sh_size; |
| int sym_cnt = syms_size / sizeof(elf_sym_t); |
| const char *name; |
| elf_sym_t sym; |
| int i, ret; |
| size_t pos; |
| |
| LOG_DBG("symbol count %u", sym_cnt); |
| |
| ext->sym_tab.sym_cnt = 0; |
| for (i = 0, pos = ldr->sects[LLEXT_MEM_SYMTAB].sh_offset; |
| i < sym_cnt; |
| i++, pos += ent_size) { |
| if (!i) { |
| /* A dummy entry */ |
| continue; |
| } |
| |
| ret = llext_seek(ldr, pos); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| ret = llext_read(ldr, &sym, ent_size); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| uint32_t stt = ELF_ST_TYPE(sym.st_info); |
| uint32_t stb = ELF_ST_BIND(sym.st_info); |
| uint32_t sect = sym.st_shndx; |
| |
| name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym.st_name); |
| |
| if ((stt == STT_FUNC || stt == STT_OBJECT) && stb == STB_GLOBAL) { |
| LOG_DBG("function symbol %d, name %s, type tag %d, bind %d, sect %d", |
| i, name, stt, stb, sect); |
| ext->sym_tab.sym_cnt++; |
| } else { |
| LOG_DBG("unhandled symbol %d, name %s, type tag %d, bind %d, sect %d", |
| i, name, stt, stb, sect); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int llext_allocate_symtab(struct llext_loader *ldr, struct llext *ext) |
| { |
| struct llext_symtable *sym_tab = &ext->sym_tab; |
| size_t syms_size = sym_tab->sym_cnt * sizeof(struct llext_symbol); |
| |
| sym_tab->syms = llext_alloc(syms_size); |
| if (!sym_tab->syms) { |
| return -ENOMEM; |
| } |
| memset(sym_tab->syms, 0, syms_size); |
| ext->alloc_size += syms_size; |
| |
| return 0; |
| } |
| |
| static int llext_export_symbols(struct llext_loader *ldr, struct llext *ext) |
| { |
| elf_shdr_t *shdr = ldr->sects + LLEXT_MEM_EXPORT; |
| struct llext_symbol *sym; |
| unsigned int i; |
| |
| if (shdr->sh_size < sizeof(struct llext_symbol)) { |
| /* Not found, no symbols exported */ |
| return 0; |
| } |
| |
| struct llext_symtable *exp_tab = &ext->exp_tab; |
| |
| exp_tab->sym_cnt = shdr->sh_size / sizeof(struct llext_symbol); |
| exp_tab->syms = llext_alloc(exp_tab->sym_cnt * sizeof(struct llext_symbol)); |
| if (!exp_tab->syms) { |
| return -ENOMEM; |
| } |
| |
| for (i = 0, sym = ext->mem[LLEXT_MEM_EXPORT]; |
| i < exp_tab->sym_cnt; |
| i++, sym++) { |
| exp_tab->syms[i].name = sym->name; |
| exp_tab->syms[i].addr = sym->addr; |
| LOG_DBG("sym %p name %s in %p", sym->addr, sym->name, exp_tab->syms + i); |
| } |
| |
| return 0; |
| } |
| |
| static int llext_copy_symbols(struct llext_loader *ldr, struct llext *ext, |
| const struct llext_load_param *ldr_parm) |
| { |
| size_t ent_size = ldr->sects[LLEXT_MEM_SYMTAB].sh_entsize; |
| size_t syms_size = ldr->sects[LLEXT_MEM_SYMTAB].sh_size; |
| int sym_cnt = syms_size / sizeof(elf_sym_t); |
| struct llext_symtable *sym_tab = &ext->sym_tab; |
| elf_sym_t sym; |
| int i, j, ret; |
| size_t pos; |
| |
| for (i = 0, pos = ldr->sects[LLEXT_MEM_SYMTAB].sh_offset, j = 0; |
| i < sym_cnt; |
| i++, pos += ent_size) { |
| if (!i) { |
| /* A dummy entry */ |
| continue; |
| } |
| |
| ret = llext_seek(ldr, pos); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| ret = llext_read(ldr, &sym, ent_size); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| uint32_t stt = ELF_ST_TYPE(sym.st_info); |
| uint32_t stb = ELF_ST_BIND(sym.st_info); |
| unsigned int shndx = sym.st_shndx; |
| |
| if ((stt == STT_FUNC || stt == STT_OBJECT) && |
| stb == STB_GLOBAL && shndx != SHN_UNDEF) { |
| const char *name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym.st_name); |
| |
| __ASSERT(j <= sym_tab->sym_cnt, "Miscalculated symbol number %u\n", j); |
| |
| sym_tab->syms[j].name = name; |
| |
| elf_shdr_t *shdr = ext->sect_hdrs + shndx; |
| uintptr_t section_addr = shdr->sh_addr; |
| |
| if (ldr_parm->pre_located && |
| (!ldr_parm->section_detached || !ldr_parm->section_detached(shdr))) { |
| sym_tab->syms[j].addr = (uint8_t *)sym.st_value + |
| (ldr->hdr.e_type == ET_REL ? section_addr : 0); |
| } else { |
| const void *base; |
| |
| base = llext_loaded_sect_ptr(ldr, ext, shndx); |
| if (!base) { |
| /* If the section is not mapped, try to peek. |
| * Be noisy about it, since this is addressing |
| * data that was missed by llext_map_sections. |
| */ |
| base = llext_peek(ldr, shdr->sh_offset); |
| if (base) { |
| LOG_DBG("section %d peeked at %p", shndx, base); |
| } else { |
| LOG_ERR("No data for section %d", shndx); |
| return -ENOTSUP; |
| } |
| } |
| |
| sym_tab->syms[j].addr = (uint8_t *)base + sym.st_value - |
| (ldr->hdr.e_type == ET_REL ? 0 : section_addr); |
| } |
| |
| LOG_DBG("function symbol %d name %s addr %p", |
| j, name, sym_tab->syms[j].addr); |
| j++; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Load a valid ELF as an extension |
| */ |
| int do_llext_load(struct llext_loader *ldr, struct llext *ext, |
| const struct llext_load_param *ldr_parm) |
| { |
| const struct llext_load_param default_ldr_parm = LLEXT_LOAD_PARAM_DEFAULT; |
| int ret; |
| |
| if (!ldr_parm) { |
| ldr_parm = &default_ldr_parm; |
| } |
| |
| /* Zero all memory that is affected by the loading process |
| * (see the NOTICE at the top of this file). |
| */ |
| memset(ext, 0, sizeof(*ext)); |
| ldr->sect_map = NULL; |
| |
| LOG_DBG("Loading ELF data..."); |
| ret = llext_prepare(ldr); |
| if (ret != 0) { |
| LOG_ERR("Failed to prepare the loader, ret %d", ret); |
| goto out; |
| } |
| |
| ret = llext_load_elf_data(ldr, ext); |
| if (ret != 0) { |
| LOG_ERR("Failed to load basic ELF data, ret %d", ret); |
| goto out; |
| } |
| |
| #ifdef CONFIG_USERSPACE |
| ret = k_mem_domain_init(&ext->mem_domain, 0, NULL); |
| if (ret != 0) { |
| LOG_ERR("Failed to initialize extenion memory domain %d", ret); |
| goto out; |
| } |
| #endif |
| |
| LOG_DBG("Finding ELF tables..."); |
| ret = llext_find_tables(ldr, ext); |
| if (ret != 0) { |
| LOG_ERR("Failed to find important ELF tables, ret %d", ret); |
| goto out; |
| } |
| |
| LOG_DBG("Allocate and copy strings..."); |
| ret = llext_copy_strings(ldr, ext); |
| if (ret != 0) { |
| LOG_ERR("Failed to copy ELF string sections, ret %d", ret); |
| goto out; |
| } |
| |
| LOG_DBG("Mapping ELF sections..."); |
| ret = llext_map_sections(ldr, ext, ldr_parm); |
| if (ret != 0) { |
| LOG_ERR("Failed to map ELF sections, ret %d", ret); |
| goto out; |
| } |
| |
| LOG_DBG("Allocate and copy regions..."); |
| ret = llext_copy_regions(ldr, ext, ldr_parm); |
| if (ret != 0) { |
| LOG_ERR("Failed to copy regions, ret %d", ret); |
| goto out; |
| } |
| |
| LOG_DBG("Counting exported symbols..."); |
| ret = llext_count_export_syms(ldr, ext); |
| if (ret != 0) { |
| LOG_ERR("Failed to count exported ELF symbols, ret %d", ret); |
| goto out; |
| } |
| |
| LOG_DBG("Allocating memory for symbol table..."); |
| ret = llext_allocate_symtab(ldr, ext); |
| if (ret != 0) { |
| LOG_ERR("Failed to allocate extension symbol table, ret %d", ret); |
| goto out; |
| } |
| |
| LOG_DBG("Copying symbols..."); |
| ret = llext_copy_symbols(ldr, ext, ldr_parm); |
| if (ret != 0) { |
| LOG_ERR("Failed to copy symbols, ret %d", ret); |
| goto out; |
| } |
| |
| if (ldr_parm->relocate_local) { |
| LOG_DBG("Linking ELF..."); |
| ret = llext_link(ldr, ext, ldr_parm); |
| if (ret != 0) { |
| LOG_ERR("Failed to link, ret %d", ret); |
| goto out; |
| } |
| } |
| |
| ret = llext_export_symbols(ldr, ext); |
| if (ret != 0) { |
| LOG_ERR("Failed to export, ret %d", ret); |
| goto out; |
| } |
| |
| llext_adjust_mmu_permissions(ext); |
| |
| out: |
| /* |
| * Free resources only used during loading. Note that this exploits |
| * the fact that freeing a NULL pointer has no effect. |
| */ |
| |
| llext_free(ldr->sect_map); |
| ldr->sect_map = NULL; |
| |
| /* Until proper inter-llext linking is implemented, the symbol table is |
| * not useful outside of the loading process; keep it only if debugging |
| * is enabled and no error is detected. |
| */ |
| if (!(IS_ENABLED(CONFIG_LLEXT_LOG_LEVEL_DBG) && ret == 0)) { |
| llext_free(ext->sym_tab.syms); |
| ext->sym_tab.sym_cnt = 0; |
| ext->sym_tab.syms = NULL; |
| } |
| |
| if (ret != 0) { |
| LOG_DBG("Failed to load extension: %d", ret); |
| |
| /* Since the loading process failed, free the resources that |
| * were allocated for the lifetime of the extension as well, |
| * such as regions and exported symbols. |
| */ |
| llext_free_regions(ext); |
| llext_free(ext->exp_tab.syms); |
| ext->exp_tab.sym_cnt = 0; |
| ext->exp_tab.syms = NULL; |
| } else { |
| LOG_DBG("loaded module, .text at %p, .rodata at %p", ext->mem[LLEXT_MEM_TEXT], |
| ext->mem[LLEXT_MEM_RODATA]); |
| } |
| |
| llext_finalize(ldr); |
| |
| return ret; |
| } |