Re: [PATCH v7 3/8] riscv: ptrace support for hardware break/watchpoints
From: Jesse Taube
Date: Tue Oct 27 2026 - 15:32:46 EST
On Wed, Sep 30, 2026 at 2:39 AM Himanshu Chauhan
<himanshu.chauhan@xxxxxxxxxxxxxxxx> wrote:
>
> Add ptrace support for hardware breakpoints and watchpoints on
> RISC-V. Debuggers can now set and query hardware debug triggers
> through the standard PTRACE_GETREGSET/SETREGSET interface using new
> NT_RISCV_HW_BREAK/WATCH note types, backed by
> register_user_hw_breakpoint()/modify_user_hw_breakpoint() and
> delivering SIGTRAP/TRAP_HWBKPT to the tracee when a trigger fires.
>
> For convenience, also add a simpler PTRACE_GETHBPREGS/SETHBPREGS
Can you split PTRACE_GETHBPREGS/SETHBPREGS into one patch and
PTRACE_GETREGSET/SETREGSET into another.
This patch is getting quite large and hard to review.
Thanks,
Jesse Taube
> request pair that lets a tracer read or write a single breakpoint or
> watchpoint directly, without going through the regset machinery.
> These request numbers live in the arch-specific ptrace range
> (0x4210/0x4211) so they don't collide with the generic
> PTRACE_PEEKDATA/PTRACE_PEEKUSR codes.
>
> Breakpoints and watchpoints share the same trigger pool on this
> architecture, so select HAVE_MIXED_BREAKPOINTS_REGS. Hook up
> thread flush/copy so per-task breakpoints are cleaned up and cleared
> across fork/exec.
>
> Signed-off-by: Himanshu Chauhan <himanshu.chauhan@xxxxxxxxxxxxxxxx>
> ---
> arch/riscv/Kconfig | 1 +
> arch/riscv/include/asm/hw_breakpoint.h | 10 +
> arch/riscv/include/asm/processor.h | 18 +
> arch/riscv/include/uapi/asm/ptrace.h | 50 +++
> arch/riscv/kernel/hw_breakpoint.c | 1 -
> arch/riscv/kernel/process.c | 5 +
> arch/riscv/kernel/ptrace.c | 507 +++++++++++++++++++++++++
> include/uapi/linux/elf.h | 4 +
> tools/include/uapi/linux/elf.h | 2 +
> 9 files changed, 597 insertions(+), 1 deletion(-)
>
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index 17fa7d4f9729..d382f5d7b6a7 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -176,6 +176,7 @@ config RISCV
> select HAVE_GCC_PLUGINS
> select HAVE_GENERIC_VDSO if MMU
> select HAVE_HW_BREAKPOINT if PERF_EVENTS
> + select HAVE_MIXED_BREAKPOINTS_REGS if PERF_EVENTS
> select HAVE_IRQ_TIME_ACCOUNTING
> select HAVE_KERNEL_BZIP2 if !EFI_ZBOOT
> select HAVE_KERNEL_GZIP if !EFI_ZBOOT
> diff --git a/arch/riscv/include/asm/hw_breakpoint.h b/arch/riscv/include/asm/hw_breakpoint.h
> index a165bb9636de..ca2ef93c020c 100644
> --- a/arch/riscv/include/asm/hw_breakpoint.h
> +++ b/arch/riscv/include/asm/hw_breakpoint.h
> @@ -14,6 +14,7 @@ struct task_struct;
>
> /* Maximum number of hardware breakpoints supported */
> #define RISCV_HW_BP_NUM_MAX 32
> +#define RISCV_MAX_BP 16
>
> #if __riscv_xlen == 64
> #define cpu_to_le cpu_to_le64
> @@ -279,6 +280,10 @@ struct arch_hw_breakpoint {
> unsigned long address;
> unsigned long len;
> unsigned int type;
> + unsigned int match;
> + unsigned int chain;
> + unsigned int select;
> + unsigned int time;
>
> /* Trigger configuration data */
> unsigned long tdata1;
> @@ -305,8 +310,13 @@ void arch_disable_hw_breakpoint(struct perf_event *bp);
> int arch_install_hw_breakpoint(struct perf_event *bp);
> void arch_uninstall_hw_breakpoint(struct perf_event *bp);
> void hw_breakpoint_pmu_read(struct perf_event *bp);
> +void clear_ptrace_hw_breakpoint(struct task_struct *tsk);
> +void flush_ptrace_hw_breakpoint(struct task_struct *tsk);
> +void ptrace_hw_copy_thread(struct task_struct *task);
>
> #else
>
> +static inline void ptrace_hw_copy_thread(struct task_struct *task) { }
> +
> #endif /* CONFIG_HAVE_HW_BREAKPOINT */
> #endif /* __RISCV_HW_BREAKPOINT_H */
> diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h
> index 815715c67f94..6f6186b9663e 100644
> --- a/arch/riscv/include/asm/processor.h
> +++ b/arch/riscv/include/asm/processor.h
> @@ -15,6 +15,7 @@
> #include <asm/ptrace.h>
> #include <asm/insn-def.h>
> #include <asm/alternative-macros.h>
> +#include <asm/hw_breakpoint.h>
> #include <asm/hwcap.h>
> #include <asm/usercfi.h>
>
> @@ -110,6 +111,19 @@ struct pt_regs;
> #define RISCV_PREEMPT_V_NEED_RESTORE 0x40000000
> #define RISCV_PREEMPT_V_IN_SCHEDULE 0x20000000
>
> +struct debug_info {
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> + /* Have we suspended stepping by a debugger? */
> + int suspended_step;
> + /* Allow breakpoints and watchpoints to be disabled for this thread. */
> + int bp_disabled;
> + int wp_disabled;
> + /* Hardware breakpoints pinned to this task. */
> + struct perf_event *hbp_break[RISCV_MAX_BP];
> + struct perf_event *hbp_watch[RISCV_MAX_BP];
> +#endif
> +};
> +
> /* CPU-specific state of a task */
> struct thread_struct {
> /* Callee-saved registers */
> @@ -133,6 +147,10 @@ struct thread_struct {
> #endif
> #ifdef CONFIG_RISCV_ISA_SSQOSID
> u32 srmcfg;
> +#endif
> + struct debug_info debug;
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> + struct perf_event *ptrace_bps[RISCV_HW_BP_NUM_MAX];
> #endif
> };
>
> diff --git a/arch/riscv/include/uapi/asm/ptrace.h b/arch/riscv/include/uapi/asm/ptrace.h
> index 3de2b7124aff..83e073dbbf6d 100644
> --- a/arch/riscv/include/uapi/asm/ptrace.h
> +++ b/arch/riscv/include/uapi/asm/ptrace.h
> @@ -10,11 +10,14 @@
>
> #include <linux/types.h>
> #include <linux/const.h>
> +#include <linux/bits.h>
>
> #define PTRACE_GETFDPIC 33
>
> #define PTRACE_GETFDPIC_EXEC 0
> #define PTRACE_GETFDPIC_INTERP 1
> +#define PTRACE_GETHBPREGS 0x4210
> +#define PTRACE_SETHBPREGS 0x4211
>
> /*
> * User-mode register state for core dumps, ptrace, sigcontext
> @@ -164,6 +167,53 @@ 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;
> + __u64 len;
> + __u64 ctrl;
> +};
> +
> #endif /* __ASSEMBLER__ */
>
> #endif /* _UAPI_ASM_RISCV_PTRACE_H */
> diff --git a/arch/riscv/kernel/hw_breakpoint.c b/arch/riscv/kernel/hw_breakpoint.c
> index c2ab5e6008e7..dd6f63953969 100644
> --- a/arch/riscv/kernel/hw_breakpoint.c
> +++ b/arch/riscv/kernel/hw_breakpoint.c
> @@ -698,7 +698,6 @@ EXPORT_SYMBOL_GPL(arch_disable_hw_breakpoint);
>
> void hw_breakpoint_pmu_read(struct perf_event *bp) { }
>
> -void flush_ptrace_hw_breakpoint(struct task_struct *tsk) { }
>
> static int __init arch_hw_breakpoint_init(void)
> {
> diff --git a/arch/riscv/kernel/process.c b/arch/riscv/kernel/process.c
> index 7cc5a6a5c020..bdcb0386cd0d 100644
> --- a/arch/riscv/kernel/process.c
> +++ b/arch/riscv/kernel/process.c
> @@ -204,6 +204,7 @@ void flush_thread(void)
> if (riscv_has_extension_unlikely(RISCV_ISA_EXT_SUPM))
> envcfg_update_bits(current, ENVCFG_PMM, ENVCFG_PMM_PMLEN_0);
> #endif
> + flush_ptrace_hw_breakpoint(current);
> }
>
> void arch_release_task_struct(struct task_struct *tsk)
> @@ -283,6 +284,10 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
> p->thread.riscv_v_flags = 0;
> if (has_vector() || has_xtheadvector())
> riscv_v_thread_alloc(p);
> + ptrace_hw_copy_thread(p);
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> + memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
> +#endif
> p->thread.sp = (unsigned long)childregs; /* kernel sp */
> return 0;
> }
> diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c
> index f336a183667e..2d4ee51e6859 100644
> --- a/arch/riscv/kernel/ptrace.c
> +++ b/arch/riscv/kernel/ptrace.c
> @@ -18,8 +18,10 @@
> #include <linux/elf.h>
> #include <linux/regset.h>
> #include <linux/sched.h>
> +#include <linux/sched/signal.h>
> #include <linux/sched/task_stack.h>
> #include <asm/usercfi.h>
> +#include <linux/hw_breakpoint.h>
>
> enum riscv_regset {
> REGSET_X,
> @@ -35,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,
> @@ -372,6 +378,397 @@ static int riscv_cfi_set(struct task_struct *target,
> }
> #endif
>
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> +/*
> + * Handle hitting a HW-breakpoint.
> + */
> +static void riscv_ptrace_hbptriggered(struct perf_event *bp,
> + struct perf_sample_data *data,
> + struct pt_regs *regs)
> +{
> + struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp);
> +
> + force_sig_fault(SIGTRAP, TRAP_HWBKPT, (void __user *)bkpt->address);
> +}
> +
> +/*
> + * Unregister breakpoints from this task and reset the pointers in
> + * the thread_struct.
> + */
> +void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
> +{
> + int i;
> + struct thread_struct *t = &tsk->thread;
> +
> + for (i = 0; i < RISCV_MAX_BP; i++) {
> + if (t->debug.hbp_break[i]) {
> + unregister_hw_breakpoint(t->debug.hbp_break[i]);
> + t->debug.hbp_break[i] = NULL;
> + }
> + }
> +
> + for (i = 0; i < RISCV_MAX_BP; i++) {
> + if (t->debug.hbp_watch[i]) {
> + unregister_hw_breakpoint(t->debug.hbp_watch[i]);
> + t->debug.hbp_watch[i] = NULL;
> + }
> + }
> +}
> +
> +void ptrace_hw_copy_thread(struct task_struct *tsk)
> +{
> + memset(&tsk->thread.debug, 0, sizeof(struct debug_info));
> +}
> +
> +static struct perf_event *ptrace_hbp_get_event(unsigned int note_type,
> + struct task_struct *tsk,
> + unsigned long idx)
> +{
> + struct perf_event *bp = ERR_PTR(-EINVAL);
> +
> + switch (note_type) {
> + case NT_RISCV_HW_BREAK:
> + if (idx >= RISCV_MAX_BP)
> + goto out;
> + idx = array_index_nospec(idx, RISCV_MAX_BP);
> + bp = tsk->thread.debug.hbp_break[idx];
> + break;
> + case NT_RISCV_HW_WATCH:
> + if (idx >= RISCV_MAX_BP)
> + goto out;
> + idx = array_index_nospec(idx, RISCV_MAX_BP);
> + bp = tsk->thread.debug.hbp_watch[idx];
> + break;
> + }
> +
> +out:
> + return bp;
> +}
> +
> +static int ptrace_hbp_set_event(unsigned int note_type,
> + struct task_struct *tsk,
> + unsigned long idx,
> + struct perf_event *bp)
> +{
> + int err = -EINVAL;
> +
> + switch (note_type) {
> + case NT_RISCV_HW_BREAK:
> + if (idx >= RISCV_MAX_BP)
> + goto out;
> + idx = array_index_nospec(idx, RISCV_MAX_BP);
> + tsk->thread.debug.hbp_break[idx] = bp;
> + err = 0;
> + break;
> + case NT_RISCV_HW_WATCH:
> + if (idx >= RISCV_MAX_BP)
> + goto out;
> + idx = array_index_nospec(idx, RISCV_MAX_BP);
> + tsk->thread.debug.hbp_watch[idx] = bp;
> + err = 0;
> + break;
> + }
> +
> +out:
> + return err;
> +}
> +
> +static struct perf_event *ptrace_hbp_create(unsigned int note_type,
> + struct task_struct *tsk,
> + unsigned long idx)
> +{
> + struct perf_event *bp;
> + struct perf_event_attr attr;
> + int err, type;
> +
> + switch (note_type) {
> + case NT_RISCV_HW_BREAK:
> + type = HW_BREAKPOINT_X;
> + break;
> + case NT_RISCV_HW_WATCH:
> + type = HW_BREAKPOINT_RW;
> + break;
> + default:
> + return ERR_PTR(-EINVAL);
> + }
> +
> + ptrace_breakpoint_init(&attr);
> +
> + /*
> + * Initialise fields to sane defaults
> + * (i.e. values that will pass validation).
> + */
> + attr.bp_addr = 0;
> + attr.bp_len = HW_BREAKPOINT_LEN_4;
> + attr.bp_type = type;
> + attr.disabled = 1;
> +
> + bp = register_user_hw_breakpoint(&attr, riscv_ptrace_hbptriggered, NULL, tsk);
> + if (IS_ERR(bp))
> + return bp;
> +
> + err = ptrace_hbp_set_event(note_type, tsk, idx, bp);
> + if (err)
> + return ERR_PTR(err);
> +
> + return bp;
> +}
> +
> +static int ptrace_hbp_fill_attr_ctrl(unsigned int note_type,
> + struct arch_hw_breakpoint *bpctrl,
> + struct perf_event_attr *attr)
> +{
> + int len, type;
> +
> + attr->disabled = 0;
> + type = bpctrl->type;
> + len = bpctrl->len;
> +
> + switch (note_type) {
> + case NT_RISCV_HW_BREAK:
> + if ((type & HW_BREAKPOINT_X) != type)
> + return -EINVAL;
> + break;
> + case NT_RISCV_HW_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(unsigned int note_type, u32 *info)
> +{
> + u8 num;
> +
> + switch (note_type) {
> + case NT_RISCV_HW_BREAK:
> + num = hw_breakpoint_slots(TYPE_INST);
> + break;
> + case NT_RISCV_HW_WATCH:
> + num = hw_breakpoint_slots(TYPE_DATA);
> + 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(unsigned int note_type,
> + struct task_struct *tsk,
> + unsigned long idx,
> + u32 *ctrl)
> +{
> + struct perf_event *bp = ptrace_hbp_get_event(note_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(unsigned int note_type,
> + struct task_struct *tsk,
> + unsigned long idx,
> + u64 *addr)
> +{
> + struct perf_event *bp = ptrace_hbp_get_event(note_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(unsigned int note_type,
> + struct task_struct *tsk,
> + unsigned long idx)
> +{
> + struct perf_event *bp = ptrace_hbp_get_event(note_type, tsk, idx);
> +
> + if (!bp)
> + bp = ptrace_hbp_create(note_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(unsigned int note_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(note_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(note_type, &bpctrl, &attr);
> + if (err)
> + return err;
> +
> + return modify_user_hw_breakpoint(bp, &attr);
> +}
> +
> +static int ptrace_hbp_set_addr(unsigned int note_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(note_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;
> + int ret, idx, num_slots;
> + u32 info, ctrl;
> + u64 addr;
> +
> + /* Resource info: number of available slots */
> + ret = ptrace_hbp_get_resource_info(note_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(note_type, target, idx, &addr);
> + if (ret)
> + return ret;
> + ret = ptrace_hbp_get_ctrl(note_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;
> + int ret, idx = 0, offset, limit;
> + u32 ctrl;
> + u64 addr;
> +
> + /* 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;
> +
> + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &addr,
> + offset, offset + PTRACE_HBP_ADDR_SZ);
> + if (ret)
> + return ret;
> +
> + ret = ptrace_hbp_set_addr(note_type, target, idx, addr);
> + if (ret)
> + return ret;
> +
> + offset += PTRACE_HBP_ADDR_SZ;
> +
> + if (!count)
> + break;
> +
> + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ctrl,
> + offset, offset + PTRACE_HBP_CTRL_SZ);
> + if (ret)
> + return ret;
> +
> + ret = ptrace_hbp_set_ctrl(note_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 = {
> [REGSET_X] = {
> USER_REGSET_NOTE_TYPE(PRSTATUS),
> @@ -421,6 +818,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 = {
> @@ -541,12 +956,104 @@ void ptrace_disable(struct task_struct *child)
> {
> }
>
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> +static int riscv_ptrace_bp_get(struct task_struct *child, unsigned long idx,
> + struct __riscv_hwdebug_state *state)
> +{
> + struct perf_event *bp;
> +
> + if (idx >= RISCV_HW_BP_NUM_MAX)
> + return -EINVAL;
> +
> + bp = child->thread.ptrace_bps[idx];
> + if (!bp)
> + return -ENOENT;
> +
> + state->addr = bp->attr.bp_addr;
> + state->len = bp->attr.bp_len;
> + state->type = bp->attr.bp_type;
> + state->ctrl = bp->attr.disabled == 1;
> +
> + return 0;
> +}
> +
> +static int riscv_ptrace_bp_set(struct task_struct *child, unsigned long idx,
> + struct __riscv_hwdebug_state *state)
> +{
> + struct perf_event *bp;
> + struct perf_event_attr attr;
> +
> + if (idx >= RISCV_HW_BP_NUM_MAX)
> + return -EINVAL;
> +
> + bp = child->thread.ptrace_bps[idx];
> + if (bp)
> + attr = bp->attr;
> + else
> + ptrace_breakpoint_init(&attr);
> +
> + attr.bp_addr = state->addr;
> + attr.bp_len = state->len;
> + attr.bp_type = state->type;
> + /* Always register disabled; enable below if requested */
> + attr.disabled = 1;
> +
> + if (!bp) {
> + bp = register_user_hw_breakpoint(&attr, riscv_ptrace_hbptriggered, NULL, child);
> + if (IS_ERR(bp))
> + return PTR_ERR(bp);
> + child->thread.ptrace_bps[idx] = bp;
> + }
> +
> + /* Enable or disable as requested by ctrl (0 = enabled, 1 = disabled) */
> + attr.disabled = state->ctrl == 1;
> + return modify_user_hw_breakpoint(bp, &attr);
> +}
> +
> +static long riscv_ptrace_gethbpregs(struct task_struct *child, unsigned long idx,
> + unsigned long __user *datap)
> +{
> + struct __riscv_hwdebug_state state;
> + long ret;
> +
> + ret = riscv_ptrace_bp_get(child, idx, &state);
> + if (ret)
> + return ret;
> + if (copy_to_user(datap, &state, sizeof(state)))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static long riscv_ptrace_sethbpregs(struct task_struct *child, unsigned long idx,
> + unsigned long __user *datap)
> +{
> + struct __riscv_hwdebug_state state;
> +
> + if (copy_from_user(&state, datap, sizeof(state)))
> + return -EFAULT;
> +
> + return riscv_ptrace_bp_set(child, idx, &state);
> +}
> +#endif /* CONFIG_HAVE_HW_BREAKPOINT */
> +
> long arch_ptrace(struct task_struct *child, long request,
> unsigned long addr, unsigned long data)
> {
> long ret = -EIO;
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> + unsigned long __user *datap = (unsigned long __user *)data;
> +#endif
>
> switch (request) {
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> + case PTRACE_GETHBPREGS:
> + ret = riscv_ptrace_gethbpregs(child, addr, datap);
> + break;
> + case PTRACE_SETHBPREGS:
> + ret = riscv_ptrace_sethbpregs(child, addr, datap);
> + break;
> +#endif
> default:
> ret = ptrace_request(child, request, addr, data);
> break;
> 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
>