llext: Add RISC-V arch-specific relocations

This commit introduces architecture-specific ELF relocations for RISC-V,
in accordance with the RISC-V PSABI specification:
https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
Also, the necessary compiler configurations for compiling LLEXT
extensions on RISC-V are added, and the llext tests are executed on
RISC-V targets.
Calling llext extensions from user threads in RISC-V is still
unsupported as of this commit.

Signed-off-by: Eric Ackermann <eric.ackermann@cispa.de>
This commit is contained in:
Eric Ackermann 2024-09-19 09:35:49 +02:00 committed by Fabio Baltieri
commit 5275d44409
11 changed files with 729 additions and 11 deletions

View file

@ -27,3 +27,4 @@ zephyr_library_sources_ifdef(CONFIG_USERSPACE userspace.S)
zephyr_library_sources_ifdef(CONFIG_SEMIHOST semihost.c)
zephyr_library_sources_ifdef(CONFIG_ARCH_STACKWALK stacktrace.c)
zephyr_linker_sources(ROM_START SORT_KEY 0x0vectors vector_table.ld)
zephyr_library_sources_ifdef(CONFIG_LLEXT elf.c)

373
arch/riscv/core/elf.c Normal file
View file

@ -0,0 +1,373 @@
/** @file
* @brief Architecture-specific relocations for RISC-V instruction sets.
*/
/*
* Copyright (c) 2024 CISPA Helmholtz Center for Information Security gGmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/llext/elf.h>
#include <zephyr/llext/llext.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
#include <zephyr/arch/riscv/elf.h>
#include <stdlib.h>
LOG_MODULE_REGISTER(elf, CONFIG_LLEXT_LOG_LEVEL);
/*
* RISC-V relocations commonly use pairs of U-type and I-type instructions.
* U-type instructions have 20-bit immediates, I-type instructions have 12-bit immediates.
* Immediates in RISC-V are always sign-extended.
* Thereby, this type of relocation can reach any address within a 2^31-1 byte range.
*/
#define RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE INT32_MAX
/* S-type has 12-bit signed immediate */
#define RISCV_MAX_JUMP_DISTANCE_S_TYPE ((1 << 11) - 1)
/* I-type has 12-bit signed immediate also */
#define RISCV_MAX_JUMP_DISTANCE_I_TYPE ((1 << 11) - 1)
/* B-type has 13-bit signed immediate */
#define RISCV_MAX_JUMP_DISTANCE_B_TYPE ((1 << 12) - 1)
/* CB-type has 9-bit signed immediate */
#define RISCV_MAX_JUMP_DISTANCE_CB_TYPE ((1 << 8) - 1)
/* CJ-type has 12-bit signed immediate (last bit implicit 0) */
#define RISCV_MAX_JUMP_DISTANCE_CJ_TYPE ((1 << 11) - 1)
static inline int riscv_relocation_fits(long long jump_target, long long max_distance,
elf_word reloc_type)
{
if (llabs(jump_target) > max_distance) {
LOG_ERR("%lld byte relocation is not possible for type %" PRIu64 " (max %lld)!",
jump_target, (uint64_t)reloc_type, max_distance);
return -ENOEXEC; /* jump too far */
}
return 0;
}
static long long last_u_type_jump_target;
/**
* @brief RISC-V specific function for relocating partially linked ELF binaries
*
* This implementation follows the official RISC-V specification:
* https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
*
*/
int arch_elf_relocate(elf_rela_t *rel, uintptr_t loc_unsigned, uintptr_t sym_base_addr_unsigned,
const char *sym_name, uintptr_t load_bias)
{
/* FIXME currently, RISC-V relocations all fit in ELF_32_R_TYPE */
elf_word reloc_type = ELF32_R_TYPE(rel->r_info);
/*
* The RISC-V specification uses the following symbolic names for the relocations:
*
* A - addend (rel->r_addend)
* B - base address (load_bias)
* G - global offset table (not supported yet)
* P - position of the relocation (loc)
* S - symbol value (sym_base_addr)
* V - value at the relocation position (*loc)
* GP - value of __global_pointer$ (not supported yet)
* TLSMODULE - TLS module for the object (not supported yet)
* TLSOFFSET - TLS static block for the object (not supported yet)
*/
intptr_t loc = (intptr_t)loc_unsigned;
uint8_t *loc8 = (uint8_t *)loc, tmp8;
uint16_t *loc16 = (uint16_t *)loc, tmp16;
uint32_t *loc32 = (uint32_t *)loc, tmp32;
uint64_t *loc64 = (uint64_t *)loc, tmp64;
/* uint32_t or uint64_t */
r_riscv_wordclass_t *loc_word = (r_riscv_wordclass_t *)loc;
uint32_t modified_operand;
uint16_t modified_compressed_operand;
int32_t imm8;
long long original_imm8, jump_target;
int16_t compressed_imm8;
__typeof__(rel->r_addend) target_alignment = 1;
const intptr_t sym_base_addr = (intptr_t)sym_base_addr_unsigned;
LOG_DBG("Relocating symbol %s at %p with base address %p load address %p type %" PRIu64,
sym_name, (void *)loc, (void *)sym_base_addr, (void *)load_bias,
(uint64_t)reloc_type);
/* FIXME not all types of relocations currently supported, especially TLS */
switch (reloc_type) {
case R_RISCV_NONE:
break;
case R_RISCV_32:
jump_target = sym_base_addr + rel->r_addend; /* S + A */
UNALIGNED_PUT((uint32_t)jump_target, loc32);
return riscv_relocation_fits(jump_target, INT32_MAX, reloc_type);
case R_RISCV_64:
/* full 64-bit range, need no range check */
UNALIGNED_PUT(sym_base_addr + rel->r_addend, loc64); /* S + A */
break;
case R_RISCV_RELATIVE:
/* either full 32-bit or 64-bit range, need no range check */
UNALIGNED_PUT(load_bias + rel->r_addend, loc_word); /* B + A */
break;
case R_RISCV_JUMP_SLOT:
/* either full 32-bit or 64-bit range, need no range check */
UNALIGNED_PUT(sym_base_addr, loc_word); /* S */
break;
case R_RISCV_BRANCH:
jump_target = sym_base_addr + rel->r_addend - loc; /* S + A - P */
modified_operand = UNALIGNED_GET(loc32);
imm8 = jump_target;
modified_operand = R_RISCV_CLEAR_BTYPE_IMM8(modified_operand);
modified_operand = R_RISCV_SET_BTYPE_IMM8(modified_operand, imm8);
UNALIGNED_PUT(modified_operand, loc32);
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_B_TYPE,
reloc_type);
case R_RISCV_JAL:
jump_target = sym_base_addr + rel->r_addend - loc; /* S + A - P */
modified_operand = UNALIGNED_GET(loc32);
imm8 = jump_target;
modified_operand = R_RISCV_CLEAR_JTYPE_IMM8(modified_operand);
modified_operand = R_RISCV_SET_JTYPE_IMM8(modified_operand, imm8);
UNALIGNED_PUT(modified_operand, loc32);
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE,
reloc_type);
case R_RISCV_CALL:
case R_RISCV_CALL_PLT:
case R_RISCV_PCREL_HI20:
modified_operand = UNALIGNED_GET(loc32);
jump_target = sym_base_addr + rel->r_addend - loc; /* S + A - P */
imm8 = jump_target;
/* bit 12 of the immediate goes to I-type instruction and might
* change the sign of the number
*/
/* in order to avoid that, we add 1 to the upper immediate if bit 12 is one */
/* see RISC-V la pseudo instruction */
imm8 += imm8 & 0x800;
original_imm8 = imm8;
modified_operand = R_RISCV_CLEAR_UTYPE_IMM8(modified_operand);
modified_operand = R_RISCV_SET_UTYPE_IMM8(modified_operand, imm8);
UNALIGNED_PUT(modified_operand, loc32);
if (reloc_type != R_RISCV_PCREL_HI20) {
/* PCREL_HI20 is only U-type, not truly U+I-type */
/* for the others, need to also modify following I-type */
loc32++;
imm8 = jump_target;
modified_operand = UNALIGNED_GET(loc32);
modified_operand = R_RISCV_CLEAR_ITYPE_IMM8(modified_operand);
modified_operand = R_RISCV_SET_ITYPE_IMM8(modified_operand, imm8);
UNALIGNED_PUT(modified_operand, loc32);
}
last_u_type_jump_target = jump_target;
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE,
reloc_type);
case R_RISCV_PCREL_LO12_I:
/* need the same jump target as preceding U-type relocation */
if (last_u_type_jump_target == 0) {
LOG_ERR("R_RISCV_PCREL_LO12_I relocation without preceding U-type "
"relocation!");
return -ENOEXEC;
}
modified_operand = UNALIGNED_GET(loc32);
jump_target = last_u_type_jump_target; /* S - P */
last_u_type_jump_target = 0;
imm8 = jump_target;
modified_operand = R_RISCV_CLEAR_ITYPE_IMM8(modified_operand);
modified_operand = R_RISCV_SET_ITYPE_IMM8(modified_operand, imm8);
UNALIGNED_PUT(modified_operand, loc32);
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE,
reloc_type);
break;
case R_RISCV_PCREL_LO12_S:
/* need the same jump target as preceding U-type relocation */
if (last_u_type_jump_target == 0) {
LOG_ERR("R_RISCV_PCREL_LO12_I relocation without preceding U-type "
"relocation!");
return -ENOEXEC;
}
modified_operand = UNALIGNED_GET(loc32);
jump_target = last_u_type_jump_target; /* S - P */
last_u_type_jump_target = 0;
imm8 = jump_target;
modified_operand = R_RISCV_CLEAR_STYPE_IMM8(modified_operand);
modified_operand = R_RISCV_SET_STYPE_IMM8(modified_operand, imm8);
UNALIGNED_PUT(modified_operand, loc32);
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE,
reloc_type);
case R_RISCV_HI20:
jump_target = sym_base_addr + rel->r_addend; /* S + A */
modified_operand = UNALIGNED_GET(loc32);
imm8 = jump_target;
/* bit 12 of the immediate goes to I-type instruction and might
* change the sign of the number
*/
/* in order to avoid that, we add 1 to the upper immediate if bit 12 is one*/
/* see RISC-V la pseudo instruction */
original_imm8 = imm8;
imm8 += imm8 & 0x800;
modified_operand = R_RISCV_CLEAR_UTYPE_IMM8(modified_operand);
modified_operand = R_RISCV_SET_UTYPE_IMM8(modified_operand, imm8);
UNALIGNED_PUT(modified_operand, loc32);
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE,
reloc_type);
case R_RISCV_LO12_I:
modified_operand = UNALIGNED_GET(loc32);
jump_target = sym_base_addr + rel->r_addend; /* S + A */
imm8 = jump_target;
/* this is always used with R_RISCV_HI20 */
modified_operand = R_RISCV_CLEAR_ITYPE_IMM8(modified_operand);
modified_operand = R_RISCV_SET_ITYPE_IMM8(modified_operand, imm8);
UNALIGNED_PUT(modified_operand, loc32);
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE,
reloc_type);
case R_RISCV_LO12_S:
modified_operand = UNALIGNED_GET(loc32);
imm8 = sym_base_addr + rel->r_addend; /* S + A */
/*
* S-type is used for stores/loads etc.
* size check is done at compile time, as it depends on the size of
* the structure we are trying to load/store
*/
modified_operand = R_RISCV_CLEAR_STYPE_IMM8(modified_operand);
modified_operand = R_RISCV_SET_STYPE_IMM8(modified_operand, imm8);
UNALIGNED_PUT(modified_operand, loc32);
break;
/* for add/sub/set, compiler needs to ensure that the ELF sections are close enough */
case R_RISCV_ADD8:
tmp8 = UNALIGNED_GET(loc8);
tmp8 += sym_base_addr + rel->r_addend; /* V + S + A */
UNALIGNED_PUT(tmp8, loc8);
break;
case R_RISCV_ADD16:
tmp16 = UNALIGNED_GET(loc16);
tmp16 += sym_base_addr + rel->r_addend; /* V + S + A */
UNALIGNED_PUT(tmp16, loc16);
break;
case R_RISCV_ADD32:
tmp32 = UNALIGNED_GET(loc32);
tmp32 += sym_base_addr + rel->r_addend; /* V + S + A */
UNALIGNED_PUT(tmp32, loc32);
break;
case R_RISCV_ADD64:
tmp64 = UNALIGNED_GET(loc64);
tmp64 += sym_base_addr + rel->r_addend; /* V + S + A */
UNALIGNED_PUT(tmp64, loc64);
break;
case R_RISCV_SUB8:
tmp8 = UNALIGNED_GET(loc8);
tmp8 -= sym_base_addr + rel->r_addend; /* V - S - A */
UNALIGNED_PUT(tmp8, loc8);
break;
case R_RISCV_SUB16:
tmp16 = UNALIGNED_GET(loc16);
tmp16 -= sym_base_addr + rel->r_addend; /* V - S - A */
UNALIGNED_PUT(tmp16, loc16);
break;
case R_RISCV_SUB32:
tmp32 = UNALIGNED_GET(loc32);
tmp32 -= sym_base_addr + rel->r_addend; /* V - S - A */
UNALIGNED_PUT(tmp32, loc32);
break;
case R_RISCV_SUB64:
tmp64 = UNALIGNED_GET(loc64);
tmp64 -= sym_base_addr + rel->r_addend; /* V - S - A */
UNALIGNED_PUT(tmp64, loc64);
break;
case R_RISCV_SUB6:
tmp8 = UNALIGNED_GET(loc8) & (0x1F);
UNALIGNED_PUT(tmp8, loc8);
tmp8 = tmp8 - sym_base_addr - rel->r_addend; /* V - S - A */
tmp8 = tmp8 & (0x1F);
tmp8 = tmp8 | UNALIGNED_GET(loc8);
UNALIGNED_PUT(tmp8, loc8);
break;
case R_RISCV_SET6:
tmp8 = UNALIGNED_GET(loc8) & (0x1F);
UNALIGNED_PUT(tmp8, loc8);
tmp8 = sym_base_addr + rel->r_addend; /* S + A */
tmp8 = tmp8 | UNALIGNED_GET(loc8);
UNALIGNED_PUT(tmp8, loc8);
break;
case R_RISCV_SET8:
tmp8 = sym_base_addr + rel->r_addend; /* S + A */
UNALIGNED_PUT(tmp8, loc8);
break;
case R_RISCV_SET16:
tmp16 = sym_base_addr + rel->r_addend; /* S + A */
UNALIGNED_PUT(tmp16, loc16);
break;
case R_RISCV_SET32:
tmp32 = sym_base_addr + rel->r_addend; /* S + A */
UNALIGNED_PUT(tmp32, loc32);
break;
case R_RISCV_32_PCREL:
jump_target = sym_base_addr + rel->r_addend - loc; /* S + A - P */
tmp32 = jump_target;
UNALIGNED_PUT(tmp32, loc32);
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE,
reloc_type);
case R_RISCV_PLT32:
jump_target = sym_base_addr + rel->r_addend - loc; /* S + A - P */
tmp32 = jump_target;
UNALIGNED_PUT(tmp32, loc32);
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_U_PLUS_I_TYPE,
reloc_type);
case R_RISCV_RVC_BRANCH:
jump_target = sym_base_addr + rel->r_addend - loc; /* S + A - P */
modified_compressed_operand = UNALIGNED_GET(loc16);
compressed_imm8 = jump_target;
modified_compressed_operand =
R_RISCV_CLEAR_CBTYPE_IMM8(modified_compressed_operand);
modified_compressed_operand =
R_RISCV_SET_CBTYPE_IMM8(modified_compressed_operand, compressed_imm8);
UNALIGNED_PUT(modified_compressed_operand, loc16);
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_CB_TYPE,
reloc_type);
case R_RISCV_RVC_JUMP:
jump_target = sym_base_addr + rel->r_addend - loc; /* S + A - P */
modified_compressed_operand = UNALIGNED_GET(loc16);
compressed_imm8 = jump_target;
modified_compressed_operand =
R_RISCV_CLEAR_CJTYPE_IMM8(modified_compressed_operand);
modified_compressed_operand =
R_RISCV_SET_CJTYPE_IMM8(modified_compressed_operand, compressed_imm8);
UNALIGNED_PUT(modified_compressed_operand, loc16);
return riscv_relocation_fits(jump_target, RISCV_MAX_JUMP_DISTANCE_CJ_TYPE,
reloc_type);
case R_RISCV_ALIGN:
/* we are supposed to move the symbol such that it is aligned to the next power of
* two >= addend
*/
/* this involves moving the symbol */
while (target_alignment < rel->r_addend) {
target_alignment *= 2;
}
LOG_ERR("Symbol %s with location %p requires alignment to %" PRIu64 " bytes!",
sym_name, (void *)loc, (uint64_t)target_alignment);
LOG_ERR("Alignment relocation is currently not supported!");
return -ENOEXEC;
/* ignored, this is primarily intended for removing instructions during link-time
* optimization
*/
case R_RISCV_RELAX:
break;
default:
LOG_ERR("Unsupported relocation type: %" PRIu64 " for symbol: %s",
(uint64_t)reloc_type, sym_name);
return -ENOEXEC;
}
return 0;
}

View file

@ -71,3 +71,23 @@ endif()
list(APPEND TOOLCHAIN_C_FLAGS -mabi=${riscv_mabi} -march=${riscv_march})
list(APPEND TOOLCHAIN_LD_FLAGS NO_SPLIT -mabi=${riscv_mabi} -march=${riscv_march})
# Flags not supported by llext linker
# (regexps are supported and match whole word)
set(LLEXT_REMOVE_FLAGS
-fno-pic
-fno-pie
-ffunction-sections
-fdata-sections
-g.*
-Os
)
# Flags to be added to llext code compilation
# mno-relax is needed to stop gcc from generating R_RISCV_ALIGN relocations,
# which are currently not supported
set(LLEXT_APPEND_FLAGS
-mabi=${riscv_mabi}
-march=${riscv_march}
-mno-relax
)

View file

@ -21,4 +21,4 @@ and introspected to some degree, as well as unloaded when no longer needed.
.. note::
The LLEXT subsystem requires architecture-specific support. It is currently
available only on ARM, ARM64 and Xtensa cores.
available only on RISC-V, ARM, ARM64 and Xtensa cores.

View file

@ -0,0 +1,298 @@
/**
* @file
* @brief RISCV-Specific constants for ELF binaries.
*
* References can be found here:
* https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
*/
/*
* Copyright (c) 2024 CISPA Helmholtz Center for Information Security gGmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_ARCH_RISCV_ELF_H
#define ZEPHYR_ARCH_RISCV_ELF_H
#include <stdint.h>
#include <zephyr/sys/util_macro.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Relocation names for RISCV-specific relocations
* @cond ignore
*/
#define R_RISCV_NONE 0
#define R_RISCV_32 1
#define R_RISCV_64 2
#define R_RISCV_RELATIVE 3
#define R_RISCV_COPY 4
#define R_RISCV_JUMP_SLOT 5
#define R_RISCV_TLS_DTPMOD32 6
#define R_RISCV_TLS_DTPMOD64 7
#define R_RISCV_TLS_DTPREL32 8
#define R_RISCV_TLS_DTPREL64 9
#define R_RISCV_TLS_TPREL32 10
#define R_RISCV_TLS_TPREL64 11
#define R_RISCV_TLSDESC 12
/* 13-15 reserved */
#define R_RISCV_BRANCH 16
#define R_RISCV_JAL 17
#define R_RISCV_CALL 18
#define R_RISCV_CALL_PLT 19
#define R_RISCV_GOT_HI20 20
#define R_RISCV_TLS_GOT_HI20 21
#define R_RISCV_TLS_GD_HI20 22
#define R_RISCV_PCREL_HI20 23
#define R_RISCV_PCREL_LO12_I 24
#define R_RISCV_PCREL_LO12_S 25
#define R_RISCV_HI20 26
#define R_RISCV_LO12_I 27
#define R_RISCV_LO12_S 28
#define R_RISCV_TPREL_HI20 29
#define R_RISCV_TPREL_LO12_I 30
#define R_RISCV_TPREL_LO12_S 31
#define R_RISCV_TPREL_ADD 32
#define R_RISCV_ADD8 33
#define R_RISCV_ADD16 34
#define R_RISCV_ADD32 35
#define R_RISCV_ADD64 36
#define R_RISCV_SUB8 37
#define R_RISCV_SUB16 38
#define R_RISCV_SUB32 39
#define R_RISCV_SUB64 40
#define R_RISCV_GOT32_PCREL 41
/* 42 reserved */
#define R_RISCV_ALIGN 43
/* next two refer to compressed instructions */
#define R_RISCV_RVC_BRANCH 44
#define R_RISCV_RVC_JUMP 45
/* 46-50 reserved */
#define R_RISCV_RELAX 51
#define R_RISCV_SUB6 52
#define R_RISCV_SET6 53
#define R_RISCV_SET8 54
#define R_RISCV_SET16 55
#define R_RISCV_SET32 56
#define R_RISCV_32_PCREL 57
#define R_RISCV_IRELATIVE 58
#define R_RISCV_PLT32 59
#define R_RISCV_SET_ULEB128 60
#define R_RISCV_SUB_ULEB128 61
#define R_RISCV_TLSDESC_HI20 62
#define R_RISCV_TLSDESC_LOAD_LO12 63
#define R_RISCV_TLSDESC_ADD_LO12 64
#define R_RISCV_TLSDESC_CALL 65
/* 66-190 reserved */
#define R_RISCV_VENDOR 191
/* 192-255 reserved */
/** @endcond */
/**
* "wordclass" from RISC-V specification
* @cond ignore
*/
#if defined(CONFIG_64BIT)
typedef uint64_t r_riscv_wordclass_t;
#else
typedef uint32_t r_riscv_wordclass_t;
#endif
/** @endcond */
/** @brief Extract bit from immediate
*
* @param imm8 immediate value (usually upper 20 or lower 12 bit)
* @param bit which bit to extract
*/
#define R_RISCV_IMM8_GET_BIT(imm8, bit) (((imm8) & BIT(bit)) >> (bit))
/** @brief Generate mask for immediate in B-type RISC-V instruction
*
* @param imm8 immediate value, lower 12 bits used;
* due to alignment requirements, imm8[0] is implicitly 0
*
*/
#define R_RISCV_BTYPE_IMM8_MASK(imm8) \
((R_RISCV_IMM8_GET_BIT(imm8, 12) << 31) | (R_RISCV_IMM8_GET_BIT(imm8, 10) << 30) | \
(R_RISCV_IMM8_GET_BIT(imm8, 9) << 29) | (R_RISCV_IMM8_GET_BIT(imm8, 8) << 28) | \
(R_RISCV_IMM8_GET_BIT(imm8, 7) << 27) | (R_RISCV_IMM8_GET_BIT(imm8, 6) << 26) | \
(R_RISCV_IMM8_GET_BIT(imm8, 5) << 25) | (R_RISCV_IMM8_GET_BIT(imm8, 4) << 11) | \
(R_RISCV_IMM8_GET_BIT(imm8, 3) << 10) | (R_RISCV_IMM8_GET_BIT(imm8, 2) << 9) | \
(R_RISCV_IMM8_GET_BIT(imm8, 1) << 8) | (R_RISCV_IMM8_GET_BIT(imm8, 11) << 7))
/** @brief Generate mask for immediate in J-type RISC-V instruction
*
* @param imm8 immediate value, lower 21 bits used;
* due to alignment requirements, imm8[0] is implicitly 0
*
*/
#define R_RISCV_JTYPE_IMM8_MASK(imm8) \
((R_RISCV_IMM8_GET_BIT(imm8, 20) << 31) | (R_RISCV_IMM8_GET_BIT(imm8, 10) << 30) | \
(R_RISCV_IMM8_GET_BIT(imm8, 9) << 29) | (R_RISCV_IMM8_GET_BIT(imm8, 8) << 28) | \
(R_RISCV_IMM8_GET_BIT(imm8, 7) << 27) | (R_RISCV_IMM8_GET_BIT(imm8, 6) << 26) | \
(R_RISCV_IMM8_GET_BIT(imm8, 5) << 25) | (R_RISCV_IMM8_GET_BIT(imm8, 4) << 24) | \
(R_RISCV_IMM8_GET_BIT(imm8, 3) << 23) | (R_RISCV_IMM8_GET_BIT(imm8, 2) << 22) | \
(R_RISCV_IMM8_GET_BIT(imm8, 1) << 21) | (R_RISCV_IMM8_GET_BIT(imm8, 11) << 20) | \
(R_RISCV_IMM8_GET_BIT(imm8, 19) << 19) | (R_RISCV_IMM8_GET_BIT(imm8, 18) << 18) | \
(R_RISCV_IMM8_GET_BIT(imm8, 17) << 17) | (R_RISCV_IMM8_GET_BIT(imm8, 16) << 16) | \
(R_RISCV_IMM8_GET_BIT(imm8, 15) << 15) | (R_RISCV_IMM8_GET_BIT(imm8, 14) << 14) | \
(R_RISCV_IMM8_GET_BIT(imm8, 13) << 13) | (R_RISCV_IMM8_GET_BIT(imm8, 12) << 12))
/** @brief Generate mask for immediate in S-type RISC-V instruction
*
* @param imm8 immediate value, lower 12 bits used
*
*/
#define R_RISCV_STYPE_IMM8_MASK(imm8) \
((R_RISCV_IMM8_GET_BIT(imm8, 11) << 31) | (R_RISCV_IMM8_GET_BIT(imm8, 10) << 30) | \
(R_RISCV_IMM8_GET_BIT(imm8, 9) << 29) | (R_RISCV_IMM8_GET_BIT(imm8, 8) << 28) | \
(R_RISCV_IMM8_GET_BIT(imm8, 7) << 27) | (R_RISCV_IMM8_GET_BIT(imm8, 6) << 26) | \
(R_RISCV_IMM8_GET_BIT(imm8, 5) << 25) | (R_RISCV_IMM8_GET_BIT(imm8, 4) << 11) | \
(R_RISCV_IMM8_GET_BIT(imm8, 3) << 10) | (R_RISCV_IMM8_GET_BIT(imm8, 2) << 9) | \
(R_RISCV_IMM8_GET_BIT(imm8, 1) << 8) | (R_RISCV_IMM8_GET_BIT(imm8, 0) << 7))
/** @brief Generate mask for immediate in compressed J-type RISC-V instruction
*
* @param imm8 immediate value, lower 12 bits used;
* due to alignment requirements, imm8[0] is implicitly 0
*
*/
#define R_RISCV_CJTYPE_IMM8_MASK(imm8) \
((R_RISCV_IMM8_GET_BIT(imm8, 11) << 12) | (R_RISCV_IMM8_GET_BIT(imm8, 4) << 11) | \
(R_RISCV_IMM8_GET_BIT(imm8, 9) << 10) | (R_RISCV_IMM8_GET_BIT(imm8, 8) << 9) | \
(R_RISCV_IMM8_GET_BIT(imm8, 10) << 8) | (R_RISCV_IMM8_GET_BIT(imm8, 6) << 7) | \
(R_RISCV_IMM8_GET_BIT(imm8, 7) << 6) | (R_RISCV_IMM8_GET_BIT(imm8, 3) << 5) | \
(R_RISCV_IMM8_GET_BIT(imm8, 2) << 4) | (R_RISCV_IMM8_GET_BIT(imm8, 1) << 3) | \
(R_RISCV_IMM8_GET_BIT(imm8, 5) << 2))
/** @brief Generate mask for immediate in compressed B-type RISC-V instruction
*
* @param imm8 immediate value, lower 9 bits used;
* due to alignment requirements, imm8[0] is implicitly 0
*
*/
#define R_RISCV_CBTYPE_IMM8_MASK(imm8) \
((R_RISCV_IMM8_GET_BIT(imm8, 8) << 12) | (R_RISCV_IMM8_GET_BIT(imm8, 4) << 11) | \
(R_RISCV_IMM8_GET_BIT(imm8, 3) << 10) | (R_RISCV_IMM8_GET_BIT(imm8, 7) << 6) | \
(R_RISCV_IMM8_GET_BIT(imm8, 6) << 5) | (R_RISCV_IMM8_GET_BIT(imm8, 2) << 4) | \
(R_RISCV_IMM8_GET_BIT(imm8, 1) << 3) | (R_RISCV_IMM8_GET_BIT(imm8, 5) << 2))
/** @brief Clear immediate bits in B-type instruction.
*
* @param operand Address of RISC-V instruction, B-type
*
*/
#define R_RISCV_CLEAR_BTYPE_IMM8(operand) ((operand) & ~R_RISCV_BTYPE_IMM8_MASK((uint32_t) -1))
/** @brief Overwrite immediate in B-type instruction
*
* @param operand Address of RISC-V instruction, B-type
* @param imm8 New immediate
*
*/
#define R_RISCV_SET_BTYPE_IMM8(operand, imm8) \
((R_RISCV_CLEAR_BTYPE_IMM8(operand)) | R_RISCV_BTYPE_IMM8_MASK(imm8))
/** @brief Clear immediate bits in J-type instruction.
*
* @param operand Address of RISC-V instruction, J-type
*
*/
#define R_RISCV_CLEAR_JTYPE_IMM8(operand) ((operand) & ~R_RISCV_JTYPE_IMM8_MASK((uint32_t) -1))
/** @brief Overwrite immediate in J-type instruction
*
* @param operand Address of RISC-V instruction, J-type
* @param imm8 New immediate
*
*/
#define R_RISCV_SET_JTYPE_IMM8(operand, imm8) \
((R_RISCV_CLEAR_JTYPE_IMM8(operand)) | R_RISCV_JTYPE_IMM8_MASK(imm8))
/** @brief Clear immediate bits in S-type instruction.
*
* @param operand Address of RISC-V instruction, S-type
*
*/
#define R_RISCV_CLEAR_STYPE_IMM8(operand) ((operand) & ~R_RISCV_STYPE_IMM8_MASK((uint32_t) -1))
/** @brief Overwrite immediate in S-type instruction
*
* @param operand Address of RISC-V instruction, S-type
* @param imm8 New immediate
*
*/
#define R_RISCV_SET_STYPE_IMM8(operand, imm8) \
((R_RISCV_CLEAR_STYPE_IMM8(operand)) | R_RISCV_STYPE_IMM8_MASK(imm8))
/** @brief Clear immediate bits in compressed J-type instruction.
*
* @param operand Address of RISC-V instruction, compressed-J-type
*
*/
#define R_RISCV_CLEAR_CJTYPE_IMM8(operand) ((operand) & ~R_RISCV_CJTYPE_IMM8_MASK((uint32_t) -1))
/** @brief Overwrite immediate in compressed J-type instruction
*
* @param operand Address of RISC-V instruction, compressed-J-type
* @param imm8 New immediate
*
*/
#define R_RISCV_SET_CJTYPE_IMM8(operand, imm8) \
((R_RISCV_CLEAR_CJTYPE_IMM8(operand)) | R_RISCV_CJTYPE_IMM8_MASK(imm8))
/** @brief Clear immediate bits in compressed B-type instruction.
*
* @param operand Address of RISC-V instruction, compressed-B-type
*
*/
#define R_RISCV_CLEAR_CBTYPE_IMM8(operand) ((operand) & ~R_RISCV_CBTYPE_IMM8_MASK((uint32_t) -1))
/** @brief Overwrite immediate in compressed B-type instruction
*
* @param operand Address of RISC-V instruction, compressed-B-type
* @param imm8 New immediate
*
*/
#define R_RISCV_SET_CBTYPE_IMM8(operand, imm8) \
((R_RISCV_CLEAR_CBTYPE_IMM8(operand)) | R_RISCV_CBTYPE_IMM8_MASK(imm8))
/** @brief Clear immediate bits in U-type instruction.
*
* @param operand Address of RISC-V instruction, U-type
*
*/
#define R_RISCV_CLEAR_UTYPE_IMM8(operand) ((operand) & ~(0xFFFFF000))
/** @brief Overwrite immediate in U-type instruction
*
* @param operand Address of RISC-V instruction, U-type
* @param imm8 New immediate
*
*/
#define R_RISCV_SET_UTYPE_IMM8(operand, imm8) \
((R_RISCV_CLEAR_UTYPE_IMM8(operand)) | ((imm8) & 0xFFFFF000))
/** @brief Clear immediate bits in I-type instruction.
*
* @param operand Address of RISC-V instruction, I-type
*
*/
#define R_RISCV_CLEAR_ITYPE_IMM8(operand) ((operand) & ~(0xFFF00000))
/** @brief Overwrite immediate in I-type instruction
*
* @param operand Address of RISC-V instruction, I-type
* @param imm8 New immediate
*
*/
#define R_RISCV_SET_ITYPE_IMM8(operand, imm8) ((R_RISCV_CLEAR_ITYPE_IMM8(operand)) | ((imm8) << 20))
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_ARCH_RISCV_ELF_H */

View file

@ -17,4 +17,8 @@ if(CONFIG_LLEXT)
fs_loader.c
)
zephyr_library_sources_ifdef(CONFIG_LLEXT_SHELL shell.c)
if(CONFIG_RISCV AND CONFIG_USERSPACE)
message(WARNING "Running LLEXT extensions from user-space threads on RISC-V is not supported!")
endif()
endif()

View file

@ -14,15 +14,17 @@ choice LLEXT_BINARY_TYPE
prompt "Binary object type for llext"
default LLEXT_TYPE_ELF_OBJECT if ARM || ARM64
default LLEXT_TYPE_ELF_SHAREDLIB if XTENSA
default LLEXT_TYPE_ELF_RELOCATABLE if RISCV
help
Object type for llext
config LLEXT_TYPE_ELF_OBJECT
bool "Single object ELF file"
depends on !RISCV
help
Build and expect object files as binary object type for the
llext subsystem. A single compiler invocation is used to
generate the object file.
generate the object file. Currently not supported on RISC-V.
config LLEXT_TYPE_ELF_RELOCATABLE
bool "Relocatable ELF file"

View file

@ -387,6 +387,14 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext,
continue;
}
/*
* Exported symbols region can also overlap
* with rodata.
*/
if (i == LLEXT_MEM_EXPORT || j == LLEXT_MEM_EXPORT) {
continue;
}
if (ldr->hdr.e_type == ET_DYN) {
/*
* Test all merged VMA ranges for overlaps

View file

@ -67,7 +67,7 @@ if(NOT CONFIG_LLEXT_TYPE_ELF_OBJECT)
)
endif()
if (CONFIG_LLEXT_TYPE_ELF_RELOCATABLE AND NOT CONFIG_ARM)
if (CONFIG_LLEXT_TYPE_ELF_RELOCATABLE AND NOT CONFIG_ARM AND NOT CONFIG_RISCV)
# Manually fix the pre_located extension's text address at a multiple of 4
get_target_property(pre_located_target pre_located_ext lib_target)
get_target_property(pre_located_file pre_located_ext pkg_input)

View file

@ -421,6 +421,12 @@ ZTEST(llext, test_find_section)
uintptr_t symbol_ptr = (uintptr_t)llext_find_sym(&ext->exp_tab, "number");
uintptr_t section_ptr = (uintptr_t)find_section_ext + section_ofs;
/*
* FIXME on RISC-V, at least for GCC, the symbols aren't always at the beginning
* of the section when CONFIG_LLEXT_TYPE_ELF_OBJECT is used, breaking this assertion.
* Currently, CONFIG_LLEXT_TYPE_ELF_OBJECT is not supported on RISC-V.
*/
zassert_equal(symbol_ptr, section_ptr,
"symbol at %p != .data section at %p (%zd bytes in the ELF)",
symbol_ptr, section_ptr, section_ofs);

View file

@ -34,73 +34,79 @@ tests:
# Run the suite with all combinations of core Kconfig options for the llext
# subsystem (storage type, ELF type, MPU/MMU etc)
llext.simple.readonly:
arch_allow: arm # Xtensa needs writable storage
arch_allow: arm riscv # Xtensa needs writable storage
filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE
extra_configs:
- arch:arm:CONFIG_ARM_MPU=n
- arch:arm:CONFIG_ARM_AARCH32_MMU=n
- arch:riscv:CONFIG_RISCV_PMP=n
- CONFIG_LLEXT_STORAGE_WRITABLE=n
llext.simple.readonly_mpu:
min_ram: 128
arch_allow: arm # Xtensa needs writable storage
arch_allow: arm # Xtensa needs writable storage, currently not supported on RISC-V
filter: CONFIG_ARCH_HAS_USERSPACE
extra_configs:
- CONFIG_USERSPACE=y
- CONFIG_LLEXT_STORAGE_WRITABLE=n
llext.simple.readonly_fs_loader:
arch_allow: arm # Xtensa needs writable storage
arch_allow: arm riscv # Xtensa needs writable storage
filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE
extra_configs:
- arch:arm:CONFIG_ARM_MPU=n
- arch:arm:CONFIG_ARM_AARCH32_MMU=n
- arch:riscv:CONFIG_RISCV_PMP=n
- CONFIG_LLEXT_STORAGE_WRITABLE=n
llext.simple.readonly_mmu:
arch_allow: arm64 arm
arch_allow: arm64 arm riscv
filter: CONFIG_ARM_MMU
integration_platforms:
- qemu_cortex_a53 # ARM Cortex-A53 (ARMv8-A ISA)
extra_configs:
- CONFIG_LLEXT_STORAGE_WRITABLE=n
llext.simple.writable:
arch_allow: arm xtensa
arch_allow: arm xtensa riscv
integration_platforms:
- qemu_xtensa/dc233c # Xtensa ISA
filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE
extra_configs:
- arch:arm:CONFIG_ARM_MPU=n
- arch:arm:CONFIG_ARM_AARCH32_MMU=n
- arch:riscv:CONFIG_RISCV_PMP=n
- CONFIG_LLEXT_STORAGE_WRITABLE=y
llext.simple.writable_relocatable:
arch_allow: arm xtensa
arch_allow: arm xtensa riscv
integration_platforms:
- qemu_xtensa/dc233c # Xtensa ISA
filter: not CONFIG_MPU and not CONFIG_MMU
extra_configs:
- arch:arm:CONFIG_ARM_MPU=n
- arch:arm:CONFIG_ARM_AARCH32_MMU=n
- arch:riscv:CONFIG_RISCV_PMP=n
- CONFIG_LLEXT_STORAGE_WRITABLE=y
- CONFIG_LLEXT_TYPE_ELF_RELOCATABLE=y
# Test the Symbol Link Identifier (SLID) linking feature on writable
# storage to cover both ARM and Xtensa architectures on the same test.
llext.simple.writable_slid_linking:
arch_allow: arm xtensa
arch_allow: arm xtensa riscv
integration_platforms:
- qemu_xtensa/dc233c # Xtensa ISA
filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE
extra_configs:
- arch:arm:CONFIG_ARM_MPU=n
- arch:arm:CONFIG_ARM_AARCH32_MMU=n
- arch:riscv:CONFIG_RISCV_PMP=n
- CONFIG_LLEXT_STORAGE_WRITABLE=y
- CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y
llext.simple.writable_relocatable_slid_linking:
arch_allow: arm xtensa
arch_allow: arm xtensa riscv
integration_platforms:
- qemu_xtensa/dc233c # Xtensa ISA
filter: not CONFIG_MPU and not CONFIG_MMU
extra_configs:
- arch:arm:CONFIG_ARM_MPU=n
- arch:arm:CONFIG_ARM_AARCH32_MMU=n
- arch:riscv:CONFIG_RISCV_PMP=n
- CONFIG_LLEXT_STORAGE_WRITABLE=y
- CONFIG_LLEXT_TYPE_ELF_RELOCATABLE=y
- CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y