tests: llext: Add RISC-V CB-Type edge case test

All immediates in RISC-V are encoded as two's complement. This commit
adds a test for relocating jumps that utilize the full range of the
immediate, in both positive and negative direction.
To this end, the test uses the compressed b-type (CB) instruction to
branch to its maximum negative (-256) and maximum positive (+254)
targets.
In case of test failure, expect relocating the corresponding llext to
fail.

Signed-off-by: Eric Ackermann <eric.ackermann@cispa.de>
This commit is contained in:
Eric Ackermann 2024-12-20 17:22:50 +01:00 committed by Benjamin Cabé
commit 4921ce2118
4 changed files with 173 additions and 1 deletions

View file

@ -79,3 +79,13 @@ if (CONFIG_LLEXT_TYPE_ELF_RELOCATABLE AND NOT CONFIG_ARM AND NOT CONFIG_RISCV)
$<TARGET_OBJECTS:${pre_located_target}> -o ${pre_located_file}
)
endif()
if(NOT CONFIG_LLEXT_TYPE_ELF_OBJECT AND CONFIG_RISCV AND CONFIG_RISCV_ISA_EXT_C)
add_llext_target(riscv_edge_case_cb_type_ext
OUTPUT ${ZEPHYR_BINARY_DIR}/riscv_edge_case_cb_type_ext.llext
SOURCES ${PROJECT_SOURCE_DIR}/src/riscv_edge_case_cb_type.c ${PROJECT_SOURCE_DIR}/src/riscv_edge_case_cb_type_trigger.S
)
generate_inc_file_for_target(app ${ZEPHYR_BINARY_DIR}/riscv_edge_case_cb_type_ext.llext
${ZEPHYR_BINARY_DIR}/include/generated/riscv_edge_case_cb_type.inc
)
endif()

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2024 CISPA Helmholtz Center for Information Security
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* This extension tests a relocation edge case in RISC-V:
* Immediates in branch/jump-type instructions are signed-extended.
* Thus, a jump with a negative offset can have a greater jump target than
* a jump with a positive offset.
* A compressed branch (cb-type) instruction is used to trigger the edge case.
* It has a 9-bit immediate (with an implicit LSB of 0), allowing it to jump
* 256 bytes backward and 254 bytes forward.
*/
#include <stdbool.h>
#include <zephyr/llext/symbol.h>
#include <zephyr/ztest_assert.h>
extern int _riscv_edge_case_cb_trigger_forward(void);
extern int _riscv_edge_case_cb_trigger_backward(void);
void test_entry(void)
{
int test_ok;
test_ok = _riscv_edge_case_cb_trigger_forward();
zassert_equal(test_ok, 0x1);
test_ok = _riscv_edge_case_cb_trigger_backward();
zassert_equal(test_ok, 0x1);
}
EXPORT_SYMBOL(test_entry);

View file

@ -0,0 +1,120 @@
/*
* Copyright (c) 2024 CISPA Helmholtz Center for Information Security
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/toolchain.h>
GTEXT(_riscv_edge_case_cb_trigger_backward)
/*
* Tests that jumping 256 bytes (the maximum) backwards
* using CB-type instruction is feasible
*/
SECTION_FUNC(TEXT, _riscv_edge_case_cb_trigger_backward)
/*
* tentative fail
* this needs precise alignment - need explicit compressed instructions
*/
addi a0, zero, 0
c.j _do_jump
_backward_jump_target:
/*
* we need to force RISC-V compressed instructions for alignment
* this directive is standard in RISC-V, i.e., not toolchain-specific
*/
.option push
.option rvc
/* we made it to the correct target - success, return true */
c.addi a0, 0x1
/* explicit compressed return */
c.jr ra
/*
* we need a distance of 256 bytes between _do_jump and _backward_jump_target to trigger
* the edge case (max jump distance for c.beqz)
* _backward_jump_target itself needs 4 bytes (two compressed instructions)
* so we need to insert 252 additional padding bytes
* we pad with return instructions here, causing the test to return 0 (failure)
*/
.rept 126
/* explicit compressed return - 2 bytes */
c.jr ra
.endr
_do_jump:
/* jump precisely 256 bytes, the maximum distance, backwards */
c.beqz a0, _backward_jump_target
/*
* in case we erroneously jump FORWARD instead of backwards,
* the jump ends in the following return sled and we return 0
* this indicates test failure
* note that maximum distance for jump forwards is 254 bytes,
* which is also the size of this sled
*/
.rept 127
/* explicit compressed return - 2 bytes */
c.jr ra
.endr
/* assembler can decide whether to emit compressed instructions */
.option pop
GTEXT(_riscv_edge_case_cb_trigger_forward)
/*
* Tests that jumping 256 bytes (the maximum) forwards
* using CB-type instruction is feasible
*/
SECTION_FUNC(TEXT, _riscv_edge_case_cb_trigger_forward)
j _test_start
/* we need to force RISC-V compressed instructions for alignment */
.option push
.option rvc
/*
* in case the relocation is incorrect and the c.beqz jumps BACKWARDS,
* e.g., after arithmetic overflow, we jump into the following return sled
* the return sled is 256 bytes long, covering the maximum backward jump
*/
.rept 128
/* explicit compressed return - 2 bytes */
c.jr ra
.endr
_test_start:
/* tentative fail */
addi a0, zero, 0
/*
* jump precisely 254 bytes, the maximum distance, forwards
* in case the relocation is applied incorrectly, we jump into the padding bytes
* this causes test failure
* we cannot jump too far forwards, 254 bytes is the maximum distance
*/
c.beqz a0, _forward_jump_target
/*
* need to insert 252 padding bytes to pad to 254 byte jump
* we pad with return instructions here, causing the test to return 0 (failure)
*/
.rept 126
/* explicit compressed return - 2 bytes */
c.jr ra
.endr
/* assembler can decide whether to emit compressed instructions */
.option pop
_forward_jump_target:
/* we made it to the correct target - success, return true */
li a0, 1
/* should not be reached - causes return false */
ret

View file

@ -321,7 +321,15 @@ static LLEXT_CONST uint8_t multi_file_ext[] ELF_ALIGN = {
#include "multi_file.inc"
};
LLEXT_LOAD_UNLOAD(multi_file)
#endif
#if defined(CONFIG_RISCV) && defined(CONFIG_RISCV_ISA_EXT_C)
static LLEXT_CONST uint8_t riscv_edge_case_cb_type_ext[] ELF_ALIGN = {
#include "riscv_edge_case_cb_type.inc"
};
LLEXT_LOAD_UNLOAD(riscv_edge_case_cb_type)
#endif /* CONFIG_RISCV && CONFIG_RISCV_ISA_EXT_C */
#endif /* !CONFIG_LLEXT_TYPE_ELF_OBJECT */
#ifndef CONFIG_USERSPACE
static LLEXT_CONST uint8_t export_dependent_ext[] ELF_ALIGN = {