Add NT_RISCV_HW_BREAK and NT_RISCV_HW_WATCH. A tracer can use PTRACE_GETREGSET and PTRACE_SETREGSET to read or program the RISC-V hardware breakpoint and watchpoint slots. The regset layout uses fixed-width address and control fields. This keeps the native and compat layouts the same, because the UAPI fields do not depend on the task word size. Reuse the ptrace-owned perf event backend from the previous patch. Signed-off-by: Himanshu Chauhan --- arch/riscv/include/uapi/asm/ptrace.h | 41 ++++ arch/riscv/kernel/ptrace.c | 340 +++++++++++++++++++++++++++ include/uapi/linux/elf.h | 4 + tools/include/uapi/linux/elf.h | 2 + 4 files changed, 387 insertions(+) diff --git a/arch/riscv/include/uapi/asm/ptrace.h b/arch/riscv/include/uapi/asm/ptrace.h index 373a1c14f207..c6beb7b287fe 100644 --- a/arch/riscv/include/uapi/asm/ptrace.h +++ b/arch/riscv/include/uapi/asm/ptrace.h @@ -10,6 +10,7 @@ #include #include +#include #define PTRACE_GETFDPIC 33 @@ -166,6 +167,46 @@ struct user_cfi_state { __u64 shstk_ptr; }; +/* + * bit[3:0] Match + * bit[8:4] Size + * bit[11:9] When + * bit[12] Select + * bit[13] Chain + * bit[16:14] Type + * bit[XLEN-1:17] Reserved + */ +#define HWDEBUG_MATCH_MASK __GENMASK(3, 0) +#define HWDEBUG_SIZE_MASK __GENMASK(8, 4) +#define HWDEBUG_WHEN_MASK __GENMASK(11, 9) +#define HWDEBUG_SELECT_MASK _BITUL(12) +#define HWDEBUG_CHAIN_MASK _BITUL(13) +#define HWDEBUG_TYPE_MASK __GENMASK(16, 14) + +#define HWDEBUG_MATCH(_ctrl) (((_ctrl) & HWDEBUG_MATCH_MASK) >> 0) +#define HWDEBUG_SIZE(_ctrl) (((_ctrl) & HWDEBUG_SIZE_MASK) >> 4) +#define HWDEBUG_WHEN(_ctrl) (((_ctrl) & HWDEBUG_WHEN_MASK) >> 9) +#define HWDEBUG_SELECT(_ctrl) (((_ctrl) & HWDEBUG_SELECT_MASK) >> 12) +#define HWDEBUG_CHAIN(_ctrl) (((_ctrl) & HWDEBUG_CHAIN_MASK) >> 13) +#define HWDEBUG_TYPE(_ctrl) (((_ctrl) & HWDEBUG_TYPE_MASK) >> 14) + +#define HWDEBUG_MK_MATCH(_match) (((_match) << 0) & HWDEBUG_MATCH_MASK) +#define HWDEBUG_MK_SIZE(_sz) (((_sz) << 4) & HWDEBUG_SIZE_MASK) +#define HWDEBUG_MK_WHEN(_when) (((_when) << 9) & HWDEBUG_WHEN_MASK) +#define HWDEBUG_MK_SELECT(_sel) (((_sel) << 12) & HWDEBUG_SELECT_MASK) +#define HWDEBUG_MK_CHAIN(_chain) (((_chain) << 13) & HWDEBUG_CHAIN_MASK) +#define HWDEBUG_MK_TYPE(_type) (((_type) << 14) & HWDEBUG_TYPE_MASK) + +struct user_hwdebug_state { + __u32 info; + __u32 pad; + struct { + __u64 addr; + __u32 control; + __u32 pad; + } dbg_regs[16]; +}; + struct __riscv_hwdebug_state { __u64 addr; __u64 type; diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c index be076865d02d..9959a8e4a307 100644 --- a/arch/riscv/kernel/ptrace.c +++ b/arch/riscv/kernel/ptrace.c @@ -37,6 +37,10 @@ enum riscv_regset { #ifdef CONFIG_RISCV_USER_CFI REGSET_CFI, #endif +#ifdef CONFIG_HAVE_HW_BREAKPOINT + REGSET_RISCV_HW_BREAK, + REGSET_RISCV_HW_WATCH, +#endif }; static int riscv_gpr_get(struct task_struct *target, @@ -526,6 +530,302 @@ static struct perf_event *ptrace_hbp_create(enum riscv_hbp_type hbp_type, return bp; } +static int ptrace_hbp_note_to_type(unsigned int note_type, + enum riscv_hbp_type *hbp_type) +{ + switch (note_type) { + case NT_RISCV_HW_BREAK: + *hbp_type = RISCV_HBP_BREAK; + return 0; + case NT_RISCV_HW_WATCH: + *hbp_type = RISCV_HBP_WATCH; + return 0; + default: + return -EINVAL; + } +} + +static int ptrace_hbp_fill_attr_ctrl(enum riscv_hbp_type hbp_type, + struct arch_hw_breakpoint *bpctrl, + struct perf_event_attr *attr) +{ + int len, type; + + attr->disabled = 0; + type = bpctrl->type; + len = bpctrl->len; + + switch (hbp_type) { + case RISCV_HBP_BREAK: + if ((type & HW_BREAKPOINT_X) != type) + return -EINVAL; + break; + case RISCV_HBP_WATCH: + if ((type & HW_BREAKPOINT_RW) != type) + return -EINVAL; + break; + default: + return -EINVAL; + } + + attr->bp_len = len; + attr->bp_type = type; + attr->bp_addr = bpctrl->address; + + return 0; +} + +static int ptrace_hbp_get_resource_info(enum riscv_hbp_type hbp_type, u32 *info) +{ + u8 num; + + /* + * The NT_RISCV_HW_BREAK/WATCH regset ABI exposes 16 register pairs. + * Cap the reported count so GETREGSET/SETREGSET never walk past the + * fixed UAPI array even if the platform has more triggers. + */ + switch (hbp_type) { + case RISCV_HBP_BREAK: + num = min_t(unsigned int, hw_breakpoint_slots(TYPE_INST), + RISCV_MAX_BP); + break; + case RISCV_HBP_WATCH: + num = min_t(unsigned int, hw_breakpoint_slots(TYPE_DATA), + RISCV_MAX_BP); + break; + default: + return -EINVAL; + } + + *info = num; + + return 0; +} + +static u32 encode_ctrl_reg(struct perf_event *bp) +{ + struct arch_hw_breakpoint *bpctrl = counter_arch_bp(bp); + u32 ctrl = 0; + + /* Expose the generic UAPI bp_type values in ptrace control bits. */ + ctrl |= HWDEBUG_MK_TYPE(bp->attr.bp_type); + ctrl |= HWDEBUG_MK_MATCH(bpctrl->match); + ctrl |= HWDEBUG_MK_SELECT(bpctrl->select); + ctrl |= HWDEBUG_MK_WHEN(bpctrl->time); + ctrl |= HWDEBUG_MK_SIZE(bp->attr.bp_len); + ctrl |= HWDEBUG_MK_CHAIN(bpctrl->chain); + + return ctrl; +} + +static int ptrace_hbp_get_ctrl(enum riscv_hbp_type hbp_type, + struct task_struct *tsk, + unsigned long idx, + u32 *ctrl) +{ + struct perf_event *bp = ptrace_hbp_get_event(hbp_type, tsk, idx); + + if (IS_ERR(bp)) + return PTR_ERR(bp); + + *ctrl = bp ? encode_ctrl_reg(bp) : 0; + return 0; +} + +static int ptrace_hbp_get_addr(enum riscv_hbp_type hbp_type, + struct task_struct *tsk, + unsigned long idx, + u64 *addr) +{ + struct perf_event *bp = ptrace_hbp_get_event(hbp_type, tsk, idx); + + if (IS_ERR(bp)) + return PTR_ERR(bp); + + *addr = bp ? counter_arch_bp(bp)->address : 0; + return 0; +} + +static struct perf_event *ptrace_hbp_get_initialised_bp(enum riscv_hbp_type hbp_type, + struct task_struct *tsk, + unsigned long idx) +{ + struct perf_event *bp = ptrace_hbp_get_event(hbp_type, tsk, idx); + + if (!bp) + bp = ptrace_hbp_create(hbp_type, tsk, idx); + + return bp; +} + +static void decode_ctrl_reg(u32 uctrl, struct arch_hw_breakpoint *bpctrl) +{ + bpctrl->type = HWDEBUG_TYPE(uctrl); + bpctrl->match = HWDEBUG_MATCH(uctrl); + bpctrl->select = HWDEBUG_SELECT(uctrl); + bpctrl->time = HWDEBUG_WHEN(uctrl); + bpctrl->len = HWDEBUG_SIZE(uctrl); + bpctrl->chain = HWDEBUG_CHAIN(uctrl); +} + +static int ptrace_hbp_set_ctrl(enum riscv_hbp_type hbp_type, + struct task_struct *tsk, + unsigned long idx, + u32 uctrl) +{ + int err; + struct perf_event *bp; + struct perf_event_attr attr; + struct arch_hw_breakpoint bpctrl; + + bp = ptrace_hbp_get_initialised_bp(hbp_type, tsk, idx); + if (IS_ERR(bp)) { + err = PTR_ERR(bp); + return err; + } + + attr = bp->attr; + decode_ctrl_reg(uctrl, &bpctrl); + bpctrl.address = attr.bp_addr; + err = ptrace_hbp_fill_attr_ctrl(hbp_type, &bpctrl, &attr); + if (err) + return err; + + return modify_user_hw_breakpoint(bp, &attr); +} + +static int ptrace_hbp_set_addr(enum riscv_hbp_type hbp_type, + struct task_struct *tsk, + unsigned long idx, + u64 addr) +{ + int err; + struct perf_event *bp; + struct perf_event_attr attr; + + bp = ptrace_hbp_get_initialised_bp(hbp_type, tsk, idx); + if (IS_ERR(bp)) { + err = PTR_ERR(bp); + return err; + } + + attr = bp->attr; + attr.bp_addr = addr; + err = modify_user_hw_breakpoint(bp, &attr); + return err; +} + +#define PTRACE_HBP_ADDR_SZ sizeof(u64) +#define PTRACE_HBP_CTRL_SZ sizeof(u32) +#define PTRACE_HBP_PAD_SZ sizeof(u32) + +static int riscv_hw_break_get(struct task_struct *target, + const struct user_regset *regset, + struct membuf to) +{ + unsigned int note_type = regset->core_note_type; + enum riscv_hbp_type hbp_type; + int ret, idx, num_slots; + u32 info, ctrl; + u64 addr; + + ret = ptrace_hbp_note_to_type(note_type, &hbp_type); + if (ret) + return ret; + + /* Resource info: number of available slots */ + ret = ptrace_hbp_get_resource_info(hbp_type, &info); + if (ret) + return ret; + + membuf_write(&to, &info, sizeof(info)); + membuf_zero(&to, sizeof(u32)); + + /* Emit one (address, ctrl, pad) entry per available slot */ + num_slots = (int)info; + for (idx = 0; idx < num_slots; idx++) { + ret = ptrace_hbp_get_addr(hbp_type, target, idx, &addr); + if (ret) + return ret; + ret = ptrace_hbp_get_ctrl(hbp_type, target, idx, &ctrl); + if (ret) + return ret; + membuf_store(&to, addr); + membuf_store(&to, ctrl); + membuf_zero(&to, sizeof(u32)); + } + return 0; +} + +static int riscv_hw_break_set(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + const void *kbuf, const void __user *ubuf) +{ + unsigned int note_type = regset->core_note_type; + enum riscv_hbp_type hbp_type; + int ret, idx = 0, offset, limit; + u32 ctrl; + u64 addr; + + ret = ptrace_hbp_note_to_type(note_type, &hbp_type); + if (ret) + return ret; + + /* Resource info and pad */ + offset = offsetof(struct user_hwdebug_state, dbg_regs); + user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, 0, offset); + + /* (address, ctrl) registers */ + limit = regset->n * regset->size; + while (count && offset < limit) { + if (count < PTRACE_HBP_ADDR_SZ) + return -EINVAL; + + /* user_regset_copyin() can do partial copies for odd offsets. */ + addr = 0; + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &addr, + offset, offset + PTRACE_HBP_ADDR_SZ); + if (ret) + return ret; + + ret = ptrace_hbp_set_addr(hbp_type, target, idx, addr); + if (ret) + return ret; + + offset += PTRACE_HBP_ADDR_SZ; + + if (!count) + break; + + /* + * Reject undersized control writes, and keep ctrl initialized in + * case user_regset_copyin() still performs a partial copy. + */ + if (count < PTRACE_HBP_CTRL_SZ) + return -EINVAL; + + ctrl = 0; + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ctrl, + offset, offset + PTRACE_HBP_CTRL_SZ); + if (ret) + return ret; + + ret = ptrace_hbp_set_ctrl(hbp_type, target, idx, ctrl); + if (ret) + return ret; + + offset += PTRACE_HBP_CTRL_SZ; + + user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, + offset, offset + PTRACE_HBP_PAD_SZ); + offset += PTRACE_HBP_PAD_SZ; + idx++; + } + + return 0; +} + #endif /* CONFIG_HAVE_HW_BREAKPOINT */ static struct user_regset riscv_user_regset[] __ro_after_init = { @@ -577,6 +877,24 @@ static struct user_regset riscv_user_regset[] __ro_after_init = { .set = riscv_cfi_set, }, #endif +#ifdef CONFIG_HAVE_HW_BREAKPOINT + [REGSET_RISCV_HW_BREAK] = { + USER_REGSET_NOTE_TYPE(RISCV_HW_BREAK), + .n = sizeof(struct user_hwdebug_state) / sizeof(u32), + .size = sizeof(u32), + .align = sizeof(u32), + .regset_get = riscv_hw_break_get, + .set = riscv_hw_break_set, + }, + [REGSET_RISCV_HW_WATCH] = { + USER_REGSET_NOTE_TYPE(RISCV_HW_WATCH), + .n = sizeof(struct user_hwdebug_state) / sizeof(u32), + .size = sizeof(u32), + .align = sizeof(u32), + .regset_get = riscv_hw_break_get, + .set = riscv_hw_break_set, + }, +#endif }; static const struct user_regset_view riscv_user_native_view = { @@ -919,6 +1237,28 @@ static const struct user_regset compat_riscv_user_regset[] = { .set = riscv_fpr_set, }, #endif +#ifdef CONFIG_HAVE_HW_BREAKPOINT + /* + * The hardware-debug regset uses fixed-width UAPI fields, so the + * native layout is also the compat layout. + */ + [REGSET_RISCV_HW_BREAK] = { + USER_REGSET_NOTE_TYPE(RISCV_HW_BREAK), + .n = sizeof(struct user_hwdebug_state) / sizeof(u32), + .size = sizeof(u32), + .align = sizeof(u32), + .regset_get = riscv_hw_break_get, + .set = riscv_hw_break_set, + }, + [REGSET_RISCV_HW_WATCH] = { + USER_REGSET_NOTE_TYPE(RISCV_HW_WATCH), + .n = sizeof(struct user_hwdebug_state) / sizeof(u32), + .size = sizeof(u32), + .align = sizeof(u32), + .regset_get = riscv_hw_break_get, + .set = riscv_hw_break_set, + }, +#endif }; static const struct user_regset_view compat_riscv_user_native_view = { diff --git a/include/uapi/linux/elf.h b/include/uapi/linux/elf.h index ee30dcd80901..1315ac35157c 100644 --- a/include/uapi/linux/elf.h +++ b/include/uapi/linux/elf.h @@ -547,6 +547,10 @@ typedef struct elf64_shdr { #define NT_RISCV_TAGGED_ADDR_CTRL 0x902 /* RISC-V tagged address control (prctl()) */ #define NN_RISCV_USER_CFI "LINUX" #define NT_RISCV_USER_CFI 0x903 /* RISC-V shadow stack state */ +#define NN_RISCV_HW_BREAK "LINUX" +#define NT_RISCV_HW_BREAK 0x904 /* RISC-V hardware breakpoint registers */ +#define NN_RISCV_HW_WATCH "LINUX" +#define NT_RISCV_HW_WATCH 0x905 /* RISCV-V hardware watchpoint registers */ #define NN_LOONGARCH_CPUCFG "LINUX" #define NT_LOONGARCH_CPUCFG 0xa00 /* LoongArch CPU config registers */ #define NN_LOONGARCH_CSR "LINUX" diff --git a/tools/include/uapi/linux/elf.h b/tools/include/uapi/linux/elf.h index 5834b83d7f9a..21f225502051 100644 --- a/tools/include/uapi/linux/elf.h +++ b/tools/include/uapi/linux/elf.h @@ -460,6 +460,8 @@ typedef struct elf64_shdr { #define NT_RISCV_CSR 0x900 /* RISC-V Control and Status Registers */ #define NT_RISCV_VECTOR 0x901 /* RISC-V vector registers */ #define NT_RISCV_TAGGED_ADDR_CTRL 0x902 /* RISC-V tagged address control (prctl()) */ +#define NT_RISCV_HW_BREAK 0x904 +#define NT_RISCV_HW_WATCH 0x905 #define NT_LOONGARCH_CPUCFG 0xa00 /* LoongArch CPU config registers */ #define NT_LOONGARCH_CSR 0xa01 /* LoongArch control and status registers */ #define NT_LOONGARCH_LSX 0xa02 /* LoongArch Loongson SIMD Extension registers */ -- 2.43.0