zoukankan      html  css  js  c++  java
  • 使用data breakpoint 追踪地址寄存器被修改的问题

    一. 介绍

      data breakpoint是一种特殊的断电,在处理检查到预设地址的值发生R/W操作时,发生断点中断。

    二. 使用方法1

      kernel有示例代码,在data_breakpoint.c中,在这里,kernel检查的是symbol的值发生变化,但是实际上测试发现直接使用寄存器地址也是可以的。

    /*
     * data_breakpoint.c - Sample HW Breakpoint file to watch kernel data address
     *
     * This program is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation; either version 2 of the License, or
     * (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
     *
     * usage: insmod data_breakpoint.ko ksym=<ksym_name>
     *
     * This file is a kernel module that places a breakpoint over ksym_name kernel
     * variable using Hardware Breakpoint register. The corresponding handler which
     * prints a backtrace is invoked every time a write operation is performed on
     * that variable.
     *
     * Copyright (C) IBM Corporation, 2009
     *
     * Author: K.Prasad <prasad@linux.vnet.ibm.com>
     */
    #include <linux/module.h>    /* Needed by all modules */
    #include <linux/kernel.h>    /* Needed for KERN_INFO */
    #include <linux/init.h>        /* Needed for the macros */
    #include <linux/kallsyms.h>
    
    #include <linux/perf_event.h>
    #include <linux/hw_breakpoint.h>
    
    struct perf_event * __percpu *sample_hbp;
    
    static char ksym_name[KSYM_NAME_LEN] = "pid_max";
    module_param_string(ksym, ksym_name, KSYM_NAME_LEN, S_IRUGO);
    MODULE_PARM_DESC(ksym, "Kernel symbol to monitor; this module will report any"
                " write operations on the kernel symbol");
    
    static void sample_hbp_handler(struct perf_event *bp,
                       struct perf_sample_data *data,
                       struct pt_regs *regs)
    {
        printk(KERN_INFO "%s value is changed
    ", ksym_name);
        dump_stack();
        printk(KERN_INFO "Dump stack from sample_hbp_handler
    ");
    }
    
    static int __init hw_break_module_init(void)
    {
        int ret;
        struct perf_event_attr attr;
    
        hw_breakpoint_init(&attr);
        attr.bp_addr = kallsyms_lookup_name(ksym_name);
        attr.bp_len = HW_BREAKPOINT_LEN_4;
        attr.bp_type = HW_BREAKPOINT_W | HW_BREAKPOINT_R;
    
        sample_hbp = register_wide_hw_breakpoint(&attr, sample_hbp_handler, NULL);
        if (IS_ERR((void __force *)sample_hbp)) {
            ret = PTR_ERR((void __force *)sample_hbp);
            goto fail;
        }
    
        printk(KERN_INFO "HW Breakpoint for %s write installed
    ", ksym_name);
    
        return 0;
    
    fail:
        printk(KERN_INFO "Breakpoint registration failed
    ");
    
        return ret;
    }
    
    static void __exit hw_break_module_exit(void)
    {
        unregister_wide_hw_breakpoint(sample_hbp);
        printk(KERN_INFO "HW Breakpoint for %s write uninstalled
    ", ksym_name);
    }
    
    module_init(hw_break_module_init);
    module_exit(hw_break_module_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("K.Prasad");
    MODULE_DESCRIPTION("ksym breakpoint");

    三. 使用方法2

      上面是使用kernel的hw_breakpoint走watchpoint来注册的,实际上这里有一层封装,我们直接使用arch/arm下面的hw_breakpoint也是可以的。

      这里直接给代码链接了:https://elixir.bootlin.com/linux/v4.6/source/arch/arm/kernel/hw_breakpoint.c

      有很多操作寄存器的地方,比较底层了,不过便于理解。

    常用函数如下:
    static int __init arch_hw_breakpoint_init(void);
    int arch_install_hw_breakpoint(struct perf_event *bp);
    void arch_uninstall_hw_breakpoint(struct perf_event *bp);
    
    static void enable_single_step(struct perf_event *bp, u32 addr);
    static void disable_single_step(struct perf_event *bp);
    
    //异常发生时进入这里处理
    static void watchpoint_handler(unsigned long addr, unsigned int fsr,
                       struct pt_regs *regs);

    四. 第一种方法的结果

    pid_max value is changed
    CPU: 2 PID: 439 Comm: kworker/u8:3 Tainted: P        W  O      4.19.166 #2
    Hardware name: xxxx (DT)
    Workqueue: events_unbound call_usermodehelper_exec_work
    Call trace:
     dump_backtrace+0x0/0x164
     show_stack+0x20/0x2c
     dump_stack+0xb8/0xf0
     sample_hbp_handler+0x28/0x3c [data_breakpoint]
     __perf_event_overflow+0x94/0xe0
     perf_swevent_event+0x98/0x104
     perf_bp_event+0x6c/0x98
     watchpoint_report+0x80/0x94
     watchpoint_handler+0x100/0x208
     do_debug_exception+0xe8/0x174
     el1_dbg+0x18/0xa8
     alloc_pid+0x80/0x284
     copy_process+0xb48/0x1960
     _do_fork+0xa0/0x434
     kernel_thread+0x40/0x50
     call_usermodehelper_exec_work+0x40/0xd8
     process_one_work+0x210/0x3e8
     worker_thread+0x228/0x3c4
     kthread+0x13c/0x14c
     ret_from_fork+0x10/0x18
    Dump stack from sample_hbp_handler

    五. 第二种方法使用示例

    //TODO

    六. 注意的地方

      data breakpoint不同于breakpoint,发生异常后,因为寄存器中的值不会被清除,所以会一直循环中断,在os上看到的讨论如下:

    在ARM上,Data Abort异常在触发它们的指令完成1之前触发。这意味着,在异常处理程序中,受指令影响的寄存器和存储器位置仍保留其旧值(或在某些情况下为未定义的值)。这样,当处理程序完成时,它必须重试中止的指令,以便被中断的程序按预期运行2。如果在处理程序返回时仍设置了监视点,则指令将再次触发它。这将导致您看到的循环。
    
    为了解决这个问题,诸如GDB之类的用户空间调试器将单步执行击中某个监视点的任何指令,而该监视点被禁用,然后继续执行。但是,底层的内核API仅直接公开了硬件观察点行为。由于您使用的是内核API,因此取决于事件处理程序,以确保观察点不会在重试指令上触发。
    
    [内核中的ARM观察点代码实际上确实支持自动单步执行,但仅在非常特定的条件下才支持。即,它要求1)没有向观察点注册任何事件处理程序,2)观察点在用户空间中,以及3)观察点未与特定的CPU关联。由于您的用例至少违反了(1)和(2),因此您必须找到另一个解决方案。] 3
    
    不幸的是,在ARM上,没有万无一失的方法来使观察点保持启用而不引起循环。GDB用于单步程序的断点模式“指令不匹配”UNPREDICTABLE在内核模式4中使用时会产生行为。最好的办法是在处理程序中禁用监视点,然后设置一个标准断点,以根据您知道不久之后将要执行的指令重新启用它。
    
    对于您的MMIO仿真驱动程序,观察点可能不是答案。除了刚才提到的问题外,大多数ARM内核还具有很少的观察点寄存器,因此该解决方案无法扩展。恐怕我对ARM的内存模型不够熟悉,无法提出替代方法。但是,Linux现有的用于为虚拟机模拟内存映射IO的代码可能是一个不错的起点。
    
    1数据异常异常有两种类型,同步异常和异步异常,由实现来决定观察点生成哪种异常。我在此答案中描述了同步异常的行为,因为这将导致您遇到的问题。
    
    2 ARMv7-A / R体系结构参考手册,B1.9.8,“数据异常中止”。
    
    3 Linux Kernel v4.6 arch/arm/kernel/hw_breakpoint.c,第634-661行。
    
    4 ARMv7-A / R体系结构参考手册,C3.3.3,“选择Monitor调试模式时出现无法预测的情况”。

    一般的,我们通过调用单步执行,并且清掉异常寄存器的值,让程序可以继续执行,这样在下次发生异常的时候还可以继续进入data watchpoint

  • 相关阅读:
    CSS3 字体
    capitalized-comments (Rules) – Eslint 中文开发手册
    heapq (Data Types) – Python 中文开发手册
    Java中的Java.lang.Math类| 1
    shell脚本的条件测试与比较
    centos7关闭防火墙
    vmwaretool安装
    mongodb
    php中0与空 Null false的区别
    git使用总结
  • 原文地址:https://www.cnblogs.com/smilingsusu/p/14343272.html
Copyright © 2011-2022 走看看