Message ID | 20230215092358.30368-3-zhangqing@loongson.cn |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:eb09:0:0:0:0:0 with SMTP id s9csp90512wrn; Wed, 15 Feb 2023 01:26:54 -0800 (PST) X-Google-Smtp-Source: AK7set/p4YygmWZ614QNxIYtDzOGu5bj6bY+iVyKHtJI5JjmU3LaDdxkLNY7aE4CVXn+JmoKOOKL X-Received: by 2002:a05:6a00:42:b0:598:b178:a3a9 with SMTP id i2-20020a056a00004200b00598b178a3a9mr1190140pfk.6.1676453213781; Wed, 15 Feb 2023 01:26:53 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1676453213; cv=none; d=google.com; s=arc-20160816; b=X5SxmVV5OgS730TJ5TeY+m9w7MqXyVjgT+IM5iPAeFqYTE86lse6shricPfA6JZpRp 9Ga3J44RqmQIRO9nxalR1R+urJ6z7BFqxCi51l19Rf64QEBYx5gj/5zb8uPKedz11UEk BbyC1vrYrjiFHtGHu2G5uiOXR+jkYYSHNd9BeiM9MaAgDgp4CnHgM+r2B5P/G0ThBcaT C+e7zyToI0xt1Wb1T50QI1rwa5R1jrep5eUY8tn0Y6pDR+4mm+FbQYlkg4fL7GDhbZ7C eBBHrrIrAFS5Vgl54ha05usKZxjopcZtXUbw63/+iSLhv7lWws15lNucAS7Fn6FWtA+c 1ykA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=Rz8gikiIVMDMco3aE7hNLaWUHec3y3/36FsQqfeM2Nc=; b=x501/+cI05zMCUkiT310l3/yXhKEsayo/Bd0X2sPdaCujrTOyPRLKDkN3b6ZUUQ5aN C4dZU/xo4T9aqwp0u+cJC8iER9oOcoM8VgkF2cvlB7rfNbwhJUNLQuxQl9C9z6x0zKL6 uyZuDUITI8ygbjgn32OcmVE6W2mRugaaGxaw1XalMnM4ZYgttO++Z2nBUzlEuX2AcH7l enqApo5r7Nkom9n3DAY5H7tU7FK4cxYsy9m1LRLsGU8NqX17e1UoovPGCQJDMMKhz8d1 a/PMpBfMuuaqkb+2U0LhTAKSlWNqZOuSd9DIhVLGQq9qel6SGSTcjlczH2tfDUtZyqx7 4LGg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id 81-20020a621954000000b005a8663221a0si2945025pfz.91.2023.02.15.01.26.41; Wed, 15 Feb 2023 01:26:53 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233858AbjBOJYH (ORCPT <rfc822;tebrre53rla2o@gmail.com> + 99 others); Wed, 15 Feb 2023 04:24:07 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35550 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233835AbjBOJYD (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Wed, 15 Feb 2023 04:24:03 -0500 Received: from loongson.cn (mail.loongson.cn [114.242.206.163]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 7C0A7222D1 for <linux-kernel@vger.kernel.org>; Wed, 15 Feb 2023 01:24:02 -0800 (PST) Received: from loongson.cn (unknown [113.200.148.30]) by gateway (Coremail) with SMTP id _____8DxmdmxpOxj+N8AAA--.2211S3; Wed, 15 Feb 2023 17:24:01 +0800 (CST) Received: from localhost.localdomain (unknown [113.200.148.30]) by localhost.localdomain (Coremail) with SMTP id AQAAf8Bxjb6upOxjWsczAA--.34752S4; Wed, 15 Feb 2023 17:24:00 +0800 (CST) From: Qing Zhang <zhangqing@loongson.cn> To: Huacai Chen <chenhuacai@kernel.org>, Oleg Nesterov <oleg@redhat.com>, WANG Xuerui <kernel@xen0n.name> Cc: Jiaxun Yang <jiaxun.yang@flygoat.com>, loongarch@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH v3 2/3] LoongArch: Add ptrace single step support Date: Wed, 15 Feb 2023 17:23:57 +0800 Message-Id: <20230215092358.30368-3-zhangqing@loongson.cn> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20230215092358.30368-1-zhangqing@loongson.cn> References: <20230215092358.30368-1-zhangqing@loongson.cn> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-CM-TRANSID: AQAAf8Bxjb6upOxjWsczAA--.34752S4 X-CM-SenderInfo: x2kd0wptlqwqxorr0wxvrqhubq/ X-Coremail-Antispam: 1Uk129KBjvJXoWxKrykuFWUWrWfAFW5tr4kXrb_yoWxtF1xpF 9xCr1kGr4rKr1xWFnxJa1UZr13Jw1kW3y2gFySyaySywnrtryrXr1vkryDZF4rA3yjga4I gr90gw4agF4UXaUanT9S1TB71UUUUjUqnTZGkaVYY2UrUUUUj1kv1TuYvTs0mT0YCTnIWj qI5I8CrVACY4xI64kE6c02F40Ex7xfYxn0WfASr-VFAUDa7-sFnT9fnUUIcSsGvfJTRUUU b3AYFVCjjxCrM7AC8VAFwI0_Jr0_Gr1l1xkIjI8I6I8E6xAIw20EY4v20xvaj40_Wr0E3s 1l1IIY67AEw4v_JFv_Jw1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0rcxSw2x7M28EF7xv wVC0I7IYx2IY67AKxVW7JVWDJwA2z4x0Y4vE2Ix0cI8IcVCY1x0267AKxVW8JVWxJwA2z4 x0Y4vEx4A2jsIE14v26r4UJVWxJr1l84ACjcxK6I8E87Iv6xkF7I0E14v26r4UJVWxJr1l n4kS14v26r1Y6r17M2AIxVAIcxkEcVAq07x20xvEncxIr21l57IF6xkI12xvs2x26I8E6x ACxx1l5I8CrVACY4xI64kE6c02F40Ex7xfMcIj6xIIjxv20xvE14v26r1q6rW5McIj6I8E 87Iv67AKxVW8JVWxJwAm72CE4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41l42xK82 IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1l4IxYO2xFxVAFwI0_Jrv_JF1lx2Iq xVAqx4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r 126r1DMIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Xr0_Ar1lIxAIcVC0I7IYx2IY 6xkF7I0E14v26r4j6F4UMIIF0xvE42xK8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87Iv67 AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I0E14v26r4j6r4UJbIYCTnIWIevJa73UjIFyTuY vjxUFc_-DUUUU X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,SPF_HELO_PASS, SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1757888605301031009?= X-GMAIL-MSGID: =?utf-8?q?1757888605301031009?= |
Series |
LoongArch: Add hardware breakpoints/watchpoints support
|
|
Commit Message
Qing Zhang
Feb. 15, 2023, 9:23 a.m. UTC
Use the generic ptrace_resume code for PTRACE_SYSCALL, PTRACE_CONT,
PTRACE_KILL and PTRACE_SINGLESTEP. This implies defining
arch_has_single_step in and implementing the
user_enable_single_step and user_disable_single_step functions.
LongArch has no hardware single-step register. the hardware single-step
function multiplex fetch instruction watchpoint(FWPS) and specifies that
the next instruction must trigger the watch exception by setting the mask bit.
Signed-off-by: Qing Zhang <zhangqing@loongson.cn>
---
arch/loongarch/include/asm/processor.h | 3 ++
arch/loongarch/include/asm/ptrace.h | 2 +
arch/loongarch/kernel/hw_breakpoint.c | 35 +++++++++++--
arch/loongarch/kernel/ptrace.c | 68 ++++++++++++++++++++++++++
arch/loongarch/kernel/traps.c | 20 ++++++--
5 files changed, 120 insertions(+), 8 deletions(-)
Comments
On 2023-02-15 17:23, Qing Zhang wrote: > Use the generic ptrace_resume code for PTRACE_SYSCALL, PTRACE_CONT, > PTRACE_KILL and PTRACE_SINGLESTEP. This implies defining > arch_has_single_step in and implementing the > user_enable_single_step and user_disable_single_step functions. > > LongArch has no hardware single-step register. the hardware single-step > function multiplex fetch instruction watchpoint(FWPS) and specifies that > the next instruction must trigger the watch exception by setting the mask bit. > > Signed-off-by: Qing Zhang <zhangqing@loongson.cn> > --- > arch/loongarch/include/asm/processor.h | 3 ++ > arch/loongarch/include/asm/ptrace.h | 2 + > arch/loongarch/kernel/hw_breakpoint.c | 35 +++++++++++-- > arch/loongarch/kernel/ptrace.c | 68 ++++++++++++++++++++++++++ > arch/loongarch/kernel/traps.c | 20 ++++++-- > 5 files changed, 120 insertions(+), 8 deletions(-) > > diff --git a/arch/loongarch/include/asm/processor.h b/arch/loongarch/include/asm/processor.h > index db060c5a976f..3ea0f1910c23 100644 > --- a/arch/loongarch/include/asm/processor.h > +++ b/arch/loongarch/include/asm/processor.h > @@ -131,6 +131,9 @@ struct thread_struct { > struct perf_event *hbp_break[LOONGARCH_MAX_BRP]; > struct perf_event *hbp_watch[LOONGARCH_MAX_WRP]; > > + /* Used by ptrace single_step */ > + unsigned long single_step; > + > /* > * FPU & vector registers, must be at last because > * they are conditionally copied at fork(). > diff --git a/arch/loongarch/include/asm/ptrace.h b/arch/loongarch/include/asm/ptrace.h > index 58596c4f8a0f..66a0e6c480a3 100644 > --- a/arch/loongarch/include/asm/ptrace.h > +++ b/arch/loongarch/include/asm/ptrace.h > @@ -150,4 +150,6 @@ static inline void user_stack_pointer_set(struct pt_regs *regs, > regs->regs[3] = val; > } > > +#define arch_has_single_step() (1) > + > #endif /* _ASM_PTRACE_H */ > diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c > index 6431cd319c32..94967b887d92 100644 > --- a/arch/loongarch/kernel/hw_breakpoint.c > +++ b/arch/loongarch/kernel/hw_breakpoint.c > @@ -153,6 +153,22 @@ static int hw_breakpoint_slot_setup(struct perf_event **slots, int max_slots, > */ > void flush_ptrace_hw_breakpoint(struct task_struct *tsk) > { > + int i; > + struct thread_struct *t = &tsk->thread; > + > + for (i = 0; i < LOONGARCH_MAX_BRP; i++) { > + if (t->hbp_break[i]) { > + unregister_hw_breakpoint(t->hbp_break[i]); > + t->hbp_break[i] = NULL; > + } > + } > + > + for (i = 0; i < LOONGARCH_MAX_WRP; i++) { > + if (t->hbp_watch[i]) { > + unregister_hw_breakpoint(t->hbp_watch[i]); > + t->hbp_watch[i] = NULL; > + } > + } > } > > void ptrace_hw_copy_thread(struct task_struct *tsk) > @@ -498,11 +514,20 @@ arch_initcall(arch_hw_breakpoint_init); > void hw_breakpoint_thread_switch(struct task_struct *next) > { > struct pt_regs *regs = task_pt_regs(next); > - > - /* Update breakpoints */ > - update_bp_registers(regs, 1, 0); > - /* Update watchpoints */ > - update_bp_registers(regs, 1, 1); > + u64 addr, mask; > + > + if (test_bit(TIF_SINGLESTEP, &task_thread_info(next)->flags)) { > + addr = read_wb_reg(CSR_CFG_ADDR, 0, 0); > + mask = read_wb_reg(CSR_CFG_MASK, 0, 0); > + if ((task_pt_regs(next)->csr_era & ~mask) == (addr & ~mask)) > + csr_write32(0x10000, LOONGARCH_CSR_FWPS); > + regs->csr_prmd |= CSR_PRMD_PWE; > + } else { > + /* Update breakpoints */ > + update_bp_registers(regs, 1, 0); > + /* Update watchpoints */ > + update_bp_registers(regs, 1, 1); > + } > } > > void hw_breakpoint_pmu_read(struct perf_event *bp) > diff --git a/arch/loongarch/kernel/ptrace.c b/arch/loongarch/kernel/ptrace.c > index bee4194177fd..52a3ee4366f4 100644 > --- a/arch/loongarch/kernel/ptrace.c > +++ b/arch/loongarch/kernel/ptrace.c > @@ -20,6 +20,7 @@ > #include <linux/context_tracking.h> > #include <linux/elf.h> > #include <linux/errno.h> > +#include <linux/hw_breakpoint.h> > #include <linux/mm.h> > #include <linux/ptrace.h> > #include <linux/regset.h> > @@ -30,6 +31,7 @@ > #include <linux/stddef.h> > #include <linux/seccomp.h> > #include <linux/uaccess.h> > +#include <linux/thread_info.h> > > #include <asm/byteorder.h> > #include <asm/cpu.h> > @@ -39,6 +41,7 @@ > #include <asm/page.h> > #include <asm/pgtable.h> > #include <asm/processor.h> > +#include <asm/ptrace.h> > #include <asm/reg.h> > #include <asm/syscall.h> > > @@ -541,3 +544,68 @@ long arch_ptrace(struct task_struct *child, long request, > > return ret; > } > + > +void ptrace_triggered(struct perf_event *bp, > + struct perf_sample_data *data, struct pt_regs *regs) > +{ > + struct perf_event_attr attr; > + > + attr = bp->attr; > + attr.disabled = true; > + modify_user_hw_breakpoint(bp, &attr); > +} > + > +static int set_single_step(struct task_struct *tsk, unsigned long addr) > +{ > + struct thread_struct *thread = &tsk->thread; > + struct perf_event *bp; > + struct perf_event_attr attr; > + struct arch_hw_breakpoint *info; > + > + bp = thread->hbp_break[0]; > + if (!bp) { > + ptrace_breakpoint_init(&attr); > + > + attr.bp_addr = addr; > + attr.bp_len = HW_BREAKPOINT_LEN_8; > + attr.bp_type = HW_BREAKPOINT_X; > + > + bp = register_user_hw_breakpoint(&attr, ptrace_triggered, > + NULL, tsk); > + if (IS_ERR(bp)) > + return PTR_ERR(bp); > + > + thread->hbp_break[0] = bp; > + } else { > + int err; > + > + attr = bp->attr; > + attr.bp_addr = addr; > + /* reenable breakpoint */ > + attr.disabled = false; > + err = modify_user_hw_breakpoint(bp, &attr); > + if (unlikely(err)) > + return err; > + > + csr_write64(attr.bp_addr, LOONGARCH_CSR_IB0ADDR); > + } > + info = counter_arch_bp(bp); > + info->mask = 0xffffffffffff; > + > + return 0; > +} > + > +/* ptrace API */ > +void user_enable_single_step(struct task_struct *task) > +{ > + struct thread_info *ti = task_thread_info(task); > + > + set_single_step(task, task_pt_regs(task)->csr_era); > + task->thread.single_step = task_pt_regs(task)->csr_era; > + set_ti_thread_flag(ti, TIF_SINGLESTEP); > +} > + > +void user_disable_single_step(struct task_struct *task) > +{ > + clear_tsk_thread_flag(task, TIF_SINGLESTEP); > +} > diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c > index 2b133079e0f3..56d7d076153c 100644 > --- a/arch/loongarch/kernel/traps.c > +++ b/arch/loongarch/kernel/traps.c > @@ -511,9 +511,23 @@ asmlinkage void noinstr do_watch(struct pt_regs *regs) > #ifdef CONFIG_HAVE_HW_BREAKPOINT > irqentry_state_t state = irqentry_enter(regs); > > - breakpoint_handler(regs); > - watchpoint_handler(regs); > - force_sig(SIGTRAP); > + if (test_tsk_thread_flag(current, TIF_SINGLESTEP)) { > + int llbit = (csr_read32(LOONGARCH_CSR_LLBCTL) & 0x1); > + unsigned long pc = regs->csr_era; > + > + if (llbit) { Hi, Qing, It should be noted here. When the ll-sc combo is encountered, it is regarded as an single instruction. So donnot clear llbit and reset CSR.FWPS.Skip until the llsc execution is completed. > + csr_write32(0x10000, LOONGARCH_CSR_FWPS); > + csr_write32(0x4, LOONGARCH_CSR_LLBCTL); > + } else if (pc == current->thread.single_step) { Note here as well. Because 3A5000 has a strange hardware issue that certain insns are occasionally not skipped when CSR.FWPS.Skip is set, such as fld.d/fst.d. Singlestep needs compare whether the csr_era is equal to the value of singlestep which last time set, as in most case they should be not equal. And for this condition expression, some potentially strange insns may cause bugs. For example, "b 0" or "jr rd" where rd is equal to its PC will cause cannot stop the singlestep. These insns is so strange that we did not consider in OW. However, I think we should consider this case for robustness in upstream. It is better to use macros avoid direct numbers when csr_write* is invoked. Thanks, Jinyang > + csr_write32(0x10000, LOONGARCH_CSR_FWPS); > + } else { > + force_sig(SIGTRAP); > + } > + } else { > + breakpoint_handler(regs); > + watchpoint_handler(regs); > + force_sig(SIGTRAP); > + } > > irqentry_exit(regs, state); > #endif
On 02/16/2023 10:07 AM, Jinyang He wrote: > > On 2023-02-15 17:23, Qing Zhang wrote: >> Use the generic ptrace_resume code for PTRACE_SYSCALL, PTRACE_CONT, >> PTRACE_KILL and PTRACE_SINGLESTEP. This implies defining >> arch_has_single_step in and implementing the >> user_enable_single_step and user_disable_single_step functions. >> >> LongArch has no hardware single-step register. the hardware single-step >> function multiplex fetch instruction watchpoint(FWPS) and specifies that >> the next instruction must trigger the watch exception by setting the >> mask bit. >> >> Signed-off-by: Qing Zhang <zhangqing@loongson.cn> >> --- >> arch/loongarch/include/asm/processor.h | 3 ++ >> arch/loongarch/include/asm/ptrace.h | 2 + >> arch/loongarch/kernel/hw_breakpoint.c | 35 +++++++++++-- >> arch/loongarch/kernel/ptrace.c | 68 ++++++++++++++++++++++++++ >> arch/loongarch/kernel/traps.c | 20 ++++++-- >> 5 files changed, 120 insertions(+), 8 deletions(-) >> >> diff --git a/arch/loongarch/include/asm/processor.h >> b/arch/loongarch/include/asm/processor.h >> index db060c5a976f..3ea0f1910c23 100644 >> --- a/arch/loongarch/include/asm/processor.h >> +++ b/arch/loongarch/include/asm/processor.h >> @@ -131,6 +131,9 @@ struct thread_struct { >> struct perf_event *hbp_break[LOONGARCH_MAX_BRP]; >> struct perf_event *hbp_watch[LOONGARCH_MAX_WRP]; >> + /* Used by ptrace single_step */ >> + unsigned long single_step; >> + >> /* >> * FPU & vector registers, must be at last because >> * they are conditionally copied at fork(). >> diff --git a/arch/loongarch/include/asm/ptrace.h >> b/arch/loongarch/include/asm/ptrace.h >> index 58596c4f8a0f..66a0e6c480a3 100644 >> --- a/arch/loongarch/include/asm/ptrace.h >> +++ b/arch/loongarch/include/asm/ptrace.h >> @@ -150,4 +150,6 @@ static inline void user_stack_pointer_set(struct >> pt_regs *regs, >> regs->regs[3] = val; >> } >> +#define arch_has_single_step() (1) >> + >> #endif /* _ASM_PTRACE_H */ >> diff --git a/arch/loongarch/kernel/hw_breakpoint.c >> b/arch/loongarch/kernel/hw_breakpoint.c >> index 6431cd319c32..94967b887d92 100644 >> --- a/arch/loongarch/kernel/hw_breakpoint.c >> +++ b/arch/loongarch/kernel/hw_breakpoint.c >> @@ -153,6 +153,22 @@ static int hw_breakpoint_slot_setup(struct >> perf_event **slots, int max_slots, >> */ >> void flush_ptrace_hw_breakpoint(struct task_struct *tsk) >> { >> + int i; >> + struct thread_struct *t = &tsk->thread; >> + >> + for (i = 0; i < LOONGARCH_MAX_BRP; i++) { >> + if (t->hbp_break[i]) { >> + unregister_hw_breakpoint(t->hbp_break[i]); >> + t->hbp_break[i] = NULL; >> + } >> + } >> + >> + for (i = 0; i < LOONGARCH_MAX_WRP; i++) { >> + if (t->hbp_watch[i]) { >> + unregister_hw_breakpoint(t->hbp_watch[i]); >> + t->hbp_watch[i] = NULL; >> + } >> + } >> } >> void ptrace_hw_copy_thread(struct task_struct *tsk) >> @@ -498,11 +514,20 @@ arch_initcall(arch_hw_breakpoint_init); >> void hw_breakpoint_thread_switch(struct task_struct *next) >> { >> struct pt_regs *regs = task_pt_regs(next); >> - >> - /* Update breakpoints */ >> - update_bp_registers(regs, 1, 0); >> - /* Update watchpoints */ >> - update_bp_registers(regs, 1, 1); >> + u64 addr, mask; >> + >> + if (test_bit(TIF_SINGLESTEP, &task_thread_info(next)->flags)) { >> + addr = read_wb_reg(CSR_CFG_ADDR, 0, 0); >> + mask = read_wb_reg(CSR_CFG_MASK, 0, 0); >> + if ((task_pt_regs(next)->csr_era & ~mask) == (addr & ~mask)) >> + csr_write32(0x10000, LOONGARCH_CSR_FWPS); >> + regs->csr_prmd |= CSR_PRMD_PWE; >> + } else { >> + /* Update breakpoints */ >> + update_bp_registers(regs, 1, 0); >> + /* Update watchpoints */ >> + update_bp_registers(regs, 1, 1); >> + } >> } >> void hw_breakpoint_pmu_read(struct perf_event *bp) >> diff --git a/arch/loongarch/kernel/ptrace.c >> b/arch/loongarch/kernel/ptrace.c >> index bee4194177fd..52a3ee4366f4 100644 >> --- a/arch/loongarch/kernel/ptrace.c >> +++ b/arch/loongarch/kernel/ptrace.c >> @@ -20,6 +20,7 @@ >> #include <linux/context_tracking.h> >> #include <linux/elf.h> >> #include <linux/errno.h> >> +#include <linux/hw_breakpoint.h> >> #include <linux/mm.h> >> #include <linux/ptrace.h> >> #include <linux/regset.h> >> @@ -30,6 +31,7 @@ >> #include <linux/stddef.h> >> #include <linux/seccomp.h> >> #include <linux/uaccess.h> >> +#include <linux/thread_info.h> >> #include <asm/byteorder.h> >> #include <asm/cpu.h> >> @@ -39,6 +41,7 @@ >> #include <asm/page.h> >> #include <asm/pgtable.h> >> #include <asm/processor.h> >> +#include <asm/ptrace.h> >> #include <asm/reg.h> >> #include <asm/syscall.h> >> @@ -541,3 +544,68 @@ long arch_ptrace(struct task_struct *child, >> long request, >> return ret; >> } >> + >> +void ptrace_triggered(struct perf_event *bp, >> + struct perf_sample_data *data, struct pt_regs *regs) >> +{ >> + struct perf_event_attr attr; >> + >> + attr = bp->attr; >> + attr.disabled = true; >> + modify_user_hw_breakpoint(bp, &attr); >> +} >> + >> +static int set_single_step(struct task_struct *tsk, unsigned long addr) >> +{ >> + struct thread_struct *thread = &tsk->thread; >> + struct perf_event *bp; >> + struct perf_event_attr attr; >> + struct arch_hw_breakpoint *info; >> + >> + bp = thread->hbp_break[0]; >> + if (!bp) { >> + ptrace_breakpoint_init(&attr); >> + >> + attr.bp_addr = addr; >> + attr.bp_len = HW_BREAKPOINT_LEN_8; >> + attr.bp_type = HW_BREAKPOINT_X; >> + >> + bp = register_user_hw_breakpoint(&attr, ptrace_triggered, >> + NULL, tsk); >> + if (IS_ERR(bp)) >> + return PTR_ERR(bp); >> + >> + thread->hbp_break[0] = bp; >> + } else { >> + int err; >> + >> + attr = bp->attr; >> + attr.bp_addr = addr; >> + /* reenable breakpoint */ >> + attr.disabled = false; >> + err = modify_user_hw_breakpoint(bp, &attr); >> + if (unlikely(err)) >> + return err; >> + >> + csr_write64(attr.bp_addr, LOONGARCH_CSR_IB0ADDR); >> + } >> + info = counter_arch_bp(bp); >> + info->mask = 0xffffffffffff; >> + >> + return 0; >> +} >> + >> +/* ptrace API */ >> +void user_enable_single_step(struct task_struct *task) >> +{ >> + struct thread_info *ti = task_thread_info(task); >> + >> + set_single_step(task, task_pt_regs(task)->csr_era); >> + task->thread.single_step = task_pt_regs(task)->csr_era; >> + set_ti_thread_flag(ti, TIF_SINGLESTEP); >> +} >> + >> +void user_disable_single_step(struct task_struct *task) >> +{ >> + clear_tsk_thread_flag(task, TIF_SINGLESTEP); >> +} >> diff --git a/arch/loongarch/kernel/traps.c >> b/arch/loongarch/kernel/traps.c >> index 2b133079e0f3..56d7d076153c 100644 >> --- a/arch/loongarch/kernel/traps.c >> +++ b/arch/loongarch/kernel/traps.c >> @@ -511,9 +511,23 @@ asmlinkage void noinstr do_watch(struct pt_regs >> *regs) >> #ifdef CONFIG_HAVE_HW_BREAKPOINT >> irqentry_state_t state = irqentry_enter(regs); >> - breakpoint_handler(regs); >> - watchpoint_handler(regs); >> - force_sig(SIGTRAP); >> + if (test_tsk_thread_flag(current, TIF_SINGLESTEP)) { >> + int llbit = (csr_read32(LOONGARCH_CSR_LLBCTL) & 0x1); >> + unsigned long pc = regs->csr_era; >> + >> + if (llbit) { > > Hi, Qing, > > > It should be noted here. When the ll-sc combo is encountered, it is > regarded as an single instruction. So donnot clear llbit and reset > CSR.FWPS.Skip until the llsc execution is completed. > >> + csr_write32(0x10000, LOONGARCH_CSR_FWPS); >> + csr_write32(0x4, LOONGARCH_CSR_LLBCTL); >> + } else if (pc == current->thread.single_step) { > Note here as well. Because 3A5000 has a strange hardware issue that > certain insns are occasionally not skipped when CSR.FWPS.Skip is set, > such as fld.d/fst.d. Singlestep needs compare whether the csr_era is > equal to the value of singlestep which last time set, as in most case > > they should be not equal. BTW, I prefer to separate this special processing from this patch (for example, add two patchs in this series, add special processing of instructions such as LL-SC, and FLD.D/FST.D, etc.), and add corresponding test cases to describe the phenomenon and reason, this is conducive to everyone's understanding of the code. > > > And for this condition expression, some potentially strange insns may > cause bugs. For example, "b 0" or "jr rd" where rd is equal to its PC > will cause cannot stop the singlestep. These insns is so strange that > we did not consider in OW. However, I think we should consider this > case for robustness in upstream. > I don't know if there will be instructions like "b 0" or "jr rd (rd = pc)" in the executable file after linking? Youling. > It is better to use macros avoid direct numbers when csr_write* is invoked. > > > Thanks, > > Jinyang > >> + csr_write32(0x10000, LOONGARCH_CSR_FWPS); >> + } else { >> + force_sig(SIGTRAP); >> + } >> + } else { >> + breakpoint_handler(regs); >> + watchpoint_handler(regs); >> + force_sig(SIGTRAP); >> + } >> irqentry_exit(regs, state); >> #endif >
On 2023-02-16 11:13, Youling Tang wrote: > > > On 02/16/2023 10:07 AM, Jinyang He wrote: >> >> On 2023-02-15 17:23, Qing Zhang wrote: >>> Use the generic ptrace_resume code for PTRACE_SYSCALL, PTRACE_CONT, >>> PTRACE_KILL and PTRACE_SINGLESTEP. This implies defining >>> arch_has_single_step in and implementing the >>> user_enable_single_step and user_disable_single_step functions. >>> >>> LongArch has no hardware single-step register. the hardware single-step >>> function multiplex fetch instruction watchpoint(FWPS) and specifies >>> that >>> the next instruction must trigger the watch exception by setting the >>> mask bit. >>> >>> Signed-off-by: Qing Zhang <zhangqing@loongson.cn> >>> --- >>> arch/loongarch/include/asm/processor.h | 3 ++ >>> arch/loongarch/include/asm/ptrace.h | 2 + >>> arch/loongarch/kernel/hw_breakpoint.c | 35 +++++++++++-- >>> arch/loongarch/kernel/ptrace.c | 68 >>> ++++++++++++++++++++++++++ >>> arch/loongarch/kernel/traps.c | 20 ++++++-- >>> 5 files changed, 120 insertions(+), 8 deletions(-) >>> >>> diff --git a/arch/loongarch/include/asm/processor.h >>> b/arch/loongarch/include/asm/processor.h >>> index db060c5a976f..3ea0f1910c23 100644 >>> --- a/arch/loongarch/include/asm/processor.h >>> +++ b/arch/loongarch/include/asm/processor.h >>> @@ -131,6 +131,9 @@ struct thread_struct { >>> struct perf_event *hbp_break[LOONGARCH_MAX_BRP]; >>> struct perf_event *hbp_watch[LOONGARCH_MAX_WRP]; >>> + /* Used by ptrace single_step */ >>> + unsigned long single_step; >>> + >>> /* >>> * FPU & vector registers, must be at last because >>> * they are conditionally copied at fork(). >>> diff --git a/arch/loongarch/include/asm/ptrace.h >>> b/arch/loongarch/include/asm/ptrace.h >>> index 58596c4f8a0f..66a0e6c480a3 100644 >>> --- a/arch/loongarch/include/asm/ptrace.h >>> +++ b/arch/loongarch/include/asm/ptrace.h >>> @@ -150,4 +150,6 @@ static inline void user_stack_pointer_set(struct >>> pt_regs *regs, >>> regs->regs[3] = val; >>> } >>> +#define arch_has_single_step() (1) >>> + >>> #endif /* _ASM_PTRACE_H */ >>> diff --git a/arch/loongarch/kernel/hw_breakpoint.c >>> b/arch/loongarch/kernel/hw_breakpoint.c >>> index 6431cd319c32..94967b887d92 100644 >>> --- a/arch/loongarch/kernel/hw_breakpoint.c >>> +++ b/arch/loongarch/kernel/hw_breakpoint.c >>> @@ -153,6 +153,22 @@ static int hw_breakpoint_slot_setup(struct >>> perf_event **slots, int max_slots, >>> */ >>> void flush_ptrace_hw_breakpoint(struct task_struct *tsk) >>> { >>> + int i; >>> + struct thread_struct *t = &tsk->thread; >>> + >>> + for (i = 0; i < LOONGARCH_MAX_BRP; i++) { >>> + if (t->hbp_break[i]) { >>> + unregister_hw_breakpoint(t->hbp_break[i]); >>> + t->hbp_break[i] = NULL; >>> + } >>> + } >>> + >>> + for (i = 0; i < LOONGARCH_MAX_WRP; i++) { >>> + if (t->hbp_watch[i]) { >>> + unregister_hw_breakpoint(t->hbp_watch[i]); >>> + t->hbp_watch[i] = NULL; >>> + } >>> + } >>> } >>> void ptrace_hw_copy_thread(struct task_struct *tsk) >>> @@ -498,11 +514,20 @@ arch_initcall(arch_hw_breakpoint_init); >>> void hw_breakpoint_thread_switch(struct task_struct *next) >>> { >>> struct pt_regs *regs = task_pt_regs(next); >>> - >>> - /* Update breakpoints */ >>> - update_bp_registers(regs, 1, 0); >>> - /* Update watchpoints */ >>> - update_bp_registers(regs, 1, 1); >>> + u64 addr, mask; >>> + >>> + if (test_bit(TIF_SINGLESTEP, &task_thread_info(next)->flags)) { >>> + addr = read_wb_reg(CSR_CFG_ADDR, 0, 0); >>> + mask = read_wb_reg(CSR_CFG_MASK, 0, 0); >>> + if ((task_pt_regs(next)->csr_era & ~mask) == (addr & ~mask)) >>> + csr_write32(0x10000, LOONGARCH_CSR_FWPS); >>> + regs->csr_prmd |= CSR_PRMD_PWE; >>> + } else { >>> + /* Update breakpoints */ >>> + update_bp_registers(regs, 1, 0); >>> + /* Update watchpoints */ >>> + update_bp_registers(regs, 1, 1); >>> + } >>> } >>> void hw_breakpoint_pmu_read(struct perf_event *bp) >>> diff --git a/arch/loongarch/kernel/ptrace.c >>> b/arch/loongarch/kernel/ptrace.c >>> index bee4194177fd..52a3ee4366f4 100644 >>> --- a/arch/loongarch/kernel/ptrace.c >>> +++ b/arch/loongarch/kernel/ptrace.c >>> @@ -20,6 +20,7 @@ >>> #include <linux/context_tracking.h> >>> #include <linux/elf.h> >>> #include <linux/errno.h> >>> +#include <linux/hw_breakpoint.h> >>> #include <linux/mm.h> >>> #include <linux/ptrace.h> >>> #include <linux/regset.h> >>> @@ -30,6 +31,7 @@ >>> #include <linux/stddef.h> >>> #include <linux/seccomp.h> >>> #include <linux/uaccess.h> >>> +#include <linux/thread_info.h> >>> #include <asm/byteorder.h> >>> #include <asm/cpu.h> >>> @@ -39,6 +41,7 @@ >>> #include <asm/page.h> >>> #include <asm/pgtable.h> >>> #include <asm/processor.h> >>> +#include <asm/ptrace.h> >>> #include <asm/reg.h> >>> #include <asm/syscall.h> >>> @@ -541,3 +544,68 @@ long arch_ptrace(struct task_struct *child, >>> long request, >>> return ret; >>> } >>> + >>> +void ptrace_triggered(struct perf_event *bp, >>> + struct perf_sample_data *data, struct pt_regs *regs) >>> +{ >>> + struct perf_event_attr attr; >>> + >>> + attr = bp->attr; >>> + attr.disabled = true; >>> + modify_user_hw_breakpoint(bp, &attr); >>> +} >>> + >>> +static int set_single_step(struct task_struct *tsk, unsigned long >>> addr) >>> +{ >>> + struct thread_struct *thread = &tsk->thread; >>> + struct perf_event *bp; >>> + struct perf_event_attr attr; >>> + struct arch_hw_breakpoint *info; >>> + >>> + bp = thread->hbp_break[0]; >>> + if (!bp) { >>> + ptrace_breakpoint_init(&attr); >>> + >>> + attr.bp_addr = addr; >>> + attr.bp_len = HW_BREAKPOINT_LEN_8; >>> + attr.bp_type = HW_BREAKPOINT_X; >>> + >>> + bp = register_user_hw_breakpoint(&attr, ptrace_triggered, >>> + NULL, tsk); >>> + if (IS_ERR(bp)) >>> + return PTR_ERR(bp); >>> + >>> + thread->hbp_break[0] = bp; >>> + } else { >>> + int err; >>> + >>> + attr = bp->attr; >>> + attr.bp_addr = addr; >>> + /* reenable breakpoint */ >>> + attr.disabled = false; >>> + err = modify_user_hw_breakpoint(bp, &attr); >>> + if (unlikely(err)) >>> + return err; >>> + >>> + csr_write64(attr.bp_addr, LOONGARCH_CSR_IB0ADDR); >>> + } >>> + info = counter_arch_bp(bp); >>> + info->mask = 0xffffffffffff; >>> + >>> + return 0; >>> +} >>> + >>> +/* ptrace API */ >>> +void user_enable_single_step(struct task_struct *task) >>> +{ >>> + struct thread_info *ti = task_thread_info(task); >>> + >>> + set_single_step(task, task_pt_regs(task)->csr_era); >>> + task->thread.single_step = task_pt_regs(task)->csr_era; >>> + set_ti_thread_flag(ti, TIF_SINGLESTEP); >>> +} >>> + >>> +void user_disable_single_step(struct task_struct *task) >>> +{ >>> + clear_tsk_thread_flag(task, TIF_SINGLESTEP); >>> +} >>> diff --git a/arch/loongarch/kernel/traps.c >>> b/arch/loongarch/kernel/traps.c >>> index 2b133079e0f3..56d7d076153c 100644 >>> --- a/arch/loongarch/kernel/traps.c >>> +++ b/arch/loongarch/kernel/traps.c >>> @@ -511,9 +511,23 @@ asmlinkage void noinstr do_watch(struct pt_regs >>> *regs) >>> #ifdef CONFIG_HAVE_HW_BREAKPOINT >>> irqentry_state_t state = irqentry_enter(regs); >>> - breakpoint_handler(regs); >>> - watchpoint_handler(regs); >>> - force_sig(SIGTRAP); >>> + if (test_tsk_thread_flag(current, TIF_SINGLESTEP)) { >>> + int llbit = (csr_read32(LOONGARCH_CSR_LLBCTL) & 0x1); >>> + unsigned long pc = regs->csr_era; >>> + >>> + if (llbit) { >> >> Hi, Qing, >> >> >> It should be noted here. When the ll-sc combo is encountered, it is >> regarded as an single instruction. So donnot clear llbit and reset >> CSR.FWPS.Skip until the llsc execution is completed. >> >>> + csr_write32(0x10000, LOONGARCH_CSR_FWPS); >>> + csr_write32(0x4, LOONGARCH_CSR_LLBCTL); >>> + } else if (pc == current->thread.single_step) { >> Note here as well. Because 3A5000 has a strange hardware issue that >> certain insns are occasionally not skipped when CSR.FWPS.Skip is set, >> such as fld.d/fst.d. Singlestep needs compare whether the csr_era is >> equal to the value of singlestep which last time set, as in most case >> >> they should be not equal. > > BTW, I prefer to separate this special processing from this patch (for > example, add two patchs in this series, add special processing of > instructions such as LL-SC, and FLD.D/FST.D, etc.), and add > corresponding test cases to describe the phenomenon and reason, this > is conducive to everyone's understanding of the code. Agree. > >> >> >> And for this condition expression, some potentially strange insns may >> cause bugs. For example, "b 0" or "jr rd" where rd is equal to its PC >> will cause cannot stop the singlestep. These insns is so strange that >> we did not consider in OW. However, I think we should consider this >> case for robustness in upstream. >> > > I don't know if there will be instructions like "b 0" or "jr rd (rd = > pc)" in the executable file after linking? So I said that is strange, and it can be fatal if happened. :-) > > Youling. > >> It is better to use macros avoid direct numbers when csr_write* is >> invoked. >> >> >> Thanks, >> >> Jinyang >> >>> + csr_write32(0x10000, LOONGARCH_CSR_FWPS); >>> + } else { >>> + force_sig(SIGTRAP); >>> + } >>> + } else { >>> + breakpoint_handler(regs); >>> + watchpoint_handler(regs); >>> + force_sig(SIGTRAP); >>> + } >>> irqentry_exit(regs, state); >>> #endif >> >
On 2023/2/16 11:13, Youling Tang wrote: > > > On 02/16/2023 10:07 AM, Jinyang He wrote: >> >> On 2023-02-15 17:23, Qing Zhang wrote: >>> Use the generic ptrace_resume code for PTRACE_SYSCALL, PTRACE_CONT, >>> PTRACE_KILL and PTRACE_SINGLESTEP. This implies defining >>> arch_has_single_step in and implementing the >>> user_enable_single_step and user_disable_single_step functions. >>> >>> LongArch has no hardware single-step register. the hardware single-step >>> function multiplex fetch instruction watchpoint(FWPS) and specifies that >>> the next instruction must trigger the watch exception by setting the >>> mask bit. >>> >>> Signed-off-by: Qing Zhang <zhangqing@loongson.cn> >>> --- >>> arch/loongarch/include/asm/processor.h | 3 ++ >>> arch/loongarch/include/asm/ptrace.h | 2 + >>> arch/loongarch/kernel/hw_breakpoint.c | 35 +++++++++++-- >>> arch/loongarch/kernel/ptrace.c | 68 ++++++++++++++++++++++++++ >>> arch/loongarch/kernel/traps.c | 20 ++++++-- >>> 5 files changed, 120 insertions(+), 8 deletions(-) >>> >>> diff --git a/arch/loongarch/include/asm/processor.h >>> b/arch/loongarch/include/asm/processor.h >>> index db060c5a976f..3ea0f1910c23 100644 >>> --- a/arch/loongarch/include/asm/processor.h >>> +++ b/arch/loongarch/include/asm/processor.h >>> @@ -131,6 +131,9 @@ struct thread_struct { >>> struct perf_event *hbp_break[LOONGARCH_MAX_BRP]; >>> struct perf_event *hbp_watch[LOONGARCH_MAX_WRP]; >>> + /* Used by ptrace single_step */ >>> + unsigned long single_step; >>> + >>> /* >>> * FPU & vector registers, must be at last because >>> * they are conditionally copied at fork(). >>> diff --git a/arch/loongarch/include/asm/ptrace.h >>> b/arch/loongarch/include/asm/ptrace.h >>> index 58596c4f8a0f..66a0e6c480a3 100644 >>> --- a/arch/loongarch/include/asm/ptrace.h >>> +++ b/arch/loongarch/include/asm/ptrace.h >>> @@ -150,4 +150,6 @@ static inline void user_stack_pointer_set(struct >>> pt_regs *regs, >>> regs->regs[3] = val; >>> } >>> +#define arch_has_single_step() (1) >>> + >>> #endif /* _ASM_PTRACE_H */ >>> diff --git a/arch/loongarch/kernel/hw_breakpoint.c >>> b/arch/loongarch/kernel/hw_breakpoint.c >>> index 6431cd319c32..94967b887d92 100644 >>> --- a/arch/loongarch/kernel/hw_breakpoint.c >>> +++ b/arch/loongarch/kernel/hw_breakpoint.c >>> @@ -153,6 +153,22 @@ static int hw_breakpoint_slot_setup(struct >>> perf_event **slots, int max_slots, >>> */ >>> void flush_ptrace_hw_breakpoint(struct task_struct *tsk) >>> { >>> + int i; >>> + struct thread_struct *t = &tsk->thread; >>> + >>> + for (i = 0; i < LOONGARCH_MAX_BRP; i++) { >>> + if (t->hbp_break[i]) { >>> + unregister_hw_breakpoint(t->hbp_break[i]); >>> + t->hbp_break[i] = NULL; >>> + } >>> + } >>> + >>> + for (i = 0; i < LOONGARCH_MAX_WRP; i++) { >>> + if (t->hbp_watch[i]) { >>> + unregister_hw_breakpoint(t->hbp_watch[i]); >>> + t->hbp_watch[i] = NULL; >>> + } >>> + } >>> } >>> void ptrace_hw_copy_thread(struct task_struct *tsk) >>> @@ -498,11 +514,20 @@ arch_initcall(arch_hw_breakpoint_init); >>> void hw_breakpoint_thread_switch(struct task_struct *next) >>> { >>> struct pt_regs *regs = task_pt_regs(next); >>> - >>> - /* Update breakpoints */ >>> - update_bp_registers(regs, 1, 0); >>> - /* Update watchpoints */ >>> - update_bp_registers(regs, 1, 1); >>> + u64 addr, mask; >>> + >>> + if (test_bit(TIF_SINGLESTEP, &task_thread_info(next)->flags)) { >>> + addr = read_wb_reg(CSR_CFG_ADDR, 0, 0); >>> + mask = read_wb_reg(CSR_CFG_MASK, 0, 0); >>> + if ((task_pt_regs(next)->csr_era & ~mask) == (addr & ~mask)) >>> + csr_write32(0x10000, LOONGARCH_CSR_FWPS); >>> + regs->csr_prmd |= CSR_PRMD_PWE; >>> + } else { >>> + /* Update breakpoints */ >>> + update_bp_registers(regs, 1, 0); >>> + /* Update watchpoints */ >>> + update_bp_registers(regs, 1, 1); >>> + } >>> } >>> void hw_breakpoint_pmu_read(struct perf_event *bp) >>> diff --git a/arch/loongarch/kernel/ptrace.c >>> b/arch/loongarch/kernel/ptrace.c >>> index bee4194177fd..52a3ee4366f4 100644 >>> --- a/arch/loongarch/kernel/ptrace.c >>> +++ b/arch/loongarch/kernel/ptrace.c >>> @@ -20,6 +20,7 @@ >>> #include <linux/context_tracking.h> >>> #include <linux/elf.h> >>> #include <linux/errno.h> >>> +#include <linux/hw_breakpoint.h> >>> #include <linux/mm.h> >>> #include <linux/ptrace.h> >>> #include <linux/regset.h> >>> @@ -30,6 +31,7 @@ >>> #include <linux/stddef.h> >>> #include <linux/seccomp.h> >>> #include <linux/uaccess.h> >>> +#include <linux/thread_info.h> >>> #include <asm/byteorder.h> >>> #include <asm/cpu.h> >>> @@ -39,6 +41,7 @@ >>> #include <asm/page.h> >>> #include <asm/pgtable.h> >>> #include <asm/processor.h> >>> +#include <asm/ptrace.h> >>> #include <asm/reg.h> >>> #include <asm/syscall.h> >>> @@ -541,3 +544,68 @@ long arch_ptrace(struct task_struct *child, >>> long request, >>> return ret; >>> } >>> + >>> +void ptrace_triggered(struct perf_event *bp, >>> + struct perf_sample_data *data, struct pt_regs *regs) >>> +{ >>> + struct perf_event_attr attr; >>> + >>> + attr = bp->attr; >>> + attr.disabled = true; >>> + modify_user_hw_breakpoint(bp, &attr); >>> +} >>> + >>> +static int set_single_step(struct task_struct *tsk, unsigned long addr) >>> +{ >>> + struct thread_struct *thread = &tsk->thread; >>> + struct perf_event *bp; >>> + struct perf_event_attr attr; >>> + struct arch_hw_breakpoint *info; >>> + >>> + bp = thread->hbp_break[0]; >>> + if (!bp) { >>> + ptrace_breakpoint_init(&attr); >>> + >>> + attr.bp_addr = addr; >>> + attr.bp_len = HW_BREAKPOINT_LEN_8; >>> + attr.bp_type = HW_BREAKPOINT_X; >>> + >>> + bp = register_user_hw_breakpoint(&attr, ptrace_triggered, >>> + NULL, tsk); >>> + if (IS_ERR(bp)) >>> + return PTR_ERR(bp); >>> + >>> + thread->hbp_break[0] = bp; >>> + } else { >>> + int err; >>> + >>> + attr = bp->attr; >>> + attr.bp_addr = addr; >>> + /* reenable breakpoint */ >>> + attr.disabled = false; >>> + err = modify_user_hw_breakpoint(bp, &attr); >>> + if (unlikely(err)) >>> + return err; >>> + >>> + csr_write64(attr.bp_addr, LOONGARCH_CSR_IB0ADDR); >>> + } >>> + info = counter_arch_bp(bp); >>> + info->mask = 0xffffffffffff; >>> + >>> + return 0; >>> +} >>> + >>> +/* ptrace API */ >>> +void user_enable_single_step(struct task_struct *task) >>> +{ >>> + struct thread_info *ti = task_thread_info(task); >>> + >>> + set_single_step(task, task_pt_regs(task)->csr_era); >>> + task->thread.single_step = task_pt_regs(task)->csr_era; >>> + set_ti_thread_flag(ti, TIF_SINGLESTEP); >>> +} >>> + >>> +void user_disable_single_step(struct task_struct *task) >>> +{ >>> + clear_tsk_thread_flag(task, TIF_SINGLESTEP); >>> +} >>> diff --git a/arch/loongarch/kernel/traps.c >>> b/arch/loongarch/kernel/traps.c >>> index 2b133079e0f3..56d7d076153c 100644 >>> --- a/arch/loongarch/kernel/traps.c >>> +++ b/arch/loongarch/kernel/traps.c >>> @@ -511,9 +511,23 @@ asmlinkage void noinstr do_watch(struct pt_regs >>> *regs) >>> #ifdef CONFIG_HAVE_HW_BREAKPOINT >>> irqentry_state_t state = irqentry_enter(regs); >>> - breakpoint_handler(regs); >>> - watchpoint_handler(regs); >>> - force_sig(SIGTRAP); >>> + if (test_tsk_thread_flag(current, TIF_SINGLESTEP)) { >>> + int llbit = (csr_read32(LOONGARCH_CSR_LLBCTL) & 0x1); >>> + unsigned long pc = regs->csr_era; >>> + >>> + if (llbit) { >> >> Hi, Qing, >> >> >> It should be noted here. When the ll-sc combo is encountered, it is >> regarded as an single instruction. So donnot clear llbit and reset >> CSR.FWPS.Skip until the llsc execution is completed. >> >>> + csr_write32(0x10000, LOONGARCH_CSR_FWPS); >>> + csr_write32(0x4, LOONGARCH_CSR_LLBCTL); >>> + } else if (pc == current->thread.single_step) { >> Note here as well. Because 3A5000 has a strange hardware issue that >> certain insns are occasionally not skipped when CSR.FWPS.Skip is set, >> such as fld.d/fst.d. Singlestep needs compare whether the csr_era is >> equal to the value of singlestep which last time set, as in most case >> >> they should be not equal. > > BTW, I prefer to separate this special processing from this patch (for > example, add two patchs in this series, add special processing of > instructions such as LL-SC, and FLD.D/FST.D, etc.), and add > corresponding test cases to describe the phenomenon and reason, this > is conducive to everyone's understanding of the code. > >> >> >> And for this condition expression, some potentially strange insns may >> cause bugs. For example, "b 0" or "jr rd" where rd is equal to its PC >> will cause cannot stop the singlestep. These insns is so strange that >> we did not consider in OW. However, I think we should consider this >> case for robustness in upstream. >> > > I don't know if there will be instructions like "b 0" or "jr rd (rd = > pc)" in the executable file after linking? FWIW `jirl A, A, 0` is apparently ubiquitous in the old world: $ objdump -d ~/loongnix-sysroot/usr/lib/loongarch64-linux-gnu/libc_nonshared.a | grep -E 'jirl\s+\$(..), \$\1' 20: 4c000021 jirl $ra, $ra, 0 24: 4c000021 jirl $ra, $ra, 0 c: 4c000021 jirl $ra, $ra, 0 One of the occurrences is in __stack_chk_fail_local, guess how popular it is among the whole old world... But fortunately a quick check against my Gentoo sysroot yielded nothing, so perhaps the new world as a whole isn't going to be affected. I've checked some of the *older* (i.e. before 2022) new world sysroots I have at hand and they seemed not affected either. As for `b 0`, they are kinda already signifying some panic/abort/halt condition, at least in a few projects that I've studied/ported. But IMO the kernel shouldn't get DoS'd even if userland gets stuck in things like this, so some kind of workaround should still be necessary.
Hi, forks, On 2023/2/16 下午2:57, WANG Xuerui wrote: > On 2023/2/16 11:13, Youling Tang wrote: >> >> >> On 02/16/2023 10:07 AM, Jinyang He wrote: >>> >>> On 2023-02-15 17:23, Qing Zhang wrote: >>>> Use the generic ptrace_resume code for PTRACE_SYSCALL, PTRACE_CONT, >>>> PTRACE_KILL and PTRACE_SINGLESTEP. This implies defining >>>> arch_has_single_step in and implementing the >>>> user_enable_single_step and user_disable_single_step functions. >>>> >>>> LongArch has no hardware single-step register. the hardware single-step >>>> function multiplex fetch instruction watchpoint(FWPS) and specifies >>>> that >>>> the next instruction must trigger the watch exception by setting the >>>> mask bit. >>>> >>>> Signed-off-by: Qing Zhang <zhangqing@loongson.cn> >>>> --- >>>> arch/loongarch/include/asm/processor.h | 3 ++ >>>> arch/loongarch/include/asm/ptrace.h | 2 + >>>> arch/loongarch/kernel/hw_breakpoint.c | 35 +++++++++++-- >>>> arch/loongarch/kernel/ptrace.c | 68 >>>> ++++++++++++++++++++++++++ >>>> arch/loongarch/kernel/traps.c | 20 ++++++-- >>>> 5 files changed, 120 insertions(+), 8 deletions(-) >>>> >>>> diff --git a/arch/loongarch/include/asm/processor.h >>>> b/arch/loongarch/include/asm/processor.h >>>> index db060c5a976f..3ea0f1910c23 100644 >>>> --- a/arch/loongarch/include/asm/processor.h >>>> +++ b/arch/loongarch/include/asm/processor.h >>>> @@ -131,6 +131,9 @@ struct thread_struct { >>>> struct perf_event *hbp_break[LOONGARCH_MAX_BRP]; >>>> struct perf_event *hbp_watch[LOONGARCH_MAX_WRP]; >>>> + /* Used by ptrace single_step */ >>>> + unsigned long single_step; >>>> + >>>> /* >>>> * FPU & vector registers, must be at last because >>>> * they are conditionally copied at fork(). >>>> diff --git a/arch/loongarch/include/asm/ptrace.h >>>> b/arch/loongarch/include/asm/ptrace.h >>>> index 58596c4f8a0f..66a0e6c480a3 100644 >>>> --- a/arch/loongarch/include/asm/ptrace.h >>>> +++ b/arch/loongarch/include/asm/ptrace.h >>>> @@ -150,4 +150,6 @@ static inline void user_stack_pointer_set(struct >>>> pt_regs *regs, >>>> regs->regs[3] = val; >>>> } >>>> +#define arch_has_single_step() (1) >>>> + >>>> #endif /* _ASM_PTRACE_H */ >>>> diff --git a/arch/loongarch/kernel/hw_breakpoint.c >>>> b/arch/loongarch/kernel/hw_breakpoint.c >>>> index 6431cd319c32..94967b887d92 100644 >>>> --- a/arch/loongarch/kernel/hw_breakpoint.c >>>> +++ b/arch/loongarch/kernel/hw_breakpoint.c >>>> @@ -153,6 +153,22 @@ static int hw_breakpoint_slot_setup(struct >>>> perf_event **slots, int max_slots, >>>> */ >>>> void flush_ptrace_hw_breakpoint(struct task_struct *tsk) >>>> { >>>> + int i; >>>> + struct thread_struct *t = &tsk->thread; >>>> + >>>> + for (i = 0; i < LOONGARCH_MAX_BRP; i++) { >>>> + if (t->hbp_break[i]) { >>>> + unregister_hw_breakpoint(t->hbp_break[i]); >>>> + t->hbp_break[i] = NULL; >>>> + } >>>> + } >>>> + >>>> + for (i = 0; i < LOONGARCH_MAX_WRP; i++) { >>>> + if (t->hbp_watch[i]) { >>>> + unregister_hw_breakpoint(t->hbp_watch[i]); >>>> + t->hbp_watch[i] = NULL; >>>> + } >>>> + } >>>> } >>>> void ptrace_hw_copy_thread(struct task_struct *tsk) >>>> @@ -498,11 +514,20 @@ arch_initcall(arch_hw_breakpoint_init); >>>> void hw_breakpoint_thread_switch(struct task_struct *next) >>>> { >>>> struct pt_regs *regs = task_pt_regs(next); >>>> - >>>> - /* Update breakpoints */ >>>> - update_bp_registers(regs, 1, 0); >>>> - /* Update watchpoints */ >>>> - update_bp_registers(regs, 1, 1); >>>> + u64 addr, mask; >>>> + >>>> + if (test_bit(TIF_SINGLESTEP, &task_thread_info(next)->flags)) { >>>> + addr = read_wb_reg(CSR_CFG_ADDR, 0, 0); >>>> + mask = read_wb_reg(CSR_CFG_MASK, 0, 0); >>>> + if ((task_pt_regs(next)->csr_era & ~mask) == (addr & ~mask)) >>>> + csr_write32(0x10000, LOONGARCH_CSR_FWPS); >>>> + regs->csr_prmd |= CSR_PRMD_PWE; >>>> + } else { >>>> + /* Update breakpoints */ >>>> + update_bp_registers(regs, 1, 0); >>>> + /* Update watchpoints */ >>>> + update_bp_registers(regs, 1, 1); >>>> + } >>>> } >>>> void hw_breakpoint_pmu_read(struct perf_event *bp) >>>> diff --git a/arch/loongarch/kernel/ptrace.c >>>> b/arch/loongarch/kernel/ptrace.c >>>> index bee4194177fd..52a3ee4366f4 100644 >>>> --- a/arch/loongarch/kernel/ptrace.c >>>> +++ b/arch/loongarch/kernel/ptrace.c >>>> @@ -20,6 +20,7 @@ >>>> #include <linux/context_tracking.h> >>>> #include <linux/elf.h> >>>> #include <linux/errno.h> >>>> +#include <linux/hw_breakpoint.h> >>>> #include <linux/mm.h> >>>> #include <linux/ptrace.h> >>>> #include <linux/regset.h> >>>> @@ -30,6 +31,7 @@ >>>> #include <linux/stddef.h> >>>> #include <linux/seccomp.h> >>>> #include <linux/uaccess.h> >>>> +#include <linux/thread_info.h> >>>> #include <asm/byteorder.h> >>>> #include <asm/cpu.h> >>>> @@ -39,6 +41,7 @@ >>>> #include <asm/page.h> >>>> #include <asm/pgtable.h> >>>> #include <asm/processor.h> >>>> +#include <asm/ptrace.h> >>>> #include <asm/reg.h> >>>> #include <asm/syscall.h> >>>> @@ -541,3 +544,68 @@ long arch_ptrace(struct task_struct *child, >>>> long request, >>>> return ret; >>>> } >>>> + >>>> +void ptrace_triggered(struct perf_event *bp, >>>> + struct perf_sample_data *data, struct pt_regs *regs) >>>> +{ >>>> + struct perf_event_attr attr; >>>> + >>>> + attr = bp->attr; >>>> + attr.disabled = true; >>>> + modify_user_hw_breakpoint(bp, &attr); >>>> +} >>>> + >>>> +static int set_single_step(struct task_struct *tsk, unsigned long >>>> addr) >>>> +{ >>>> + struct thread_struct *thread = &tsk->thread; >>>> + struct perf_event *bp; >>>> + struct perf_event_attr attr; >>>> + struct arch_hw_breakpoint *info; >>>> + >>>> + bp = thread->hbp_break[0]; >>>> + if (!bp) { >>>> + ptrace_breakpoint_init(&attr); >>>> + >>>> + attr.bp_addr = addr; >>>> + attr.bp_len = HW_BREAKPOINT_LEN_8; >>>> + attr.bp_type = HW_BREAKPOINT_X; >>>> + >>>> + bp = register_user_hw_breakpoint(&attr, ptrace_triggered, >>>> + NULL, tsk); >>>> + if (IS_ERR(bp)) >>>> + return PTR_ERR(bp); >>>> + >>>> + thread->hbp_break[0] = bp; >>>> + } else { >>>> + int err; >>>> + >>>> + attr = bp->attr; >>>> + attr.bp_addr = addr; >>>> + /* reenable breakpoint */ >>>> + attr.disabled = false; >>>> + err = modify_user_hw_breakpoint(bp, &attr); >>>> + if (unlikely(err)) >>>> + return err; >>>> + >>>> + csr_write64(attr.bp_addr, LOONGARCH_CSR_IB0ADDR); >>>> + } >>>> + info = counter_arch_bp(bp); >>>> + info->mask = 0xffffffffffff; >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +/* ptrace API */ >>>> +void user_enable_single_step(struct task_struct *task) >>>> +{ >>>> + struct thread_info *ti = task_thread_info(task); >>>> + >>>> + set_single_step(task, task_pt_regs(task)->csr_era); >>>> + task->thread.single_step = task_pt_regs(task)->csr_era; >>>> + set_ti_thread_flag(ti, TIF_SINGLESTEP); >>>> +} >>>> + >>>> +void user_disable_single_step(struct task_struct *task) >>>> +{ >>>> + clear_tsk_thread_flag(task, TIF_SINGLESTEP); >>>> +} >>>> diff --git a/arch/loongarch/kernel/traps.c >>>> b/arch/loongarch/kernel/traps.c >>>> index 2b133079e0f3..56d7d076153c 100644 >>>> --- a/arch/loongarch/kernel/traps.c >>>> +++ b/arch/loongarch/kernel/traps.c >>>> @@ -511,9 +511,23 @@ asmlinkage void noinstr do_watch(struct pt_regs >>>> *regs) >>>> #ifdef CONFIG_HAVE_HW_BREAKPOINT >>>> irqentry_state_t state = irqentry_enter(regs); >>>> - breakpoint_handler(regs); >>>> - watchpoint_handler(regs); >>>> - force_sig(SIGTRAP); >>>> + if (test_tsk_thread_flag(current, TIF_SINGLESTEP)) { >>>> + int llbit = (csr_read32(LOONGARCH_CSR_LLBCTL) & 0x1); >>>> + unsigned long pc = regs->csr_era; >>>> + >>>> + if (llbit) { >>> >>> Hi, Qing, >>> >>> >>> It should be noted here. When the ll-sc combo is encountered, it is >>> regarded as an single instruction. So donnot clear llbit and reset >>> CSR.FWPS.Skip until the llsc execution is completed. >>> >>>> + csr_write32(0x10000, LOONGARCH_CSR_FWPS); >>>> + csr_write32(0x4, LOONGARCH_CSR_LLBCTL); >>>> + } else if (pc == current->thread.single_step) { >>> Note here as well. Because 3A5000 has a strange hardware issue that >>> certain insns are occasionally not skipped when CSR.FWPS.Skip is set, >>> such as fld.d/fst.d. Singlestep needs compare whether the csr_era is >>> equal to the value of singlestep which last time set, as in most case >>> >>> they should be not equal. >> >> BTW, I prefer to separate this special processing from this patch (for >> example, add two patchs in this series, add special processing of >> instructions such as LL-SC, and FLD.D/FST.D, etc.), and add >> corresponding test cases to describe the phenomenon and reason, this >> is conducive to everyone's understanding of the code. >> In order to ensure the integrity of the patch function, it is better to add comments without splitting. >>> >>> >>> And for this condition expression, some potentially strange insns may >>> cause bugs. For example, "b 0" or "jr rd" where rd is equal to its PC >>> will cause cannot stop the singlestep. These insns is so strange that >>> we did not consider in OW. However, I think we should consider this >>> case for robustness in upstream. >>> >> >> I don't know if there will be instructions like "b 0" or "jr rd (rd = >> pc)" in the executable file after linking? > FWIW `jirl A, A, 0` is apparently ubiquitous in the old world: > > $ objdump -d > ~/loongnix-sysroot/usr/lib/loongarch64-linux-gnu/libc_nonshared.a | grep > -E 'jirl\s+\$(..), \$\1' > 20: 4c000021 jirl $ra, $ra, 0 > 24: 4c000021 jirl $ra, $ra, 0 > c: 4c000021 jirl $ra, $ra, 0 > > One of the occurrences is in __stack_chk_fail_local, guess how popular > it is among the whole old world... > > But fortunately a quick check against my Gentoo sysroot yielded nothing, > so perhaps the new world as a whole isn't going to be affected. I've > checked some of the *older* (i.e. before 2022) new world sysroots I have > at hand and they seemed not affected either. > > As for `b 0`, they are kinda already signifying some panic/abort/halt > condition, at least in a few projects that I've studied/ported. > > But IMO the kernel shouldn't get DoS'd even if userland gets stuck in > things like this, so some kind of workaround should still be necessary. > Got it, the fix is already in v4. Thanks, -Qing
diff --git a/arch/loongarch/include/asm/processor.h b/arch/loongarch/include/asm/processor.h index db060c5a976f..3ea0f1910c23 100644 --- a/arch/loongarch/include/asm/processor.h +++ b/arch/loongarch/include/asm/processor.h @@ -131,6 +131,9 @@ struct thread_struct { struct perf_event *hbp_break[LOONGARCH_MAX_BRP]; struct perf_event *hbp_watch[LOONGARCH_MAX_WRP]; + /* Used by ptrace single_step */ + unsigned long single_step; + /* * FPU & vector registers, must be at last because * they are conditionally copied at fork(). diff --git a/arch/loongarch/include/asm/ptrace.h b/arch/loongarch/include/asm/ptrace.h index 58596c4f8a0f..66a0e6c480a3 100644 --- a/arch/loongarch/include/asm/ptrace.h +++ b/arch/loongarch/include/asm/ptrace.h @@ -150,4 +150,6 @@ static inline void user_stack_pointer_set(struct pt_regs *regs, regs->regs[3] = val; } +#define arch_has_single_step() (1) + #endif /* _ASM_PTRACE_H */ diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c index 6431cd319c32..94967b887d92 100644 --- a/arch/loongarch/kernel/hw_breakpoint.c +++ b/arch/loongarch/kernel/hw_breakpoint.c @@ -153,6 +153,22 @@ static int hw_breakpoint_slot_setup(struct perf_event **slots, int max_slots, */ void flush_ptrace_hw_breakpoint(struct task_struct *tsk) { + int i; + struct thread_struct *t = &tsk->thread; + + for (i = 0; i < LOONGARCH_MAX_BRP; i++) { + if (t->hbp_break[i]) { + unregister_hw_breakpoint(t->hbp_break[i]); + t->hbp_break[i] = NULL; + } + } + + for (i = 0; i < LOONGARCH_MAX_WRP; i++) { + if (t->hbp_watch[i]) { + unregister_hw_breakpoint(t->hbp_watch[i]); + t->hbp_watch[i] = NULL; + } + } } void ptrace_hw_copy_thread(struct task_struct *tsk) @@ -498,11 +514,20 @@ arch_initcall(arch_hw_breakpoint_init); void hw_breakpoint_thread_switch(struct task_struct *next) { struct pt_regs *regs = task_pt_regs(next); - - /* Update breakpoints */ - update_bp_registers(regs, 1, 0); - /* Update watchpoints */ - update_bp_registers(regs, 1, 1); + u64 addr, mask; + + if (test_bit(TIF_SINGLESTEP, &task_thread_info(next)->flags)) { + addr = read_wb_reg(CSR_CFG_ADDR, 0, 0); + mask = read_wb_reg(CSR_CFG_MASK, 0, 0); + if ((task_pt_regs(next)->csr_era & ~mask) == (addr & ~mask)) + csr_write32(0x10000, LOONGARCH_CSR_FWPS); + regs->csr_prmd |= CSR_PRMD_PWE; + } else { + /* Update breakpoints */ + update_bp_registers(regs, 1, 0); + /* Update watchpoints */ + update_bp_registers(regs, 1, 1); + } } void hw_breakpoint_pmu_read(struct perf_event *bp) diff --git a/arch/loongarch/kernel/ptrace.c b/arch/loongarch/kernel/ptrace.c index bee4194177fd..52a3ee4366f4 100644 --- a/arch/loongarch/kernel/ptrace.c +++ b/arch/loongarch/kernel/ptrace.c @@ -20,6 +20,7 @@ #include <linux/context_tracking.h> #include <linux/elf.h> #include <linux/errno.h> +#include <linux/hw_breakpoint.h> #include <linux/mm.h> #include <linux/ptrace.h> #include <linux/regset.h> @@ -30,6 +31,7 @@ #include <linux/stddef.h> #include <linux/seccomp.h> #include <linux/uaccess.h> +#include <linux/thread_info.h> #include <asm/byteorder.h> #include <asm/cpu.h> @@ -39,6 +41,7 @@ #include <asm/page.h> #include <asm/pgtable.h> #include <asm/processor.h> +#include <asm/ptrace.h> #include <asm/reg.h> #include <asm/syscall.h> @@ -541,3 +544,68 @@ long arch_ptrace(struct task_struct *child, long request, return ret; } + +void ptrace_triggered(struct perf_event *bp, + struct perf_sample_data *data, struct pt_regs *regs) +{ + struct perf_event_attr attr; + + attr = bp->attr; + attr.disabled = true; + modify_user_hw_breakpoint(bp, &attr); +} + +static int set_single_step(struct task_struct *tsk, unsigned long addr) +{ + struct thread_struct *thread = &tsk->thread; + struct perf_event *bp; + struct perf_event_attr attr; + struct arch_hw_breakpoint *info; + + bp = thread->hbp_break[0]; + if (!bp) { + ptrace_breakpoint_init(&attr); + + attr.bp_addr = addr; + attr.bp_len = HW_BREAKPOINT_LEN_8; + attr.bp_type = HW_BREAKPOINT_X; + + bp = register_user_hw_breakpoint(&attr, ptrace_triggered, + NULL, tsk); + if (IS_ERR(bp)) + return PTR_ERR(bp); + + thread->hbp_break[0] = bp; + } else { + int err; + + attr = bp->attr; + attr.bp_addr = addr; + /* reenable breakpoint */ + attr.disabled = false; + err = modify_user_hw_breakpoint(bp, &attr); + if (unlikely(err)) + return err; + + csr_write64(attr.bp_addr, LOONGARCH_CSR_IB0ADDR); + } + info = counter_arch_bp(bp); + info->mask = 0xffffffffffff; + + return 0; +} + +/* ptrace API */ +void user_enable_single_step(struct task_struct *task) +{ + struct thread_info *ti = task_thread_info(task); + + set_single_step(task, task_pt_regs(task)->csr_era); + task->thread.single_step = task_pt_regs(task)->csr_era; + set_ti_thread_flag(ti, TIF_SINGLESTEP); +} + +void user_disable_single_step(struct task_struct *task) +{ + clear_tsk_thread_flag(task, TIF_SINGLESTEP); +} diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c index 2b133079e0f3..56d7d076153c 100644 --- a/arch/loongarch/kernel/traps.c +++ b/arch/loongarch/kernel/traps.c @@ -511,9 +511,23 @@ asmlinkage void noinstr do_watch(struct pt_regs *regs) #ifdef CONFIG_HAVE_HW_BREAKPOINT irqentry_state_t state = irqentry_enter(regs); - breakpoint_handler(regs); - watchpoint_handler(regs); - force_sig(SIGTRAP); + if (test_tsk_thread_flag(current, TIF_SINGLESTEP)) { + int llbit = (csr_read32(LOONGARCH_CSR_LLBCTL) & 0x1); + unsigned long pc = regs->csr_era; + + if (llbit) { + csr_write32(0x10000, LOONGARCH_CSR_FWPS); + csr_write32(0x4, LOONGARCH_CSR_LLBCTL); + } else if (pc == current->thread.single_step) { + csr_write32(0x10000, LOONGARCH_CSR_FWPS); + } else { + force_sig(SIGTRAP); + } + } else { + breakpoint_handler(regs); + watchpoint_handler(regs); + force_sig(SIGTRAP); + } irqentry_exit(regs, state); #endif