前几天兰舍群里有个很精彩的讨论。兰友博渊把自己调试UEFI时看到的一个调用栈分享给大家,并且情不自禁的说:这个栈回溯实在太美了。
另一位兰友看了后,很谦虚的发出一问:“朱老师,请问一下,如何欣赏这种美?”
过了几分钟后,博渊给出了一个很生动的回答:
“不敢,是低水平的同学而已。此栈回溯,展示了具体而复杂的相互调用关系,每一层是如何往下具体翻译的,看一眼,便整体上有了更好的感觉和把握。接下来,更好地选择阅读代码或再下断点,用调试器学习活代码。
此法,像中医的针,在某个穴位扎一针下去,探是否得气,而后左旋、右旋,寒热虚实,是否有邪,何种邪,便心中清楚。”
博渊是兰舍里的第一批兰友,非常喜欢调试技术,也喜欢分享。特别值得一提的是,他还喜欢中医,在他的朋友圈里,经常看到他使用中医方法给家人或者朋友治病的故事,常常都是手到病除。
在上面的回答中,博渊也使用他熟悉的中医来做类比,非常生动。这种比喻方法也是我第一次见到。
我经常把调试器比作宝剑,形容它可以深入到系统内部。比如,多年前在《程序员》杂志上写调试专栏时,我建议的名字就叫“调试之剑”。看了博渊的比喻后,我觉得显然更好一些,因为针比剑更细,更精准。针到病除,更能代表调试技术的特点。
坦率说,我也非常喜欢看调用栈。当遇到漂亮的调用栈时,也常常有博渊那样的欣喜之情。大约在一年前,我也写过一篇关于调用栈的文章,名叫《穿越两大空间的调用栈》。文章中,我介绍了我喜欢调用栈的原因:
“人是有经历的,软件也如此。
简历记录着一个人的经历,而调用栈(call stack)则记录着软件的经历。看一个人的简历可以快速了解一个人。观察调用栈,则可以快速理解软件。
因为此,我非常喜欢看软件的调用栈。每当看到一个漂亮的调用栈,我常常如获至宝,端详许久。”
上文提到的跨越两大空间的调用栈因为涵盖软件在两个空间里的执行经过,具有更大的意义,当然也是更美丽迷人的。
去年9月,虽然把这个功能实现而且开始工作了。但其实还有一个比较大的问题。那就是需要事先把so文件从目标机复制到调试机。因为一旦目标机中断到调试器后,就没有办法执行复制动作了。如果go起来复制,那么下次中断的位置可能不一样了。我把这个矛盾称为“内核调试矛盾”。
内核调试矛盾:当把一个内核中断调试器后,又需要这个内核的服务,比如文件系统。
因为有这个硬伤,所以我很多时候故意不提这个功能。但心中每每想起,还是想把这个问题彻底解决。
今年暑假,格蠹再次投入力量研发NDB的新版本:ND3。
在ND3中,集成了自动从符号服务器下载符号的功能,使用的是DebugInfod技术。感谢我的交大校友Alice,她在出国留学前到格蠹实习,全心投入到符号模块(NDW),用了几周时间,集成代码,反复测试。终于让自动下载符号的功能稳定工作。用户只要输入如下命令就可以启用这个功能。
.sympath+ did*c:\nanocode\sym*
其中的did代表DebugInfoD。
有了did功能后,今天我再一次完善“跨界调用栈”功能。
先是解决了获取当前线程结构体地址(current指针)的问题,然后又解决了从内存读取BuildID的问题,此前都是从磁盘上完整的ELF文件中解析,又有上面说的矛盾。
上海的这个夏天特别热,很久没有下雨了。今天临近下班时,外面终于传来了久违的雨声,太好了。
刚好在外面开始下雨的时候,新一版的跨界调用栈功能开始工作了。
kn
# Frame Base Return Address Call Site
00 ffffff80`0e8bbe20 ffffff80`0829c7d4 lk!vfs_read [fs/read_write.c @ 55]
01 ffffff80`0e8bbe70 ffffff80`0829c864 lk!ksys_read+0x64 [fs/read_write.c @ 55]
02 ffffff80`0e8bbe80 ffffff80`08098f6c lk!__arm64_sys_read+0x14 [fs/read_write.c @ 55]
03 ffffff80`0e8bbeb0 ffffff80`080990a8 lk!el0_svc_common.constprop.0+0x64 [arch/arm64/kernel/syscall.c @ 96]
04 ffffff80`0e8bbec0 ffffff80`08083d08 lk!el0_svc_handler+0x28 [arch/arm64/kernel/syscall.c @ 96]
05 ffffff80`0e8bbec0 0000007f`b7a31148 lk!el0_svc+0x8 [arch/arm64/kernel/entry.S @ 454]
06 0000007f`f6acefd0 0000007f`b7da5c00 libc!read+0x28
07 0000007f`f6aceff0 0000007f`b7dacb08 libsystemd_shared_255_so!flush_timer+0x50 [/usr/include/aarch64-linux-gnu/bits/unistd.h @ 28]
08 0000007f`f6acf0d0 0000007f`b7dadcf4 libsystemd_shared_255_so!sd_event_wait+0x394 [../src/libsystemd/sd-event/sd-event.c @ 4663]
09 0000007f`f6acf130 0000007f`b7fbc818 libsystemd_shared_255_so!sd_event_run+0x144 [../src/libsystemd/sd-event/sd-event.c @ 4862]
-----------------------------------
[DbgInfodDownloader]: download success from url https://debuginfod.ubuntu.com/buildid/0e666e2579bcaa397c446e947bc898f453dfcb8e/debuginfo !
nd_callback code= 816 1
[DbgInfodDownloader]: download success from url https://debuginfod.ubuntu.com/buildid/2cf4edc790d914e1ba52d9868c68db9dafd6f43c/debuginfo !
nd_callback code= 816 1
nd_callback code= 816 1
nd_callback code= 801 1
run textquery... 1
-----------------------------------
Loading symbols for 0000007f`b7ee0000 libsystemd-core-255.so -> libsystemd-core-255.so
-----------------------------------
nd_callback code= 816 1
nd_callback code= 816 1
nd_callback code= 816 1
nd_callback code= 801 1
run textquery... 1
-----------------------------------
0a 0000007f`f6acf190 00000055`939ab1d8 libsystemd_core_255_so!manager_loop+0x6c4 [../src/core/manager.c @ 3288]
-----------------------------------
[DbgInfodDownloader]: download success from url https://debuginfod.ubuntu.com/buildid/d4410b4a1392f517351738d74b01572b31b31f13/debuginfo !
nd_callback code= 816 1
[DbgInfodDownloader]: download success from url https://debuginfod.ubuntu.com/buildid/f270b19598a29bf309a440a28b3000739838c6e4/debuginfo !
上面是打开调试信息后的输出,可以看到栈回溯功能的工作过程,一层层寻找父函数,找到一层后,再找下一层。如果发现下一层的模块还没有加载,那么就会使用did机制从服务器下载。因为是从符号服务器下载符号文件,所以不需要依赖目标机,完美地解决了上面提到的矛盾。
新的跨界栈回溯功能成功后,我立刻就把上面这张截图发给了博渊。很快我便收到博渊的回复。“实在是太漂亮了”,外加三朵玫瑰。
多年前去深圳,偶遇一家叫菜根香的饭店。在那里学会了一句话:“嚼得菜根,百事可做。”套用一下,“识得栈回溯之美,可解千种软件难题。”
(2024-8-21草稿,22日清早收尾完成,朝霞满天)
(写文章很辛苦,恳请各位读者点击“在看”,也欢迎转发)
*************************************************
正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生
扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物
也欢迎关注格友公众号