2023-02-25 15:52:57 +08:00
|
|
|
/* SPDX-License-Identifier: GPL-2.0 */
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2022-2023 Loongson Technology Corporation Limited
|
|
|
|
*/
|
|
|
|
#ifndef __ASM_HW_BREAKPOINT_H
|
|
|
|
#define __ASM_HW_BREAKPOINT_H
|
|
|
|
|
|
|
|
#include <asm/loongarch.h>
|
|
|
|
|
|
|
|
#ifdef __KERNEL__
|
|
|
|
|
|
|
|
/* Breakpoint */
|
|
|
|
#define LOONGARCH_BREAKPOINT_EXECUTE (0 << 0)
|
|
|
|
|
|
|
|
/* Watchpoints */
|
|
|
|
#define LOONGARCH_BREAKPOINT_LOAD (1 << 0)
|
|
|
|
#define LOONGARCH_BREAKPOINT_STORE (1 << 1)
|
|
|
|
|
|
|
|
struct arch_hw_breakpoint_ctrl {
|
|
|
|
u32 __reserved : 28,
|
|
|
|
len : 2,
|
|
|
|
type : 2;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct arch_hw_breakpoint {
|
|
|
|
u64 address;
|
|
|
|
u64 mask;
|
|
|
|
struct arch_hw_breakpoint_ctrl ctrl;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Lengths */
|
|
|
|
#define LOONGARCH_BREAKPOINT_LEN_1 0b11
|
|
|
|
#define LOONGARCH_BREAKPOINT_LEN_2 0b10
|
|
|
|
#define LOONGARCH_BREAKPOINT_LEN_4 0b01
|
|
|
|
#define LOONGARCH_BREAKPOINT_LEN_8 0b00
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Limits.
|
|
|
|
* Changing these will require modifications to the register accessors.
|
|
|
|
*/
|
2025-01-26 21:49:59 +08:00
|
|
|
#define LOONGARCH_MAX_BRP 14
|
|
|
|
#define LOONGARCH_MAX_WRP 14
|
2023-02-25 15:52:57 +08:00
|
|
|
|
|
|
|
/* Virtual debug register bases. */
|
|
|
|
#define CSR_CFG_ADDR 0
|
|
|
|
#define CSR_CFG_MASK (CSR_CFG_ADDR + LOONGARCH_MAX_BRP)
|
|
|
|
#define CSR_CFG_CTRL (CSR_CFG_MASK + LOONGARCH_MAX_BRP)
|
|
|
|
#define CSR_CFG_ASID (CSR_CFG_CTRL + LOONGARCH_MAX_WRP)
|
|
|
|
|
|
|
|
/* Debug register names. */
|
|
|
|
#define LOONGARCH_CSR_NAME_ADDR ADDR
|
|
|
|
#define LOONGARCH_CSR_NAME_MASK MASK
|
|
|
|
#define LOONGARCH_CSR_NAME_CTRL CTRL
|
|
|
|
#define LOONGARCH_CSR_NAME_ASID ASID
|
|
|
|
|
|
|
|
/* Accessor macros for the debug registers. */
|
|
|
|
#define LOONGARCH_CSR_WATCH_READ(N, REG, T, VAL) \
|
|
|
|
do { \
|
|
|
|
if (T == 0) \
|
|
|
|
VAL = csr_read64(LOONGARCH_CSR_##IB##N##REG); \
|
|
|
|
else \
|
|
|
|
VAL = csr_read64(LOONGARCH_CSR_##DB##N##REG); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define LOONGARCH_CSR_WATCH_WRITE(N, REG, T, VAL) \
|
|
|
|
do { \
|
|
|
|
if (T == 0) \
|
|
|
|
csr_write64(VAL, LOONGARCH_CSR_##IB##N##REG); \
|
|
|
|
else \
|
|
|
|
csr_write64(VAL, LOONGARCH_CSR_##DB##N##REG); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
/* Exact number */
|
|
|
|
#define CSR_FWPC_NUM 0x3f
|
|
|
|
#define CSR_MWPC_NUM 0x3f
|
|
|
|
|
|
|
|
#define CTRL_PLV_ENABLE 0x1e
|
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>
2024-06-21 10:18:40 +08:00
|
|
|
#define CTRL_PLV0_ENABLE 0x02
|
|
|
|
#define CTRL_PLV3_ENABLE 0x10
|
2023-02-25 15:52:57 +08:00
|
|
|
|
|
|
|
#define MWPnCFG3_LoadEn 8
|
|
|
|
#define MWPnCFG3_StoreEn 9
|
|
|
|
|
|
|
|
#define MWPnCFG3_Type_mask 0x3
|
|
|
|
#define MWPnCFG3_Size_mask 0x3
|
|
|
|
|
|
|
|
static inline u32 encode_ctrl_reg(struct arch_hw_breakpoint_ctrl ctrl)
|
|
|
|
{
|
|
|
|
return (ctrl.len << 10) | (ctrl.type << 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void decode_ctrl_reg(u32 reg, struct arch_hw_breakpoint_ctrl *ctrl)
|
|
|
|
{
|
|
|
|
reg >>= 8;
|
|
|
|
ctrl->type = reg & MWPnCFG3_Type_mask;
|
|
|
|
reg >>= 2;
|
|
|
|
ctrl->len = reg & MWPnCFG3_Size_mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct task_struct;
|
|
|
|
struct notifier_block;
|
|
|
|
struct perf_event;
|
|
|
|
struct perf_event_attr;
|
|
|
|
|
|
|
|
extern int arch_bp_generic_fields(struct arch_hw_breakpoint_ctrl ctrl,
|
LoongArch: Fix watchpoint setting error
In the current code, when debugging the following code using gdb,
"invalid argument ..." message will be displayed.
lihui@bogon:~$ cat test.c
#include <stdio.h>
int a = 0;
int main()
{
a = 1;
return 0;
}
lihui@bogon:~$ gcc -g test.c -o test
lihui@bogon:~$ gdb test
...
(gdb) watch a
Hardware watchpoint 1: a
(gdb) r
...
Invalid argument setting hardware debug registers
There are mainly two types of issues.
1. Some incorrect judgment condition existed in user_watch_state
argument parsing, causing -EINVAL to be returned.
When setting up a watchpoint, gdb uses the ptrace interface,
ptrace(PTRACE_SETREGSET, tid, NT_LOONGARCH_HW_WATCH, (void *) &iov)).
Register values in user_watch_state as follows:
addr[0] = 0x0, mask[0] = 0x0, ctrl[0] = 0x0
addr[1] = 0x0, mask[1] = 0x0, ctrl[1] = 0x0
addr[2] = 0x0, mask[2] = 0x0, ctrl[2] = 0x0
addr[3] = 0x0, mask[3] = 0x0, ctrl[3] = 0x0
addr[4] = 0x0, mask[4] = 0x0, ctrl[4] = 0x0
addr[5] = 0x0, mask[5] = 0x0, ctrl[5] = 0x0
addr[6] = 0x0, mask[6] = 0x0, ctrl[6] = 0x0
addr[7] = 0x12000803c, mask[7] = 0x0, ctrl[7] = 0x610
In arch_bp_generic_fields(), return -EINVAL when ctrl.len is
LOONGARCH_BREAKPOINT_LEN_8(0b00). So delete the incorrect judgment here.
In ptrace_hbp_fill_attr_ctrl(), when note_type is NT_LOONGARCH_HW_WATCH
and ctrl[0] == 0x0, if ((type & HW_BREAKPOINT_RW) != type) will return
-EINVAL. Here ctrl.type should be set based on note_type, and unnecessary
judgments can be removed.
2. The watchpoint argument was not set correctly due to unnecessary
offset and alignment_mask.
Modify ptrace_hbp_fill_attr_ctrl() and hw_breakpoint_arch_parse(), which
ensure the watchpont argument is set correctly.
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
Cc: stable@vger.kernel.org
Signed-off-by: Hui Li <lihui@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
2024-06-21 10:18:40 +08:00
|
|
|
int *gen_len, int *gen_type);
|
2023-02-25 15:52:57 +08:00
|
|
|
extern int arch_check_bp_in_kernelspace(struct arch_hw_breakpoint *hw);
|
|
|
|
extern int hw_breakpoint_arch_parse(struct perf_event *bp,
|
|
|
|
const struct perf_event_attr *attr,
|
|
|
|
struct arch_hw_breakpoint *hw);
|
|
|
|
extern int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
|
|
|
|
unsigned long val, void *data);
|
|
|
|
|
|
|
|
extern int arch_install_hw_breakpoint(struct perf_event *bp);
|
|
|
|
extern void arch_uninstall_hw_breakpoint(struct perf_event *bp);
|
|
|
|
extern int hw_breakpoint_slots(int type);
|
|
|
|
extern void hw_breakpoint_pmu_read(struct perf_event *bp);
|
|
|
|
|
|
|
|
void breakpoint_handler(struct pt_regs *regs);
|
|
|
|
void watchpoint_handler(struct pt_regs *regs);
|
|
|
|
|
|
|
|
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
|
|
|
extern void ptrace_hw_copy_thread(struct task_struct *task);
|
|
|
|
extern void hw_breakpoint_thread_switch(struct task_struct *next);
|
|
|
|
#else
|
|
|
|
static inline void ptrace_hw_copy_thread(struct task_struct *task)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
static inline void hw_breakpoint_thread_switch(struct task_struct *next)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Determine number of BRP registers available. */
|
|
|
|
static inline int get_num_brps(void)
|
|
|
|
{
|
|
|
|
return csr_read64(LOONGARCH_CSR_FWPC) & CSR_FWPC_NUM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Determine number of WRP registers available. */
|
|
|
|
static inline int get_num_wrps(void)
|
|
|
|
{
|
|
|
|
return csr_read64(LOONGARCH_CSR_MWPC) & CSR_MWPC_NUM;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* __KERNEL__ */
|
|
|
|
#endif /* __ASM_BREAKPOINT_H */
|