Add functions to decode RVC load and store instructions

For GDB to fully support hardware watchpoints, OpenOCD needs to tell GDB
which data address has been hit. OpenOCD relies on a target-specific
hit_watchpoint function to do this. If GDB is not given the address, it
will not print the hit variable name or its old and new value.

There does not seem to be a way for the hardware to tell us which
trigger
was hit (0.13 introduced the 'hit bit' but this is optional).
Alternatively,
we can decode the instruction at dpc and find out which memory address
it accesses.

This commit adds support for RVC (compressed) load and store
instructions.

Related to:
https://github.com/riscv-collab/riscv-openocd/issues/688
https://github.com/riscv-collab/riscv-openocd/pull/291
This commit is contained in:
Aleksey Lotosh 2024-03-10 20:32:37 +03:00
parent 687f00c060
commit 69cf9babfb
1 changed files with 468 additions and 31 deletions

View File

@ -1616,6 +1616,440 @@ static int riscv_hit_trigger_hit_bit(struct target *target, uint32_t *unique_id)
return ERROR_OK;
}
/**
* These functions are needed to extract individual bits (for offset)
* from the instruction
*/
// c.lwsp rd_n0 c_uimm8sphi c_uimm8splo - offset[5] offset[4:2|7:6]
static uint16_t get_offset_clwsp(riscv_insn_t instruction)
{
uint16_t offset_4to2and7to6_bits =
get_field32(instruction, INSN_FIELD_C_UIMM8SPLO);
uint16_t offset_4to2_bits = offset_4to2and7to6_bits >> 2;
uint16_t offset_7to6_bits = offset_4to2and7to6_bits & 0x3;
uint16_t offset_5_bit = get_field32(instruction, INSN_FIELD_C_UIMM8SPHI);
return (offset_4to2_bits << 2) + (offset_5_bit << 5)
+ (offset_7to6_bits << 6);
}
// c.ldsp rd_n0 c_uimm9sphi c_uimm9splo - offset[5] offset[4:3|8:6]
static uint16_t get_offset_cldsp(riscv_insn_t instruction)
{
uint16_t offset_4to3and8to6_bits =
get_field32(instruction, INSN_FIELD_C_UIMM9SPLO);
uint16_t offset_4to3_bits = offset_4to3and8to6_bits >> 3;
uint16_t offset_8to6_bits = offset_4to3and8to6_bits & 0x7;
uint16_t offset_5_bit = get_field32(instruction, INSN_FIELD_C_UIMM9SPHI);
return (offset_4to3_bits << 3) + (offset_5_bit << 5)
+ (offset_8to6_bits << 6);
}
// c.swsp c_rs2 c_uimm8sp_s - offset[5:2|7:6]
static uint16_t get_offset_cswsp(riscv_insn_t instruction)
{
uint16_t offset_5to2and7to6_bits =
get_field32(instruction, INSN_FIELD_C_UIMM8SP_S);
uint16_t offset_5to2_bits = offset_5to2and7to6_bits >> 2;
uint16_t offset_7to6_bits = offset_5to2and7to6_bits & 0x3;
return (offset_5to2_bits << 2) + (offset_7to6_bits << 6);
}
// c.sdsp c_rs2 c_uimm9sp_s - offset[5:3|8:6]
static uint16_t get_offset_csdsp(riscv_insn_t instruction)
{
uint16_t offset_5to3and8to6_bits =
get_field32(instruction, INSN_FIELD_C_UIMM9SP_S);
uint16_t offset_5to3_bits = offset_5to3and8to6_bits >> 3;
uint16_t offset_8to6_bits = offset_5to3and8to6_bits & 0x7;
return (offset_5to3_bits << 3) + (offset_8to6_bits << 6);
}
// c.lw rd_p rs1_p c_uimm7lo c_uimm7hi - offset[2|6] offset[5:3]
static uint16_t get_offset_clw(riscv_insn_t instruction)
{
uint16_t offset_2and6_bits = get_field32(instruction, INSN_FIELD_C_UIMM7LO);
uint16_t offset_2_bit = offset_2and6_bits >> 1;
uint16_t offset_6_bit = offset_2and6_bits & 0x1;
uint16_t offset_5to3_bits = get_field32(instruction, INSN_FIELD_C_UIMM7HI);
return (offset_2_bit << 2) + (offset_5to3_bits << 3) + (offset_6_bit << 6);
}
// c.ld rd_p rs1_p c_uimm8lo c_uimm8hi - offset[7:6] offset[5:3]
static uint16_t get_offset_cld(riscv_insn_t instruction)
{
uint16_t offset_7to6_bits = get_field32(instruction, INSN_FIELD_C_UIMM8LO);
uint16_t offset_5to3_bits = get_field32(instruction, INSN_FIELD_C_UIMM8HI);
return (offset_5to3_bits << 3) + (offset_7to6_bits << 6);
}
// c.lq rd_p rs1_p c_uimm9lo c_uimm9hi - offset[7:6] offset[5|4|8]
static uint16_t get_offset_clq(riscv_insn_t instruction)
{
uint16_t offset_7to6_bits = get_field32(instruction, INSN_FIELD_C_UIMM9LO);
uint16_t offset_5to4and8_bits =
get_field32(instruction, INSN_FIELD_C_UIMM9HI);
uint16_t offset_5to4_bits = offset_5to4and8_bits >> 1;
uint16_t offset_8_bit = offset_5to4and8_bits & 0x1;
return (offset_5to4_bits << 4) + (offset_7to6_bits << 6)
+ (offset_8_bit << 8);
}
// c.lqsp rd_n0 c_uimm10sphi c_uimm10splo - offset[5] offset[4|9:6]
static uint16_t get_offset_clqsp(riscv_insn_t instruction)
{
uint16_t offset_4and9to6_bits =
get_field32(instruction, INSN_FIELD_C_UIMM10SPLO);
uint16_t offset_4_bit = offset_4and9to6_bits >> 4;
uint16_t offset_9to6_bits = offset_4and9to6_bits & 0xf;
uint16_t offset_5_bit = get_field32(instruction, INSN_FIELD_C_UIMM10SPHI);
return (offset_4_bit << 4) + (offset_5_bit << 5) + (offset_9to6_bits << 6);
}
// c.sqsp c_rs2 c_uimm10sp_s - offset[5:4|9:6]
static uint16_t get_offset_csqsp(riscv_insn_t instruction)
{
uint16_t offset_5to4and9to6_bits =
get_field32(instruction, INSN_FIELD_C_UIMM10SP_S);
uint16_t offset_5to4_biits = offset_5to4and9to6_bits >> 4;
uint16_t offset_9to6_bits = offset_5to4and9to6_bits & 0xf;
return (offset_5to4_biits << 4) + (offset_9to6_bits << 6);
}
/**
* Decode rs1' register num for RVC.
* See "Table: Registers specified by the three-bit rs1, rs2, and rd fields
* of the CIW, CL, CS, CA, and CB formats" in "The RISC-V Instruction Set Manual
* Volume I: Unprivileged ISA".
* */
static uint32_t get_rs1_c(riscv_insn_t instruction)
{
return GDB_REGNO_S0 + get_field32(instruction, INSN_FIELD_C_SREG1);
}
static uint32_t get_opcode(const riscv_insn_t instruction)
{
// opcode is first 7 bits of the instruction
uint32_t opcode = instruction & INSN_FIELD_OPCODE;
if ((instruction & 0x03) < 0x03) { // opcode size RVC
// RVC MASK_C = 0xe003 for load/store instructions
opcode = instruction & MASK_C_LD;
}
return opcode;
}
static int get_loadstore_membase_regno(struct target *target,
const riscv_insn_t instruction, int *regid)
{
uint32_t opcode = get_opcode(instruction);
int rs;
switch (opcode) {
case MATCH_LB:
case MATCH_FLH & ~INSN_FIELD_FUNCT3:
case MATCH_SB:
case MATCH_FSH & ~INSN_FIELD_FUNCT3:
rs = get_field32(instruction, INSN_FIELD_RS1);
break;
case MATCH_C_LWSP:
case MATCH_C_LDSP: // if xlen >= 64 or MATCH_C_FLWSP:
case MATCH_C_FLDSP: // or MATCH_C_LQSP if xlen == 128
case MATCH_C_SWSP:
case MATCH_C_SDSP: // if xlen >= 64 or MATCH_C_FSWSP:
case MATCH_C_FSDSP: // or MATCH_C_SQSP if xlen == 128
rs = GDB_REGNO_SP;
break;
case MATCH_C_LW:
case MATCH_C_FLW: // or MATCH_C_LD if xlen >= 64
case MATCH_C_FLD: // or MATCH_C_LQ if xlen == 128
case MATCH_C_SW:
case MATCH_C_FSW: // or MATCH_C_SD if xlen >= 64
case MATCH_C_FSD: // or MATCH_C_SQ if xlen == 128
rs = get_rs1_c(instruction);
break;
default:
LOG_TARGET_DEBUG(target, "0x%" PRIx32 " is not a RV32I or \"C\" load or"
" store", instruction);
return ERROR_FAIL;
}
*regid = rs;
return ERROR_OK;
}
static int get_loadstore_memoffset(struct target *target,
const riscv_insn_t instruction, int16_t *memoffset)
{
uint32_t opcode = get_opcode(instruction);
int16_t offset;
switch (opcode) {
case MATCH_LB:
case MATCH_FLH & ~INSN_FIELD_FUNCT3:
case MATCH_SB:
case MATCH_FSH & ~INSN_FIELD_FUNCT3:
if (opcode == MATCH_SB || opcode == (MATCH_FSH & ~INSN_FIELD_FUNCT3)) {
offset = get_field32(instruction, INSN_FIELD_IMM12LO) |
(get_field32(instruction, INSN_FIELD_IMM12HI) << 5);
} else if (opcode == MATCH_LB ||
opcode == (MATCH_FLH & ~INSN_FIELD_FUNCT3)) {
offset = get_field32(instruction, INSN_FIELD_IMM12);
} else {
assert(false);
}
/* sign extend 12-bit imm to 16-bits */
if (offset & (1 << 11))
offset |= 0xf000;
break;
case MATCH_C_LWSP:
offset = get_offset_clwsp(instruction);
break;
case MATCH_C_LDSP: // if xlen >= 64 or MATCH_C_FLWSP:
if (riscv_xlen(target) > 32) { // MATCH_C_LDSP
offset = get_offset_cldsp(instruction);
} else { // MATCH_C_FLWSP
offset = get_offset_clwsp(instruction);
}
break;
case MATCH_C_FLDSP: // or MATCH_C_LQSP if xlen == 128
if (riscv_xlen(target) == 128) { // MATCH_C_LQSP
offset = get_offset_clqsp(instruction);
} else { // MATCH_C_FLDSP
offset = get_offset_cldsp(instruction);
}
break;
case MATCH_C_SWSP:
offset = get_offset_cswsp(instruction);
break;
case MATCH_C_SDSP: // if xlen >= 64 or MATCH_C_FSWSP:
if (riscv_xlen(target) > 32) { // MATCH_C_SDSP
offset = get_offset_csdsp(instruction);
} else { // MATCH_C_FSWSP
offset = get_offset_cswsp(instruction);
}
break;
case MATCH_C_FSDSP: // or MATCH_C_SQSP if xlen == 128
if (riscv_xlen(target) == 128) { // MATCH_C_SQSP
offset = get_offset_csqsp(instruction);
} else { // MATCH_C_FSDSP
offset = get_offset_csdsp(instruction); // same as C.SDSP
}
break;
case MATCH_C_LW:
offset = get_offset_clw(instruction);
break;
case MATCH_C_FLW: // or MATCH_C_LD if xlen >= 64
if (riscv_xlen(target) > 32) { // MATCH_C_LD
offset = get_offset_cld(instruction);
} else { // MATCH_C_FLW
offset = get_offset_clw(instruction); // same as C.FLW
}
break;
case MATCH_C_FLD: // or MATCH_C_LQ if xlen == 128
if (riscv_xlen(target) == 128) { // MATCH_C_LQ
offset = get_offset_clq(instruction);
} else { // MATCH_C_FLD
offset = get_offset_cld(instruction); // same as C.LD
}
break;
case MATCH_C_SW:
offset = get_offset_clw(instruction); // same as C.LW
break;
case MATCH_C_FSW: // or MATCH_C_SD if xlen >= 64
if (riscv_xlen(target) > 32) { // MATCH_C_SD
offset = get_offset_cld(instruction); // same as C.LD
} else { // MATCH_C_FSW
offset = get_offset_clw(instruction); // same as C.LW
}
break;
case MATCH_C_FSD: // or MATCH_C_SQ if xlen == 128
if (riscv_xlen(target) == 128) { // MATCH_C_SQ
offset = get_offset_clq(instruction); // same as C.LQ
} else { // MATCH_C_FSD
offset = get_offset_cld(instruction); // same as C.LD
}
break;
default:
LOG_TARGET_DEBUG(target, "0x%" PRIx32 " is not a RV32I or \"C\" load or"
" store", instruction);
return ERROR_FAIL;
}
*memoffset = offset;
return ERROR_OK;
}
static int verify_loadstore(struct target *target,
const riscv_insn_t instruction, bool *is_read)
{
uint32_t opcode = get_opcode(instruction);
bool misa_f = riscv_supports_extension(target, 'F');
bool misa_d = riscv_supports_extension(target, 'D');
enum watchpoint_rw rw;
switch (opcode) {
case MATCH_LB:
case MATCH_FLH & ~INSN_FIELD_FUNCT3:
rw = WPT_READ;
break;
case MATCH_SB:
case MATCH_FSH & ~INSN_FIELD_FUNCT3:
rw = WPT_WRITE;
break;
case MATCH_C_LWSP:
if (get_field32(instruction, INSN_FIELD_RD) == 0) {
LOG_TARGET_DEBUG(target,
"The code points with rd = x0 are reserved for C.LWSP");
return ERROR_FAIL;
}
rw = WPT_READ;
break;
case MATCH_C_LDSP: // if xlen >= 64 or MATCH_C_FLWSP:
if (riscv_xlen(target) > 32) { // MATCH_C_LDSP
if (get_field32(instruction, INSN_FIELD_RD) == 0) {
LOG_TARGET_DEBUG(target,
"The code points with rd = x0 are reserved for C.LDSP");
return ERROR_FAIL;
}
} else { // MATCH_C_FLWSP
if (!misa_f) {
LOG_TARGET_DEBUG(target, "Matched C.FLWSP but target doesn\'t "
"have the \"F\" extension");
return ERROR_FAIL;
}
}
rw = WPT_READ;
break;
case MATCH_C_FLDSP: // or MATCH_C_LQSP if xlen == 128
if (riscv_xlen(target) == 128) { // MATCH_C_LQSP
if (get_field32(instruction, INSN_FIELD_RD) == 0) {
LOG_TARGET_DEBUG(target,
"The code points with rd = x0 are reserved for C.LQSP");
return ERROR_FAIL;
}
} else { // MATCH_C_FLDSP
if (!misa_d) {
LOG_TARGET_DEBUG(target, "Matched C.FLDSP but target doesn\'t "
"have the \"D\" extension");
return ERROR_FAIL;
}
}
rw = WPT_READ;
break;
case MATCH_C_SWSP:
rw = WPT_WRITE;
break;
case MATCH_C_SDSP: // if xlen >= 64 or MATCH_C_FSWSP:
if (riscv_xlen(target) == 32) { // MATCH_C_FSWSP
if (!misa_f) {
LOG_TARGET_DEBUG(target, "Matched C.FSWSP but target doesn\'t "
"have the \"F\" extension");
return ERROR_FAIL;
}
}
rw = WPT_WRITE;
break;
case MATCH_C_FSDSP: // or MATCH_C_SQSP if xlen == 128
if (riscv_xlen(target) != 128) { // MATCH_C_SQSP
if (!misa_d) {
LOG_TARGET_DEBUG(target, "Matched C.FSDSP but target doesn\'t "
"have the \"D\" extension");
return ERROR_FAIL;
}
}
rw = WPT_WRITE;
break;
case MATCH_C_LW:
rw = WPT_READ;
break;
case MATCH_C_FLW: // or MATCH_C_LD if xlen >= 64
if (riscv_xlen(target) == 32) { // MATCH_C_FLW
if (!misa_f) {
LOG_TARGET_DEBUG(target, "Matched C.FLW but target doesn\'t "
"have the \"F\" extension");
return ERROR_FAIL;
}
}
rw = WPT_READ;
break;
case MATCH_C_FLD: // or MATCH_C_LQ if xlen == 128
if (riscv_xlen(target) != 128) { // MATCH_C_FLD
if (!misa_d) {
LOG_TARGET_DEBUG(target, "Matched C.FLD but target doesn\'t "
"have the \"D\" extension");
return ERROR_FAIL;
}
}
rw = WPT_READ;
break;
case MATCH_C_SW:
rw = WPT_WRITE;
break;
case MATCH_C_FSW: // or MATCH_C_SD if xlen >= 64
if (riscv_xlen(target) == 32) { // MATCH_C_FSW
if (!misa_f) {
LOG_TARGET_DEBUG(target, "Matched C.FSW but target doesn\'t "
"have the \"F\" extension");
return ERROR_FAIL;
}
}
rw = WPT_WRITE;
break;
case MATCH_C_FSD: // or MATCH_C_SQ if xlen == 128
if (riscv_xlen(target) != 128) { // MATCH_C_FSD
if (!misa_d) {
LOG_TARGET_DEBUG(target, "Matched C.FSD but target doesn\'t "
"have the \"D\" extension");
return ERROR_FAIL;
}
}
rw = WPT_WRITE;
break;
default:
LOG_TARGET_DEBUG(target, "0x%" PRIx32 " is not a RV32I or \"C\" load or"
" store", instruction);
return ERROR_FAIL;
}
if (rw == WPT_WRITE) {
*is_read = false;
LOG_TARGET_DEBUG(target, "0x%" PRIx32 " is store instruction",
instruction);
} else {
*is_read = true;
LOG_TARGET_DEBUG(target, "0x%" PRIx32 " is load instruction",
instruction);
}
return ERROR_OK;
}
/* Sets *hit_watchpoint to the first watchpoint identified as causing the
* current halt.
*
@ -1638,18 +2072,20 @@ static int riscv_hit_watchpoint(struct target *target, struct watchpoint **hit_w
}
riscv_reg_t dpc;
riscv_get_register(target, &dpc, GDB_REGNO_DPC);
if (riscv_get_register(target, &dpc, GDB_REGNO_DPC) != ERROR_OK)
return ERROR_FAIL;
const uint8_t length = 4;
LOG_TARGET_DEBUG(target, "dpc is 0x%" PRIx64, dpc);
/* fetch the instruction at dpc */
uint8_t buffer[length];
if (target_read_buffer(target, dpc, length, buffer) != ERROR_OK) {
LOG_TARGET_ERROR(target, "Failed to read instruction at dpc 0x%" PRIx64, dpc);
LOG_TARGET_ERROR(target, "Failed to read instruction at dpc 0x%" PRIx64,
dpc);
return ERROR_FAIL;
}
uint32_t instruction = 0;
riscv_insn_t instruction = 0;
for (int i = 0; i < length; i++) {
LOG_TARGET_DEBUG(target, "Next byte is %x", buffer[i]);
@ -1657,40 +2093,39 @@ static int riscv_hit_watchpoint(struct target *target, struct watchpoint **hit_w
}
LOG_TARGET_DEBUG(target, "Full instruction is %x", instruction);
/* find out which memory address is accessed by the instruction at dpc */
/* opcode is first 7 bits of the instruction */
uint8_t opcode = instruction & 0x7F;
uint32_t rs1;
int16_t imm;
riscv_reg_t mem_addr;
int rs;
target_addr_t mem_addr;
int16_t memoffset;
if (opcode == MATCH_LB || opcode == MATCH_SB) {
rs1 = (instruction & 0xf8000) >> 15;
riscv_get_register(target, &mem_addr, rs1);
if (opcode == MATCH_SB) {
LOG_TARGET_DEBUG(target, "%x is store instruction", instruction);
imm = ((instruction & 0xf80) >> 7) | ((instruction & 0xfe000000) >> 20);
} else {
LOG_TARGET_DEBUG(target, "%x is load instruction", instruction);
imm = (instruction & 0xfff00000) >> 20;
}
/* sign extend 12-bit imm to 16-bits */
if (imm & (1 << 11))
imm |= 0xf000;
mem_addr += imm;
LOG_TARGET_DEBUG(target, "Memory address=0x%" PRIx64, mem_addr);
} else {
LOG_TARGET_DEBUG(target, "%x is not a RV32I load or store", instruction);
if (get_loadstore_membase_regno(target, instruction, &rs) != ERROR_OK)
return ERROR_FAIL;
if (riscv_get_register(target, &mem_addr, rs) != ERROR_OK)
return ERROR_FAIL;
if (get_loadstore_memoffset(target, instruction, &memoffset) != ERROR_OK)
return ERROR_FAIL;
mem_addr += memoffset;
bool is_load;
if (verify_loadstore(target, instruction, &is_load) != ERROR_OK)
return ERROR_FAIL;
}
struct watchpoint *wp = target->watchpoints;
while (wp) {
/*TODO support length/mask */
if (wp->address == mem_addr) {
/* TODO support mask and check read/write/access */
/* TODO check for intersection of the access range and watchpoint range
Recommended matching:
if (intersects(mem_addr, mem_addr + ref_size, wp->address, wp->address + wp->length))
*/
if (mem_addr >= wp->address &&
mem_addr < (wp->address + wp->length)) {
*hit_watchpoint = wp;
LOG_TARGET_DEBUG(target, "Hit address=%" TARGET_PRIxADDR, wp->address);
LOG_TARGET_DEBUG(target, "WP hit found: %s 0x%" TARGET_PRIxADDR
" covered by %s wp at address 0x%" TARGET_PRIxADDR,
is_load ? "Load from" : "Store to", mem_addr,
(wp->rw == WPT_READ ?
"read" : (wp->rw == WPT_WRITE ? "write" : "access")),
wp->address);
return ERROR_OK;
}
wp = wp->next;
@ -1701,6 +2136,8 @@ static int riscv_hit_watchpoint(struct target *target, struct watchpoint **hit_w
*
* OpenOCD will behave as if this function had never been implemented i.e.
* report the halt to GDB with no address information. */
LOG_TARGET_DEBUG(target, "No watchpoint found that would cover %s 0x%"
TARGET_PRIxADDR, is_load ? "load from" : "store to", mem_addr);
return ERROR_FAIL;
}