diff --git a/bindings/dotnet/UnicornEngine/Const/Riscv.fs b/bindings/dotnet/UnicornEngine/Const/Riscv.fs index fe61ec2c1c..244e5fec45 100644 --- a/bindings/dotnet/UnicornEngine/Const/Riscv.fs +++ b/bindings/dotnet/UnicornEngine/Const/Riscv.fs @@ -222,7 +222,8 @@ module Riscv = let UC_RISCV_REG_F30 = 188 let UC_RISCV_REG_F31 = 189 let UC_RISCV_REG_PC = 190 - let UC_RISCV_REG_ENDING = 191 + let UC_RISCV_REG_PRIV = 191 + let UC_RISCV_REG_ENDING = 192 // Alias registers let UC_RISCV_REG_ZERO = 1 diff --git a/bindings/go/unicorn/riscv_const.go b/bindings/go/unicorn/riscv_const.go index b41ec2322a..08458f77a6 100644 --- a/bindings/go/unicorn/riscv_const.go +++ b/bindings/go/unicorn/riscv_const.go @@ -217,7 +217,8 @@ const ( RISCV_REG_F30 = 188 RISCV_REG_F31 = 189 RISCV_REG_PC = 190 - RISCV_REG_ENDING = 191 + RISCV_REG_PRIV = 191 + RISCV_REG_ENDING = 192 // Alias registers RISCV_REG_ZERO = 1 diff --git a/bindings/java/src/main/java/unicorn/RiscvConst.java b/bindings/java/src/main/java/unicorn/RiscvConst.java index 27b65bd472..5814180974 100644 --- a/bindings/java/src/main/java/unicorn/RiscvConst.java +++ b/bindings/java/src/main/java/unicorn/RiscvConst.java @@ -219,7 +219,8 @@ public interface RiscvConst { public static final int UC_RISCV_REG_F30 = 188; public static final int UC_RISCV_REG_F31 = 189; public static final int UC_RISCV_REG_PC = 190; - public static final int UC_RISCV_REG_ENDING = 191; + public static final int UC_RISCV_REG_PRIV = 191; + public static final int UC_RISCV_REG_ENDING = 192; // Alias registers public static final int UC_RISCV_REG_ZERO = 1; diff --git a/bindings/pascal/unicorn/RiscvConst.pas b/bindings/pascal/unicorn/RiscvConst.pas index 0eb6e7a2b0..075e271c65 100644 --- a/bindings/pascal/unicorn/RiscvConst.pas +++ b/bindings/pascal/unicorn/RiscvConst.pas @@ -220,7 +220,8 @@ interface UC_RISCV_REG_F30 = 188; UC_RISCV_REG_F31 = 189; UC_RISCV_REG_PC = 190; - UC_RISCV_REG_ENDING = 191; + UC_RISCV_REG_PRIV = 191; + UC_RISCV_REG_ENDING = 192; // Alias registers UC_RISCV_REG_ZERO = 1; diff --git a/bindings/python/unicorn/riscv_const.py b/bindings/python/unicorn/riscv_const.py index 1765bfdb73..3e63376fd5 100644 --- a/bindings/python/unicorn/riscv_const.py +++ b/bindings/python/unicorn/riscv_const.py @@ -215,7 +215,8 @@ UC_RISCV_REG_F30 = 188 UC_RISCV_REG_F31 = 189 UC_RISCV_REG_PC = 190 -UC_RISCV_REG_ENDING = 191 +UC_RISCV_REG_PRIV = 191 +UC_RISCV_REG_ENDING = 192 # Alias registers UC_RISCV_REG_ZERO = 1 diff --git a/bindings/ruby/unicorn_gem/lib/unicorn_engine/riscv_const.rb b/bindings/ruby/unicorn_gem/lib/unicorn_engine/riscv_const.rb index 741cfebb1d..33203d0a4d 100644 --- a/bindings/ruby/unicorn_gem/lib/unicorn_engine/riscv_const.rb +++ b/bindings/ruby/unicorn_gem/lib/unicorn_engine/riscv_const.rb @@ -217,7 +217,8 @@ module UnicornEngine UC_RISCV_REG_F30 = 188 UC_RISCV_REG_F31 = 189 UC_RISCV_REG_PC = 190 - UC_RISCV_REG_ENDING = 191 + UC_RISCV_REG_PRIV = 191 + UC_RISCV_REG_ENDING = 192 # Alias registers UC_RISCV_REG_ZERO = 1 diff --git a/bindings/rust/src/riscv.rs b/bindings/rust/src/riscv.rs index 073a4c30d0..53c5990bc3 100644 --- a/bindings/rust/src/riscv.rs +++ b/bindings/rust/src/riscv.rs @@ -201,7 +201,8 @@ pub enum RegisterRISCV { F30 = 188, F31 = 189, PC = 190, - ENDING = 191, + PRIV = 191, + ENDING = 192, } impl RegisterRISCV { diff --git a/bindings/zig/unicorn/riscv_const.zig b/bindings/zig/unicorn/riscv_const.zig index 3e713449c2..00a34001f7 100644 --- a/bindings/zig/unicorn/riscv_const.zig +++ b/bindings/zig/unicorn/riscv_const.zig @@ -217,7 +217,8 @@ pub const riscvConst = enum(c_int) { RISCV_REG_F30 = 188, RISCV_REG_F31 = 189, RISCV_REG_PC = 190, - RISCV_REG_ENDING = 191, + RISCV_REG_PRIV = 191, + RISCV_REG_ENDING = 192, // Alias registers RISCV_REG_ZERO = 1, diff --git a/include/unicorn/riscv.h b/include/unicorn/riscv.h index c4527a4b75..cf1595ae4f 100644 --- a/include/unicorn/riscv.h +++ b/include/unicorn/riscv.h @@ -235,6 +235,8 @@ typedef enum uc_riscv_reg { UC_RISCV_REG_PC, // PC register + UC_RISCV_REG_PRIV, // Virtual register for the current privilege level + UC_RISCV_REG_ENDING, // <-- mark the end of the list or registers //> Alias registers diff --git a/qemu/target/riscv/unicorn.c b/qemu/target/riscv/unicorn.c index 69e782d4f0..80c282580c 100644 --- a/qemu/target/riscv/unicorn.c +++ b/qemu/target/riscv/unicorn.c @@ -79,6 +79,63 @@ static void riscv_release(void *ctx) static void reg_reset(struct uc_struct *uc) {} +static uc_err reg_read_priv(CPURISCVState *env, target_ulong *value) +{ + // This structure is based on RISC-V Debug Specification 1.0.0-rc3, + // Section 4.10.1, Virtual Debug Registers: Privilege Mode. + // This encoding should match the decoding in reg_write_priv. + target_ulong priv_value = 0; + switch (env->priv) { + default: + // No other value should be possible, but we'll report + // 0 (U-Mode) in this case since that's most conservative. + break; + case PRV_U: + priv_value = 0; + break; + case PRV_S: + priv_value = 1; + break; + case PRV_M: + priv_value = 3; + break; + } + if (riscv_cpu_virt_enabled(env)) { + // The "v" bit is set to indicate either VS or VU mode. + priv_value |= 0b100; + } + *value = priv_value; + return UC_ERR_OK; +} + +static uc_err reg_write_priv(CPURISCVState *env, target_ulong value) +{ + // This structure is based on RISC-V Debug Specification 1.0.0-rc3, + // Section 4.10.1, Virtual Debug Registers: Privilege Mode. + // This decoding should match the encoding in reg_read_priv. + if ((value & ~0b111) != 0) { + // Only the low three bits are settable. + return UC_ERR_ARG; + } + target_ulong prv = value & 0b11; + bool v = (value & 0b100) != 0; + switch (prv) { + default: + return UC_ERR_ARG; + case 0: + riscv_cpu_set_mode(env, PRV_U); + break; + case 1: + riscv_cpu_set_mode(env, PRV_S); + break; + case 3: + riscv_cpu_set_mode(env, PRV_M); + break; + } + riscv_cpu_set_virt_enabled(env, v); + return UC_ERR_OK; +} + DEFAULT_VISIBILITY uc_err reg_read(void *_env, int mode, unsigned int regid, void *value, size_t *size) @@ -121,6 +178,20 @@ uc_err reg_read(void *_env, int mode, unsigned int regid, void *value, #else CHECK_REG_TYPE(uint32_t); *(uint32_t *)value = env->pc; +#endif + break; + case UC_RISCV_REG_PRIV:; + target_ulong priv_value; + ret = reg_read_priv(env, &priv_value); + if (ret != UC_ERR_OK) { + return ret; + } +#ifdef TARGET_RISCV64 + CHECK_REG_TYPE(uint64_t); + *(uint64_t *)value = priv_value; +#else + CHECK_REG_TYPE(uint32_t); + *(uint32_t *)value = priv_value; #endif break; } @@ -174,6 +245,17 @@ uc_err reg_write(void *_env, int mode, unsigned int regid, const void *value, #endif *setpc = 1; break; + case UC_RISCV_REG_PRIV: +#ifdef TARGET_RISCV64 + CHECK_REG_TYPE(uint64_t); + uint64_t val; + val = *(uint64_t *)value; +#else + CHECK_REG_TYPE(uint32_t); + uint32_t val; + val = *(uint32_t *)value; +#endif + ret = reg_write_priv(env, (target_ulong)val); } } diff --git a/tests/unit/test_riscv.c b/tests/unit/test_riscv.c index 880f57c927..24b9f35e3a 100644 --- a/tests/unit/test_riscv.c +++ b/tests/unit/test_riscv.c @@ -720,6 +720,76 @@ static void test_riscv_mmu(void) TEST_CHECK(data_value == data_result); } +static void test_riscv_priv(void) +{ + uc_engine *uc; + uc_err err; + uint32_t m_entry_address = 0x1000; + uint32_t main_address = 0x3000; + uint64_t priv_value = ~0; + uint64_t pc = ~0; + uint64_t reg_value; + + /* + li t0, 0 + csrw mstatus, t0 + li t1, 0x3000 + csrw mepc, t1 + mret + */ + char code_m_entry[] = "\x93\x02\x00\x00" + "\x73\x90\x02\x30" + "\x37\x33\x00\x00" + "\x73\x10\x13\x34" + "\x73\x00\x20\x30"; + + /* + csrw sscratch, t0 + nop + */ + char code_main[] = "\x73\x90\x02\x14" + "\x13\x00\x00\x00"; + int main_end_address = main_address + sizeof(code_main) - 1; + + OK(uc_open(UC_ARCH_RISCV, UC_MODE_RISCV64, &uc)); + OK(uc_ctl_tlb_mode(uc, UC_TLB_CPU)); + OK(uc_mem_map(uc, m_entry_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_map(uc, main_address, 0x1000, UC_PROT_ALL)); + OK(uc_mem_write(uc, m_entry_address, &code_m_entry, sizeof(code_m_entry))); + OK(uc_mem_write(uc, main_address, &code_main, sizeof(code_main))); + + // Before anything executes we should be in M-Mode + OK(uc_reg_read(uc, UC_RISCV_REG_PRIV, &priv_value)); + TEST_ASSERT(priv_value == 3); + + // We'll put a sentinel value in sscratch so we can determine whether we've + // successfully written to it below. + reg_value = 0xffff; + OK(uc_reg_write(uc, UC_RISCV_REG_SSCRATCH, ®_value)); + + // Run until we reach the "csrw" at the start of code_main, at which + // point we should be in U-Mode due to the mret instruction. + OK(uc_emu_start(uc, m_entry_address, main_address, 0, 10)); + + OK(uc_reg_read(uc, UC_RISCV_REG_PC, &pc)); + TEST_ASSERT(pc == main_address); + OK(uc_reg_read(uc, UC_RISCV_REG_PRIV, &priv_value)); + TEST_ASSERT(priv_value == 0); // Now in U-Mode + + // U-Mode can't write to sscratch, so execution at this point should + // cause an invalid instruction exception. + err = uc_emu_start(uc, main_address, main_end_address, 0, 0); + OK(uc_reg_read(uc, UC_RISCV_REG_PC, &pc)); + TEST_ASSERT(err == UC_ERR_EXCEPTION); + + // ...but if we force S-Mode then we should be able to set it successfully. + priv_value = 1; + OK(uc_reg_write(uc, UC_RISCV_REG_PRIV, &priv_value)); + OK(uc_emu_start(uc, main_address, main_end_address, 0, 0)); + OK(uc_reg_read(uc, UC_RISCV_REG_SSCRATCH, ®_value)); + TEST_ASSERT(reg_value == 0); +} + TEST_LIST = { {"test_riscv32_nop", test_riscv32_nop}, {"test_riscv64_nop", test_riscv64_nop}, @@ -744,4 +814,5 @@ TEST_LIST = { {"test_riscv_correct_address_in_long_jump_hook", test_riscv_correct_address_in_long_jump_hook}, {"test_riscv_mmu", test_riscv_mmu}, + {"test_riscv_priv", test_riscv_priv}, {NULL, NULL}};