面试问题 -
一旦你的代码出现问题,通常很容易调试程序.你可以把手表,断点等放在一边.由于调试器,生活更容易.
但是如何在没有调试器的情况下调试程序?
我知道一种可能的方法是将print语句放在代码中的任何地方,以便检查问题.
除此之外还有其他方法吗?
作为一般性问题,它不受任何特定语言的限制.请分享您对如何做到的想法?
编辑 - 在提交您的答案时,请提及有关任何概念的有用资源(如果您有的话).例如,记录
这对那些根本不了解它的人会很有用.(这包括我,在某些情况下:)更新: Michal Sznajderhas提出了一个真正的"最佳"答案,并使其成为一个社区维基.真的值得很多投票.
Mic*_*der 54
实际上你有很多可能性.重新编译源代码或不重新编译.
ToString()或创建一些EnumToString()功能(适合您的语言)最后一些一般提示:
awk,grep或perl)一起为您提供令人难以置信的分析包.如果您有超过32K的记录,请考虑使用Access作为数据源.一般来说,调试就像科学一样:你不会创造它而发现它.通常它喜欢在刑事案件中寻找凶手.所以给自己买一顶帽子,永不放弃.
首先,调试实际上做了什么?高级调试器为您提供机器挂钩以暂停执行,检查变量并可能修改正在运行的程序的状态.大多数程序不需要所有这些来调试它们.有很多方法:
跟踪:实现某种日志记录机制,或使用现有的日志记录机制,如dtrace().通常值得实现某种类似printf的函数,它可以将通常格式化的输出输出到系统日志中.然后将状态从程序中的关键点抛出到此日志中.信不信由你,在复杂的程序中,这比使用真正的调试器进行原始调试更有用.日志帮助你知道你是怎么得到了麻烦,而陷阱的崩溃调试器假设你可以反向工程你如何到达那里的,你已经处于任何状态.对于使用其他复杂库的应用程序,你不拥有该在它们中间崩溃,日志往往更有用.但是在编写日志消息时需要一定的纪律.
程序/库自我意识:为了解决非常具体的崩溃事件,我经常在系统库上实现包装器,例如malloc/free/realloc,这些扩展可以执行诸如步行内存,检测双重释放,尝试释放未分配的指针之类的东西检查明显的缓冲区溢出等.通常你也可以为重要的内部数据类型做这类事情 - 通常你可以对链接列表之类的东西进行自我完整性检查(它们不能循环,它们不能点到LA-LA土地).即使对于像OS同步对象,往往你只需要知道哪个线程,还是什么文件和行号(可拍摄__FILE__,__LINE__)的同步对象的最后一个用户是帮助你找出竞争条件.
如果你像我一样疯了,你可以在你自己的程序中实现自己的迷你调试器.这实际上只是自反射编程语言中的一种选择,或者在具有某些OS挂钩的C语言中.在Windows/DOS中编译C/C++时,可以实现"崩溃挂钩"回调,该回调在触发任何程序错误时执行.编译程序时,您可以构建一个.map文件来确定所有公共函数的相对地址(这样您就可以通过从.map中给出的地址减去main()的地址来计算加载器的初始偏移量.文件).因此,当发生崩溃时(例如,即使在运行期间按下^ C,您也可以找到无限循环),您可以获取堆栈指针并在返回地址内扫描它以获得偏移量.您通常可以查看您的寄存器,并实现一个简单的控制台,让您检查所有这些.瞧,你实现了一半真正的调试器.保持这种状态,您可以重现VxWorks的控制台调试机制.
另一种方法是逻辑演绎.这与#1有关.基本上,程序中的任何崩溃或异常行为都会在停止按预期运行时发生.您需要有一些反馈方法来了解程序何时正常运行然后异常运行.然后,您的目标是找到程序从正确行为到错误行为的确切条件.使用printf()/日志或其他反馈(例如在嵌入式系统中启用设备 - PC有一个扬声器,但有些主板还有一个用于BIOS阶段报告的数字显示器;嵌入式系统通常会有一个COM端口,您可以使用)您可以通过程序的工具,至少推导出关于程序运行状态的良好和不良行为的二进制状态.
相关方法是关于代码版本的逻辑推论.程序通常在一个状态下完美运行,但某些后续版本不再有效.如果您使用良好的源代码控制,并且在编程团队中强制执行"树顶必须始终正常工作"的理念,那么您可以使用二进制搜索来查找发生故障的代码的确切版本.然后,您可以使用差异来推断出哪些代码更改会暴露错误.如果差异太大,那么您的任务是尝试以较小的步骤重做代码更改,以便更有效地应用二进制搜索.