你有没有遇到过这种情况:辛辛苦苦写完一段代码,一运行就卡住,或者直接崩溃,但日志什么也没留下。这时候普通打印调试已经不够用了,得上点硬货——逆向调试器。
什么是逆向调试器?
简单说,它不是等程序出错再查,而是让你“倒着看”程序是怎么走到这一步的。比如某个变量突然变成 null,传统调试只能在出错那一刻发现,而逆向调试器能告诉你这个变量之前在哪一步被改掉的。
常见工具推荐
Windows 上用 WinDbg 搭配时间旅行调试(TTD),Linux 下可以用 rr 工具。rr 是开源的,支持命令行操作,适合开发环境。
比如你在 Ubuntu 上跑一个会崩溃的 C 程序:
rr record ./my_program
程序运行过程中会被完整记录下来。等它崩了,执行回放:
rr replay
进入调试界面后,你可以用 reverse-continue 命令往回走,直到找到出问题的那一行代码。就像视频倒放一样直观。
实际场景:定位野指针
有个老项目总在用户上传图片时崩溃,但只在客户机器上出现。本地没法复现,日志也看不出啥。用 rr 记录生产环境的简化版本后,回放时执行:
watch *0x7ffff7ed2010
监控某块内存的读写。结果发现几分钟前就有函数往这块已释放的内存写数据。顺藤摸瓜找到了那个没置空的指针。修完之后问题再没出现。
Android 也能用
安卓 native 层崩溃也可以借助逆向调试思路。NDK 提供的 lldb 虽不完全支持倒放,但结合 core dump 和符号表,能还原崩溃前的调用栈。比如 app 在某个机型上闪退,抓取 tombstone 文件后:
ndk-stack -sym /path/to/symbols < crash.log
就能看到具体是哪一行 C++ 代码出了问题。虽然不算严格意义上的“逆向”,但思路一致:从结果反推过程。
小贴士
逆向调试器对性能有一定影响,别在高并发线上服务直接开全量记录。可以先在测试环境复现,或者只在特定路径触发记录。另外,编译时记得加调试符号(-g 参数),不然回放时看不到变量名。
学会这招,下次遇到神出鬼没的 bug,不用靠猜了。