register_wide_hw_breakpoint 持续触发处理程序回调

register_wide_hw_breakpoint continually triggers handler callback

在Linux内核中,当我用register_wide_hw_breakpoint注册的断点被触发时,回调处理程序会无限运行,直到断点被注销。

背景:为了测试我们正在制作的某些硬件的驱动程序,我正在编写第二个模拟硬件接口的内核模块。我的目的是在硬件中作为控制寄存器的内存位置设置一个观察点,以便写入此 'register' 可以触发仿真器驱动程序的操作。

参见here for a complete sample。 我设置断点如下:

hw_breakpoint_init(&attr);
attr.bp_addr = kallsyms_lookup_name("test_value");
attr.bp_len = HW_BREAKPOINT_LEN_4;
attr.bp_type = HW_BREAKPOINT_W;
test_hbp = register_wide_hw_breakpoint(&attr, test_hbp_handler, NULL);

但是当 test_value 被写入时,回调 (test_hbp_handler) 被连续触发,而没有控制 return 即将写入 [=12= 的代码].

1) 我应该做些什么不同的事情才能使其按预期工作(return 执行触发断点的代码)?

2) 如何捕获写入内存位置的值?

如果这很重要: $ uname -a Linux socfpga-cyclone5 3.10.37-ltsi-rt37-05714-ge4ee387 #1 SMP PREEMPT RT Mon Jan 5 17:51:35 UTC 2015 armv7l GNU/Linux

这是设计使然。当命中 ARM 硬件观察点时,它会生成数据中止异常。在 ARM 上,数据中止异常在触发它们的指令完成 1 之前 触发 。这意味着,在异常处理程序中,受指令影响的寄存器和内存位置仍保留其旧值(或者,在某些情况下,未定义的值)。因此,当处理程序完成时,它必须重试中止的指令,以便中断的程序按预期运行2。如果在处理程序 returns 时仍设置观察点,指令将再次触发它。这会导致您看到的循环。

为了解决这个问题,像 GDB 这样的用户空间调试器在恢复执行之前单步执行任何命中观察点并禁用该观察点的指令。然而,底层内核 API 只是直接公开了硬件观察点行为。由于您使用的是内核 API,由您的事件处理程序来确保观察点不会在重试指令上触发。

[内核中的 ARM watchpoint 代码实际上支持自动单步执行,但仅限于非常特定的条件。即,它要求 1) 没有事件处理程序注册到观察点,2) 观察点在用户空间中,以及 3) 观察点不与特定的 CPU 相关联。由于您的用例至少违反了(1)和(2),您必须找到其他解决方案。]3

不幸的是,在 ARM 上,没有万无一失的方法可以在不引起循环的情况下保持观察点启用。 GDB 用于单步执行程序的断点模式,"instruction mismatch," 在内核模式4 中使用时会产生UNPREDICTABLE 行为。您可以做的最好的事情是在您的处理程序中禁用观察点,然后设置一个标准断点以在您知道将很快执行的指令上重新启用它。

对于您的 MMIO 仿真驱动程序,观察点可能不是答案。除了刚刚提到的问题之外,大多数 ARM 内核的观察点寄存器很少,因此该解决方案无法扩展。恐怕我对 ARM 的内存模型不够熟悉,无法提出替代方法。但是,Linux 的 existing code 用于模拟虚拟机的内存映射 IO 可能是一个很好的起点。


1有两种类型的数据中止异常,同步的和异步的,由实现来决定观察点生成哪一种。我在这个答案中描述了同步异常的行为,因为这就是导致您遇到的问题的原因。

2ARMv7-A/R 体系结构参考手册,B1.9.8,"Data Abort exception."

3Linux 内核 v4.6, arch/arm/kernel/hw_breakpoint.c, lines 634-661.

4ARMv7-A/R 体系结构参考手册,C3.3.3,"UNPREDICTABLE cases when Monitor debug-mode is selected."