blob: 51bb4ca171c05a067c3a23c9553f513014e99bd5 [file] [log] [blame]
/*
* 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/kernel.h>
#include <zephyr/cache.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL);
#include <string.h>
#include "llext_priv.h"
#ifdef CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID
#define SYM_NAME_OR_SLID(name, slid) ((const char *) slid)
#else
#define SYM_NAME_OR_SLID(name, slid) name
#endif
__weak int arch_elf_relocate(elf_rela_t *rel, uintptr_t loc,
uintptr_t sym_base_addr, const char *sym_name, uintptr_t load_bias)
{
return -EOPNOTSUPP;
}
__weak void arch_elf_relocate_local(struct llext_loader *ldr, struct llext *ext,
const elf_rela_t *rel, const elf_sym_t *sym, size_t got_offset)
{
}
/*
* Find the section containing the supplied offset and return file offset for
* that value
*/
static size_t llext_file_offset(struct llext_loader *ldr, size_t offset)
{
unsigned int i;
for (i = 0; i < LLEXT_MEM_COUNT; i++)
if (ldr->sects[i].sh_addr <= offset &&
ldr->sects[i].sh_addr + ldr->sects[i].sh_size > offset)
return offset - ldr->sects[i].sh_addr + ldr->sects[i].sh_offset;
return offset;
}
static void llext_link_plt(struct llext_loader *ldr, struct llext *ext,
elf_shdr_t *shdr, bool do_local, elf_shdr_t *tgt)
{
unsigned int sh_cnt = shdr->sh_size / shdr->sh_entsize;
/*
* CPU address where the .text section is stored, we use .text just as a
* reference point
*/
uint8_t *text = ext->mem[LLEXT_MEM_TEXT];
LOG_DBG("Found %p in PLT %u size %zu cnt %u text %p",
(void *)llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr->sh_name),
shdr->sh_type, (size_t)shdr->sh_entsize, sh_cnt, (void *)text);
const elf_shdr_t *sym_shdr = ldr->sects + LLEXT_MEM_SYMTAB;
unsigned int sym_cnt = sym_shdr->sh_size / sym_shdr->sh_entsize;
for (unsigned int i = 0; i < sh_cnt; i++) {
elf_rela_t rela;
int ret = llext_seek(ldr, shdr->sh_offset + i * shdr->sh_entsize);
if (!ret) {
ret = llext_read(ldr, &rela, sizeof(rela));
}
if (ret < 0) {
LOG_ERR("PLT: failed to read RELA #%u, trying to continue", i);
continue;
}
/* Index in the symbol table */
unsigned int j = ELF32_R_SYM(rela.r_info);
if (j >= sym_cnt) {
LOG_WRN("PLT: idx %u >= %u", j, sym_cnt);
continue;
}
elf_sym_t sym_tbl;
ret = llext_seek(ldr, sym_shdr->sh_offset + j * sizeof(elf_sym_t));
if (!ret) {
ret = llext_read(ldr, &sym_tbl, sizeof(sym_tbl));
}
if (ret < 0) {
LOG_ERR("PLT: failed to read symbol table #%u RELA #%u, trying to continue",
j, i);
continue;
}
uint32_t stt = ELF_ST_TYPE(sym_tbl.st_info);
if (stt != STT_FUNC &&
stt != STT_SECTION &&
stt != STT_OBJECT &&
(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;
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,
SYM_NAME_OR_SLID(name, sym_tbl.st_value));
if (!link_addr)
link_addr = llext_find_sym(&ext->sym_tab, name);
if (!link_addr) {
LOG_WRN("PLT: cannot find idx %u name %s", j, name);
continue;
}
/* Resolve the symbol */
*(const void **)(text + got_offset) = link_addr;
break;
case STB_LOCAL:
if (do_local) {
arch_elf_relocate_local(ldr, ext, &rela, &sym_tbl, got_offset);
}
}
LOG_DBG("symbol %s offset %#zx r-offset %#zx .text offset %#zx stb %u",
name, got_offset,
(size_t)rela.r_offset, (size_t)ldr->sects[LLEXT_MEM_TEXT].sh_offset, stb);
}
}
int llext_link(struct llext_loader *ldr, struct llext *ext, bool do_local)
{
uintptr_t loc = 0;
elf_rela_t rel;
elf_sym_t sym;
elf_word rel_cnt = 0;
const char *name;
int i, ret;
for (i = 0; i < ldr->sect_cnt; ++i) {
elf_shdr_t *shdr = ldr->sect_hdrs + i;
/* find relocation sections */
if (shdr->sh_type != SHT_REL && shdr->sh_type != SHT_RELA) {
continue;
}
rel_cnt = shdr->sh_size / shdr->sh_entsize;
name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr->sh_name);
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) {
loc = (uintptr_t)ext->mem[LLEXT_MEM_BSS];
} 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) {
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, 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;
} else if (strcmp(name, ".rel.dyn") == 0) {
/* we assume that first load segment starts at MEM_TEXT */
loc = (uintptr_t)ext->mem[LLEXT_MEM_TEXT];
}
LOG_DBG("relocation section %s (%d) linked to section %d has %zd relocations",
name, i, shdr->sh_link, (size_t)rel_cnt);
for (int j = 0; j < rel_cnt; j++) {
/* get each relocation entry */
ret = llext_seek(ldr, shdr->sh_offset + j * shdr->sh_entsize);
if (ret != 0) {
return ret;
}
ret = llext_read(ldr, &rel, shdr->sh_entsize);
if (ret != 0) {
return ret;
}
/* get corresponding symbol */
ret = llext_seek(ldr, ldr->sects[LLEXT_MEM_SYMTAB].sh_offset
+ ELF_R_SYM(rel.r_info) * sizeof(elf_sym_t));
if (ret != 0) {
return ret;
}
ret = llext_read(ldr, &sym, sizeof(elf_sym_t));
if (ret != 0) {
return ret;
}
name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym.st_name);
LOG_DBG("relocation %d:%d info %zx (type %zd, sym %zd) offset %zd sym_name "
"%s sym_type %d sym_bind %d sym_ndx %d",
i, j, (size_t)rel.r_info, (size_t)ELF_R_TYPE(rel.r_info),
(size_t)ELF_R_SYM(rel.r_info),
(size_t)rel.r_offset, name, ELF_ST_TYPE(sym.st_info),
ELF_ST_BIND(sym.st_info), sym.st_shndx);
uintptr_t link_addr, op_loc;
op_loc = loc + rel.r_offset;
if (ELF_R_SYM(rel.r_info) == 0) {
/* no symbol ex: R_ARM_V4BX relocation, R_ARM_RELATIVE */
link_addr = 0;
} else if (sym.st_shndx == SHN_UNDEF) {
/* If symbol is undefined, then we need to look it up */
link_addr = (uintptr_t)llext_find_sym(NULL,
SYM_NAME_OR_SLID(name, sym.st_value));
if (link_addr == 0) {
LOG_ERR("Undefined symbol with no entry in "
"symbol table %s, offset %zd, link section %d",
name, (size_t)rel.r_offset, shdr->sh_link);
return -ENODATA;
}
LOG_INF("found symbol %s at 0x%lx", name, link_addr);
} else if (sym.st_shndx == SHN_ABS) {
/* Absolute symbol */
link_addr = sym.st_value;
} else if ((sym.st_shndx < ldr->hdr.e_shnum) &&
!IN_RANGE(sym.st_shndx, SHN_LORESERVE, SHN_HIRESERVE)) {
/* This check rejects all relocations whose target symbol
* has a section index higher than the maximum possible
* in this ELF file, or belongs in the reserved range:
* they will be caught by the `else` below and cause an
* error to be returned. This aborts the LLEXT's loading
* and prevents execution of improperly relocated code,
* which is dangerous.
*
* Note that the unsupported SHN_COMMON section is rejected
* as part of this check. Also note that SHN_ABS would be
* rejected as well, but we want to handle it properly:
* for this reason, this check must come AFTER handling
* the case where the symbol's section index is SHN_ABS!
*
*
* For regular symbols, the link address is obtained by
* adding st_value to the start address of the section
* in which the target symbol resides.
*/
link_addr = (uintptr_t)ext->mem[ldr->sect_map[sym.st_shndx]]
+ sym.st_value;
} else {
LOG_ERR("rela section %d, entry %d: cannot apply relocation: "
"target symbol has unexpected section index %d (0x%X)",
i, j, sym.st_shndx, sym.st_shndx);
return -ENOEXEC;
}
LOG_INF("writing relocation symbol %s type %zd sym %zd at addr 0x%lx "
"addr 0x%lx",
name, (size_t)ELF_R_TYPE(rel.r_info), (size_t)ELF_R_SYM(rel.r_info),
op_loc, link_addr);
/* relocation */
ret = arch_elf_relocate(&rel, op_loc, link_addr, name,
(uintptr_t)ext->mem[LLEXT_MEM_TEXT]);
if (ret != 0) {
return ret;
}
}
}
#ifdef CONFIG_CACHE_MANAGEMENT
/* Make sure changes to ext sections are flushed to RAM */
for (i = 0; i < LLEXT_MEM_COUNT; ++i) {
if (ext->mem[i]) {
sys_cache_data_flush_range(ext->mem[i], ext->mem_size[i]);
sys_cache_instr_invd_range(ext->mem[i], ext->mem_size[i]);
}
}
#endif
return 0;
}