一、SymView

SymView工具用来显示符号文件中包含的符号表和符号数据。目前支持微软的Visual C/C++和C#编译器产生的DBG格式的符号文件和PDB格式的符号文件。

SymView提供了以下多种方式来查看或者搜索符号文件的内容:

  • 符号表
  • PDB或DBG文件中可以包含6种符号表,分别为符号表、源文件表、节贡献表、段表、注入源代码表和帧数据表。
  • 编译素材(Compiland)
  • 用于产生可执行文件的各种素材文件,包括OBJ、DLL、RES等。
  • 数据流
  • 比如描述FPO的FPO数据流。
  • 符号类标(Tag)
  • 根据所描述对象,符号被分为函数、常量、用户定义类型(UDT)、类型、常量等30多个类型。
  • 搜索
  • 可以按照符号逻辑关系或者符号名称等搜索符号。

下图是SymView的一个截图,左侧提供了上面介绍的5种观察视图。右侧上方的网格窗口用于显示左侧选中表格或者符号类的所有数据行或者符号。右侧下方用于显示上方选中行的子符号或者相关信息(属性)。 例如,目前使用的符号类表视图,选中的是UDT类型,右侧上方显示的是目前符号文件(NT内核的公共符号文件)中的所有UDT类型的符号。右侧下方显示的是上方选中的_DBGKD_BREAKPOINTEX类型的所有属性。

最下方的窗口用户显示SymView程序自身的调试信息,可能包含它工作过程中遇到的异常或者错误情况。

下载地址:http://advdbg.org/books/swdbg/t_symview.aspx

二、Pdbripper

这款Pdb文件提取工具可以将符号文件提取出来,用于单独查看调试文件是否正确。非常好用的一款小工具。

下载地址:http://ntinfo.biz/index.html

 

当一个异常发生时,操作系统要向引起异常的线程的栈里压入三个结构,这三个结构是:E X C E P T I O N _ R E C O R D结构、C O N T E X T结构和E X C E P T I O N _ P O I N T E R S结构。

一、E X C E P T I O N _ R E C O R D

这个结构包含有关最近发生的异常的详细信息,这些信息独立于C P U,定义如下:

typedefstruct_EXCEPTION_RECORD

{

DWORD ExceptionCode;

DWORD ExceptionFlags;

struct_EXCEPTION_RECORD *ExceptionRecord;

PVOID ExceptionAddress;

DWORD NumberParameters;

ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

} EXCEPTION_RECORD;

ExceptionCode:包含异常的代码。这同内部函数G e t E x c e p t i o nCo d e()返回的信息是一样的。这是一个硬件异常产生的代码或者由RaiseException函数指定产生的软件异常码。

ExceptionFlags:包含有关异常的标志。当前只有两个值,分别是0(指出一个可以继续的异常)和E X C E P T I O N _ N O N C O N T I N U A B L E(指出一个不可继续的异常)。在一个不可继续的异常之后,若要继续执行,会引发一个E X C E P T I O N _ N O N C O N T I N U A B L E _E X C E P T I O N异常。

ExceptionRecord:指向另一个未处理异常的E X C E P T I O N _ R E C O R D结构。在处理一个异常的时候,有可能引发另外一个异常。例如,异常过滤器中的代码就可能用零来除一个数。当嵌套异常发生时,可将异常记录链接起来,以提供另外的信息。如果在处理一个异常过滤器的过程当中又产生一个异常,就发生了嵌套异常。如果没有未处理异常,这个成员就包含一个N U L L。

ExceptionAddress:指出产生异常的C P U指令的地址

NumberParameters: 规定了与异常相联系的参数数量(0 到1 5 )。这是在E x c e p t i o n I n f o r m a t i o n数组中定义的元素数量。对几乎所有的异常来说,这个值都是零。

ExceptionInformation: 规定一个附加参数的数组,用来描述异常。对大多数异常来说,数组元素是未定义的。RaiseException函数可以指定这个参数数组。下面的表格展示了哪些异常码的数组元素是定义了的。

Exception codeMeaning
EXCEPTION_ACCESS_VIOLATION

The first element of the array contains a read-write flag that indicates the type of operation that caused the access violation. 数组的第一个元素包含了一个读写标志,表示引起访问违规的操作类型。If this value is zero, the thread attempted to read the inaccessible data. 如果这个值为0,表示线程试图读取不可访问的数据。If this value is 1, the thread attempted to write to an inaccessible address. 如果这个值为1,表示线程试图写入不可访问的地址。If this value is 8, the thread causes a user-mode data execution prevention (DEP) violation.如果这个值是8,表示线程线程引发了一个用户模式的DEP违规。

The second array element specifies the virtual address of the inaccessible data.数组的第二个元素指定了不可访问数据的虚拟地址。

EXCEPTION_IN_PAGE_ERROR

The first element of the array contains a read-write flag that indicates the type of operation that caused the access violation. 数组的第一个元素包含了一个读写标志,用于表示引起访问违规的操作类型。If this value is zero, the thread attempted to read the inaccessible data. 如果值为0,表示线程试图读取不可访问的数据。If this value is 1, the thread attempted to write to an inaccessible address. 如果值为1,表示线程试图写入不可访问的地址。If this value is 8, the thread causes a user-mode data execution prevention (DEP) violation.如果值为8,表示线程引起了一个用户模式的DEP违规。

The second array element specifies the virtual address of the inaccessible data.数组的第二个元素指定了不可访问数据的虚拟地址。

The third array element specifies the underlying NTSTATUS code that resulted in the exception.数组的第三个元素表示底层的NTSTATUS码引起的本次异常。

二、C O N T E X T

包含处理器特定的寄存器数据。系统使用上下文结构执行各种内部操作。

typedef struct _WOW64_CONTEXT {
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
WOW64_FLOATING_SAVE_AREA FloatSave;
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;
DWORD SegSs;
BYTE ExtendedRegisters[WOW64_MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;

三、E X C E P T I O N _ P O I N T E R S

这个结构只有两个数据成员,二者都是指针,分别指向被压入栈的E X C E P T I O N _ R E C O R D和C O N T E X T结构:

typedefstruct _EXCEPTION_POINTERS

{

PEXCEPTION_RECORDExceptionRecord;

PCONTEXTContextRecord;

} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

 

当我们在调试程序异常时可以利用这些数据结构的数据来分析定位问题代码。

一、什么是异常

异常指的是在程序运行过程中发生的异常事件,通常是由外部问题(如硬件错误、输入错误)所导致的。简单来说异常就是对于非预期状况的处理,当我们在运行某个程序时出现了异常状况,就会进入异常处理流程。

二、异常分类

根据异常来源分,可以分为如下两种异常:

  • 硬件异常
    硬件异常是由cpu产生,其中硬件异常又和中断、系统调用等行为有着密切的联系。硬件异常可以分为三种:fault,在处理此类异常时,操作系统会将遭遇异常时的“现场”保存下来(比如EIP、CS等寄存器的值),然后将调用相应的异常处理函数,如果对异常的处理成功了(没成功的情况会在下文中提到),那就恢复到原始现场,继续执行。最经典的fault例子莫过于Page Fault了,在分页机制下,当我们读到某个还未载入到内存的页时,就会触发该异常,操作系统会将该页载入内存,然后重新执行读取该页的指令,这是分页机制实现的重要机制;trap,在处理此类异常时,操作系统会将异常的“下文”保存,在处理异常后,直接执行导致异常的指令的下一条指令。我们在调试过程中常用的断点操作就是基于这类异常的,当我们在某处下断点时调试器会将原本此处的指令对应的十六进制保存下来,然后替换第一个字节替换为0xCC的,也就是int 3,造成断点异常,中断(此处的中断用的是break,而我们一般说的中断是interrupt,请读者务必区分清楚)到调试器,程序在运行到此处就会停止等待下一步的指令,而当我们继续执行时调试器就会将该指令替换为原来的指令,程序也就恢复正常执行了。不知道大家有没有注意过,在进行程序调试时经常会看见hex界面显示大量的“烫烫烫”,这其实是0xcc对应的中文字符,因为这些地址的内容程序并不想让我们访问,一旦我们访问这些地址,就会读到0xcc,程序也就“中断”了;abort,中止异常,主要是处理严重的硬件错误等,这类异常不会恢复执行,会强制性退出。
  • 软件异常
    软件异常是由操作系统或应用程序产生的,它又包含了windows为我们定义好的异常处理和我们自己写的异常处理(各种编程语言中的try-catch结构)。这类异常追根溯源都是基于RaiseException这个用户态API和NtRaiseException的内核服务建立起来的。

三、异常和错误的区别

我们平时编程过程中,异常和错误我们都会遇到。一般在编译期,我们会遇到很多编译错误,在链接期可能也会产生链接错误,这些错误严格来说还算不上程序错误。层序运行时,Windwos系统的各种API执行失败返回时,通过线程局部存储保存的error信息是错误,一般这种错误不会改变线程的执行路径,当然,如果我们不检测处理,最终会导致异常退出。异常一定是程序运行期产生的,异常发生时一定会改变线程的执行路径。

windows系统里,为了保证系统内核的强壮和稳定,为了保证用户程序的强壮和稳定,提供了异常处理机制,来帮助程序员和系统使用人员处理异常。简单来说,当CPU执行代码时,发生异常,会把异常告知操作系统,操作系统首先会让程序自身处理这个异常,程序自身有能力(程序中注册的有异常处理函数)处理,程序就继续运行;程序自身没有能力处理(程序中没有注册异常处理函数),这个异常还没有被处理,就有操作系统来处理,就提示用户是调用调试器调试还是结束程序。如果在有调试器参与的情况下,程序出现异常,操作系统不会把异常处理权交给程序自身而是调试器,调试器可以把处理权继续转交给程序,也可以自己处理。为了完成前面讲的过程,Windwos信息提供了SEH和VEH两种处理机制。下面分别简单介绍。
一、SEH异常处理机制

SEH是windows操作系统异常处理机制,叫结构化异常处理,在程序源代码中使用__try,__except,__finally关键字来具体实现。

Windows操作系统(自Windows95起),对每个用户线程,都设立一个异常处理帧链表来处理异常事件。该链表的每个异常处理帧由两个成员组成,分别是链表上一项地址、当前异常处理器地址,组成了结构_EXCEPTION_REGISTRATION_RECORD。异常处理器是指一个处理异常的回调函数(callback function)。线程信息块(thread information block)的开始处(即FS:[0]指向的内存,FS是CPU的一个段寄存器)保存了异常处理帧链表的表头项的地址。程序执行遇到异常事件而中断时,操作系统的RtlDispatchException函数会从FS:[0]指向的链表表头依次调用每个节点包含异常处理回调函数,直到某个异常处理回调函数的返回值为0表示已经处理该异常,该线程可以恢复执行。链表最末一项是操作系统在装入线程时设置的指向kernel32!UnhandledExceptionFilter函数,该函数总是向用户显示“Application error”对话框。上述异常处理器程序及链表,是由用户程序自己安装的。链表各节点保存在程序调用栈(call stack)上。Windows异常处理机制支持嵌套异常的处理,即在执行异常处理回调函数时再次发生异常。这种情况下仍遵照普通异常处理机制,操作系统RtlDispatchException函数再入处理新出现的嵌套的异常。嵌套的异常的处理函数得到DispatcherContext参数值即为在执行时发生了新异常的异常帧的地址。

根据异常来源,一般分硬件异常和软件异常,它们处理的流程大致一样,下面简单讲一下。

如果是硬件异常,CPU会根据中断类型号转而执行对应的中断处理程序。CPU会在IDT中查找对应的函数来处理,各个异常处理函数不仅仅处理异常还需要将异常信息封装,以便对后续处理,KiTrapXX例程在完成针对本异常的特别动作后,通常会调用CommonDispatchException函数,它会在栈中分配一个EXCEPTION_RECORD结构,并把异常信息存储到该结构中。在准备好这个结构后,它会调用内核中的KiDispatchExcption函数来分发异常。

如果是软甲异常,是通过直接或间接调用内核服务KiRaiseException而产生的。函数内部会把Context上下背景文复制到当前线程的内核栈,接下来调用KiDispatchExcption函数来进行分发。

综上所述,不管什么异常最后都会调用内核中的KiDispatchExcption函数进行分发,也就是说Windows用统一的方式来管理。异常封装完成后,系统会调用nt!KiDispatchException来处理异常,所以分析KiDispatchException函数就可以了解异常是如何被处理的。

首先来看看KiDisPatchException函数的函数原型

void KiDispatchException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN KPROCESSOR_MODE PreviousMode,
IN BOOLEAN FirstChance
)

ExceptionRecord也就是前面提的描述异常的结构,TrapFrame指向的结构用来描述发生异常时候的上下文,PreviousMode来说明异常来自Kernel还是User,最后的FirstChance用来表示异常是不是第一次被处理,实际上这些结构的集合就形成了一个虚拟的、完整的“异常”结构,再去进行下面的处理。下面先来看看KiDispatchException 的分发示意图:

从图中我们可以看到,KiDispatchException会先调用KeContextFromKframes函数,目的是根据TrapFrame参数指向的KTRAP_FRAME结构产生一个CONTEXT结构,以供向调试器和异常处理器函数报告异常时使用。右边是内核的异常,左边边是用户的异常。

内核异常处理分发流程:

但PreviousMode为0时,就会进入Kernel的异常分发,系统会维护一个KiDebugRoutine的函数,当内核的调试器启动时,它就帮我们把异常送往了内核调试器,而在未启动时,它只是一个“存根”函数(stub),返回一个False。这一步也就是图中的debug

当第一次debug返回False后会接着调用RtlDispatchException,试图寻找已经注册的结构化异常处理器(SEH),函数的原型:BOOLEAN RtlDispatchException(PEXCEPTION_RECORD ExceptionRecord,PCONTEXT ContextRecord)。如果没有处理的话就会进行第二轮调试,重复上面的debug内容,如果依然是没有启用调试器的话就那么就会把这个异常当作UnhandleException,也就是我们常说的未处理异常,在kernel下未处理异常可是个大问题,毕竟这可是操作系统最最重要的也最最完善的内核,这样的未处理异常一般都不是小问题,为了防止异常引发更大的问题,这时候系统就会调用KeBugCheckEx中止系统运行显示蓝屏,并将导致异常的地址打印在屏幕上。

具体步骤如下:

    ①系统会先检测是否有内核调试器,如果没有,就跳过这一步,如果有,就把异常处理的权限交给内核调试器,并且注明是第一次来执行的这个异常(FirstChance),内核调试器如果处理了该异常就继续回到原来异常地方继续执行,如果没有处理则发生中断,将控制权交给用户,用户决定是否继续处理