Skip to content

Commit

Permalink
llext: add support for relocatable objects on Xtensa
Browse files Browse the repository at this point in the history
Some toolchains cannot create shared objects for Xtensa, with them we
have to use relocatable objects. Add support for them to llext.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
  • Loading branch information
lyakh authored and dleach02 committed Apr 6, 2024
1 parent 4879681 commit 2ccf775
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 27 deletions.
31 changes: 23 additions & 8 deletions arch/xtensa/core/elf.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,34 @@ LOG_MODULE_DECLARE(llext);
* supporting modules must implement this.
*/
void arch_elf_relocate_local(struct llext_loader *ldr, struct llext *ext,
elf_rela_t *rel, size_t got_offset)
const elf_rela_t *rel, const elf_sym_t *sym, size_t got_offset)
{
uint8_t *text = ext->mem[LLEXT_MEM_TEXT];
int type = ELF32_R_TYPE(rel->r_info);
elf_word *got_entry = (elf_word *)(text + got_offset);
uintptr_t sh_addr;

if (type == R_XTENSA_RELATIVE) {
elf_word ptr_offset = *(elf_word *)(text + got_offset);

LOG_DBG("relocation type %u offset %#x value %#x",
type, got_offset, ptr_offset);
if (ELF_ST_TYPE(sym->st_info) == STT_SECTION) {
elf_shdr_t *shdr = llext_peek(ldr, ldr->hdr.e_shoff +
sym->st_shndx * ldr->hdr.e_shentsize);
sh_addr = shdr->sh_addr;
} else {
sh_addr = ldr->sects[LLEXT_MEM_TEXT].sh_addr;
}

switch (type) {
case R_XTENSA_RELATIVE:
/* Relocate a local symbol: Xtensa specific */
*(elf_word *)(text + got_offset) = (elf_word)(text + ptr_offset -
ldr->sects[LLEXT_MEM_TEXT].sh_addr);
*got_entry += (uintptr_t)text - sh_addr;
break;
case R_XTENSA_32:
*got_entry += sh_addr;
break;
default:
LOG_DBG("unsupported relocation type %u", type);

return;
}

LOG_DBG("relocation to %#x type %u at %p", *got_entry, type, (void *)got_entry);
}
3 changes: 2 additions & 1 deletion include/zephyr/llext/llext.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,11 @@ ssize_t llext_find_section(struct llext_loader *loader, const char *search_name)
* @param[in] loader Extension loader data and context
* @param[in] ext Extension to call function in
* @param[in] rel Relocation data provided by elf
* @param[in] sym Corresponding symbol table entry
* @param[in] got_offset Offset within a relocation table
*/
void arch_elf_relocate_local(struct llext_loader *loader, struct llext *ext,
elf_rela_t *rel, size_t got_offset);
const elf_rela_t *rel, const elf_sym_t *sym, size_t got_offset);

/**
* @}
Expand Down
3 changes: 3 additions & 0 deletions include/zephyr/llext/loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ 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];
Expand Down
94 changes: 76 additions & 18 deletions subsys/llext/llext.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ static sys_slist_t _llext_list = SYS_SLIST_STATIC_INIT(&_llext_list);

static struct k_mutex llext_lock = Z_MUTEX_INITIALIZER(llext_lock);

ssize_t llext_find_section(struct llext_loader *ldr, const char *search_name)
static elf_shdr_t *llext_section_by_name(struct llext_loader *ldr, const char *search_name)
{
elf_shdr_t *shdr;
unsigned int i;
Expand All @@ -44,19 +44,26 @@ ssize_t llext_find_section(struct llext_loader *ldr, const char *search_name)
shdr = llext_peek(ldr, pos);
if (!shdr) {
/* The peek() method isn't supported */
return -EOPNOTSUPP;
return NULL;
}

const char *name = llext_peek(ldr,
ldr->sects[LLEXT_MEM_SHSTRTAB].sh_offset +
shdr->sh_name);

if (!strcmp(name, search_name)) {
return shdr->sh_offset;
return shdr;
}
}

return -ENOENT;
return NULL;
}

ssize_t llext_find_section(struct llext_loader *ldr, const char *search_name)
{
elf_shdr_t *shdr = llext_section_by_name(ldr, search_name);

return shdr ? shdr->sh_offset : -ENOENT;
}

/*
Expand Down Expand Up @@ -214,9 +221,12 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext)
{
int i, ret;
size_t pos;
elf_shdr_t shdr;
elf_shdr_t shdr, rodata = {.sh_addr = ~0},
high_shdr = {.sh_offset = 0}, low_shdr = {.sh_offset = ~0};
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) {
Expand All @@ -230,12 +240,38 @@ 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;
}
}

name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr.sh_name);

LOG_DBG("section %d name %s", i, name);

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) {
Expand All @@ -255,6 +291,13 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext)
ldr->sect_map[i] = mem_idx;
}

ldr->prog_data_size = high_shdr.sh_size + high_shdr.sh_offset - low_shdr.sh_offset;

/* No verbatim .rodata, use an automatically selected one */
if (!ldr->sects[LLEXT_MEM_RODATA].sh_size) {
ldr->sects[LLEXT_MEM_RODATA] = rodata;
}

return 0;
}

Expand Down Expand Up @@ -556,12 +599,12 @@ static size_t llext_file_offset(struct llext_loader *ldr, size_t offset)
}

__weak void arch_elf_relocate_local(struct llext_loader *ldr, struct llext *ext,
elf_rela_t *rel, size_t got_offset)
const elf_rela_t *rel, const elf_sym_t *sym, size_t got_offset)
{
}

static void llext_link_plt(struct llext_loader *ldr, struct llext *ext,
elf_shdr_t *shdr, bool do_local)
elf_shdr_t *shdr, bool do_local, elf_shdr_t *tgt)
{
unsigned int sh_cnt = shdr->sh_size / shdr->sh_entsize;
/*
Expand Down Expand Up @@ -613,25 +656,36 @@ static void llext_link_plt(struct llext_loader *ldr, struct llext *ext,
}

uint32_t stt = ELF_ST_TYPE(sym_tbl.st_info);
uint32_t stb = ELF_ST_BIND(sym_tbl.st_info);

if (stt != STT_FUNC && (stt != STT_NOTYPE || sym_tbl.st_shndx != SHN_UNDEF)) {
if (stt != STT_FUNC &&
stt != STT_SECTION &&
(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 = llext_file_offset(ldr, rela.r_offset) -
ldr->sects[LLEXT_MEM_TEXT].sh_offset;
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, name);

if (!link_addr)
link_addr = llext_find_sym(&ext->sym_tab, name);

Expand All @@ -648,9 +702,9 @@ static void llext_link_plt(struct llext_loader *ldr, struct llext *ext,
/* Resolve the symbol */
*(const void **)(text + got_offset) = link_addr;
break;
case STB_LOCAL:
case STB_LOCAL:
if (do_local) {
arch_elf_relocate_local(ldr, ext, &rela, got_offset);
arch_elf_relocate_local(ldr, ext, &rela, &sym_tbl, got_offset);
}
}

Expand Down Expand Up @@ -697,23 +751,27 @@ static int llext_link(struct llext_loader *ldr, struct llext *ext, bool do_local

name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr.sh_name);

if (strcmp(name, ".rel.text") == 0 ||
strcmp(name, ".rela.text") == 0) {
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 ||
strcmp(name, ".rela.data") == 0) {
} 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);
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;
}

Expand Down

0 comments on commit 2ccf775

Please sign in to comment.