硬件断点只能在模块初始化时设置

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_breakpointioctl 调用的非特权进程可能在 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;
    }