diff --git a/arch/RISCV/RISCVMapping.c b/arch/RISCV/RISCVMapping.c index 286440d66d..a52217d63b 100644 --- a/arch/RISCV/RISCVMapping.c +++ b/arch/RISCV/RISCVMapping.c @@ -434,6 +434,39 @@ riscv_insn RISCV_map_insn(const char *name) return RISCV_INS_INVALID; } +void RISCV_reg_access(const cs_insn *insn, cs_regs regs_read, + uint8_t *regs_read_count, cs_regs regs_write, + uint8_t *regs_write_count) +{ + const cs_riscv *riscv = &(insn->detail->riscv); + uint8_t read_count = 0; + uint8_t write_count = 0; + + for (int j = 0; j < riscv->op_count; j++) { + const cs_riscv_op *op = &riscv->operands[j]; + + if (op->type == RISCV_OP_REG) { + if ((op->access & CS_AC_WRITE) && + !arr_exist(regs_write, write_count, op->reg)) { + regs_write[write_count++] = (uint16_t)op->reg; + } + if ((op->access & CS_AC_READ) && + !arr_exist(regs_read, read_count, op->reg)) { + regs_read[read_count++] = (uint16_t)op->reg; + } + } else if (op->type == RISCV_OP_MEM) { + if (op->mem.base != RISCV_REG_INVALID && + !arr_exist(regs_read, read_count, op->mem.base)) { + regs_read[read_count++] = + (uint16_t)op->mem.base; + } + } + } + + *regs_read_count = read_count; + *regs_write_count = write_count; +} + void RISCV_init(MCRegisterInfo *MRI) { MCRegisterInfo_InitMCRegisterInfo(MRI, RISCVRegDesc, RISCV_REG_ENDING, diff --git a/arch/RISCV/RISCVMapping.h b/arch/RISCV/RISCVMapping.h index 26deaaac7c..cad7bbe47b 100644 --- a/arch/RISCV/RISCVMapping.h +++ b/arch/RISCV/RISCVMapping.h @@ -38,4 +38,8 @@ riscv_insn RISCV_map_insn(const char *name); void RISCV_init(MCRegisterInfo *MRI); +void RISCV_reg_access(const cs_insn *insn, cs_regs regs_read, + uint8_t *regs_read_count, cs_regs regs_write, + uint8_t *regs_write_count); + #endif diff --git a/arch/RISCV/RISCVModule.c b/arch/RISCV/RISCVModule.c index cfc3afa9e2..426d755a9b 100644 --- a/arch/RISCV/RISCVModule.c +++ b/arch/RISCV/RISCVModule.c @@ -31,6 +31,7 @@ cs_err RISCV_global_init(cs_struct *ud) ud->group_name = RISCV_group_name; ud->insn_map = RISCV_insns; ud->insn_map_size = RISCV_insn_count; + ud->reg_access = RISCV_reg_access; return CS_ERR_OK; } diff --git a/bindings/python/tests/test_all.py b/bindings/python/tests/test_all.py index 9361d315f8..63f7e1685d 100755 --- a/bindings/python/tests/test_all.py +++ b/bindings/python/tests/test_all.py @@ -6,6 +6,7 @@ import test_customized_mnem import test_compatibility_layer import test_riscv_sysreg +import test_riscv_reg_access errors = [] errors.extend(test_lite.test_class()) @@ -14,6 +15,7 @@ errors.extend(test_customized_mnem.test()) errors.extend(test_compatibility_layer.test_compatibility()) errors.extend(test_riscv_sysreg.test()) +errors.extend(test_riscv_reg_access.test()) if errors: print("Some errors happened. Please check the output") diff --git a/bindings/python/tests/test_riscv_reg_access.py b/bindings/python/tests/test_riscv_reg_access.py new file mode 100644 index 0000000000..15c95ba3d6 --- /dev/null +++ b/bindings/python/tests/test_riscv_reg_access.py @@ -0,0 +1,102 @@ +import unittest +from capstone import * +from capstone.riscv import * +import unittest + +class TestRiscvRegAccess(unittest.TestCase): + def setUp(self): + self.cs = Cs(CS_ARCH_RISCV, CS_MODE_RISCV64) + self.cs.option(CS_OPT_DETAIL, CS_OPT_DETAIL_REAL | CS_OPT_ON) + + def test_addi(self): + # addi a0, a1, 10 + code = b"\x13\x85\xa5\x00" + insns = list(self.cs.disasm(code, 0)) + self.assertEqual(len(insns), 1) + insn = insns[0] + + read, write = insn.regs_access() + # a1 = RISCV_REG_X11, a0 = RISCV_REG_X10 + self.assertIn(RISCV_REG_X11, read) + self.assertIn(RISCV_REG_X10, write) + self.assertEqual(len(read), 1) + self.assertEqual(len(write), 1) + + def test_jalr(self): + # jalr ra, a1, 0 -> 0x000580e7 (rd=x1=ra, rs1=x11=a1, imm=0) + code = b"\xe7\x80\x05\x00" + insns = list(self.cs.disasm(code, 0)) + self.assertEqual(len(insns), 1) + insn = insns[0] + + read, write = insn.regs_access() + # ra = RISCV_REG_X1 + self.assertIn(RISCV_REG_X11, read) + self.assertIn(RISCV_REG_X1, write) + self.assertEqual(len(read), 1) + self.assertEqual(len(write), 1) + + def test_lb(self): + # lb a0, 0(sp) + code = b"\x03\x05\x01\x00" + insns = list(self.cs.disasm(code, 0)) + self.assertEqual(len(insns), 1) + insn = insns[0] + + read, write = insn.regs_access() + # sp = RISCV_REG_X2 + self.assertIn(RISCV_REG_X2, read) + self.assertIn(RISCV_REG_X10, write) + self.assertEqual(len(read), 1) + self.assertEqual(len(write), 1) + + def test_caddi(self): + # c.addi a0, 10 (0x0529) + code = b"\x29\x05" + insns = list(self.cs.disasm(code, 0)) + self.assertEqual(len(insns), 1) + insn = insns[0] + + read, write = insn.regs_access() + # x10 is both read and written + self.assertIn(RISCV_REG_X10, read) + self.assertIn(RISCV_REG_X10, write) + self.assertEqual(len(read), 1) + self.assertEqual(len(write), 1) + + def test_ecall(self): + # ecall + code = b"\x73\x00\x00\x00" + insns = list(self.cs.disasm(code, 0)) + self.assertEqual(len(insns), 1) + insn = insns[0] + + read, write = insn.regs_access() + self.assertEqual(len(read), 0) + self.assertEqual(len(write), 0) + + def test_csrrw(self): + # csrrw a0, sstatus, a1 + code = b"\x73\x95\x05\x10" + insns = list(self.cs.disasm(code, 0)) + self.assertEqual(len(insns), 1) + insn = insns[0] + + read, write = insn.regs_access() + # CSRs should NOT be in the reg_access list + self.assertIn(RISCV_REG_X11, read) + self.assertIn(RISCV_REG_X10, write) + self.assertEqual(len(read), 1) + self.assertEqual(len(write), 1) + +def test(): + loader = unittest.TestLoader() + suite = loader.loadTestsFromTestCase(TestRiscvRegAccess) + runner = unittest.TextTestRunner(verbosity=2) + return runner.run(suite).failures + +def main(): + unittest.main() + +if __name__ == '__main__': + main() diff --git a/docs/cs_v6_release_guide.md b/docs/cs_v6_release_guide.md index 361ad48efd..9f67ab783b 100644 --- a/docs/cs_v6_release_guide.md +++ b/docs/cs_v6_release_guide.md @@ -249,8 +249,12 @@ Nonetheless, we hope this additional information is useful to you. Note that `+noalias` "overpowers" `noaliascompressed` in the second case: despite `+noaliascompressed` being false, meaning aliases are wanted for compressed instructions, `+noalias` being true means ALL aliases are supressed, and this takes precedence. Other than that, case 1 and case 3 work as intuitively expected, and case 4 is redundant. So a single-sentence description of this table is: if `+noalias` is given then no aliases will be printed for any instruction, but if not given then aliases will be printed for non-compressed instruction and alias printing for compressed instruction futher checks `+noaliascompressed` before proceeding. +- Added `reg_access` capstone callback to return all read and written registers for the instructions, including registers used as part of memory operands. + * Note that `reg_access` does NOT treat CSRs as registers, detailed reasons for why can be found in [the PR implementing the feature](https://github.com/capstone-engine/capstone/pull/2895) + * Note that `reg_access` does NOT treat reading the PC's value as reading a register, detailed reasons for why can be found in [the PR implementing the feature](https://github.com/capstone-engine/capstone/pull/2895) + > [!NOTE] -> All extensions above are disabled by default unless enabled by their option name or the corresponding command line flag in cstool. Any other extension is always enabled and can't be disabled. +> All `CS_MODE_RISCV_*` extensions above are disabled by default unless enabled by their option name or the corresponding command line flag in cstool. Any other extension is always enabled and can't be disabled. > [!NOTE] > RISC-V has a massive, sprawling list of extensions, but Capstone's internal implementaton choice of using a 32-bit mode field is not enough to cover all of them. For now, those extension flags above were added because their encoding space is conflicting with either each other or other extensions. More flags can be added later if bug reports come in requesting finer-grained extension control. However, the current implementation using bitfields imposes a strict upper limit and would likely be refactored for a more expansive mechanism in the future. See [this issue](https://github.com/capstone-engine/capstone/issues/2848) for more details. diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 114677833c..c0ffe3f40d 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.15) enable_testing() set(UNIT_TEST_SOURCES sstream.c utils.c) if(CAPSTONE_RISCV_SUPPORT) - list(APPEND UNIT_TEST_SOURCES riscv_op_count_iter.c riscv_sysreg.c) + list(APPEND UNIT_TEST_SOURCES riscv_op_count_iter.c riscv_sysreg.c riscv_reg_access.c) endif() include_directories(include) diff --git a/tests/unit/riscv_reg_access.c b/tests/unit/riscv_reg_access.c new file mode 100644 index 0000000000..0182532bb6 --- /dev/null +++ b/tests/unit/riscv_reg_access.c @@ -0,0 +1,146 @@ +#include "unit_test.h" +#include +#include +#include + +static bool test_reg_access(csh handle, const uint8_t *code, size_t code_size, + const uint16_t *expected_read, + size_t expected_read_count, + const uint16_t *expected_write, + size_t expected_write_count) +{ + cs_insn *insn; + size_t count = cs_disasm(handle, code, code_size, 0, 1, &insn); + if (count == 0) { + printf("Failed to disassemble instruction\n"); + return false; + } + // debugging print, useful but noisy + //printf("\n\n======================= TEST GOT INSTRUCTION TEXT: %s %s \n\n======================= (num operands: %d)\n", + // insn->mnemonic, insn->op_str, insn->detail->riscv.op_count); + cs_regs regs_read, regs_write; + uint8_t regs_read_count, regs_write_count; + + cs_err err = cs_regs_access(handle, insn, regs_read, ®s_read_count, + regs_write, ®s_write_count); + if (err != CS_ERR_OK) { + printf("cs_regs_access failed with error: %d\n", err); + cs_free(insn, count); + return false; + } + + bool success = true; + if (regs_read_count != expected_read_count) { + printf("Read count mismatch: expected %zu, got %u\n", + expected_read_count, regs_read_count); + success = false; + } else { + for (size_t i = 0; i < expected_read_count; i++) { + bool found = false; + for (size_t j = 0; j < regs_read_count; j++) { + if (regs_read[j] == expected_read[i]) { + found = true; + break; + } + } + if (!found) { + printf("Expected read register %d not found\n", + expected_read[i]); + success = false; + } + } + } + + if (regs_write_count != expected_write_count) { + printf("Write count mismatch: expected %zu, got %u\n", + expected_write_count, regs_write_count); + success = false; + } else { + for (size_t i = 0; i < expected_write_count; i++) { + bool found = false; + for (size_t j = 0; j < regs_write_count; j++) { + if (regs_write[j] == expected_write[i]) { + found = true; + break; + } + } + if (!found) { + printf("Expected write register %d not found\n", + expected_write[i]); + success = false; + } + } + } + + cs_free(insn, count); + return success; +} + +int main(void) +{ + csh handle; + if (cs_open(CS_ARCH_RISCV, CS_MODE_RISCV64, &handle) != CS_ERR_OK) { + return 1; + } + cs_option(handle, CS_OPT_DETAIL, CS_OPT_DETAIL_REAL | CS_OPT_ON); + + bool success[10]; + memset(success, true, sizeof(success)); + + // addi a0, a1, 10 -> 0x00a58513 + printf("Test 0: Testing addi a0, a1, 10\n"); + uint8_t addi_code[] = { 0x13, 0x85, 0xa5, 0x00 }; + uint16_t addi_read[] = { RISCV_REG_X11 }; // a1 + uint16_t addi_write[] = { RISCV_REG_X10 }; // a0 + success[0] = test_reg_access(handle, addi_code, sizeof(addi_code), + addi_read, 1, addi_write, 1); + // jalr ra, a1, 0 -> 0x000580e7 (rd=x1=ra, rs1=x11=a1, imm=0) + printf("Test 1: Testing jalr ra, a1, 0\n"); + uint8_t jalr_code[] = { 0xe7, 0x80, 0x05, 0x00 }; + uint16_t jalr_read[] = { RISCV_REG_X11 }; + uint16_t jalr_write[] = { RISCV_REG_X1 }; // ra + success[1] = test_reg_access(handle, jalr_code, sizeof(jalr_code), + jalr_read, 1, jalr_write, 1); + // lb a0, 0(sp) -> 0x00010503 + printf("Test 2: Testing lb a0, 0(sp)\n"); + uint8_t lb_code[] = { 0x03, 0x05, 0x01, 0x00 }; + uint16_t lb_read[] = { RISCV_REG_X2 }; // sp + uint16_t lb_write[] = { RISCV_REG_X10 }; + success[2] = test_reg_access(handle, lb_code, sizeof(lb_code), lb_read, + 1, lb_write, 1); + + // c.addi a0, 10 -> 0x0529 + printf("Test 3: Testing c.addi a0, 10\n"); + uint8_t caddi_code[] = { 0x29, 0x05 }; + uint16_t caddi_read[] = { RISCV_REG_X10 }; // x10 is both read and write + uint16_t caddi_write[] = { RISCV_REG_X10 }; + success[3] = test_reg_access(handle, caddi_code, sizeof(caddi_code), + caddi_read, 1, caddi_write, 1); + + // ecall -> 0x00000073 + printf("Test 4: Testing ecall\n"); + uint8_t ecall_code[] = { 0x73, 0x00, 0x00, 0x00 }; + success[4] = test_reg_access(handle, ecall_code, sizeof(ecall_code), + NULL, 0, NULL, 0); + + // csrrw a0, sstatus, a1 -> 0x10059533 (Wait, CSRRW is 0x10059573?) + // 0x10059573: csrrw x10, sstatus, x11 + printf("Test 5: Testing csrrw a0, sstatus, a1\n"); + uint8_t csrrw_code[] = { 0x73, 0x95, 0x05, 0x10 }; + uint16_t csrrw_read[] = { + RISCV_REG_X11 + }; // sstatus (CSR) should NOT be here + uint16_t csrrw_write[] = { RISCV_REG_X10 }; + success[5] = test_reg_access(handle, csrrw_code, sizeof(csrrw_code), + csrrw_read, 1, csrrw_write, 1); + + cs_close(&handle); + bool all_success = true; + for (int i = 0; i < sizeof(success) / sizeof(success[0]); i++) { + if (!success[i]) { + printf("Test %d failed\n", i); + all_success = false; + } + } + return all_success ? 0 : 1; +}