From 7d8fe2ab1164e51448557d1a8b7cf1f6ae632450 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 11 Nov 2024 05:09:45 -0800 Subject: [PATCH] riscv: Expose privilege level as pseudo-register PRIV (#1989) Unlike some other architectures, RISC-V does not expose the current privilege mode in any architecturally-defined register. That is intentional to make it easier to implement virtualization in software, but a Unicorn caller operates outside of the emulated hart and so it can and should be able to observe and change the current privilege mode in order to properly emulate certain behaviors of a real CPU. The current privilege level is therefore now exposed as a new pseudo-register using the name "priv", which matches the name of the virtual register used by RISC-V's debug extension to allow the debugger to read and change the privilege mode while the hart is halted. Unicorn's use of it is conceptually similar to a debugger. The bit encoding of this register is the same as specified in RISC-V Debug Specification v1.0-rc3 Section 4.10.1. It's defined as a "virtual" register exposing a subset of fields from the dcsr register, although here it's implemented directly inside the Unicorn code because QEMU doesn't currently have explicit support for the CSRs from the debug specification. If it supports "dcsr" in a future release then this implementation could change to wrap reading and writing that CSR and then projecting the "prv" and "v" bitfields into the correct locations for the virtual register. --- bindings/dotnet/UnicornEngine/Const/Riscv.fs | 3 +- bindings/go/unicorn/riscv_const.go | 3 +- .../src/main/java/unicorn/RiscvConst.java | 3 +- bindings/pascal/unicorn/RiscvConst.pas | 3 +- bindings/python/unicorn/riscv_const.py | 3 +- .../lib/unicorn_engine/riscv_const.rb | 3 +- bindings/rust/src/riscv.rs | 3 +- bindings/zig/unicorn/riscv_const.zig | 3 +- include/unicorn/riscv.h | 2 + qemu/target/riscv/unicorn.c | 82 +++++++++++++++++++ tests/unit/test_riscv.c | 71 ++++++++++++++++ 11 files changed, 171 insertions(+), 8 deletions(-) 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}};