硬件断点只能在模块初始化时设置
Hardware breakpoints can only be set at module initialization
我想在我的内核模块中使用 register_wide_hw_breakpoint
来观察我正在使用的内存页面的页面结构的变化(用于调试目的)。
但是,似乎我只能在模块初始化函数中注册断点。函数 returns -1 (EPERM?) 如果我从 ioctl-handler 中使用它。 This blogpost 让我假设这应该是可能的。
我是 运行 5.1.0 内核 Intel(R) Xeon(R) Silver 4215。
示例代码:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
#include <linux/kallsyms.h>
static uint32_t val = 0;
void inc_val(void)
{
val++;
}
struct perf_event * __percpu *sample_hbp;
static void sample_hbp_handler(struct perf_event *bp, struct perf_sample_data *data, struct pt_regs *regs)
{
pr_info("My module: val changed!");
}
int test_hw_breakpoint(void)
{
struct perf_event_attr attr;
hw_breakpoint_init(&attr);
attr.bp_addr = (unsigned long)&val;
attr.bp_len = HW_BREAKPOINT_LEN_4;
attr.bp_type = HW_BREAKPOINT_W;
pr_info("My module: HW breakpoint at 0x%llx\n", attr.bp_addr);
sample_hbp = register_wide_hw_breakpoint(&attr, sample_hbp_handler, NULL);
if (IS_ERR((void __force *)sample_hbp))
{
int ret = PTR_ERR((void __force *)sample_hbp);
pr_info("My module: Breakpoint registration failed: %d\n", ret);
return ret;
}
inc_val();
pr_info("My module: val: %d", val);
inc_val();
pr_info("My module: val: %d", val);
unregister_wide_hw_breakpoint(sample_hbp);
return 0;
}
static int mod_open(struct inode *inode, struct file *file)
{
(void)inode;
(void)file;
pr_info("My module: Opening...\n");
return 0;
}
static int mod_release(struct inode *inode, struct file *file)
{
(void)inode;
(void)file;
pr_info("My module: Releasing...\n");
return 0;
}
static long mod_ioctl(struct file *file, unsigned num, uintptr_t param)
{
(void)file;
(void)num;
(void)param;
pr_info("My module: Ioctl...\n");
test_hw_breakpoint();
return 0;
}
static struct file_operations fops = {
.open = mod_open,
.release = mod_release,
.unlocked_ioctl = mod_ioctl,
};
static struct miscdevice mod_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mymod!io",
.fops = &fops,
.mode = 0666,
};
int mod_init(void)
{
int ret;
ret = misc_register(&mod_dev);
if (ret != 0)
return ret;
test_hw_breakpoint();
return 0;
}
void mod_exit(void)
{
misc_deregister(&mod_dev);
}
static int __init od_init(void)
{
int ret;
pr_info("My module: Initializing...\n");
ret = mod_init();
if (ret != 0)
return -1;
return 0;
}
static void __exit od_exit(void)
{
pr_info("My module: Terminating...\n");
mod_exit();
}
module_init(od_init)
module_exit(od_exit)
MODULE_LICENSE("GPL");
在 dmesg 中产生以下输出:
[ +0.031269] My module: Initializing...
[ +0.000086] My module: HW breakpoint at 0xffffffffc04ae4c8
[ +0.000191] My module: val changed!
[ +0.000002] My module: val: 1
[ +0.000003] My module: val changed!
[ +0.000001] My module: val: 2
[ +0.002405] My module: Opening...
[ +0.000003] My module: Ioctl...
[ +0.000003] My module: HW breakpoint at 0xffffffffc04ae4c8
[ +0.000019] My module: Breakpoint registration failed: -1
[ +0.000003] My module: Releasing...
我不知道模块初始化时要观察的内存地址。有什么办法可以在运行时添加数据断点吗?
当进程发出系统调用时,它会切换到内核模式,但内核代码在“进程上下文”中是运行。理论上,该进程可以在 运行 处于内核模式时执行任何操作。然而,典型的内核代码会检查调用进程是否有能力做它想做的事情。一个进程有一个它被允许做的事情的能力列表:见 capabilities(7).
在 OP 的特殊情况下,发出导致调用 register_wide_hw_breakpoint
的 ioctl
调用的非特权进程可能在 hw_breakpoint_parse
函数中的检查失败时失败在“kernel/events/hw_breakpoint.c”中:
if (arch_check_bp_in_kernelspace(hw)) {
if (attr->exclude_kernel)
return -EINVAL;
/*
* Don't let unprivileged users set a breakpoint in the trap
* path to avoid trap recursion attacks.
*/
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
}
我想在我的内核模块中使用 register_wide_hw_breakpoint
来观察我正在使用的内存页面的页面结构的变化(用于调试目的)。
但是,似乎我只能在模块初始化函数中注册断点。函数 returns -1 (EPERM?) 如果我从 ioctl-handler 中使用它。 This blogpost 让我假设这应该是可能的。
我是 运行 5.1.0 内核 Intel(R) Xeon(R) Silver 4215。
示例代码:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
#include <linux/kallsyms.h>
static uint32_t val = 0;
void inc_val(void)
{
val++;
}
struct perf_event * __percpu *sample_hbp;
static void sample_hbp_handler(struct perf_event *bp, struct perf_sample_data *data, struct pt_regs *regs)
{
pr_info("My module: val changed!");
}
int test_hw_breakpoint(void)
{
struct perf_event_attr attr;
hw_breakpoint_init(&attr);
attr.bp_addr = (unsigned long)&val;
attr.bp_len = HW_BREAKPOINT_LEN_4;
attr.bp_type = HW_BREAKPOINT_W;
pr_info("My module: HW breakpoint at 0x%llx\n", attr.bp_addr);
sample_hbp = register_wide_hw_breakpoint(&attr, sample_hbp_handler, NULL);
if (IS_ERR((void __force *)sample_hbp))
{
int ret = PTR_ERR((void __force *)sample_hbp);
pr_info("My module: Breakpoint registration failed: %d\n", ret);
return ret;
}
inc_val();
pr_info("My module: val: %d", val);
inc_val();
pr_info("My module: val: %d", val);
unregister_wide_hw_breakpoint(sample_hbp);
return 0;
}
static int mod_open(struct inode *inode, struct file *file)
{
(void)inode;
(void)file;
pr_info("My module: Opening...\n");
return 0;
}
static int mod_release(struct inode *inode, struct file *file)
{
(void)inode;
(void)file;
pr_info("My module: Releasing...\n");
return 0;
}
static long mod_ioctl(struct file *file, unsigned num, uintptr_t param)
{
(void)file;
(void)num;
(void)param;
pr_info("My module: Ioctl...\n");
test_hw_breakpoint();
return 0;
}
static struct file_operations fops = {
.open = mod_open,
.release = mod_release,
.unlocked_ioctl = mod_ioctl,
};
static struct miscdevice mod_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mymod!io",
.fops = &fops,
.mode = 0666,
};
int mod_init(void)
{
int ret;
ret = misc_register(&mod_dev);
if (ret != 0)
return ret;
test_hw_breakpoint();
return 0;
}
void mod_exit(void)
{
misc_deregister(&mod_dev);
}
static int __init od_init(void)
{
int ret;
pr_info("My module: Initializing...\n");
ret = mod_init();
if (ret != 0)
return -1;
return 0;
}
static void __exit od_exit(void)
{
pr_info("My module: Terminating...\n");
mod_exit();
}
module_init(od_init)
module_exit(od_exit)
MODULE_LICENSE("GPL");
在 dmesg 中产生以下输出:
[ +0.031269] My module: Initializing...
[ +0.000086] My module: HW breakpoint at 0xffffffffc04ae4c8
[ +0.000191] My module: val changed!
[ +0.000002] My module: val: 1
[ +0.000003] My module: val changed!
[ +0.000001] My module: val: 2
[ +0.002405] My module: Opening...
[ +0.000003] My module: Ioctl...
[ +0.000003] My module: HW breakpoint at 0xffffffffc04ae4c8
[ +0.000019] My module: Breakpoint registration failed: -1
[ +0.000003] My module: Releasing...
我不知道模块初始化时要观察的内存地址。有什么办法可以在运行时添加数据断点吗?
当进程发出系统调用时,它会切换到内核模式,但内核代码在“进程上下文”中是运行。理论上,该进程可以在 运行 处于内核模式时执行任何操作。然而,典型的内核代码会检查调用进程是否有能力做它想做的事情。一个进程有一个它被允许做的事情的能力列表:见 capabilities(7).
在 OP 的特殊情况下,发出导致调用 register_wide_hw_breakpoint
的 ioctl
调用的非特权进程可能在 hw_breakpoint_parse
函数中的检查失败时失败在“kernel/events/hw_breakpoint.c”中:
if (arch_check_bp_in_kernelspace(hw)) {
if (attr->exclude_kernel)
return -EINVAL;
/*
* Don't let unprivileged users set a breakpoint in the trap
* path to avoid trap recursion attacks.
*/
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
}