Skip to content

mtvec readback retains upper BASE bits that are silently truncated on trap entry #261

Description

@jf-cc727

Bug Description

NutShell accepts and reads back mtvec = 0x200000008000005c, but a subsequent synchronous trap redirects execution to 0x000000008000005c.

The test writes a high-tagged direct-mode trap vector, verifies that csrr mtvec returns the same value, and executes ecall. The reference model uses the held XLEN-wide BASE as the trap target; because that address is not fetchable in the test platform, it then reports an instruction access fault at the high address. NutShell instead discards the upper bits and executes the low-address handler (li a0, 42).

This report does not assume that an implementation must support every possible mtvec value. mtvec is WARL, so NutShell may legalize unsupported address bits. The bug is the externally visible inconsistency between the value retained/read back by the CSR and the address actually used for trap redirection.

RISC-V Specification Requirement

The key requirement is internal consistency. If NutShell reads back mtvec = 0x200000008000005c, then that is the trap-vector BASE value software is allowed to observe. A later Direct-mode trap must use that same held BASE, unless the implementation first legalizes the unsupported high bits and exposes the legalized value on readback.

The Machine Privileged Specification defines mtvec as an MXLEN-bit WARL read/write register. An implementation may restrict the set of values it can hold. However:

  • the held BASE value is the trap-vector base;
  • in Direct mode, all traps set pc to BASE;
  • the low two address bits are supplied as zero to form the XLEN-bit address.

Reference: https://docs.riscv.org/reference/isa/v20260120/priv/machine.html#_machine_trap_vector_base_address_mtvec_register

A compliant implementation can therefore either legalize unsupported high bits on write (and expose the legalized value on readback), or preserve the held XLEN-wide BASE when generating the trap target. It must not report one held value while silently using a different low alias.

Steps to Reproduce

  1. Run the supplied poc/program.elf under difftest.
  2. The program forms low_trap_entry | 0x2000000000000000.
  3. It writes the result to mtvec, reads it back, and executes ecall if readback is unchanged.

Essential source:

la   t0, low_trap_entry
li   t1, 0x2000000000000000
or   t0, t0, t1
csrw mtvec, t0
csrr t2, mtvec
bne  t2, t0, pass
ecall

Expected Result

Either of the following is internally consistent:

  1. NutShell legalizes/masks unsupported high BASE bits when writing mtvec; csrr mtvec returns the legalized value, so the program detects that the original value was not retained; or
  2. NutShell retains the full value and uses 0x200000008000005c as the trap target, allowing the fetch path to execute there or raise the appropriate instruction access fault.

The supplied Spike trace follows the second behavior and reports an instruction access fault (mcause=1) at the high target.

Actual Result

NutShell reads back the full high value but jumps to the low alias:

csrw mtvec, t0           data 200000008000005c
csrr t2, mtvec           data 200000008000005c
ecall
commit pc 000000008000005c ... li a0, 42
...
REF pc/mtvec/mepc/mtval = 200000008000005c
DUT pc                  = 000000008000005c
Image

NS-2.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions