分类: 嵌入式
2012-08-09 18:16:47
调试器简介
严格的讲,调试器是帮助程序员跟踪,分隔和从软件中移除bug的工具。它帮助程序员更进一步理解程序。一开始,主要是开发人员使用它,后来测试人员,维护人员也开始使用它。
调试器的发展历程:
调试器的设计和开发要遵循四个关键的原则:
按照划分的标准不同,调试器主要分为一下几类:
调试器的架构
调试器之间的区别更多的是体现在他们展现给用户的窗口。至于底层结构都是很相近的。下图展示了调试器的总体架构:
调试器内核
调试器服务于所有的调试器视图。包括进程控制,执行引擎,表达式计算,符号表管理四部分。
操作系统接口
调试器内核为了访问被调试程序,必须使用操作系统提供的一系列例程。
硬件调试功能
调试器控制被调试程序的能力主要是依靠硬件支持和操作系统的调试机制。调试器需要最少三种的硬件功能的支持:
1. 提供设置断点的方法;
2. 通知操作系统发生中断或者陷阱的功能;
3. 当中断或者陷阱发生时,直接读写寄存器,包括程序计数器。
通用的硬件调试机制
1. 断点支持
断点功能是通过特定的指令来实现的。对于变长指令的处理器,断点指令通常是最短的指令,下图给出了四个处理器的断点指令:
2. 单步调试支持
单步调试是指执行一条指令就产生一次中断,是用户可以查找每条指令的执行状态。一般的处理器都提供一个模式位来实现单步调试功能。
3. 错误检测支持
错误检测功能是指当操作系统检测到错误发生时,他通知调试器被它调试的程序发生了错误。
4. 检测点支持
用来查看被调试程序的地址空间(数据空间)。
5. 多线程支持
6. 多处理器支持
调试器的操作系统支持功能
为了控制一个被调试程序的过程,调试器需要一种机制去通知操作系统该可执行文件希望被控制。即一旦被调试程序由于某些原因停止的时候,调试器需要获取详细的信息使得他知道被调试程序是什么原因造成他停止的。
调试器是用户级的程序,并不是操作系统的一部分,并不能运行特权级指令,因此,它只能通过调用操作系统的系统调用来实现对特权级指令的访问。
调试器运行被调试程序,并将控制权转交给被调试程序,需要进行上下文切换。在一个简单的断点功能实现,有6个主要的转换:
1. 当调试器运行到断点指令的时候,产生陷阱跳转到操作系统;
2. 通过操作系统,跳转到调试器,调试器开始运行;
3. 调试器请求被调试程序的状态信息,该请求送到操作系统进行处理;
4. 转换到被调试程序文本以获取信息,被调试程序激活;
5. 返回信息给操作系统;
6. 转换到调试器以处理信息。
一旦使用图形界面调试器,过程会更加的复杂。
对于多线程调试的支持;
l 一旦进程创建和删除,操作系统必须通知调试器;
l 能够询问和设置特定进程的进程状态;
l 能够检测到应用程序停止,或者线程停止。
例子:UNIX ptrace()
UNIX ptrace 是操作系统支持调试器的一个真实的API。
控制执行
调试器的核心是它的进程控制和运行控制。为了能够调试程序,调试器必须能够对被调试程序进行状态设置,断点设置,运行进程,终止进程。
控制执行主要包含一下几个功能:
1. 创建被调试程序
调试器做的第一件工作,就是创建被调试程序。一般通过两种手段:一种是为调试程序创建被调试进程,另一种是将调试器附到被调试进程上。
2. 附到被调试进程
当一个进程发生错误异常,并且在被刷出(内存刷新)内存的时候,允许调试器挂到出错进程以此来检查内存镜像。这个时候,用户不能再继续执行进程。
3. 设置断点
设置断点的功能是在可执行文本中插入特殊的指令来实现的。当程序执行到该特殊指令的时候,就产生陷阱,陷到操作系统。
4. 使被调试程序运行
当调试中断产生的时候,调试器属于激活进程,而被调试程序属于未激活进程。调试器产生一个系统中断请求恢复被调用函数的执行,操作系统对被调试程序进行上下文切换,恢复被调用程序的现场状态,然后执行被调用程序。
执行区间的调试事件生成类型:
l 断点,单步调试事件
l 线程创建/删除事件
l 进程创建/删除事件
l 检测点事件
l 模块加载/卸载事件
l 异常事件
l 其他事件
断点和单步调试 断点通常需要两层的表示: l 逻辑表示:指在源代码中设置的断点,用来告诉用户的; l 物理表示:指真实的在机器码中写入,是用来告诉物理机器的。断点必须存储写入位置的机器指令,以便能够在移除断点的时候恢复原来的指令。 断点存在条件断点。 断点存在多对一的关系,即多个用户在同一个地方设置断点(多个逻辑断点对应一个物理断点),当然也有多对多的关系。下图展示了这样的一个关系:
临时断点 临时断点是指只运行一次的断点。 内部断点 内部断点对用户是不可见的。他们是被调试器设置的。 一般主要用于: l 单步调试:内部断点和运行到内部断点; l 跳出函数:在函数返回地址设置内部断点; l 进入函数
查看程序的上下文信息 一般要查找程序的上下文信息主要有以下几种方法: 通过源代码查看程序执行到代码的那一部分 程序堆栈是由硬件,操作系统和编译器共同支持的: 硬件: 提供堆栈指针; 操作系统:为每个进程建立堆栈空间,并管理堆栈。一旦堆栈溢出,而产生一个错误;