| /* | 
 |  * 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; | 
 | 	} | 
 | 	ext->alloc_size += sect_map_sz; | 
 | 	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; | 
 | 	int shstrtab_ndx = ldr->hdr.e_shstrndx; | 
 | 	int strtab_ndx = -1; | 
 |  | 
 | 	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 %#zx: name %d, type %d, flags %#zx, " | 
 | 			"addr %#zx, align %#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_addralign, | 
 | 			(size_t)shdr->sh_size, | 
 | 			shdr->sh_link, | 
 | 			shdr->sh_info); | 
 |  | 
 | 		if (shdr->sh_type == SHT_SYMTAB && ldr->hdr.e_type == ET_REL) { | 
 | 			LOG_DBG("symtab at %d", i); | 
 | 			ldr->sects[LLEXT_MEM_SYMTAB] = *shdr; | 
 | 			ldr->sect_map[i].mem_idx = LLEXT_MEM_SYMTAB; | 
 | 			strtab_ndx = shdr->sh_link; | 
 | 			table_cnt++; | 
 | 		} else if (shdr->sh_type == SHT_DYNSYM && ldr->hdr.e_type == ET_DYN) { | 
 | 			LOG_DBG("dynsym at %d", i); | 
 | 			ldr->sects[LLEXT_MEM_SYMTAB] = *shdr; | 
 | 			ldr->sect_map[i].mem_idx = LLEXT_MEM_SYMTAB; | 
 | 			strtab_ndx = shdr->sh_link; | 
 | 			table_cnt++; | 
 | 		} else if (shdr->sh_type == SHT_STRTAB && i == shstrtab_ndx) { | 
 | 			LOG_DBG("shstrtab at %d", i); | 
 | 			ldr->sects[LLEXT_MEM_SHSTRTAB] = *shdr; | 
 | 			ldr->sect_map[i].mem_idx = LLEXT_MEM_SHSTRTAB; | 
 | 			table_cnt++; | 
 | 		} else if (shdr->sh_type == SHT_STRTAB && i == strtab_ndx) { | 
 | 			LOG_DBG("strtab at %d", i); | 
 | 			ldr->sects[LLEXT_MEM_STRTAB] = *shdr; | 
 | 			ldr->sect_map[i].mem_idx = LLEXT_MEM_STRTAB; | 
 | 			table_cnt++; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	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; | 
 | } | 
 |  | 
 | /* First (bottom) and last (top) entries of a region, inclusive, for a specific field. */ | 
 | #define REGION_BOT(reg, field) (size_t)(reg->field + reg->sh_info) | 
 | #define REGION_TOP(reg, field) (size_t)(reg->field + reg->sh_size - 1) | 
 |  | 
 | /* Check if two regions x and y have any overlap on a given field. Any shared value counts. */ | 
 | #define REGIONS_OVERLAP_ON(x, y, f) \ | 
 | 	((REGION_BOT(x, f) <= REGION_BOT(y, f) && REGION_TOP(x, f) >= REGION_BOT(y, f)) || \ | 
 | 	 (REGION_BOT(y, f) <= REGION_BOT(x, f) && REGION_TOP(y, f) >= REGION_BOT(x, f))) | 
 |  | 
 | /* | 
 |  * Loops through all defined ELF sections and collapses those with similar | 
 |  * usage flags into LLEXT "regions", taking alignment constraints into account. | 
 |  * Checks the generated regions for overlaps and calculates the offset of each | 
 |  * section within its region. | 
 |  * | 
 |  * This information is stored in the ldr->sects and ldr->sect_map arrays. | 
 |  */ | 
 | 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_section_name(ldr, ext, shdr); | 
 |  | 
 | 		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; | 
 |  | 
 | 		/* | 
 | 		 * Some applications may require specific ELF sections to not | 
 | 		 * be included in their default memory regions; e.g. the ELF | 
 | 		 * file may contain executable sections that are designed to 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)); | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		/* Make sure this section is compatible with the existing 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); | 
 | 		size_t addralign = MAX(region->sh_addralign, shdr->sh_addralign); | 
 |  | 
 | 		region->sh_addr = address; | 
 | 		region->sh_offset = bot_ofs; | 
 | 		region->sh_size = top_ofs - bot_ofs; | 
 | 		region->sh_addralign = addralign; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Make sure each of the mapped sections satisfies its alignment | 
 | 	 * requirement when placed in the region. | 
 | 	 * | 
 | 	 * The ELF standard already guarantees that each section's offset in | 
 | 	 * the file satisfies its own alignment, and since only powers of 2 can | 
 | 	 * be specified, a solution satisfying the largest alignment will also | 
 | 	 * work for any smaller one. Aligning the ELF region to the largest | 
 | 	 * requirement among the contained sections will then guarantee that | 
 | 	 * all are properly aligned. | 
 | 	 * | 
 | 	 * However, adjusting the region's start address will make the region | 
 | 	 * appear larger than it actually is, and might even make it overlap | 
 | 	 * with others. To allow for further precise adjustments, the length of | 
 | 	 * the calculated pre-padding area is stored in the 'sh_info' field of | 
 | 	 * the region descriptor, which is not used on any SHF_ALLOC section. | 
 | 	 */ | 
 | 	for (i = 0; i < LLEXT_MEM_COUNT; i++) { | 
 | 		elf_shdr_t *region = ldr->sects + i; | 
 |  | 
 | 		if (region->sh_type == SHT_NULL || region->sh_size == 0) { | 
 | 			/* Skip empty regions */ | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		size_t prepad = region->sh_offset & (region->sh_addralign - 1); | 
 |  | 
 | 		if (ldr->hdr.e_type == ET_DYN) { | 
 | 			/* Only shared files populate sh_addr fields */ | 
 | 			if (prepad > region->sh_addr) { | 
 | 				LOG_ERR("Bad section alignment in region %d", i); | 
 | 				return -ENOEXEC; | 
 | 			} | 
 |  | 
 | 			region->sh_addr -= prepad; | 
 | 		} | 
 | 		region->sh_offset -= prepad; | 
 | 		region->sh_size += prepad; | 
 | 		region->sh_info = prepad; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * 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) && | 
 | 			    (x->sh_flags & SHF_ALLOC) && (y->sh_flags & SHF_ALLOC)) { | 
 | 				/* | 
 | 				 * Test regions that have VMA ranges for overlaps | 
 | 				 */ | 
 | 				if (REGIONS_OVERLAP_ON(x, y, sh_addr)) { | 
 | 					LOG_ERR("Region %d VMA range (%#zx-%#zx) " | 
 | 						"overlaps with %d (%#zx-%#zx)", | 
 | 						i, REGION_BOT(x, sh_addr), REGION_TOP(x, sh_addr), | 
 | 						j, REGION_BOT(y, sh_addr), REGION_TOP(y, sh_addr)); | 
 | 					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 (REGIONS_OVERLAP_ON(x, y, sh_offset)) { | 
 | 				LOG_ERR("Region %d ELF file range (%#zx-%#zx) " | 
 | 					"overlaps with %d (%#zx-%#zx)", | 
 | 					i, REGION_BOT(x, sh_offset), REGION_TOP(x, sh_offset), | 
 | 					j, REGION_BOT(y, sh_offset), REGION_TOP(y, sh_offset)); | 
 | 				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. | 
 | 	 * Also mark the regions that include relocation targets. | 
 | 	 */ | 
 | 	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 (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA) { | 
 | 			enum llext_mem target_region = ldr->sect_map[shdr->sh_info].mem_idx; | 
 |  | 
 | 			if (target_region != LLEXT_MEM_COUNT) { | 
 | 				ldr->sects[target_region].sh_flags |= SHF_LLEXT_HAS_RELOCS; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		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_symbol_name(ldr, ext, &sym); | 
 |  | 
 | 		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, | 
 | 				const struct llext_load_param *ldr_parm) | 
 | { | 
 | 	struct llext_symtable *exp_tab = &ext->exp_tab; | 
 | 	struct llext_symbol *sym; | 
 | 	unsigned int i; | 
 |  | 
 | 	if (IS_ENABLED(CONFIG_LLEXT_IMPORT_ALL_GLOBALS)) { | 
 | 		/* Use already discovered global symbols */ | 
 | 		exp_tab->sym_cnt = ext->sym_tab.sym_cnt; | 
 | 		sym = ext->sym_tab.syms; | 
 | 	} else { | 
 | 		/* Only use symbols in the .exported_sym section */ | 
 | 		exp_tab->sym_cnt = ldr->sects[LLEXT_MEM_EXPORT].sh_size | 
 | 				   / sizeof(struct llext_symbol); | 
 | 		sym = ext->mem[LLEXT_MEM_EXPORT]; | 
 | 	} | 
 |  | 
 | 	if (!exp_tab->sym_cnt) { | 
 | 		/* No symbols exported */ | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	exp_tab->syms = llext_alloc(exp_tab->sym_cnt * sizeof(struct llext_symbol)); | 
 | 	if (!exp_tab->syms) { | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < exp_tab->sym_cnt; i++, sym++) { | 
 | 		/* | 
 | 		 * Offsets in objects, built for pre-defined addresses have to | 
 | 		 * be translated to memory locations for symbol name access | 
 | 		 * during dependency resolution. | 
 | 		 */ | 
 | 		const char *name = NULL; | 
 |  | 
 | 		if (ldr_parm->pre_located) { | 
 | 			ssize_t name_offset = llext_file_offset(ldr, (uintptr_t)sym->name); | 
 |  | 
 | 			if (name_offset > 0) { | 
 | 				name = llext_peek(ldr, name_offset); | 
 | 			} | 
 | 		} | 
 | 		if (!name) { | 
 | 			name = sym->name; | 
 | 		} | 
 |  | 
 | 		exp_tab->syms[i].name = name; | 
 | 		exp_tab->syms[i].addr = sym->addr; | 
 | 		LOG_DBG("sym %p name %s", sym->addr, sym->name); | 
 | 	} | 
 |  | 
 | 	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_symbol_name(ldr, ext, &sym); | 
 |  | 
 | 			__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, ldr_parm); | 
 | 	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, ldr_parm); | 
 | 	if (ret != 0) { | 
 | 		LOG_ERR("Failed to export, ret %d", ret); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	if (!ldr_parm->pre_located) { | 
 | 		llext_adjust_mmu_permissions(ext); | 
 | 	} | 
 |  | 
 | out: | 
 | 	/* | 
 | 	 * Free resources only used during loading, unless explicitly requested. | 
 | 	 * Note that this exploits the fact that freeing a NULL pointer has no effect. | 
 | 	 */ | 
 |  | 
 | 	if (ret != 0 || !ldr_parm->keep_section_info) { | 
 | 		llext_free_inspection_data(ldr, ext); | 
 | 	} | 
 |  | 
 | 	/* 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 llext: %zu bytes in heap, .text at %p, .rodata at %p", | 
 | 			ext->alloc_size, ext->mem[LLEXT_MEM_TEXT], ext->mem[LLEXT_MEM_RODATA]); | 
 | 	} | 
 |  | 
 | 	llext_finalize(ldr); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | int llext_free_inspection_data(struct llext_loader *ldr, struct llext *ext) | 
 | { | 
 | 	if (ldr->sect_map) { | 
 | 		ext->alloc_size -= ext->sect_cnt * sizeof(ldr->sect_map[0]); | 
 | 		llext_free(ldr->sect_map); | 
 | 		ldr->sect_map = NULL; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } |