Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

riscv: Expose privilege level as pseudo-register "priv" #1989

Merged
merged 1 commit into from
Nov 11, 2024
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
3 changes: 2 additions & 1 deletion bindings/dotnet/UnicornEngine/Const/Riscv.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion bindings/go/unicorn/riscv_const.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion bindings/java/src/main/java/unicorn/RiscvConst.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion bindings/pascal/unicorn/RiscvConst.pas
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion bindings/python/unicorn/riscv_const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion bindings/ruby/unicorn_gem/lib/unicorn_engine/riscv_const.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion bindings/rust/src/riscv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ pub enum RegisterRISCV {
F30 = 188,
F31 = 189,
PC = 190,
ENDING = 191,
PRIV = 191,
ENDING = 192,
}

impl RegisterRISCV {
Expand Down
3 changes: 2 additions & 1 deletion bindings/zig/unicorn/riscv_const.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions include/unicorn/riscv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 82 additions & 0 deletions qemu/target/riscv/unicorn.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Contributor Author

@apparentlymart apparentlymart Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PRV_U, PRV_S, and PRV_M constants do happen to match this encoding and so I originally had this written to just accept values directly, but I decided to write this out more explicitly as a switch because it seemed unwise to expose the assumption that QEMU's priv field will always match the debug spec's encoding in future versions, and also unwise to expose the possibility of setting the reserved value 2 that might take on a different meaning in a future QEMU version.

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)
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
}

Expand Down
71 changes: 71 additions & 0 deletions tests/unit/test_riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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, &reg_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, &reg_value));
TEST_ASSERT(reg_value == 0);
}

TEST_LIST = {
{"test_riscv32_nop", test_riscv32_nop},
{"test_riscv64_nop", test_riscv64_nop},
Expand All @@ -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}};
Loading