|
7 | 7 | * SPDX-License-Identifier: Apache-2.0
|
8 | 8 | */
|
9 | 9 | #include <zephyr/llext/elf.h>
|
| 10 | +#include <zephyr/llext/llext_internal.h> |
10 | 11 | #include <zephyr/llext/llext.h>
|
| 12 | +#include <zephyr/llext/loader.h> |
| 13 | + |
11 | 14 | #include <zephyr/logging/log.h>
|
12 | 15 | #include <zephyr/sys/util.h>
|
13 | 16 |
|
@@ -61,6 +64,134 @@ static inline int riscv_relocation_fits(long long jump_target, long long max_dis
|
61 | 64 |
|
62 | 65 | static long long last_u_type_jump_target;
|
63 | 66 |
|
| 67 | +static size_t riscv_last_rel_idx; |
| 68 | + |
| 69 | +/** |
| 70 | + * @brief On RISC-V, PC-relative relocations (PCREL_LO12_I, PCREL_LO12_S) do not refer to |
| 71 | + * the actual symbol. Instead, they refer to the location of a different instruction in the |
| 72 | + * same section, which has a PCREL_HI20 relocation. The relocation offset is then computed based |
| 73 | + * on the location and symbol from the HI20 relocation. 20 bits from the offset go into the |
| 74 | + * instruction that has the HI20 relocation, and 12 bits go into the PCREL_LO12 instruction. |
| 75 | + * |
| 76 | + * @param[in] ldr llext loader |
| 77 | + * @param[in] ext current extension |
| 78 | + * @param[in] pcrel_lo12 the elf relocation structure for the PCREL_LO12I/S relocation. |
| 79 | + * @param[in] shdr ELF section header for the relocation |
| 80 | + * @param[in] sym ELF symbol for PCREL_LO12I |
| 81 | + * @param[out] link_addr_out computed link address |
| 82 | + * |
| 83 | + */ |
| 84 | +static int llext_riscv_find_sym_pcrel(struct llext_loader *ldr, struct llext *ext, |
| 85 | + const elf_rela_t *pcrel_lo12, const elf_shdr_t *shdr, |
| 86 | + const elf_sym_t *sym, intptr_t *link_addr_out) |
| 87 | +{ |
| 88 | + int ret; |
| 89 | + elf_rela_t candidate; |
| 90 | + uintptr_t candidate_loc; |
| 91 | + elf_word reloc_type; |
| 92 | + elf_sym_t candidate_sym; |
| 93 | + uintptr_t link_addr; |
| 94 | + const char *symbol_name; |
| 95 | + int iteration_start = riscv_last_rel_idx; |
| 96 | + bool is_first = true; |
| 97 | + const elf_word rel_cnt = shdr->sh_size / shdr->sh_entsize; |
| 98 | + const uintptr_t sect_base = (uintptr_t)llext_loaded_sect_ptr(ldr, ext, shdr->sh_info); |
| 99 | + bool found_candidate = false; |
| 100 | + |
| 101 | + if (iteration_start >= rel_cnt) { |
| 102 | + /* value left over from a different section */ |
| 103 | + iteration_start = 0; |
| 104 | + } |
| 105 | + |
| 106 | + reloc_type = ELF32_R_TYPE(pcrel_lo12->r_info); |
| 107 | + |
| 108 | + if (reloc_type != R_RISCV_PCREL_LO12_I && reloc_type != R_RISCV_PCREL_LO12_S) { |
| 109 | + /* this function does not apply - the symbol is already correct */ |
| 110 | + return 0; |
| 111 | + } |
| 112 | + |
| 113 | + for (int i = iteration_start; i != iteration_start || is_first; i++) { |
| 114 | + |
| 115 | + is_first = false; |
| 116 | + |
| 117 | + /* get each relocation entry */ |
| 118 | + ret = llext_seek(ldr, shdr->sh_offset + i * shdr->sh_entsize); |
| 119 | + if (ret != 0) { |
| 120 | + return ret; |
| 121 | + } |
| 122 | + |
| 123 | + ret = llext_read(ldr, &candidate, shdr->sh_entsize); |
| 124 | + if (ret != 0) { |
| 125 | + return ret; |
| 126 | + } |
| 127 | + |
| 128 | + /* FIXME currently, RISC-V relocations all fit in ELF_32_R_TYPE */ |
| 129 | + reloc_type = ELF32_R_TYPE(candidate.r_info); |
| 130 | + |
| 131 | + candidate_loc = sect_base + candidate.r_offset; |
| 132 | + |
| 133 | + /* |
| 134 | + * RISC-V ELF specification: "value" of the symbol for the PCREL_LO12 relocation |
| 135 | + * is actually the offset of the PCREL_HI20 relocation instruction from section |
| 136 | + * start |
| 137 | + */ |
| 138 | + if (candidate.r_offset == sym->st_value && reloc_type == R_RISCV_PCREL_HI20) { |
| 139 | + found_candidate = true; |
| 140 | + |
| 141 | + /* |
| 142 | + * start here in next iteration |
| 143 | + * it is fairly likely (albeit not guaranteed) that we require PCREL_HI20 |
| 144 | + * relocations in order |
| 145 | + * we can safely write this even if an error occurs after the loop - |
| 146 | + * in that case,we can safely abort the execution anyway |
| 147 | + */ |
| 148 | + riscv_last_rel_idx = i; |
| 149 | + |
| 150 | + break; |
| 151 | + } |
| 152 | + |
| 153 | + if (i + 1 >= rel_cnt) { |
| 154 | + /* wrap around and search in previously processed indices as well */ |
| 155 | + i = -1; |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + if (!found_candidate) { |
| 160 | + LOG_ERR("Could not find R_RISCV_PCREL_HI20 relocation for " |
| 161 | + "R_RISCV_PCREL_LO12 relocation!"); |
| 162 | + return -ENOEXEC; |
| 163 | + } |
| 164 | + |
| 165 | + /* we found a match - need to compute the relocation for this instruction */ |
| 166 | + /* lower 12 bits go to the PCREL_LO12 relocation */ |
| 167 | + |
| 168 | + /* get corresponding / "actual" symbol */ |
| 169 | + ret = llext_seek(ldr, ldr->sects[LLEXT_MEM_SYMTAB].sh_offset + |
| 170 | + ELF_R_SYM(candidate.r_info) * sizeof(elf_sym_t)); |
| 171 | + if (ret != 0) { |
| 172 | + return ret; |
| 173 | + } |
| 174 | + |
| 175 | + ret = llext_read(ldr, &candidate_sym, sizeof(elf_sym_t)); |
| 176 | + if (ret != 0) { |
| 177 | + return ret; |
| 178 | + } |
| 179 | + |
| 180 | + symbol_name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, candidate_sym.st_name); |
| 181 | + |
| 182 | + ret = llext_lookup_symbol(ldr, ext, &link_addr, &candidate, &candidate_sym, |
| 183 | + symbol_name, shdr); |
| 184 | + |
| 185 | + if (ret != 0) { |
| 186 | + return ret; |
| 187 | + } |
| 188 | + |
| 189 | + *link_addr_out = (intptr_t)(link_addr + candidate.r_addend - candidate_loc); /* S + A - P */ |
| 190 | + |
| 191 | + /* found the matching entry */ |
| 192 | + return 0; |
| 193 | +} |
| 194 | + |
64 | 195 | /**
|
65 | 196 | * @brief RISC-V specific function for relocating partially linked ELF binaries
|
66 | 197 | *
|
@@ -101,16 +232,28 @@ int arch_elf_relocate(struct llext_loader *ldr, struct llext *ext, elf_rela_t *r
|
101 | 232 | long long original_imm8, jump_target;
|
102 | 233 | int16_t compressed_imm8;
|
103 | 234 | __typeof__(rel->r_addend) target_alignment = 1;
|
104 |
| - const intptr_t sym_base_addr = (intptr_t)sym_base_addr_unsigned; |
| 235 | + intptr_t sym_base_addr = (intptr_t)sym_base_addr_unsigned; |
| 236 | + int ret; |
| 237 | + |
| 238 | + /* |
| 239 | + * For HI20/LO12 ("PCREL") relocation pairs, we need a helper function to |
| 240 | + * determine the address for the LO12 relocation, as it depends on the |
| 241 | + * value in the HI20 relocation. |
| 242 | + */ |
| 243 | + ret = llext_riscv_find_sym_pcrel(ldr, ext, rel, shdr, sym, &sym_base_addr); |
| 244 | + |
| 245 | + if (ret != 0) { |
| 246 | + LOG_ERR("Failed to resolve RISC-V PCREL relocation for symbol %s at %p " |
| 247 | + "with base address %p load address %p type %" PRIu64, |
| 248 | + sym_name, (void *)loc, (void *)sym_base_addr, (void *)load_bias, |
| 249 | + (uint64_t)reloc_type); |
| 250 | + return ret; |
| 251 | + } |
105 | 252 |
|
106 | 253 | LOG_DBG("Relocating symbol %s at %p with base address %p load address %p type %" PRIu64,
|
107 | 254 | sym_name, (void *)loc, (void *)sym_base_addr, (void *)load_bias,
|
108 | 255 | (uint64_t)reloc_type);
|
109 | 256 |
|
110 |
| - ARG_UNUSED(ldr); |
111 |
| - ARG_UNUSED(shdr); |
112 |
| - ARG_UNUSED(sym); |
113 |
| - |
114 | 257 | /* FIXME not all types of relocations currently supported, especially TLS */
|
115 | 258 |
|
116 | 259 | switch (reloc_type) {
|
@@ -187,38 +330,29 @@ int arch_elf_relocate(struct llext_loader *ldr, struct llext *ext, elf_rela_t *r
|
187 | 330 | return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE,
|
188 | 331 | reloc_type);
|
189 | 332 | case R_RISCV_PCREL_LO12_I:
|
190 |
| - /* need the same jump target as preceding U-type relocation */ |
191 |
| - if (last_u_type_jump_target == 0) { |
192 |
| - LOG_ERR("R_RISCV_PCREL_LO12_I relocation without preceding U-type " |
193 |
| - "relocation!"); |
194 |
| - return -ENOEXEC; |
195 |
| - } |
| 333 | + /* |
| 334 | + * Jump target is resolved in llext_riscv_find_sym_pcrel in llext_link.c |
| 335 | + * as it depends on other relocations. |
| 336 | + */ |
196 | 337 | modified_operand = UNALIGNED_GET(loc32);
|
197 |
| - jump_target = last_u_type_jump_target; /* S - P */ |
198 |
| - last_u_type_jump_target = 0; |
199 |
| - imm8 = jump_target; |
| 338 | + imm8 = (int32_t)sym_base_addr; /* already computed */ |
200 | 339 | modified_operand = R_RISCV_CLEAR_ITYPE_IMM8(modified_operand);
|
201 | 340 | modified_operand = R_RISCV_SET_ITYPE_IMM8(modified_operand, imm8);
|
202 | 341 | UNALIGNED_PUT(modified_operand, loc32);
|
203 |
| - return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE, |
204 |
| - reloc_type); |
| 342 | + /* we have checked that this fits with the associated relocation */ |
205 | 343 | break;
|
206 | 344 | case R_RISCV_PCREL_LO12_S:
|
207 |
| - /* need the same jump target as preceding U-type relocation */ |
208 |
| - if (last_u_type_jump_target == 0) { |
209 |
| - LOG_ERR("R_RISCV_PCREL_LO12_I relocation without preceding U-type " |
210 |
| - "relocation!"); |
211 |
| - return -ENOEXEC; |
212 |
| - } |
| 345 | + /* |
| 346 | + * Jump target is resolved in llext_riscv_find_sym_pcrel in llext_link.c |
| 347 | + * as it depends on other relocations. |
| 348 | + */ |
213 | 349 | modified_operand = UNALIGNED_GET(loc32);
|
214 |
| - jump_target = last_u_type_jump_target; /* S - P */ |
215 |
| - last_u_type_jump_target = 0; |
216 |
| - imm8 = jump_target; |
| 350 | + imm8 = (int32_t)sym_base_addr; /* already computed */ |
217 | 351 | modified_operand = R_RISCV_CLEAR_STYPE_IMM8(modified_operand);
|
218 | 352 | modified_operand = R_RISCV_SET_STYPE_IMM8(modified_operand, imm8);
|
219 | 353 | UNALIGNED_PUT(modified_operand, loc32);
|
220 |
| - return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE, |
221 |
| - reloc_type); |
| 354 | + /* we have checked that this fits with the associated relocation */ |
| 355 | + break; |
222 | 356 | case R_RISCV_HI20:
|
223 | 357 | jump_target = sym_base_addr + rel->r_addend; /* S + A */
|
224 | 358 | modified_operand = UNALIGNED_GET(loc32);
|
|
0 commit comments