Windows程序员进阶系列:《软件调试》之一:调试基础

来源:互联网 发布:如何屏蔽网络监控 编辑:程序博客网 时间:2024/04/30 22:56

        Windows程序员进阶系列:《软件调试》之一:调试基础

 

     一位著名的计算机科学家曾说过:软件调试要比编写代码困难一倍。因此在阅读《软件调试》这本书之前,我已经做好了攻坚克难的准备。希望广大读者也要心里有个谱,做好这个准备。

      软件调试是软件开发和维护中一项非常繁重的任务。 一方面是难度很高,另一方面是任务繁重。因此在一个典型的软件团队中,往往会花费可观的时间用在软件调试中。在进入软件调试这个领域之前,熟悉一些基础知识是必要的。这些基础知识设计软件调试原理、方法和技巧。

 

     软件调试:指重现软件故障、定位故障根源,最终解决软件问题的过程。另一种通俗的解释是:使用调试工具求解软件问题的过程。英语原词为software debugbug原意为昆虫。后来用来表示软件中的缺陷。de词头为去除和分离。debug也就指分离取出软件问题。

软件调试基本过程

     一:重现故障。在调试系统上重复导致的故障。

     二:定位根源。利用各种调试工具,寻找导致软件故障的根源。      这就是软件调试的核心工作。

     三:探索和实现解决方案。根据寻找到的故障根源,设计解决方案。

     第二步定位根源是最关键的,也是软件调试过程的核心和灵魂。

     软件调试具有难度大、难以估算完成时间和关联性强的特点。首先为了探寻问题的根源,一般都需要深入到模块或os底层。而底层代码大多以原始形态存在。这是很困难的。再者随着软件规模的不断增大,调试也就越来越难。再者,很多调试机制是oscpu和调试器共同协作的复杂过程,并且与软件编译有非常密切的关系。因此软件调试与cpu和操作系统原理有着非常紧密的关系。这也说明软件调试具有广泛的关联性。

     从学习者的角度看,软件调试的关联性使其成为让学习者达到融会贯通境界的一个绝好途径。在一个程序员对cpu、操作系统、编译器和编程语言都基本掌握后,可以通过学习软件调试技术来加深对这些知识的理解。这也是博主此次下决心学信软件调试的主要原因之一。

      一个程序员,如果没有一定的技术积累,而一味的去追求所谓时髦的东西,那么他始终处于被动状态,被人牵着鼻子走。

 

软件调试的分类:

      按目标代码的执行模式,软件调试可以分为:内核态调试和用户态调试。顾名思义,内核态调试只是调试运行在内核态的程序。而用户态调试是指调试运行在用户态的程序。后面将会对这两种调试方式进行详细介绍。

      按调试器与调试目标的相对位置,分为:本机调试和远程调试。如果调试程序和调试目标运行在同一机器上,这种调试被称为本机调试。如果处于不同的计算机上则是远程调试。使用内核调试调试Windows引擎,需要两台计算机。这是远程调试。

     按活动目标的活动分:活动目标调试和转储文件调试。活动目标调试就是指调试实际运行的程序。转储文件存储发生问题时的系统状态的快照,包含当时内存中所有信息包括代码和各种数据。转储文件以文件的形式将调试目标的内存状态凝固下来,因此它是调试产品期问题、系统崩溃和程序崩溃的一种简单而有效的方法。由于转储文件一般很大,将转储文件分为大、中、小三个规格。Windows提供了会程序和整个操作系统产生转储文件的机制,可以在不停止程序或系统运行的情况下产生转储文件。

 

 断点

      断点是调试器调试时最常用的技术之一。它的基本原理是在某一位置设置一个中断点,当cpu执行到这个位置时,便停止被调试的程序。中断到调试器中,让调试者分析和调试。

根据断点的设置位置不同,可以分为以下几种:

     一:代码断点。设置断点的位置为某一段代码的起始处。当cpu执行到此代码时产生中断。

     二:数据断点。设置断点的位置为数据的起始地址。当被调试程序访问此数据的地址时,命中断点。可以定义触发断点的方式为:读/写断点。

     三:IO断点。 设置断点的位置为某一IO地址。当程序访问指定IO地址的端口时中断到调试器。

根据断点的设置方式,可以分为:软件断点和硬件断点。

     软件断点是通过向指定的代码位置插入专用的断点指令和实现的。比如IA32cpuINT3指令。机器码为0xCC

     硬件断点是通过设置cpu的调试寄存器来实现的,IA32cpu定义了8个调试寄存器。DR0-DR7。可以同时设置最多四个硬件断点。

硬件断点可以设置数据、代码和IO断点。而软件断点只能设置代码断点。

 

追踪点:一种特殊的断点。当该断点被命中时,调试器收到通知后,会查找断点列表。发现它是追踪点后便执行该追踪点所定义的行为。通常为打印提示信息和变量值,然后直接回复调试目标的运行。因为对追踪点处理方式是执行相应动作并立即回复执行,因此调试者通常会没有感觉到调试目标被中断到调试器的过程。

 

条件断点 :当用户设置条件断点时,调试器实际插入的还是一个调试断点,在断点命中收到调试事件后,它会检查该断点的附加条件,如果条件满足便中断到调试器。如果不满足,则立即回复调试目标的运行。

单步执行

 

单步执行:应用程序按照某个步长,一步一步执行。

根据步长大小可以分为: 

     每次执行一条汇编指令:这是汇编语言一级的单步跟踪。 实现方式时,设置CPU的标志寄存器的TF(Trap flag)位,这可以让cpu每执行完一条指令便产生一个调试异常,中断到调试器。

     每次执行源代码一条语句:又称为源码级的单步调试。高级语言的单步执行,是通过多次汇编级的单步执行来实现的,当调试器收到调试事件时,会判断程序指针IP是否还属于当前的高级语言语句。如果是,便再次设置单步执行标志,并立刻恢复执行,如此继续下去。调试器通常是通过符号文件中的源代码行信息来判断程序指针所属的源代码行的。

     每次执行一个程序分支:又被成为分支到分支单步跟踪。这是通过设置IA32cpuDbgCtl MSR寄存器的BTF(Branch trace flag)标志后,再设置TF标志来实现的。以便让cpu执行到下一分支的指令时触发异常。WinDbg通过tb命令来执行到下一分支。

     每次执行一个任务(线程):当指定任务被调度执行时中断到调试器。当IA32 cpu切换到一个新的线程时,它会检查任务状态段(TSS)的T标志。如果该标志为1,便产生调试异常。目前的调试器还不支持此操作。

 

打印输出调试信息

     这是一种很普遍的软件调试方式。其基本思想是在程序中编写专门用于打印调试信息的语句,将程序运行的位置、状态和变量值打印出来。这种方法优点是方便。但是这需要在被调试程序中加入代码。如果某处没有代码,则无法查看那里的信息。如果要增加打印语句,便需要重新编译程序。这还非常影响程序执行的效率。

      在Windows中,驱动程序可以通过Dbgprint/DbgPrintEx来打印调试信息。应用程序可以通过OutputDebugString来打印调试信息。控制台程序可以通过print打印。

 

日志

     与输出调试信息类似,日志是另一种辅助调试手段。其基本思想是在程序中加入特定代码将程序运行的状态信息写到日志文件或数据库中。日志文件通常按时间取名。许多需要长时间运行的服务器都有日志机制。Windows提供了基本的日志记录、观察和管理的功能(Event log)。后面会有介绍。

 

事件追踪

      打印调试信息和日志都是以文本的方式输出和记录信息的,因此不适合处理数据量庞大且速度要求高的情况。而事件追踪机制这是对这一需求而设计的。它使用结构化的二进制来记录数据。观察时在转换成文本形式。它非常适用于监视频繁且复杂的软件过程。

      ETW(Event Trace for windos)Windows操作系统内建的事件追踪机制。后面会详细介绍。

 

栈回溯

      目前主流的cpu架构都是使用栈来进行函数调用的。栈上记录了函数的返回地址。通过递归式的寻找放在栈上的函数的返回地址,便可以追溯出当前线程的函数调用序列。这就是栈回溯的基本原理。

      从栈上得到的函数返回地址都是数值,不是函数名称。为了便于理解,可以利用调试符号文件将返回地址翻译成函数名称。大多数调试器都支持在编译时生成调试符号。vs更不会例外。微软的调试符号服务器包含了Windows的多个版本的系统文件的符号。后面也会介绍。

 

反汇编

     反汇编是将目标代码翻译成汇编代码。符号文件对于反汇编具有重要意义。反汇编工具可以通过调试符号得到函数名称或是变量名称。这使得汇编代码具有良好的可读性。WinDbguul命令用于反汇编。

 

观察和修改数据

     观察被调试程序的数据是了解程序内部状态的一种直接方法。很多调试器提供了查看和修改数据的功能。WinDbgde系列命令用以查看和修改数据。r系列命令可以修改和查看寄存器值。

 

学习软件调试的意义

     学习软件调试可以提供程序员的工作效率。相信这是每个程序员都希望的。谁也不希望大晚上的对着一堆bug而不知所措。掌握好软件调试还可以使程序员很快的了解模块或是系统内部、架构和工作流程。同时软件调试技术具有很好的稳定性。一朝学会终生受用。由于现在国内程序员都不太重视软件调试,掌握好软件调试可以让你在项目组脱颖而出。

      每个人学习软件调试的目的各不相同。对于我来说,之所以学习软件调试是为了能够深入理解程序和操作系统内部。让自己在程序这条路上走的更远。

      在阅读《软件调试》第一章时觉得里面竟然会有一些软件工程理论的介绍。看过作者的介绍,我想这跟作者的工作经历是有很大关系的。作者强调了团队协作的重要性,从谁的bug开始谈起。应该使用某某模块的bug,而不是某人的bug。程序员也是人,且大多自傲,更需要互相尊重。只有营造出良好的团队氛围,才能谈团队凝聚力。

                                             本博文参考自《软件调试》张银奎著。如有纰漏,请不吝指正!谢谢!
                                                                                2013.1.31于山西大同
原创粉丝点击