1 /*
2 * Copyright (c) 2023 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/llext/elf.h>
8 #include <zephyr/llext/llext.h>
9 #include <zephyr/llext/llext_internal.h>
10 #include <zephyr/llext/loader.h>
11 #include <zephyr/logging/log.h>
12
13 LOG_MODULE_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL);
14
15 /*
16 * ELF relocation tables on Xtensa contain relocations of different types. They
17 * specify how the relocation should be performed. Which relocations are used
18 * depends on the type of the ELF object (e.g. shared or partially linked
19 * object), structure of the object (single or multiple source files), compiler
20 * flags used (e.g. -fPIC), etc. Also not all relocation table entries should be
21 * acted upon. Some of them describe relocations that have already been
22 * resolved by the linker. We have to distinguish them from actionable
23 * relocations and only need to handle the latter ones.
24 */
25 #define R_XTENSA_NONE 0
26 #define R_XTENSA_32 1
27 #define R_XTENSA_RTLD 2
28 #define R_XTENSA_GLOB_DAT 3
29 #define R_XTENSA_JMP_SLOT 4
30 #define R_XTENSA_RELATIVE 5
31 #define R_XTENSA_PLT 6
32 #define R_XTENSA_ASM_EXPAND 11
33 #define R_XTENSA_SLOT0_OP 20
34
xtensa_elf_relocate(struct llext_loader * ldr,struct llext * ext,const elf_rela_t * rel,uintptr_t addr,uint8_t * loc,int type,uint32_t stb,const struct llext_load_param * ldr_parm)35 static void xtensa_elf_relocate(struct llext_loader *ldr, struct llext *ext,
36 const elf_rela_t *rel, uintptr_t addr,
37 uint8_t *loc, int type, uint32_t stb,
38 const struct llext_load_param *ldr_parm)
39 {
40 elf_word *got_entry = (elf_word *)loc;
41
42 switch (type) {
43 case R_XTENSA_RELATIVE:
44 if (ldr_parm->pre_located) {
45 /* Relative relocations are already correct in the pre-located case */
46 break;
47 }
48
49 /* Relocate a local symbol: Xtensa specific. Seems to only be used with PIC */
50 unsigned int sh_ndx;
51
52 for (sh_ndx = 0; sh_ndx < ext->sect_cnt; sh_ndx++) {
53 if (ext->sect_hdrs[sh_ndx].sh_addr <= *got_entry &&
54 *got_entry <
55 ext->sect_hdrs[sh_ndx].sh_addr + ext->sect_hdrs[sh_ndx].sh_size)
56 break;
57 }
58
59 if (sh_ndx == ext->sect_cnt) {
60 LOG_ERR("%#x not found in any of the sections", *got_entry);
61 return;
62 }
63
64 *got_entry += (uintptr_t)llext_loaded_sect_ptr(ldr, ext, sh_ndx) -
65 ext->sect_hdrs[sh_ndx].sh_addr;
66 break;
67 case R_XTENSA_GLOB_DAT:
68 case R_XTENSA_JMP_SLOT:
69 if (stb == STB_GLOBAL) {
70 *got_entry = addr;
71 }
72 break;
73 case R_XTENSA_32:
74 /* Used for both LOCAL and GLOBAL bindings */
75 *got_entry += addr;
76 break;
77 case R_XTENSA_SLOT0_OP:
78 /* Apparently only actionable with LOCAL bindings */
79 ;
80 elf_sym_t rsym;
81 int ret = llext_seek(ldr, ldr->sects[LLEXT_MEM_SYMTAB].sh_offset +
82 ELF_R_SYM(rel->r_info) * sizeof(elf_sym_t));
83
84 if (!ret) {
85 ret = llext_read(ldr, &rsym, sizeof(elf_sym_t));
86 }
87 if (ret) {
88 LOG_ERR("Failed to read a symbol table entry, LLEXT linking might fail.");
89 return;
90 }
91
92 /*
93 * So far in all observed use-cases
94 * llext_loaded_sect_ptr(ldr, ext, rsym.st_shndx) was already
95 * available as the "addr" argument of this function, supplied
96 * by arch_elf_relocate_local() from its non-STT_SECTION branch.
97 */
98 uintptr_t link_addr = (uintptr_t)llext_loaded_sect_ptr(ldr, ext, rsym.st_shndx) +
99 rsym.st_value + rel->r_addend;
100 ssize_t value = (link_addr - (((uintptr_t)got_entry + 3) & ~3)) >> 2;
101
102 /* Check the opcode */
103 if ((loc[0] & 0xf) == 1 && !loc[1] && !loc[2]) {
104 /* L32R: low nibble is 1 */
105 loc[1] = value & 0xff;
106 loc[2] = (value >> 8) & 0xff;
107 } else if ((loc[0] & 0xf) == 5 && !(loc[0] & 0xc0) && !loc[1] && !loc[2]) {
108 /* CALLn: low nibble is 5 */
109 loc[0] = (loc[0] & 0x3f) | ((value << 6) & 0xc0);
110 loc[1] = (value >> 2) & 0xff;
111 loc[2] = (value >> 10) & 0xff;
112 } else {
113 LOG_DBG("%p: unhandled OPC or no relocation %02x%02x%02x inf %#x offs %#x",
114 (void *)loc, loc[2], loc[1], loc[0],
115 rel->r_info, rel->r_offset);
116 break;
117 }
118
119 break;
120 case R_XTENSA_ASM_EXPAND:
121 /* Nothing to do */
122 break;
123 default:
124 LOG_DBG("Unsupported relocation type %u", type);
125
126 return;
127 }
128
129 LOG_DBG("Applied relocation to %#x type %u at %p",
130 *(uint32_t *)((uintptr_t)got_entry & ~3), type, (void *)got_entry);
131 }
132
133 /**
134 * @brief Architecture specific function for STB_LOCAL ELF relocations
135 */
arch_elf_relocate_local(struct llext_loader * ldr,struct llext * ext,const elf_rela_t * rel,const elf_sym_t * sym,uint8_t * rel_addr,const struct llext_load_param * ldr_parm)136 void arch_elf_relocate_local(struct llext_loader *ldr, struct llext *ext, const elf_rela_t *rel,
137 const elf_sym_t *sym, uint8_t *rel_addr,
138 const struct llext_load_param *ldr_parm)
139 {
140 int type = ELF32_R_TYPE(rel->r_info);
141 uintptr_t sh_addr;
142
143 if (ELF_ST_TYPE(sym->st_info) == STT_SECTION) {
144 elf_shdr_t *shdr = ext->sect_hdrs + sym->st_shndx;
145
146 /* shdr->sh_addr is NULL when not built for a specific address */
147 sh_addr = shdr->sh_addr &&
148 (!ldr_parm->section_detached || !ldr_parm->section_detached(shdr)) ?
149 shdr->sh_addr : (uintptr_t)llext_loaded_sect_ptr(ldr, ext, sym->st_shndx);
150 } else {
151 sh_addr = ldr->sects[LLEXT_MEM_TEXT].sh_addr;
152 }
153
154 xtensa_elf_relocate(ldr, ext, rel, sh_addr, rel_addr, type, ELF_ST_BIND(sym->st_info),
155 ldr_parm);
156 }
157
158 /**
159 * @brief Architecture specific function for STB_GLOBAL ELF relocations
160 */
arch_elf_relocate_global(struct llext_loader * ldr,struct llext * ext,const elf_rela_t * rel,const elf_sym_t * sym,uint8_t * rel_addr,const void * link_addr)161 void arch_elf_relocate_global(struct llext_loader *ldr, struct llext *ext, const elf_rela_t *rel,
162 const elf_sym_t *sym, uint8_t *rel_addr, const void *link_addr)
163 {
164 int type = ELF32_R_TYPE(rel->r_info);
165
166 /* For global relocations we expect the initial value for R_XTENSA_RELATIVE to be zero */
167 if (type == R_XTENSA_RELATIVE && *(elf_word *)rel_addr) {
168 LOG_WRN("global: non-zero relative value %#x", *(elf_word *)rel_addr);
169 }
170
171 xtensa_elf_relocate(ldr, ext, rel, (uintptr_t)link_addr, rel_addr, type,
172 ELF_ST_BIND(sym->st_info), NULL);
173 }
174