A: 前言
自从Prasad Krishnan兄(也许该叫大叔)引入Hardware Breakpoint统一管理机制后,
结束了各个内核模块中对Hardware Breakpoint处理各自为政的状态,一统Hardware
Breakpoint的天下.
KGDB也无例外,为了对别人负责,也需要迁移到Hardware Breakpoint Layer, 其实
kgdb对使用Hardware Breakpoint Layer是很不爽的。为避免大家心里暗怨我瞎说,
特举例如下:
1 2 3 4 5 6 7 8 9 10 | 1: 使用Hardware Breakpoint Layer,增加了kgdb对系统的依赖性,这潜在的影响 kgdb的稳定性。 2: 当kgdb用于早期调试时(如系统初始化阶段),Hardware Breakpoint Layer有 可能还没有初始化,那时候还是得kgdb自己来管理硬件断点。从这个角度来说kgdb支持 Hardware Breakpoint Layer确实比较鸡肋。 3: Hardware Breakpoint Layer依赖notifier_chain来实现的,有时候kgdb会避免 使用notifier_chain的(这样就可以单步调试notifier的相关代码),一旦使用了Hardware Breakpoint Layer,就意味着在有硬件断点触发的情况下,不能在notifier处设置断点。 |
迫于向大家看齐的压力,kgdb还是忍痛接受了Hardware Breakpoint Layer,反正
Hardware Breakpoint Layer还是有一个好处的,就是not evil,不会破坏别人的硬件断点设置,
没有人会来抱怨你冲掉他们的硬断点设置了。
为了让kgdb这个怪物支持Hardware Breakpoint Layer,Hardware Breakpoint Layer
还是给予了一定的协助的,如提供一些无锁操作函数等等.如为原来有锁的release_bp_slot提供
一个无锁的dbg_release_bp_slot.
kgdb由于使用了NMI这个强大的DD,如果调用一些持锁的函数,基本有概率会"无理由"的把你
给锁住,哭都不知道找谁去。同样,在Jason大牛的一番艰苦奋战下,kgdb基本把Hardware
Breakpoint Layer给摆平了.
B: 问题和现象
但在某天某时某分(秒就算了),我发现在对硬断点增/减多次操作后,在操作就再也不成功了。
经过多次重启机器的麻木实验后,发现规律如下:
前四次是肯定可以成功,而后面的操作肯定是不成功的。
而"四"正好是X86的硬件断点最大个数,也是Hardware Breakpoint Layer定义的最大操作数。
从这个表象上看,很可能是kgdb没有把要删除的断点给删掉,于是开始dig kgdb硬断点相关代码.
kgdb增加硬断点简要流程:
1 2 3 4 5 6 7 8 9 10 11 | gdb --> 发送"Z1+breakaddress" --> kgdb kgdb --> kernel/debug/gdbstub.c: gdb_serial_stub() --> gdb_cmd_break() --> arch_kgdb_ops.set_hw_breakpoint() --> arch/x86/kernel/kgdb.c: kgdb_set_hw_break() --> hw_break_reserve_slot() --> dbg_reserve_bp_slot() ->在所有CPU上注册硬断点(Hardware Breakpoint Layer API) |
想了解为什么发送"Z1+breakaddress" 给kgdb就是增加硬断点的朋友,看参考附录里的gdb远程协议。
kgdb删除硬断点流程
1 2 3 4 5 6 7 8 9 10 11 | gdb --> 发送"z1+breakaddress" --> kgdb kgdb --> kernel/debug/gdbstub.c: gdb_serial_stub() --> gdb_cmd_break() --> arch_kgdb_ops.remove_hw_breakpoint() --> arch/x86/kernel/kgdb.c: kgdb_remove_hw_break() --> hw_break_release_slot() --> dbg_release_bp_slot() -->在所有CPU上删除硬断点(Hardware Breakpoint Layer API) |
C: 问题原因
从前面分析的原因来看,好像没问题啊,该加的加了,该删的删了,问题出在哪呢?
为避免硬/软断点影响kgdb,在kgdb主程序kgdb_cpu_enter运行过程中,所有的断点是被disable的。
对于硬件断点来说,它会显示的调用kgdb_disable_hw_debug()来disable所有的硬件断点,
在kgdb离开时,调用kgdb_correct_hw_break()来使能/增加需要激活的硬件断点。
来看看
kgdb_disable_hw_debug()的实现:
1 2 3 4 | arch/x86/kernel/kgdb.c: kgdb_disable_hw_debug() --> arch_uninstall_hw_breakpoint() --> 删除当前CPU上硬断点(Hardware Breakpoint Layer API) |
从上面看出,kgdb_disable_hw_debug()只是删除当前cpu上的硬件断点,如果要达到
全部删除的效果,应该每个CPU都得调用一次,而当前的调用路径,只要master debug的CPU
才会调用,其它的CPU都不会运行.
D: BUG解决方法
解决方法很简单,就是所有的cpu都运行kgdb_disable_hw_debug(),解决方法见如下patch:
[patch 1/2]debug_core: disable hw_breakpoints on all cores in kgdb_cpu_enter()
[patch 2/2]x86,kgdb: remove unnecessary call to kgdb_correct_hw_break()
E: Others
1:如果对Hardware Breakpoint Layer感兴趣,可以看看Prasad Krishnan兄的论文:
《Hardware Breakpoint (or watchpoint) usage in Linux Kernel》
2: gdb远程协议,记录gdb与kgdb的通信规范:
《gdb Remote Serial Protocol》
[…] Breakpoint Layer的内容,可阅读:《抓虫日记之 kgdb 和 删除硬断点 》 更多有关x86硬件调试寄存器信息,可阅读:《x86 […]