]> git.dujemihanovic.xyz Git - linux.git/commitdiff
LoongArch: Trigger user-space watchpoints correctly
authorHui Li <lihui@loongson.cn>
Fri, 21 Jun 2024 02:18:40 +0000 (10:18 +0800)
committerHuacai Chen <chenhuacai@loongson.cn>
Fri, 21 Jun 2024 02:18:40 +0000 (10:18 +0800)
In the current code, gdb can set the watchpoint successfully through
ptrace interface, but watchpoint will not be triggered.

When debugging the following code using gdb.

lihui@bogon:~$ cat test.c
  #include <stdio.h>
  int a = 0;
  int main()
  {
a = 1;
printf("a = %d\n", a);
return 0;
  }
lihui@bogon:~$ gcc -g test.c -o test
lihui@bogon:~$ gdb test
...
(gdb) watch a
...
(gdb) r
...
a = 1
[Inferior 1 (process 4650) exited normally]

No watchpoints were triggered, the root causes are:

1. Kernel uses perf_event and hw_breakpoint framework to control
   watchpoint, but the perf_event corresponding to watchpoint is
   not enabled. So it needs to be enabled according to MWPnCFG3
   or FWPnCFG3 PLV bit field in ptrace_hbp_set_ctrl(), and privilege
   is set according to the monitored addr in hw_breakpoint_control().
   Furthermore, add a judgment in ptrace_hbp_set_addr() to ensure
   kernel-space addr cannot be monitored in user mode.

2. The global enable control for all watchpoints is the WE bit of
   CSR.CRMD, and hardware sets the value to 0 when an exception is
   triggered. When the ERTN instruction is executed to return, the
   hardware restores the value of the PWE field of CSR.PRMD here.
   So, before a thread containing watchpoints be scheduled, the PWE
   field of CSR.PRMD needs to be set to 1. Add this modification in
   hw_breakpoint_control().

All changes according to the LoongArch Reference Manual:
https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#control-and-status-registers-related-to-watchpoints
https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#basic-control-and-status-registers

With this patch:

lihui@bogon:~$ gdb test
...
(gdb) watch a
Hardware watchpoint 1: a
(gdb) r
...
Hardware watchpoint 1: a

Old value = 0
New value = 1
main () at test.c:6
6 printf("a = %d\n", a);
(gdb) c
Continuing.
a = 1
[Inferior 1 (process 775) exited normally]

Cc: stable@vger.kernel.org
Signed-off-by: Hui Li <lihui@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
arch/loongarch/include/asm/hw_breakpoint.h
arch/loongarch/kernel/hw_breakpoint.c
arch/loongarch/kernel/ptrace.c

index a8ce580f4fc6ff12d0a29f71fe6a0697e761bae9..d78330916bd18a99189b5140e704ba69a34c5f7e 100644 (file)
@@ -75,6 +75,8 @@ do {                                                          \
 #define CSR_MWPC_NUM           0x3f
 
 #define CTRL_PLV_ENABLE                0x1e
+#define CTRL_PLV0_ENABLE       0x02
+#define CTRL_PLV3_ENABLE       0x10
 
 #define MWPnCFG3_LoadEn                8
 #define MWPnCFG3_StoreEn       9
index 950b2b8a82ee07bed4bc21909be0c9aa65935380..e882df1f72db81ba758ca722c6957629ee7978e8 100644 (file)
@@ -174,11 +174,21 @@ void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
 static int hw_breakpoint_control(struct perf_event *bp,
                                 enum hw_breakpoint_ops ops)
 {
-       u32 ctrl;
+       u32 ctrl, privilege;
        int i, max_slots, enable;
+       struct pt_regs *regs;
        struct perf_event **slots;
        struct arch_hw_breakpoint *info = counter_arch_bp(bp);
 
+       if (arch_check_bp_in_kernelspace(info))
+               privilege = CTRL_PLV0_ENABLE;
+       else
+               privilege = CTRL_PLV3_ENABLE;
+
+       /*  Whether bp belongs to a task. */
+       if (bp->hw.target)
+               regs = task_pt_regs(bp->hw.target);
+
        if (info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {
                /* Breakpoint */
                slots = this_cpu_ptr(bp_on_reg);
@@ -204,13 +214,15 @@ static int hw_breakpoint_control(struct perf_event *bp,
                write_wb_reg(CSR_CFG_ASID, i, 0, 0);
                write_wb_reg(CSR_CFG_ASID, i, 1, 0);
                if (info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {
-                       write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE);
+                       write_wb_reg(CSR_CFG_CTRL, i, 0, privilege);
                } else {
                        ctrl = encode_ctrl_reg(info->ctrl);
-                       write_wb_reg(CSR_CFG_CTRL, i, 1, ctrl | CTRL_PLV_ENABLE);
+                       write_wb_reg(CSR_CFG_CTRL, i, 1, ctrl | privilege);
                }
                enable = csr_read64(LOONGARCH_CSR_CRMD);
                csr_write64(CSR_CRMD_WE | enable, LOONGARCH_CSR_CRMD);
+               if (bp->hw.target)
+                       regs->csr_prmd |= CSR_PRMD_PWE;
                break;
        case HW_BREAKPOINT_UNINSTALL:
                /* Reset the FWPnCFG/MWPnCFG 1~4 register. */
@@ -222,6 +234,8 @@ static int hw_breakpoint_control(struct perf_event *bp,
                write_wb_reg(CSR_CFG_CTRL, i, 1, 0);
                write_wb_reg(CSR_CFG_ASID, i, 0, 0);
                write_wb_reg(CSR_CFG_ASID, i, 1, 0);
+               if (bp->hw.target)
+                       regs->csr_prmd &= ~CSR_PRMD_PWE;
                break;
        }
 
index 16b756c6049bc85b8e8b322581c5bd7177659e3b..200109de1971ad5ec382dc1a6647c5c29eedc20a 100644 (file)
@@ -608,9 +608,14 @@ static int ptrace_hbp_set_ctrl(unsigned int note_type,
                return -EINVAL;
        }
 
-       err = ptrace_hbp_fill_attr_ctrl(note_type, ctrl, &attr);
-       if (err)
-               return err;
+       if (uctrl & CTRL_PLV_ENABLE) {
+               err = ptrace_hbp_fill_attr_ctrl(note_type, ctrl, &attr);
+               if (err)
+                       return err;
+               attr.disabled = 0;
+       } else {
+               attr.disabled = 1;
+       }
 
        return modify_user_hw_breakpoint(bp, &attr);
 }
@@ -641,6 +646,10 @@ static int ptrace_hbp_set_addr(unsigned int note_type,
        struct perf_event *bp;
        struct perf_event_attr attr;
 
+       /* Kernel-space address cannot be monitored by user-space */
+       if ((unsigned long)addr >= XKPRANGE)
+               return -EINVAL;
+
        bp = ptrace_hbp_get_initialised_bp(note_type, tsk, idx);
        if (IS_ERR(bp))
                return PTR_ERR(bp);