8
8
*/
9
9
#include <zephyr/llext/elf.h>
10
10
#include <zephyr/llext/llext.h>
11
+ #include <zephyr/llext/llext_internal.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,18 +64,166 @@ 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
*
67
198
* This implementation follows the official RISC-V specification:
68
199
* https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
69
200
*
70
201
*/
71
- int arch_elf_relocate (elf_rela_t * rel , uintptr_t loc_unsigned , uintptr_t sym_base_addr_unsigned ,
72
- const char * sym_name , uintptr_t load_bias )
202
+ int arch_elf_relocate (struct llext_loader * ldr , struct llext * ext , elf_rela_t * rel ,
203
+ const elf_shdr_t * shdr )
73
204
{
74
205
/* FIXME currently, RISC-V relocations all fit in ELF_32_R_TYPE */
75
206
elf_word reloc_type = ELF32_R_TYPE (rel -> r_info );
207
+ const uintptr_t load_bias = llext_text_start (ext );
208
+ const uintptr_t loc_unsigned = llext_get_reloc_instruction_location (ldr , ext , shdr , rel );
209
+ elf_sym_t sym ;
210
+ uintptr_t sym_base_addr_unsigned ;
211
+ const char * sym_name ;
212
+ int ret ;
213
+
214
+ ret = llext_read_symbol (ldr , ext , rel , & sym );
215
+ if (ret != 0 ) {
216
+ LOG_ERR ("Could not read symbol from binary!" );
217
+ return ret ;
218
+ }
219
+
220
+ sym_name = llext_symbol_name (ldr , ext , & sym );
221
+ ret = llext_lookup_symbol (ldr , ext , & sym_base_addr_unsigned , rel , & sym , sym_name , shdr );
222
+
223
+ if (ret != 0 ) {
224
+ LOG_ERR ("Could not find symbol %s!" , sym_name );
225
+ return ret ;
226
+ }
76
227
/*
77
228
* The RISC-V specification uses the following symbolic names for the relocations:
78
229
*
@@ -99,7 +250,22 @@ int arch_elf_relocate(elf_rela_t *rel, uintptr_t loc_unsigned, uintptr_t sym_bas
99
250
long long original_imm8 , jump_target ;
100
251
int16_t compressed_imm8 ;
101
252
__typeof__ (rel -> r_addend ) target_alignment = 1 ;
102
- const intptr_t sym_base_addr = (intptr_t )sym_base_addr_unsigned ;
253
+ intptr_t sym_base_addr = (intptr_t )sym_base_addr_unsigned ;
254
+
255
+ /*
256
+ * For HI20/LO12 ("PCREL") relocation pairs, we need a helper function to
257
+ * determine the address for the LO12 relocation, as it depends on the
258
+ * value in the HI20 relocation.
259
+ */
260
+ ret = llext_riscv_find_sym_pcrel (ldr , ext , rel , shdr , & sym , & sym_base_addr );
261
+
262
+ if (ret != 0 ) {
263
+ LOG_ERR ("Failed to resolve RISC-V PCREL relocation for symbol %s at %p "
264
+ "with base address %p load address %p type %" PRIu64 ,
265
+ sym_name , (void * )loc , (void * )sym_base_addr , (void * )load_bias ,
266
+ (uint64_t )reloc_type );
267
+ return ret ;
268
+ }
103
269
104
270
LOG_DBG ("Relocating symbol %s at %p with base address %p load address %p type %" PRIu64 ,
105
271
sym_name , (void * )loc , (void * )sym_base_addr , (void * )load_bias ,
@@ -181,38 +347,29 @@ int arch_elf_relocate(elf_rela_t *rel, uintptr_t loc_unsigned, uintptr_t sym_bas
181
347
return riscv_relocation_fits (jump_target , RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE ,
182
348
reloc_type );
183
349
case R_RISCV_PCREL_LO12_I :
184
- /* need the same jump target as preceding U-type relocation */
185
- if (last_u_type_jump_target == 0 ) {
186
- LOG_ERR ("R_RISCV_PCREL_LO12_I relocation without preceding U-type "
187
- "relocation!" );
188
- return - ENOEXEC ;
189
- }
350
+ /*
351
+ * Jump target is resolved in llext_riscv_find_sym_pcrel in llext_link.c
352
+ * as it depends on other relocations.
353
+ */
190
354
modified_operand = UNALIGNED_GET (loc32 );
191
- jump_target = last_u_type_jump_target ; /* S - P */
192
- last_u_type_jump_target = 0 ;
193
- imm8 = jump_target ;
355
+ imm8 = (int32_t )sym_base_addr ; /* already computed */
194
356
modified_operand = R_RISCV_CLEAR_ITYPE_IMM8 (modified_operand );
195
357
modified_operand = R_RISCV_SET_ITYPE_IMM8 (modified_operand , imm8 );
196
358
UNALIGNED_PUT (modified_operand , loc32 );
197
- return riscv_relocation_fits (jump_target , RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE ,
198
- reloc_type );
359
+ /* we have checked that this fits with the associated relocation */
199
360
break ;
200
361
case R_RISCV_PCREL_LO12_S :
201
- /* need the same jump target as preceding U-type relocation */
202
- if (last_u_type_jump_target == 0 ) {
203
- LOG_ERR ("R_RISCV_PCREL_LO12_I relocation without preceding U-type "
204
- "relocation!" );
205
- return - ENOEXEC ;
206
- }
362
+ /*
363
+ * Jump target is resolved in llext_riscv_find_sym_pcrel in llext_link.c
364
+ * as it depends on other relocations.
365
+ */
207
366
modified_operand = UNALIGNED_GET (loc32 );
208
- jump_target = last_u_type_jump_target ; /* S - P */
209
- last_u_type_jump_target = 0 ;
210
- imm8 = jump_target ;
367
+ imm8 = (int32_t )sym_base_addr ; /* already computed */
211
368
modified_operand = R_RISCV_CLEAR_STYPE_IMM8 (modified_operand );
212
369
modified_operand = R_RISCV_SET_STYPE_IMM8 (modified_operand , imm8 );
213
370
UNALIGNED_PUT (modified_operand , loc32 );
214
- return riscv_relocation_fits ( jump_target , RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE ,
215
- reloc_type ) ;
371
+ /* we have checked that this fits with the associated relocation */
372
+ break ;
216
373
case R_RISCV_HI20 :
217
374
jump_target = sym_base_addr + rel -> r_addend ; /* S + A */
218
375
modified_operand = UNALIGNED_GET (loc32 );
0 commit comments