这是一个调试调试器(Debug Debugger)的故事。
一般来说,调试器是用来调试其它程序的程序。但是,调试器本身也有问题,也需要调试,这种情况的调试有个简单的说法,叫DbgDbg。WinDBG调试器的.dbgdbg命令就是为这个目的设计的,而且命令字就用了这个缩写。
国庆前,我们对NanoCode调试器使用的OpenOCD引擎做了一次较大幅度的升级,从本来使用的0.11版本升级到最新的0.12版本。
升级后,小伙伴们先发现并解决了一些简单的问题。临近双节假期时,仍有一个问题没有解决,这个问题的重现步骤和症状如下:
设置断点,执行g恢复执行
断点命中
执行g恢复目标执行
目标系统会再次中断到调试器
调试器自动恢复目标执行
目标系统会再次中断到调试器,然后不断重复上面两个步骤
可以说,断点是调试器的最重要功能。这个功能的实现也是很复杂的。根据多年来与调试器打交道的经验,我预感到这个问题的难度不一般。
假期前一天的下午,我亲自上手调查这个问题。调试一番,发现问题发生在著名的“单步走出断点”过程中。在恢复目标执行时,调试器会逐个检查所有断点,如果发现是在走出某个断点,那么便会开始“单步走出断点”过程。其基本步骤如下:
A. 延迟落实这个断点,为其设置defer标志,也就是延缓设置这个断点。
B. 将恢复执行从普通的go改为单步一次。
B. 当单步执行一次又中断下来后,调试器会判断刚才的单步是不是在“单步走出断点”,如果是,那么便自动恢复执行。
如果上述步骤顺利的话,那么用户一般不会察觉到这个单步动作。
“单步走出断点”逻辑涉及到事件处理、断点管理等多个模块,代码比较复杂。继续分析一番,发现问题出在单步失败,也就是上面的动作B。得出这个结论后,已经到了下班时间,而且要放假了,于是便收工,准备节后再战。
在回家的路上,我不由得又想起这个问题,为何单步失败呢?起初我怀疑是读取PC寄存器的问题。因为跟踪单步过程,所有操作都是成功的。但是读到的PC寄存器值却和单步前一模一样。也就是单步一次,PC指针没有动。这是不合理的。
双节假期里,我先拿出一点时间增强了读取寄存器的方法,确保不受寄存器的cache机制干扰,读到最新的值。
改变寄存器读法后,粘在断点的问题还是存在。
排除了寄存器原因后,我扩大搜索范围,继续撒网搜索。为了方便比较,我把老的代码也加回到项目里,让新旧两套代码可以很方便的切换,对比运行。
这样跟踪一番后,新的线索出现了。对于出问题的情况,单步一次后,从底层收到的中断原因是断点。
而正常情况时,收到的中断原因是单步。
接下来的问题是,为什么单步一次,收到的中断原因是断点,而不是期待的单步呢?
难道是新版本OpenOCD的代码修改了有关的逻辑。但这个怀疑很快被排除了,因为在没有断点时,做纯粹的单步没有问题,可以收到“DBG_REASON_SINGLESTEP”。
继续跟踪一番后,我突然想起,是不是中断到调试器时,清除断点动作没有做好。输入!bp,执行OpenCCD的列断点命令,果然如此:
[ndb]!bp
keep_alive() was not invoked in the 1000 mstimelimit. GDB alive packet not sent! (33653 ms).
Workaround: increase "set remotetimeout" in GDB
Hardware
breakpoint(IVA): addr=0xffffff800828d8d8, len=0x4,
num=0
看到上面这个信息,我眼前一亮,感觉这下应该是找到真BUG了。
跟踪删除断点动作,果然如此,断点对象的group_id字段为cdcdcdcd,显然是没有初始化。
比较新旧代码,发现合并代码时,漏掉了一句重要的赋值语句。
将漏掉的一行代码合并到新代码后,问题不见了。
这个bug是在9月30日解决的,刚好是中秋(9月29)和国庆(10月1日)两个节日的中间一天。现在是10月6日的晚上,长达8天的双节假期就要结束,特意在假期结束前把这个在假期里解决的bug记录和分享出来,希望可以帮助格友们“收拢放心”,切换回工作模式。^_^
(写文章很辛苦,恳请各位读者点击“在看”,感谢转发)
*************************************************
正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生
扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物
也欢迎关注格友公众号