Skip to content

Commit 3f6c93f

Browse files
llext: Support non-paired RISC-V PCREL Relocation
Currently, RISC-V's architecture-specific relocations assume that all relocations of type R_RISCV_PCREL_LO12_I and _S are processed immediately after the R_RISCV_PCREL_HI20 relocation that they share a relocation target with. While this is the case most of the time, the RISC-V PSABI specification does not guarantee that. This commit corrects this by determining the R_RISCV_PCREL_HI20 relocation based on the symbol value of the R_RISCV_PCREL_LO12 relocation, as specified in the PSABI. Signed-off-by: Eric Ackermann <[email protected]>
1 parent 18a62ce commit 3f6c93f

File tree

4 files changed

+179
-41
lines changed

4 files changed

+179
-41
lines changed

arch/riscv/core/elf.c

Lines changed: 161 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
* SPDX-License-Identifier: Apache-2.0
88
*/
99
#include <zephyr/llext/elf.h>
10+
#include <zephyr/llext/llext_internal.h>
1011
#include <zephyr/llext/llext.h>
12+
#include <zephyr/llext/loader.h>
13+
1114
#include <zephyr/logging/log.h>
1215
#include <zephyr/sys/util.h>
1316

@@ -61,6 +64,134 @@ static inline int riscv_relocation_fits(long long jump_target, long long max_dis
6164

6265
static long long last_u_type_jump_target;
6366

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+
64195
/**
65196
* @brief RISC-V specific function for relocating partially linked ELF binaries
66197
*
@@ -101,16 +232,28 @@ int arch_elf_relocate(struct llext_loader *ldr, struct llext *ext, elf_rela_t *r
101232
long long original_imm8, jump_target;
102233
int16_t compressed_imm8;
103234
__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+
}
105252

106253
LOG_DBG("Relocating symbol %s at %p with base address %p load address %p type %" PRIu64,
107254
sym_name, (void *)loc, (void *)sym_base_addr, (void *)load_bias,
108255
(uint64_t)reloc_type);
109256

110-
ARG_UNUSED(ldr);
111-
ARG_UNUSED(shdr);
112-
ARG_UNUSED(sym);
113-
114257
/* FIXME not all types of relocations currently supported, especially TLS */
115258

116259
switch (reloc_type) {
@@ -187,38 +330,29 @@ int arch_elf_relocate(struct llext_loader *ldr, struct llext *ext, elf_rela_t *r
187330
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE,
188331
reloc_type);
189332
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+
*/
196337
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 */
200339
modified_operand = R_RISCV_CLEAR_ITYPE_IMM8(modified_operand);
201340
modified_operand = R_RISCV_SET_ITYPE_IMM8(modified_operand, imm8);
202341
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 */
205343
break;
206344
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+
*/
213349
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 */
217351
modified_operand = R_RISCV_CLEAR_STYPE_IMM8(modified_operand);
218352
modified_operand = R_RISCV_SET_STYPE_IMM8(modified_operand, imm8);
219353
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;
222356
case R_RISCV_HI20:
223357
jump_target = sym_base_addr + rel->r_addend; /* S + A */
224358
modified_operand = UNALIGNED_GET(loc32);

include/zephyr/llext/llext_internal.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,20 @@
1111
extern "C" {
1212
#endif
1313

14+
#include <zephyr/llext/llext.h>
15+
1416
/**
1517
* @file
1618
* @brief Private header for linkable loadable extensions
1719
*/
1820

1921
/** @cond ignore */
2022

21-
struct llext_loader;
22-
struct llext;
23+
static inline const char *llext_string(const struct llext_loader *ldr, const struct llext *ext,
24+
enum llext_mem mem_idx, unsigned int idx)
25+
{
26+
return (char *)ext->mem[mem_idx] + idx;
27+
}
2328

2429
struct llext_elf_sect_map {
2530
enum llext_mem mem_idx;
@@ -33,6 +38,13 @@ static inline uintptr_t llext_text_start(const struct llext *ext)
3338
return (uintptr_t)ext->mem[LLEXT_MEM_TEXT];
3439
}
3540

41+
/**
42+
* Determine address of a symbol.
43+
*/
44+
int llext_lookup_symbol(struct llext_loader *ldr, struct llext *ext, uintptr_t *link_addr,
45+
const elf_rela_t *rel, const elf_sym_t *sym, const char *name,
46+
const elf_shdr_t *shdr);
47+
3648
/** @endcond */
3749

3850
#ifdef __cplusplus

subsys/llext/llext_link.c

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <zephyr/llext/llext_internal.h>
1313
#include <zephyr/kernel.h>
1414
#include <zephyr/cache.h>
15+
#include <zephyr/arch/riscv/elf.h>
1516

1617
#include <zephyr/logging/log.h>
1718
LOG_MODULE_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL);
@@ -143,12 +144,9 @@ static const void *llext_find_extension_sym(const char *sym_name, struct llext *
143144
return se.addr;
144145
}
145146

146-
/*
147-
* Determine address of a symbol.
148-
*/
149-
static int llext_lookup_symbol(struct llext_loader *ldr, struct llext *ext, uintptr_t *link_addr,
150-
const elf_rela_t *rel, const elf_sym_t *sym, const char *name,
151-
const elf_shdr_t *shdr)
147+
int llext_lookup_symbol(struct llext_loader *ldr, struct llext *ext, uintptr_t *link_addr,
148+
const elf_rela_t *rel, const elf_sym_t *sym, const char *name,
149+
const elf_shdr_t *shdr)
152150
{
153151
if (ELF_R_SYM(rel->r_info) == 0) {
154152
/*

subsys/llext/llext_priv.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,6 @@ static inline void llext_free(void *ptr)
4949
int do_llext_load(struct llext_loader *ldr, struct llext *ext,
5050
const struct llext_load_param *ldr_parm);
5151

52-
static inline const char *llext_string(const struct llext_loader *ldr, const struct llext *ext,
53-
enum llext_mem mem_idx, unsigned int idx)
54-
{
55-
return (char *)ext->mem[mem_idx] + idx;
56-
}
57-
5852
/*
5953
* Relocation (llext_link.c)
6054
*/

0 commit comments

Comments
 (0)