lkml.org 
[lkml]   [2023]   [Feb]   [18]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
SubjectRe: [PATCH v4 2/3] LoongArch: Add ptrace single step support
From
Date
Hi, Jinyang

On 2023/2/18 上午9:21, Jinyang He wrote:
>
> On 2023-02-17 18:00, Qing Zhang wrote:
>> Hi, Youling
>>
>> On 2023/2/17 下午5:50, Youling Tang wrote:
>>> Hi, Qing
>>>
>>> On 02/17/2023 10:37 AM, 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.
>>>> Some scenarios use CSR.FWPS.Skip to ignore the next hit result, not
>>>> to trigger
>>>> the watchpoint exception, and proceed to the next instruction.
>>>>
>>>> Signed-off-by: Qing Zhang <zhangqing@loongson.cn>
>>>> ---
>>>>  arch/loongarch/include/asm/inst.h      | 39 +++++++++++++++
>>>>  arch/loongarch/include/asm/loongarch.h |  3 ++
>>>>  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          | 34 +++++++++++--
>>>>  7 files changed, 176 insertions(+), 8 deletions(-)
>>>>
>>>> diff --git a/arch/loongarch/include/asm/inst.h
>>>> b/arch/loongarch/include/asm/inst.h
>>>> index ba18ce8fbdf2..00c6f261d9a2 100644
>>>> --- a/arch/loongarch/include/asm/inst.h
>>>> +++ b/arch/loongarch/include/asm/inst.h
>>>> @@ -368,6 +368,45 @@ static inline bool is_stack_alloc_ins(union
>>>> loongarch_instruction *ip)
>>>>          is_imm12_negative(ip->reg2i12_format.immediate);
>>>>  }
>>>>
>>>> +static inline bool branch_ins_target_pc(union loongarch_instruction
>>>> *ip)
>>>> +{
>>>> +    switch (ip->reg0i26_format.opcode) {
>>>> +    case b_op:
>>>> +    case bl_op:
>>>> +        if (ip->reg0i26_format.immediate_l == 0
>>>> +           && ip->reg0i26_format.immediate_h == 0)
>>>> +            return false;
>>>> +    }
>>>> +
>>>> +    switch (ip->reg1i21_format.opcode) {
>>>> +    case beqz_op:
>>>> +    case bnez_op:
>>>> +    case bceqz_op:
>>>> +        if (ip->reg1i21_format.immediate_l == 0
>>>> +           && ip->reg1i21_format.immediate_h == 0)
>>>> +            return false;
>>>> +    }
>>>> +
>>>> +    switch (ip->reg2i16_format.opcode) {
>>>> +    case jirl_op:
>>>> +        if (ip->reg2i16_format.rj == 0x1
>>>> +           && ip->reg2i16_format.rd == 0x1
>>> LOONGARCH_GPR_RA can be used instead of 0x1.
>>>
>>>> +           && ip->reg2i16_format.immediate == 0)
>>>> +            return false;
>>>> +        break;
>>>> +    case beq_op:
>>>> +    case bne_op:
>>>> +    case blt_op:
>>>> +    case bge_op:
>>>> +    case bltu_op:
>>>> +    case bgeu_op:
>>>> +        if (ip->reg2i16_format.immediate == 0)
>>>> +            return false;
>>>> +    }
>>>> +
>>>> +    return true;
>>>> +}
>>>> +
>>>>  void simu_pc(struct pt_regs *regs, union loongarch_instruction insn);
>>>>  void simu_branch(struct pt_regs *regs, union loongarch_instruction
>>>> insn);
>>>>
>>>> diff --git a/arch/loongarch/include/asm/loongarch.h
>>>> b/arch/loongarch/include/asm/loongarch.h
>>>> index e9aed583a064..65b7dcdea16d 100644
>>>> --- a/arch/loongarch/include/asm/loongarch.h
>>>> +++ b/arch/loongarch/include/asm/loongarch.h
>>>> @@ -1055,6 +1055,9 @@ static __always_inline void iocsr_write64(u64
>>>> val, u32 reg)
>>>>  #define LOONGARCH_CSR_DERA        0x501    /* debug era */
>>>>  #define LOONGARCH_CSR_DESAVE        0x502    /* debug save */
>>>>
>>>> +#define CSR_FWPC_SKIP_SHIFT        16
>>>> +#define CSR_FWPC_SKIP            (_ULCAST_(1) << CSR_FWPC_SKIP_SHIFT)
>>>> +
>>>>  /*
>>>>   * CSR_ECFG IM
>>>>   */
>>>> 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..75d3652fbe00 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(CSR_FWPC_SKIP, 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;
>>> If `mask` is only used for user space address mask, it should not be
>>> fixed to 0xffffffffffff, the user space address size may have many
>>> situations, which can be defined according to TASK_SIZE.
>>>
>>> Use (TASK_SIZE - 1) instead.
>>>
>> ok, got it.
>
> But according to to [1], the priority of watchpoint exception is highest
> in the priority of exception which all fetching stage. Thus, we may get
> the exception unexpected if set MASK to (TASK_SIZE - 1).
>
> e.g.
>     0x1000: addi.d $rd, $zero, -1 # a way to set a kernel address in rd
>     0x1004: jr $rd
>
> If singlestep is set at 0x1004, it will fetch insn in kernel address and
> and get operation address error exception if we set MASK to (TASK_SIZE -
> 1).
>
>
> [1]
> https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#exception-priority
>
>
In the future, breakpoints at kernel_enable_single_step will be added,
possibly with mask set to -1.
Now, because FWPS CTRL is set to 1 for PLV0-3, mask is set to
0xffffffffffff at user_enable_single_step.
As discussed below, user_code will be added to the breakpoint framework.

Thanks,
-Qing
>
> Thanks,
>
> Jinyang
>
>
>>>> +
>>>> +    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..a59275a43bd1 100644
>>>> --- a/arch/loongarch/kernel/traps.c
>>>> +++ b/arch/loongarch/kernel/traps.c
>>>> @@ -511,9 +511,37 @@ 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;
>>>> +        union loongarch_instruction *ip = (union
>>>> loongarch_instruction *)pc;
>>>> +
>>>> +        if (llbit) {
>>>> +        /*
>>>> +         * When the ll-sc combo is encountered, it is regarded as
>>>> an single
>>>> +         * instruction. So don't clear llbit and reset
>>>> CSR.FWPS.Skip until
>>>> +         * the llsc execution is completed.
>>>> +         */
>>> Comments should be aligned with code, similar modifications in other
>>> places.
>>>
>>>> +            csr_write32(CSR_FWPC_SKIP, LOONGARCH_CSR_FWPS);
>>>> +            csr_write32(CSR_LLBCTL_KLO, LOONGARCH_CSR_LLBCTL);
>>>> +        } else if (pc == current->thread.single_step) {
>>>> +        /*
>>>> +         * Maybe some hardware Bug caused that certain insns are
>>>> occasionally
>>>> +         * not skipped when CSR.FWPS.Skip is set, such as
>>>> fld.d/fst.d. So Singlestep
>>>> +         * needs to compare whether the csr_era is equal to the
>>>> value of singlestep
>>>> +         * which last time set.
>>>> +         */
>>>> +            if (branch_ins_target_pc(ip))
>>>> +            /* Prevent rd == pc from causing single step to fail to
>>>> stop */
>>> IMO, that comment description is not accurate enough.ok, I'll add
>>> more comments.
>>
>> Thanks,
>> -Qing
>>> Youling.
>>>
>>>> + csr_write32(CSR_FWPC_SKIP, LOONGARCH_CSR_FWPS);
>>>> +        } else {
>>>> +            force_sig(SIGTRAP);
>>>> +        }
>>>> +    } else {
>>>> +        breakpoint_handler(regs);
>>>> +        watchpoint_handler(regs);
>>>> +        force_sig(SIGTRAP);
>>>> +    }
>>>>
>>>>      irqentry_exit(regs, state);
>>>>  #endif
>>>>
>>>
>>
>

\
 
 \ /
  Last update: 2023-03-27 00:27    [W:0.062 / U:0.412 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site