Commit 959f1b35 authored by Luca Burelli's avatar Luca Burelli Committed by Benjamin Cabé
Browse files

llext: support alignment requirements for sections

This patch adds support for alignment requirements for sections in the
loader. The alignment requirements are taken from the ELF file and
checked when the section is mapped to a memory region and when the
region is copied to memory (or directly used from the ELF buffer).

Supporting a larger alignment requirement than the region's current
alignment is done by adjusting the region's start address. This needs
care to avoid detecting false overlaps with other regions and to ensure
that the region address and size reported by the inspection APIs are
correct. The extra offset needed to re-adjust these values is stored in
the 'sh_info' field of the region descriptor.

Signed-off-by: default avatarLuca Burelli <l.burelli@arduino.cc>
parent 1b48f743
Loading
Loading
Loading
Loading
+10 −4
Changes for include/zephyr/llext/inspect.h: 10 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -58,11 +58,13 @@ static inline int llext_get_region_info(const struct llext_loader *ldr,
	if (hdr) {
		*hdr = &ldr->sects[region];
	}

	/* address and size compensated for alignment prepad */
	if (addr) {
		*addr = ext->mem[region];
		*addr = (void *)((uintptr_t)ext->mem[region] + ldr->sects[region].sh_info);
	}
	if (size) {
		*size = ext->mem_size[region];
		*size = ext->mem_size[region] - ldr->sects[region].sh_info;
	}

	return 0;
@@ -117,14 +119,18 @@ static inline int llext_get_section_info(const struct llext_loader *ldr,
		return -ENOTSUP;
	}

	enum llext_mem mem_idx = ldr->sect_map[shndx].mem_idx;

	if (hdr) {
		*hdr = &ext->sect_hdrs[shndx];
	}
	if (region) {
		*region = ldr->sect_map[shndx].mem_idx;
		*region = mem_idx;
	}

	/* offset compensated for alignment prepad */
	if (offset) {
		*offset = ldr->sect_map[shndx].offset;
		*offset = ldr->sect_map[shndx].offset - ldr->sects[mem_idx].sh_info;
	}

	return 0;
+52 −4
Changes for subsys/llext/llext_load.c: 52 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -163,13 +163,14 @@ static int llext_find_tables(struct llext_loader *ldr, struct llext *ext)
		elf_shdr_t *shdr = ext->sect_hdrs + i;

		LOG_DBG("section %d at %#zx: name %d, type %d, flags %#zx, "
			"addr %#zx, size %zd, link %d, info %d",
			"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);
@@ -210,7 +211,7 @@ static int llext_find_tables(struct llext_loader *ldr, struct llext *ext)
}

/* First (bottom) and last (top) entries of a region, inclusive, for a specific field. */
#define REGION_BOT(reg, field) (size_t)(reg->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. */
@@ -219,8 +220,12 @@ static int llext_find_tables(struct llext_loader *ldr, struct llext *ext)
	 (REGION_BOT(y, f) <= REGION_BOT(x, f) && REGION_TOP(y, f) >= REGION_BOT(x, f)))

/*
 * Maps the ELF sections into regions according to their usage flags,
 * calculating ldr->sects and ldr->sect_map.
 * 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)
@@ -371,10 +376,53 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext,
		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;
	}

	/*
+30 −5
Changes for subsys/llext/llext_mem.c: 30 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -64,7 +64,7 @@ static int llext_copy_region(struct llext_loader *ldr, struct llext *ext,
	int ret;
	elf_shdr_t *region = ldr->sects + mem_idx;
	uintptr_t region_alloc = region->sh_size;
	uintptr_t region_align = LLEXT_PAGE_SIZE;
	uintptr_t region_align = region->sh_addralign;

	if (!region_alloc) {
		return 0;
@@ -81,12 +81,19 @@ static int llext_copy_region(struct llext_loader *ldr, struct llext *ext,
			/* Region has data in the file, check if peek() is supported */
			ext->mem[mem_idx] = llext_peek(ldr, region->sh_offset);
			if (ext->mem[mem_idx]) {
				if (IS_ALIGNED(ext->mem[mem_idx], region_align) ||
				    ldr_parm->pre_located) {
					/* Map this region directly to the ELF buffer */
				llext_init_mem_part(ext, mem_idx, (uintptr_t)ext->mem[mem_idx],
					llext_init_mem_part(ext, mem_idx,
							    (uintptr_t)ext->mem[mem_idx],
							    region_alloc);
					ext->mem_on_heap[mem_idx] = false;
					return 0;
				}

				LOG_WRN("Cannot peek region %d: %p not aligned to %#zx",
					mem_idx, ext->mem[mem_idx], (size_t)region_align);
			}
		} else if (ldr_parm->pre_located) {
			/*
			 * In pre-located files all regions, including BSS,
@@ -114,7 +121,7 @@ static int llext_copy_region(struct llext_loader *ldr, struct llext *ext,
		/* On ARM with an MPU, regions must be sized and aligned to the same
		 * power of two (larger than 32).
		 */
		uintptr_t block_size = MAX(region_alloc, LLEXT_PAGE_SIZE);
		uintptr_t block_size = MAX(MAX(region_alloc, region_align), LLEXT_PAGE_SIZE);

		block_size = 1 << LOG2CEIL(block_size); /* align to next power of two */
		region_alloc = block_size;
@@ -122,10 +129,13 @@ static int llext_copy_region(struct llext_loader *ldr, struct llext *ext,
	} else {
		/* Otherwise, round the region to multiples of LLEXT_PAGE_SIZE. */
		region_alloc = ROUND_UP(region_alloc, LLEXT_PAGE_SIZE);
		region_align = MAX(region_align, LLEXT_PAGE_SIZE);
	}

	ext->mem[mem_idx] = llext_aligned_alloc(region_align, region_alloc);
	if (!ext->mem[mem_idx]) {
		LOG_ERR("Failed allocating %zd bytes %zd-aligned for region %d",
			(size_t)region_alloc, (size_t)region_align, mem_idx);
		return -ENOMEM;
	}

@@ -137,12 +147,27 @@ static int llext_copy_region(struct llext_loader *ldr, struct llext *ext,
	if (region->sh_type == SHT_NOBITS) {
		memset(ext->mem[mem_idx], 0, region->sh_size);
	} else {
		ret = llext_seek(ldr, region->sh_offset);
		uintptr_t base = (uintptr_t)ext->mem[mem_idx];
		size_t offset = region->sh_offset;
		size_t length = region->sh_size;

		if (region->sh_flags & SHF_ALLOC) {
			/* zero out any prepad bytes, not part of the data area */
			size_t prepad = region->sh_info;

			memset((void *)base, 0, prepad);
			base += prepad;
			offset += prepad;
			length -= prepad;
		}

		/* actual data area without prepad bytes */
		ret = llext_seek(ldr, offset);
		if (ret != 0) {
			goto err;
		}

		ret = llext_read(ldr, ext->mem[mem_idx], region->sh_size);
		ret = llext_read(ldr, (void *)base, length);
		if (ret != 0) {
			goto err;
		}