From nobody Thu Sep 11 16:25:05 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A6610C61DA4 for ; Sat, 18 Feb 2023 06:39:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229715AbjBRGjl (ORCPT ); Sat, 18 Feb 2023 01:39:41 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43384 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229687AbjBRGjc (ORCPT ); Sat, 18 Feb 2023 01:39:32 -0500 Received: from loongson.cn (mail.loongson.cn [114.242.206.163]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 70A5348E04 for ; Fri, 17 Feb 2023 22:39:24 -0800 (PST) Received: from loongson.cn (unknown [113.200.148.30]) by gateway (Coremail) with SMTP id _____8DxXNqacvBj5iQCAA--.4974S3; Sat, 18 Feb 2023 14:39:22 +0800 (CST) Received: from localhost.localdomain (unknown [113.200.148.30]) by localhost.localdomain (Coremail) with SMTP id AQAAf8Bxb+SXcvBjN+41AA--.63946S5; Sat, 18 Feb 2023 14:39:22 +0800 (CST) From: Qing Zhang To: Huacai Chen , Oleg Nesterov , WANG Xuerui Cc: Jiaxun Yang , loongarch@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH v5 3/3] LoongArch: ptrace: expose hardware breakpoints to debuggers Date: Sat, 18 Feb 2023 14:39:19 +0800 Message-Id: <20230218063919.5639-4-zhangqing@loongson.cn> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20230218063919.5639-1-zhangqing@loongson.cn> References: <20230218063919.5639-1-zhangqing@loongson.cn> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-CM-TRANSID: AQAAf8Bxb+SXcvBjN+41AA--.63946S5 X-CM-SenderInfo: x2kd0wptlqwqxorr0wxvrqhubq/ X-Coremail-Antispam: 1Uk129KBjvJXoW3KryfWr1fZF48XryDJrykAFb_yoWktry3pF 47C3yUtrWUtrZ2kr4ftws8Ar15Gw4IvrWxGrWfuw1Sya4jqrZ8K3W8Kr90vr4fC348ua4f Aa909w4Y9ayUX3DanT9S1TB71UUUUjUqnTZGkaVYY2UrUUUUj1kv1TuYvTs0mT0YCTnIWj qI5I8CrVACY4xI64kE6c02F40Ex7xfYxn0WfASr-VFAUDa7-sFnT9fnUUIcSsGvfJTRUUU b3AYFVCjjxCrM7AC8VAFwI0_Jr0_Gr1l1xkIjI8I6I8E6xAIw20EY4v20xvaj40_Wr0E3s 1l1IIY67AEw4v_Jrv_JF1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0rcxSw2x7M28EF7xv wVC0I7IYx2IY67AKxVW5JVW7JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267AKxVW8JVWxJwA2z4 x0Y4vEx4A2jsIE14v26r4UJVWxJr1l84ACjcxK6I8E87Iv6xkF7I0E14v26r4UJVWxJr1l n4kS14v26r1Y6r17M2AIxVAIcxkEcVAq07x20xvEncxIr21l57IF6xkI12xvs2x26I8E6x ACxx1l5I8CrVACY4xI64kE6c02F40Ex7xfMcIj6xIIjxv20xvE14v26rWY6Fy7McIj6I8E 87Iv67AKxVW8JVWxJwAm72CE4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41l42xK82 IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1l4IxYO2xFxVAFwI0_Jrv_JF1lx2Iq xVAqx4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r 126r1DMIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Xr0_Ar1lIxAIcVC0I7IYx2IY 6xkF7I0E14v26r4j6F4UMIIF0xvE42xK8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87Iv67 AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I0E14v26r4j6r4UJbIYCTnIWIevJa73UjIFyTuY vjxUVCD7UUUUU Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Implement regset-based ptrace interface that exposes hardware breakpoints to user-space debuggers to query and set instruction and data breakpoints. Signed-off-by: Qing Zhang --- arch/loongarch/include/uapi/asm/ptrace.h | 9 + arch/loongarch/kernel/ptrace.c | 416 +++++++++++++++++++++++ include/uapi/linux/elf.h | 2 + 3 files changed, 427 insertions(+) diff --git a/arch/loongarch/include/uapi/asm/ptrace.h b/arch/loongarch/incl= ude/uapi/asm/ptrace.h index 46eb40932bb1..7ac4a0e44570 100644 --- a/arch/loongarch/include/uapi/asm/ptrace.h +++ b/arch/loongarch/include/uapi/asm/ptrace.h @@ -56,6 +56,15 @@ struct user_lasx_state { uint64_t vregs[32*4]; }; =20 +struct user_watch_state { + uint16_t dbg_info; + struct { + uint64_t addr; + uint64_t mask; + uint32_t ctrl; + } dbg_regs[8]; +}; + #define PTRACE_SYSEMU 0x1f #define PTRACE_SYSEMU_SINGLESTEP 0x20 =20 diff --git a/arch/loongarch/kernel/ptrace.c b/arch/loongarch/kernel/ptrace.c index 2a057b66abe1..918d5afb0c3b 100644 --- a/arch/loongarch/kernel/ptrace.c +++ b/arch/loongarch/kernel/ptrace.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -333,6 +334,399 @@ static int simd_set(struct task_struct *target, =20 #endif /* CONFIG_CPU_HAS_LSX */ =20 +#ifdef CONFIG_HAVE_HW_BREAKPOINT + +/* + * Handle hitting a HW-breakpoint. + */ +static void ptrace_hbptriggered(struct perf_event *bp, + struct perf_sample_data *data, + struct pt_regs *regs) +{ + int i; + struct arch_hw_breakpoint *bkpt =3D counter_arch_bp(bp); + + for (i =3D 0; i < LOONGARCH_MAX_BRP; ++i) + if (current->thread.hbp_break[i] =3D=3D bp) + break; + + for (i =3D 0; i < LOONGARCH_MAX_WRP; ++i) + if (current->thread.hbp_watch[i] =3D=3D bp) + break; + + force_sig_ptrace_errno_trap(i, (void __user *)bkpt->address); +} + +static struct perf_event *ptrace_hbp_get_event(unsigned int note_type, + struct task_struct *tsk, + unsigned long idx) +{ + struct perf_event *bp =3D ERR_PTR(-EINVAL); + + switch (note_type) { + case NT_LOONGARCH_HW_BREAK: + if (idx >=3D LOONGARCH_MAX_BRP) + goto out; + idx =3D array_index_nospec(idx, LOONGARCH_MAX_BRP); + bp =3D tsk->thread.hbp_break[idx]; + break; + case NT_LOONGARCH_HW_WATCH: + if (idx >=3D LOONGARCH_MAX_WRP) + goto out; + idx =3D array_index_nospec(idx, LOONGARCH_MAX_WRP); + bp =3D tsk->thread.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 =3D -EINVAL; + + switch (note_type) { + case NT_LOONGARCH_HW_BREAK: + if (idx >=3D LOONGARCH_MAX_BRP) + goto out; + idx =3D array_index_nospec(idx, LOONGARCH_MAX_BRP); + tsk->thread.hbp_break[idx] =3D bp; + err =3D 0; + break; + case NT_LOONGARCH_HW_WATCH: + if (idx >=3D LOONGARCH_MAX_WRP) + goto out; + idx =3D array_index_nospec(idx, LOONGARCH_MAX_WRP); + tsk->thread.hbp_watch[idx] =3D bp; + err =3D 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_LOONGARCH_HW_BREAK: + type =3D HW_BREAKPOINT_X; + break; + case NT_LOONGARCH_HW_WATCH: + type =3D 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 =3D 0; + attr.bp_len =3D HW_BREAKPOINT_LEN_4; + attr.bp_type =3D type; + attr.disabled =3D 1; + + bp =3D register_user_hw_breakpoint(&attr, ptrace_hbptriggered, NULL, tsk); + if (IS_ERR(bp)) + return bp; + + err =3D 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_ctrl ctrl, + struct perf_event_attr *attr) +{ + int err, len, type, offset; + + err =3D arch_bp_generic_fields(ctrl, &len, &type, &offset); + if (err) + return err; + + switch (note_type) { + case NT_LOONGARCH_HW_BREAK: + if ((type & HW_BREAKPOINT_X) !=3D type) + return -EINVAL; + break; + case NT_LOONGARCH_HW_WATCH: + if ((type & HW_BREAKPOINT_RW) !=3D type) + return -EINVAL; + break; + default: + return -EINVAL; + } + + attr->bp_len =3D len; + attr->bp_type =3D type; + attr->bp_addr +=3D offset; + + return 0; +} + +static int ptrace_hbp_get_resource_info(unsigned int note_type, u16 *info) +{ + u8 num; + u16 reg =3D 0; + + switch (note_type) { + case NT_LOONGARCH_HW_BREAK: + num =3D hw_breakpoint_slots(TYPE_INST); + break; + case NT_LOONGARCH_HW_WATCH: + num =3D hw_breakpoint_slots(TYPE_DATA); + break; + default: + return -EINVAL; + } + + reg |=3D num; + + *info =3D reg; + return 0; +} + +static int ptrace_hbp_get_ctrl(unsigned int note_type, + struct task_struct *tsk, + unsigned long idx, + u32 *ctrl) +{ + struct perf_event *bp =3D ptrace_hbp_get_event(note_type, tsk, idx); + + if (IS_ERR(bp)) + return PTR_ERR(bp); + + *ctrl =3D bp ? encode_ctrl_reg(counter_arch_bp(bp)->ctrl) : 0; + return 0; +} + +static int ptrace_hbp_get_mask(unsigned int note_type, + struct task_struct *tsk, + unsigned long idx, + u64 *mask) +{ + struct perf_event *bp =3D ptrace_hbp_get_event(note_type, tsk, idx); + + if (IS_ERR(bp)) + return PTR_ERR(bp); + + *mask =3D bp ? counter_arch_bp(bp)->mask : 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 =3D ptrace_hbp_get_event(note_type, tsk, idx); + + if (IS_ERR(bp)) + return PTR_ERR(bp); + + *addr =3D 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 =3D ptrace_hbp_get_event(note_type, tsk, idx); + + if (!bp) + bp =3D ptrace_hbp_create(note_type, tsk, idx); + + return bp; +} + +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_ctrl ctrl; + + bp =3D ptrace_hbp_get_initialised_bp(note_type, tsk, idx); + if (IS_ERR(bp)) { + err =3D PTR_ERR(bp); + return err; + } + + attr =3D bp->attr; + decode_ctrl_reg(uctrl, &ctrl); + err =3D ptrace_hbp_fill_attr_ctrl(note_type, ctrl, &attr); + if (err) + return err; + + return modify_user_hw_breakpoint(bp, &attr); +} + +static int ptrace_hbp_set_mask(unsigned int note_type, + struct task_struct *tsk, + unsigned long idx, + u64 mask) +{ + int err; + struct perf_event *bp; + struct perf_event_attr attr; + struct arch_hw_breakpoint *info; + + bp =3D ptrace_hbp_get_initialised_bp(note_type, tsk, idx); + if (IS_ERR(bp)) { + err =3D PTR_ERR(bp); + return err; + } + + attr =3D bp->attr; + info =3D counter_arch_bp(bp); + info->mask =3D mask; + err =3D modify_user_hw_breakpoint(bp, &attr); + return err; +} + +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 =3D ptrace_hbp_get_initialised_bp(note_type, tsk, idx); + if (IS_ERR(bp)) { + err =3D PTR_ERR(bp); + return err; + } + + attr =3D bp->attr; + attr.bp_addr =3D addr; + err =3D modify_user_hw_breakpoint(bp, &attr); + return err; +} + +#define PTRACE_HBP_ADDR_SZ sizeof(u64) +#define PTRACE_HBP_MASK_SZ sizeof(u64) +#define PTRACE_HBP_CTRL_SZ sizeof(u32) + +static int hw_break_get(struct task_struct *target, + const struct user_regset *regset, + struct membuf to) +{ + unsigned int note_type =3D regset->core_note_type; + int ret, idx =3D 0; + u16 info; + u32 ctrl; + u64 addr, mask; + + /* Resource info */ + ret =3D ptrace_hbp_get_resource_info(note_type, &info); + if (ret) + return ret; + + membuf_write(&to, &info, sizeof(info)); + /* (address, ctrl) registers */ + while (to.left) { + ret =3D ptrace_hbp_get_addr(note_type, target, idx, &addr); + if (ret) + return ret; + + ret =3D ptrace_hbp_get_mask(note_type, target, idx, &mask); + if (ret) + return ret; + + ret =3D ptrace_hbp_get_ctrl(note_type, target, idx, &ctrl); + if (ret) + return ret; + + membuf_store(&to, addr); + membuf_store(&to, mask); + membuf_store(&to, ctrl); + idx++; + } + return 0; +} + +static int 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 =3D regset->core_note_type; + int ret, idx =3D 0, offset, limit; + u32 ctrl; + u64 addr, mask; + + /* Resource info */ + offset =3D offsetof(struct user_watch_state, dbg_regs); + user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, 0, offset); + + /* (address, ctrl) registers */ + limit =3D regset->n * regset->size; + while (count && offset < limit) { + if (count < PTRACE_HBP_ADDR_SZ) + return -EINVAL; + + ret =3D user_regset_copyin(&pos, &count, &kbuf, &ubuf, &addr, + offset, offset + PTRACE_HBP_ADDR_SZ); + if (ret) + return ret; + + ret =3D ptrace_hbp_set_addr(note_type, target, idx, addr); + if (ret) + return ret; + offset +=3D PTRACE_HBP_ADDR_SZ; + + if (!count) + break; + + ret =3D user_regset_copyin(&pos, &count, &kbuf, &ubuf, &mask, + offset, offset + PTRACE_HBP_ADDR_SZ); + if (ret) + return ret; + + ret =3D ptrace_hbp_set_mask(note_type, target, idx, mask); + if (ret) + return ret; + offset +=3D PTRACE_HBP_MASK_SZ; + + ret =3D user_regset_copyin(&pos, &count, &kbuf, &ubuf, &mask, + offset, offset + PTRACE_HBP_MASK_SZ); + if (ret) + return ret; + + ret =3D ptrace_hbp_set_ctrl(note_type, target, idx, ctrl); + if (ret) + return ret; + offset +=3D PTRACE_HBP_CTRL_SZ; + idx++; + } + + return 0; +} +#endif + struct pt_regs_offset { const char *name; int offset; @@ -412,6 +806,10 @@ enum loongarch_regset { #ifdef CONFIG_CPU_HAS_LASX REGSET_LASX, #endif +#ifdef CONFIG_HAVE_HW_BREAKPOINT + REGSET_HW_BREAK, + REGSET_HW_WATCH, +#endif }; =20 static const struct user_regset loongarch64_regsets[] =3D { @@ -459,6 +857,24 @@ static const struct user_regset loongarch64_regsets[] = =3D { .set =3D simd_set, }, #endif +#ifdef CONFIG_HAVE_HW_BREAKPOINT + [REGSET_HW_BREAK] =3D { + .core_note_type =3D NT_LOONGARCH_HW_BREAK, + .n =3D sizeof(struct user_watch_state) / sizeof(u32), + .size =3D sizeof(u32), + .align =3D sizeof(u32), + .regset_get =3D hw_break_get, + .set =3D hw_break_set, + }, + [REGSET_HW_WATCH] =3D { + .core_note_type =3D NT_LOONGARCH_HW_WATCH, + .n =3D sizeof(struct user_watch_state) / sizeof(u32), + .size =3D sizeof(u32), + .align =3D sizeof(u32), + .regset_get =3D hw_break_get, + .set =3D hw_break_set, + }, +#endif }; =20 static const struct user_regset_view user_loongarch64_view =3D { diff --git a/include/uapi/linux/elf.h b/include/uapi/linux/elf.h index 4c6a8fa5e7ed..3cf66946a0bb 100644 --- a/include/uapi/linux/elf.h +++ b/include/uapi/linux/elf.h @@ -444,6 +444,8 @@ typedef struct elf64_shdr { #define NT_LOONGARCH_LSX 0xa02 /* LoongArch Loongson SIMD Extension regist= ers */ #define NT_LOONGARCH_LASX 0xa03 /* LoongArch Loongson Advanced SIMD Extens= ion registers */ #define NT_LOONGARCH_LBT 0xa04 /* LoongArch Loongson Binary Translation re= gisters */ +#define NT_LOONGARCH_HW_BREAK 0xa05 /* LoongArch hardware breakpoint r= egisters */ +#define NT_LOONGARCH_HW_WATCH 0xa06 /* LoongArch hardware watchpoint r= egisters */ =20 /* Note types with note name "GNU" */ #define NT_GNU_PROPERTY_TYPE_0 5 --=20 2.36.0