LoongArch: Trigger user-space watchpoints correctly
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>
This commit is contained in:
@@ -75,6 +75,8 @@ do { \
|
|||||||
#define CSR_MWPC_NUM 0x3f
|
#define CSR_MWPC_NUM 0x3f
|
||||||
|
|
||||||
#define CTRL_PLV_ENABLE 0x1e
|
#define CTRL_PLV_ENABLE 0x1e
|
||||||
|
#define CTRL_PLV0_ENABLE 0x02
|
||||||
|
#define CTRL_PLV3_ENABLE 0x10
|
||||||
|
|
||||||
#define MWPnCFG3_LoadEn 8
|
#define MWPnCFG3_LoadEn 8
|
||||||
#define MWPnCFG3_StoreEn 9
|
#define MWPnCFG3_StoreEn 9
|
||||||
|
|||||||
@@ -174,11 +174,21 @@ void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
|
|||||||
static int hw_breakpoint_control(struct perf_event *bp,
|
static int hw_breakpoint_control(struct perf_event *bp,
|
||||||
enum hw_breakpoint_ops ops)
|
enum hw_breakpoint_ops ops)
|
||||||
{
|
{
|
||||||
u32 ctrl;
|
u32 ctrl, privilege;
|
||||||
int i, max_slots, enable;
|
int i, max_slots, enable;
|
||||||
|
struct pt_regs *regs;
|
||||||
struct perf_event **slots;
|
struct perf_event **slots;
|
||||||
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
|
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) {
|
if (info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {
|
||||||
/* Breakpoint */
|
/* Breakpoint */
|
||||||
slots = this_cpu_ptr(bp_on_reg);
|
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, 0, 0);
|
||||||
write_wb_reg(CSR_CFG_ASID, i, 1, 0);
|
write_wb_reg(CSR_CFG_ASID, i, 1, 0);
|
||||||
if (info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {
|
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 {
|
} else {
|
||||||
ctrl = encode_ctrl_reg(info->ctrl);
|
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);
|
enable = csr_read64(LOONGARCH_CSR_CRMD);
|
||||||
csr_write64(CSR_CRMD_WE | enable, LOONGARCH_CSR_CRMD);
|
csr_write64(CSR_CRMD_WE | enable, LOONGARCH_CSR_CRMD);
|
||||||
|
if (bp->hw.target)
|
||||||
|
regs->csr_prmd |= CSR_PRMD_PWE;
|
||||||
break;
|
break;
|
||||||
case HW_BREAKPOINT_UNINSTALL:
|
case HW_BREAKPOINT_UNINSTALL:
|
||||||
/* Reset the FWPnCFG/MWPnCFG 1~4 register. */
|
/* 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_CTRL, i, 1, 0);
|
||||||
write_wb_reg(CSR_CFG_ASID, i, 0, 0);
|
write_wb_reg(CSR_CFG_ASID, i, 0, 0);
|
||||||
write_wb_reg(CSR_CFG_ASID, i, 1, 0);
|
write_wb_reg(CSR_CFG_ASID, i, 1, 0);
|
||||||
|
if (bp->hw.target)
|
||||||
|
regs->csr_prmd &= ~CSR_PRMD_PWE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -608,9 +608,14 @@ static int ptrace_hbp_set_ctrl(unsigned int note_type,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ptrace_hbp_fill_attr_ctrl(note_type, ctrl, &attr);
|
if (uctrl & CTRL_PLV_ENABLE) {
|
||||||
if (err)
|
err = ptrace_hbp_fill_attr_ctrl(note_type, ctrl, &attr);
|
||||||
return err;
|
if (err)
|
||||||
|
return err;
|
||||||
|
attr.disabled = 0;
|
||||||
|
} else {
|
||||||
|
attr.disabled = 1;
|
||||||
|
}
|
||||||
|
|
||||||
return modify_user_hw_breakpoint(bp, &attr);
|
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 *bp;
|
||||||
struct perf_event_attr attr;
|
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);
|
bp = ptrace_hbp_get_initialised_bp(note_type, tsk, idx);
|
||||||
if (IS_ERR(bp))
|
if (IS_ERR(bp))
|
||||||
return PTR_ERR(bp);
|
return PTR_ERR(bp);
|
||||||
|
|||||||
Reference in New Issue
Block a user