Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions arch/RISCV/RISCVMapping.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions arch/RISCV/RISCVMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions arch/RISCV/RISCVModule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 2 additions & 0 deletions bindings/python/tests/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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")
Expand Down
102 changes: 102 additions & 0 deletions bindings/python/tests/test_riscv_reg_access.py
Original file line number Diff line number Diff line change
@@ -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()
6 changes: 5 additions & 1 deletion docs/cs_v6_release_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
146 changes: 146 additions & 0 deletions tests/unit/riscv_reg_access.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#include "unit_test.h"
#include <capstone/capstone.h>
#include <stdio.h>
#include <string.h>

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, &regs_read_count,
regs_write, &regs_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;
}
Loading