mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Fix several bugs in the RISC-V kgdb implementation:
- The element of dbg_reg_def[] that is supposed to pertain to the S1
register embeds instead the struct pt_regs offset of the A1
register. Fix this to use the S1 register offset in struct pt_regs.
- The sleeping_thread_to_gdb_regs() function copies the value of the
S10 register into the gdb_regs[] array element meant for the S9
register, and copies the value of the S11 register into the array
element meant for the S10 register. It also neglects to copy the
value of the S11 register. Fix all of these issues.
Fixes: fe89bd2be8 ("riscv: Add KGDB support")
Cc: Vincent Chen <vincent.chen@sifive.com>
Link: https://patch.msgid.link/fde376f8-bcfd-bfe4-e467-07d8f7608d05@kernel.org
Signed-off-by: Paul Walmsley <pjw@kernel.org>
379 lines
11 KiB
C
379 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2020 SiFive
|
|
*/
|
|
|
|
#include <linux/ptrace.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/bug.h>
|
|
#include <linux/kgdb.h>
|
|
#include <linux/irqflags.h>
|
|
#include <linux/string.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/gdb_xml.h>
|
|
#include <asm/insn.h>
|
|
|
|
enum {
|
|
NOT_KGDB_BREAK = 0,
|
|
KGDB_SW_BREAK,
|
|
KGDB_COMPILED_BREAK,
|
|
KGDB_SW_SINGLE_STEP
|
|
};
|
|
|
|
static unsigned long stepped_address;
|
|
static unsigned int stepped_opcode;
|
|
|
|
static int decode_register_index(unsigned long opcode, int offset)
|
|
{
|
|
return (opcode >> offset) & 0x1F;
|
|
}
|
|
|
|
static int decode_register_index_short(unsigned long opcode, int offset)
|
|
{
|
|
return ((opcode >> offset) & 0x7) + 8;
|
|
}
|
|
|
|
/* Calculate the new address for after a step */
|
|
static int get_step_address(struct pt_regs *regs, unsigned long *next_addr)
|
|
{
|
|
unsigned long pc = regs->epc;
|
|
unsigned long *regs_ptr = (unsigned long *)regs;
|
|
unsigned int rs1_num, rs2_num;
|
|
int op_code;
|
|
|
|
if (get_kernel_nofault(op_code, (void *)pc))
|
|
return -EINVAL;
|
|
if ((op_code & __INSN_LENGTH_MASK) != __INSN_LENGTH_GE_32) {
|
|
if (riscv_insn_is_c_jalr(op_code) ||
|
|
riscv_insn_is_c_jr(op_code)) {
|
|
rs1_num = decode_register_index(op_code, RVC_C2_RS1_OPOFF);
|
|
*next_addr = regs_ptr[rs1_num];
|
|
} else if (riscv_insn_is_c_j(op_code) ||
|
|
riscv_insn_is_c_jal(op_code)) {
|
|
*next_addr = RVC_EXTRACT_JTYPE_IMM(op_code) + pc;
|
|
} else if (riscv_insn_is_c_beqz(op_code)) {
|
|
rs1_num = decode_register_index_short(op_code,
|
|
RVC_C1_RS1_OPOFF);
|
|
if (!rs1_num || regs_ptr[rs1_num] == 0)
|
|
*next_addr = RVC_EXTRACT_BTYPE_IMM(op_code) + pc;
|
|
else
|
|
*next_addr = pc + 2;
|
|
} else if (riscv_insn_is_c_bnez(op_code)) {
|
|
rs1_num =
|
|
decode_register_index_short(op_code, RVC_C1_RS1_OPOFF);
|
|
if (rs1_num && regs_ptr[rs1_num] != 0)
|
|
*next_addr = RVC_EXTRACT_BTYPE_IMM(op_code) + pc;
|
|
else
|
|
*next_addr = pc + 2;
|
|
} else {
|
|
*next_addr = pc + 2;
|
|
}
|
|
} else {
|
|
if ((op_code & __INSN_OPCODE_MASK) == __INSN_BRANCH_OPCODE) {
|
|
bool result = false;
|
|
long imm = RV_EXTRACT_BTYPE_IMM(op_code);
|
|
unsigned long rs1_val = 0, rs2_val = 0;
|
|
|
|
rs1_num = decode_register_index(op_code, RVG_RS1_OPOFF);
|
|
rs2_num = decode_register_index(op_code, RVG_RS2_OPOFF);
|
|
if (rs1_num)
|
|
rs1_val = regs_ptr[rs1_num];
|
|
if (rs2_num)
|
|
rs2_val = regs_ptr[rs2_num];
|
|
|
|
if (riscv_insn_is_beq(op_code))
|
|
result = (rs1_val == rs2_val) ? true : false;
|
|
else if (riscv_insn_is_bne(op_code))
|
|
result = (rs1_val != rs2_val) ? true : false;
|
|
else if (riscv_insn_is_blt(op_code))
|
|
result =
|
|
((long)rs1_val <
|
|
(long)rs2_val) ? true : false;
|
|
else if (riscv_insn_is_bge(op_code))
|
|
result =
|
|
((long)rs1_val >=
|
|
(long)rs2_val) ? true : false;
|
|
else if (riscv_insn_is_bltu(op_code))
|
|
result = (rs1_val < rs2_val) ? true : false;
|
|
else if (riscv_insn_is_bgeu(op_code))
|
|
result = (rs1_val >= rs2_val) ? true : false;
|
|
if (result)
|
|
*next_addr = imm + pc;
|
|
else
|
|
*next_addr = pc + 4;
|
|
} else if (riscv_insn_is_jal(op_code)) {
|
|
*next_addr = RV_EXTRACT_JTYPE_IMM(op_code) + pc;
|
|
} else if (riscv_insn_is_jalr(op_code)) {
|
|
rs1_num = decode_register_index(op_code, RVG_RS1_OPOFF);
|
|
if (rs1_num)
|
|
*next_addr = ((unsigned long *)regs)[rs1_num];
|
|
*next_addr += RV_EXTRACT_ITYPE_IMM(op_code);
|
|
} else if (riscv_insn_is_sret(op_code)) {
|
|
*next_addr = pc;
|
|
} else {
|
|
*next_addr = pc + 4;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int do_single_step(struct pt_regs *regs)
|
|
{
|
|
/* Determine where the target instruction will send us to */
|
|
unsigned long addr = 0;
|
|
int error = get_step_address(regs, &addr);
|
|
|
|
if (error)
|
|
return error;
|
|
|
|
/* Store the op code in the stepped address */
|
|
error = get_kernel_nofault(stepped_opcode, (void *)addr);
|
|
if (error)
|
|
return error;
|
|
|
|
stepped_address = addr;
|
|
|
|
/* Replace the op code with the break instruction */
|
|
error = copy_to_kernel_nofault((void *)stepped_address,
|
|
arch_kgdb_ops.gdb_bpt_instr,
|
|
BREAK_INSTR_SIZE);
|
|
/* Flush and return */
|
|
if (!error) {
|
|
flush_icache_range(addr, addr + BREAK_INSTR_SIZE);
|
|
kgdb_single_step = 1;
|
|
atomic_set(&kgdb_cpu_doing_single_step,
|
|
raw_smp_processor_id());
|
|
} else {
|
|
stepped_address = 0;
|
|
stepped_opcode = 0;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/* Undo a single step */
|
|
static void undo_single_step(struct pt_regs *regs)
|
|
{
|
|
if (stepped_opcode != 0) {
|
|
copy_to_kernel_nofault((void *)stepped_address,
|
|
(void *)&stepped_opcode, BREAK_INSTR_SIZE);
|
|
flush_icache_range(stepped_address,
|
|
stepped_address + BREAK_INSTR_SIZE);
|
|
}
|
|
stepped_address = 0;
|
|
stepped_opcode = 0;
|
|
kgdb_single_step = 0;
|
|
atomic_set(&kgdb_cpu_doing_single_step, -1);
|
|
}
|
|
|
|
struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = {
|
|
{DBG_REG_ZERO, GDB_SIZEOF_REG, -1},
|
|
{DBG_REG_RA, GDB_SIZEOF_REG, offsetof(struct pt_regs, ra)},
|
|
{DBG_REG_SP, GDB_SIZEOF_REG, offsetof(struct pt_regs, sp)},
|
|
{DBG_REG_GP, GDB_SIZEOF_REG, offsetof(struct pt_regs, gp)},
|
|
{DBG_REG_TP, GDB_SIZEOF_REG, offsetof(struct pt_regs, tp)},
|
|
{DBG_REG_T0, GDB_SIZEOF_REG, offsetof(struct pt_regs, t0)},
|
|
{DBG_REG_T1, GDB_SIZEOF_REG, offsetof(struct pt_regs, t1)},
|
|
{DBG_REG_T2, GDB_SIZEOF_REG, offsetof(struct pt_regs, t2)},
|
|
{DBG_REG_FP, GDB_SIZEOF_REG, offsetof(struct pt_regs, s0)},
|
|
{DBG_REG_S1, GDB_SIZEOF_REG, offsetof(struct pt_regs, s1)},
|
|
{DBG_REG_A0, GDB_SIZEOF_REG, offsetof(struct pt_regs, a0)},
|
|
{DBG_REG_A1, GDB_SIZEOF_REG, offsetof(struct pt_regs, a1)},
|
|
{DBG_REG_A2, GDB_SIZEOF_REG, offsetof(struct pt_regs, a2)},
|
|
{DBG_REG_A3, GDB_SIZEOF_REG, offsetof(struct pt_regs, a3)},
|
|
{DBG_REG_A4, GDB_SIZEOF_REG, offsetof(struct pt_regs, a4)},
|
|
{DBG_REG_A5, GDB_SIZEOF_REG, offsetof(struct pt_regs, a5)},
|
|
{DBG_REG_A6, GDB_SIZEOF_REG, offsetof(struct pt_regs, a6)},
|
|
{DBG_REG_A7, GDB_SIZEOF_REG, offsetof(struct pt_regs, a7)},
|
|
{DBG_REG_S2, GDB_SIZEOF_REG, offsetof(struct pt_regs, s2)},
|
|
{DBG_REG_S3, GDB_SIZEOF_REG, offsetof(struct pt_regs, s3)},
|
|
{DBG_REG_S4, GDB_SIZEOF_REG, offsetof(struct pt_regs, s4)},
|
|
{DBG_REG_S5, GDB_SIZEOF_REG, offsetof(struct pt_regs, s5)},
|
|
{DBG_REG_S6, GDB_SIZEOF_REG, offsetof(struct pt_regs, s6)},
|
|
{DBG_REG_S7, GDB_SIZEOF_REG, offsetof(struct pt_regs, s7)},
|
|
{DBG_REG_S8, GDB_SIZEOF_REG, offsetof(struct pt_regs, s8)},
|
|
{DBG_REG_S9, GDB_SIZEOF_REG, offsetof(struct pt_regs, s9)},
|
|
{DBG_REG_S10, GDB_SIZEOF_REG, offsetof(struct pt_regs, s10)},
|
|
{DBG_REG_S11, GDB_SIZEOF_REG, offsetof(struct pt_regs, s11)},
|
|
{DBG_REG_T3, GDB_SIZEOF_REG, offsetof(struct pt_regs, t3)},
|
|
{DBG_REG_T4, GDB_SIZEOF_REG, offsetof(struct pt_regs, t4)},
|
|
{DBG_REG_T5, GDB_SIZEOF_REG, offsetof(struct pt_regs, t5)},
|
|
{DBG_REG_T6, GDB_SIZEOF_REG, offsetof(struct pt_regs, t6)},
|
|
{DBG_REG_EPC, GDB_SIZEOF_REG, offsetof(struct pt_regs, epc)},
|
|
{DBG_REG_STATUS, GDB_SIZEOF_REG, offsetof(struct pt_regs, status)},
|
|
{DBG_REG_BADADDR, GDB_SIZEOF_REG, offsetof(struct pt_regs, badaddr)},
|
|
{DBG_REG_CAUSE, GDB_SIZEOF_REG, offsetof(struct pt_regs, cause)},
|
|
};
|
|
|
|
char *dbg_get_reg(int regno, void *mem, struct pt_regs *regs)
|
|
{
|
|
if (regno >= DBG_MAX_REG_NUM || regno < 0)
|
|
return NULL;
|
|
|
|
if (dbg_reg_def[regno].offset != -1)
|
|
memcpy(mem, (void *)regs + dbg_reg_def[regno].offset,
|
|
dbg_reg_def[regno].size);
|
|
else
|
|
memset(mem, 0, dbg_reg_def[regno].size);
|
|
return dbg_reg_def[regno].name;
|
|
}
|
|
|
|
int dbg_set_reg(int regno, void *mem, struct pt_regs *regs)
|
|
{
|
|
if (regno >= DBG_MAX_REG_NUM || regno < 0)
|
|
return -EINVAL;
|
|
|
|
if (dbg_reg_def[regno].offset != -1)
|
|
memcpy((void *)regs + dbg_reg_def[regno].offset, mem,
|
|
dbg_reg_def[regno].size);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *task)
|
|
{
|
|
/* Initialize to zero */
|
|
memset((char *)gdb_regs, 0, NUMREGBYTES);
|
|
|
|
gdb_regs[DBG_REG_SP_OFF] = task->thread.sp;
|
|
gdb_regs[DBG_REG_FP_OFF] = task->thread.s[0];
|
|
gdb_regs[DBG_REG_S1_OFF] = task->thread.s[1];
|
|
gdb_regs[DBG_REG_S2_OFF] = task->thread.s[2];
|
|
gdb_regs[DBG_REG_S3_OFF] = task->thread.s[3];
|
|
gdb_regs[DBG_REG_S4_OFF] = task->thread.s[4];
|
|
gdb_regs[DBG_REG_S5_OFF] = task->thread.s[5];
|
|
gdb_regs[DBG_REG_S6_OFF] = task->thread.s[6];
|
|
gdb_regs[DBG_REG_S7_OFF] = task->thread.s[7];
|
|
gdb_regs[DBG_REG_S8_OFF] = task->thread.s[8];
|
|
gdb_regs[DBG_REG_S9_OFF] = task->thread.s[9];
|
|
gdb_regs[DBG_REG_S10_OFF] = task->thread.s[10];
|
|
gdb_regs[DBG_REG_S11_OFF] = task->thread.s[11];
|
|
gdb_regs[DBG_REG_EPC_OFF] = task->thread.ra;
|
|
}
|
|
|
|
void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long pc)
|
|
{
|
|
regs->epc = pc;
|
|
}
|
|
|
|
noinline void arch_kgdb_breakpoint(void)
|
|
{
|
|
asm(".global kgdb_compiled_break\n"
|
|
"kgdb_compiled_break: ebreak\n");
|
|
}
|
|
|
|
void kgdb_arch_handle_qxfer_pkt(char *remcom_in_buffer,
|
|
char *remcom_out_buffer)
|
|
{
|
|
if (!strncmp(remcom_in_buffer, gdb_xfer_read_target,
|
|
sizeof(gdb_xfer_read_target)))
|
|
strscpy(remcom_out_buffer, riscv_gdb_stub_target_desc, BUFMAX);
|
|
else if (!strncmp(remcom_in_buffer, gdb_xfer_read_cpuxml,
|
|
sizeof(gdb_xfer_read_cpuxml)))
|
|
strscpy(remcom_out_buffer, riscv_gdb_stub_cpuxml, BUFMAX);
|
|
}
|
|
|
|
static inline void kgdb_arch_update_addr(struct pt_regs *regs,
|
|
char *remcom_in_buffer)
|
|
{
|
|
unsigned long addr;
|
|
char *ptr;
|
|
|
|
ptr = &remcom_in_buffer[1];
|
|
if (kgdb_hex2long(&ptr, &addr))
|
|
regs->epc = addr;
|
|
}
|
|
|
|
int kgdb_arch_handle_exception(int vector, int signo, int err_code,
|
|
char *remcom_in_buffer, char *remcom_out_buffer,
|
|
struct pt_regs *regs)
|
|
{
|
|
int err = 0;
|
|
|
|
undo_single_step(regs);
|
|
|
|
switch (remcom_in_buffer[0]) {
|
|
case 'c':
|
|
case 'D':
|
|
case 'k':
|
|
if (remcom_in_buffer[0] == 'c')
|
|
kgdb_arch_update_addr(regs, remcom_in_buffer);
|
|
break;
|
|
case 's':
|
|
kgdb_arch_update_addr(regs, remcom_in_buffer);
|
|
err = do_single_step(regs);
|
|
break;
|
|
default:
|
|
err = -1;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int kgdb_riscv_kgdbbreak(unsigned long addr)
|
|
{
|
|
if (stepped_address == addr)
|
|
return KGDB_SW_SINGLE_STEP;
|
|
if (atomic_read(&kgdb_setting_breakpoint))
|
|
if (addr == (unsigned long)&kgdb_compiled_break)
|
|
return KGDB_COMPILED_BREAK;
|
|
|
|
return kgdb_has_hit_break(addr);
|
|
}
|
|
|
|
static int kgdb_riscv_notify(struct notifier_block *self, unsigned long cmd,
|
|
void *ptr)
|
|
{
|
|
struct die_args *args = (struct die_args *)ptr;
|
|
struct pt_regs *regs = args->regs;
|
|
unsigned long flags;
|
|
int type;
|
|
|
|
if (user_mode(regs))
|
|
return NOTIFY_DONE;
|
|
|
|
type = kgdb_riscv_kgdbbreak(regs->epc);
|
|
if (type == NOT_KGDB_BREAK && cmd == DIE_TRAP)
|
|
return NOTIFY_DONE;
|
|
|
|
local_irq_save(flags);
|
|
|
|
if (kgdb_handle_exception(type == KGDB_SW_SINGLE_STEP ? 0 : 1,
|
|
args->signr, cmd, regs))
|
|
return NOTIFY_DONE;
|
|
|
|
if (type == KGDB_COMPILED_BREAK)
|
|
regs->epc += 4;
|
|
|
|
local_irq_restore(flags);
|
|
|
|
return NOTIFY_STOP;
|
|
}
|
|
|
|
static struct notifier_block kgdb_notifier = {
|
|
.notifier_call = kgdb_riscv_notify,
|
|
};
|
|
|
|
int kgdb_arch_init(void)
|
|
{
|
|
register_die_notifier(&kgdb_notifier);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void kgdb_arch_exit(void)
|
|
{
|
|
unregister_die_notifier(&kgdb_notifier);
|
|
}
|
|
|
|
/*
|
|
* Global data
|
|
*/
|
|
#ifdef CONFIG_RISCV_ISA_C
|
|
const struct kgdb_arch arch_kgdb_ops = {
|
|
.gdb_bpt_instr = {0x02, 0x90}, /* c.ebreak */
|
|
};
|
|
#else
|
|
const struct kgdb_arch arch_kgdb_ops = {
|
|
.gdb_bpt_instr = {0x73, 0x00, 0x10, 0x00}, /* ebreak */
|
|
};
|
|
#endif
|