From 4921ce21188131393a73aa90a3768211596fa206 Mon Sep 17 00:00:00 2001 From: Eric Ackermann Date: Fri, 20 Dec 2024 17:22:50 +0100 Subject: [PATCH] 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 --- tests/subsys/llext/CMakeLists.txt | 10 ++ .../llext/src/riscv_edge_case_cb_type.c | 34 +++++ .../src/riscv_edge_case_cb_type_trigger.S | 120 ++++++++++++++++++ tests/subsys/llext/src/test_llext.c | 10 +- 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 tests/subsys/llext/src/riscv_edge_case_cb_type.c create mode 100644 tests/subsys/llext/src/riscv_edge_case_cb_type_trigger.S diff --git a/tests/subsys/llext/CMakeLists.txt b/tests/subsys/llext/CMakeLists.txt index de19cb0ac539..03c406bbef53 100644 --- a/tests/subsys/llext/CMakeLists.txt +++ b/tests/subsys/llext/CMakeLists.txt @@ -79,3 +79,13 @@ if (CONFIG_LLEXT_TYPE_ELF_RELOCATABLE AND NOT CONFIG_ARM AND NOT CONFIG_RISCV) $ -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() diff --git a/tests/subsys/llext/src/riscv_edge_case_cb_type.c b/tests/subsys/llext/src/riscv_edge_case_cb_type.c new file mode 100644 index 000000000000..d9b341a8e4ac --- /dev/null +++ b/tests/subsys/llext/src/riscv_edge_case_cb_type.c @@ -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 +#include +#include + +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); diff --git a/tests/subsys/llext/src/riscv_edge_case_cb_type_trigger.S b/tests/subsys/llext/src/riscv_edge_case_cb_type_trigger.S new file mode 100644 index 000000000000..2d931c44185b --- /dev/null +++ b/tests/subsys/llext/src/riscv_edge_case_cb_type_trigger.S @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024 CISPA Helmholtz Center for Information Security + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +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 diff --git a/tests/subsys/llext/src/test_llext.c b/tests/subsys/llext/src/test_llext.c index 56f676c4e8f9..9ac695311f03 100644 --- a/tests/subsys/llext/src/test_llext.c +++ b/tests/subsys/llext/src/test_llext.c @@ -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 = {