Windows调试1.WinDbg基本使用-异常基础知识

Posted ltyandy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Windows调试1.WinDbg基本使用-异常基础知识相关的知识,希望对你有一定的参考价值。

WinDbg 的基本使用

  • WinDbg 支持的调试方式

    • 直接调试(打开一个 exe 程序) 附加调试

    • (附加到一个已经在运行的进程上)

      • 入侵式:可以改变代码的执行流程和寄存器的内容。

      • 非入侵式:不可以改变代码的执行流程,实际上就是挂起了目标进程,对目标进程的线 程环境和内存进行远程访问操作。

    • 基础指令:

      • dd dw dt da du 等:

      • 查看内存信息 ed ew ea 等:

      • 编辑内存信息 t p g 等: 流程控制

  • 中断与异常

    • 中断: 通常由外部硬件产生,属于异步事件,处理器可以不进行处理 异常:

    • 通常是由内部主动触发,属于同步事件,必须要进行处理

    • 中断和异常被统一管理,可以在 WinDbg 使用 !idt 查看中断描述符表。

  • 异常的种类

    • 错误:已经执行了,但是没有执行成功,eip 指向了当前的错误指令

      • 内存访问错误,除零错误,分页错误,硬件执行断点

  • 陷阱:已经执行了,并且执行成功,eip 指向了当前指令的下一条

    • 软件断点(int 3) 单步异常(TF) 硬件读写断点

    • 终止:一旦产生,无法修复,会直接崩溃

     

  • SEH(结构化异常处理)

    • 关键字: 需要和 C++(try throw catch) 异常处理的关键字区分开来

    • 终止处理程序:

      • 使用的关键字有 __ try __ ?nally __ leave ,保证__ ?nally块绝对被执行,通常被用作清理工 作

    • 异常处理程序: 使用关键字 __ try 和 __ except,当 __ try 有异常产生时,进行相应的处理。

    • 过滤表达式中存放的可以是任何表达式,但是返回值必须是 0 1 -1

      • EXCEPTION_CONTINUE_EXECUTION: 表示重新执行,修复了才会用

      • EXCEPTION_CONTINUE_SEARCH: 表示无法修复,继续向下以 VEH SEH UEH 的顺序寻 找

      • EXCEPTION_EXECUTE_HANDLER:表示执行 __ except 中的代码,影响执行流程

    • GetExceptionCode:过滤表达式和 except 块,获取异常类型

    • GetExceptionInfomation:只能个用于过滤表达式,获取寄存器和异常信息

  • 终止异常处理程序和普通异常处理程序都是线程相关的,可以有多个

     

    UEH(顶层异常处理程序)

  • 是异常处理的最后一道关卡,通常用于执行错误信息的收集工作,也被用于反调试

  • 设置异常处理的函数是 SetUnhandledExceptionFilter()

  • 进程相关,被存放在一个全局变量中,只能有一个

  • UEH的调用位于SEH之后,如果所有SEH都无法处理才会被调用

  • 如果程序处于被调试状态,那么UEH函数不会被调用

     

    VEH\\VCH(向量化异常处理程序)

  • VEH 和 VCH 都被存放在一个全局的链表中,可以设置多个

  • VEH的调用是最先的,如果所有VEH都无法处理异常,则会调用UEH

  • VCH只会在异常被处理的时候被调用,如果异常没有被处理程序会崩溃

    技术图片

     

     

技术图片

终结处理器

// 终结处理器: 保证程序在执行的过程中,一定会执行 _finally 块的代码
//  - 保证无论 __try 是以何种方式退出的,最终都会执行 __finally
//  - 不能够处理异常,通常只能用于执行清理工作
?
// __try: 保存的通常是需要进行检测的代码
// __finally: 保存的是一定会执行的一段代码
// __leave: 用于正常退出 __try 块
//goto :非正常退出
int main()

    __try 
    
        printf("__try  ... \\n");
?
        // 推荐使用 __leave 退出代码块,使用跳转语句会产生多余的函数调用
        // __leave 对应实际是一条 jmp 语句,执行更加的迅速,用于正常退出__try块
        __leave;
?
        // 使用跳转指令退出 __try 块,例如 continue break goto return,属于非正常退出
        goto label_exit;
    
    __finally
    
        // 通常用于执行某一些特定的清理工作,比如关闭句柄或释放内存
        printf("__finally  ... \\n");
?
        // 使用 AbnormalTermination 判断是否是异常退出的
        if (AbnormalTermination())
            printf("异常退出代码块");
        else
            printf("正常退出代码块");
    
?
label_exit:
?
    return 0;

 

 

异常处理SEH

// SEH 的两种实现方式是不能同时存在的,但是可以嵌套
//      两种功能指__try__finanly跟__try__except
// SEH 的处理函数被保存在了栈中,所以不同的线程拥有各自的处理函数
?
// 异常处理程序(SEH):  可以用于捕获产生的异常,并且对它执行相应的处理
?
// __try: 是需要被保护(可能产生异常)的代码
// __except: 存放过滤表达式和异常处理块
?
?
// 保存异常信息和线程环境的异常结构体
// typedef struct _EXCEPTION_POINTERS 
//    PEXCEPTION_RECORD ExceptionRecord;        // 保存了[异常类型]和[产生异常的指令所在的位置]
//    PCONTEXT ContextRecord;                   // 保存的是异常发生时的寄存器环境,通过修改可以修复异常
//  EXCEPTION_POINTERS, * PEXCEPTION_POINTERS;
?
?
// 过滤函数: 用于根据不同的情况,返回不同类型的值
//  GetExceptionCode()返回值是 unsigned long
//  typedef unsigned long       DWORD;
DWORD FilterHandler(DWORD ExceptionCode, PEXCEPTION_POINTERS ExceptionInfo)
 
    // 假设产生的是一个整数除零异常,尝试对异常进行处理,并返回继续执行
    if (ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
    
        ExceptionInfo->ContextRecord->Ecx = 1;
        return EXCEPTION_CONTINUE_EXECUTION;
        //这个是-1
    
?
    // 否则其它的异常不做处理,向上传递,这个是0
    return EXCEPTION_CONTINUE_SEARCH;

?
?
int main()

    __try
    
        printf("__try  ... \\n");
?
        // 可能会产生异常的指令,只有产生了异常,才会执行 ___except
        __asm mov eax, 100
        __asm xor edx, edx
        __asm xor ecx, ecx
        __asm idiv ecx
?
        // 处理异常有意义么? 没有意义的,具体需要分析目标的处理函数
        printf("异常已经被处理类!\\n");
?
        // 一个触发内存访问异常的代码
        // *(DWORD*)0 = 0;
    
?
    // __except() 过滤表达式的内容可以是任意形式的,但是它的值必须是下面的三个之一,通常是函数调用
    //  - EXCEPTION_EXECUTE_HANDLER(1): 表示捕获到了异常,需要执行异常处理块的代码,并继续往下执行
    //  - EXCEPTION_CONTINUE_SEARCH(0): 表示无能为力,交给其它异常处理函数,通常没有处理的返回这一个
    //  - EXCEPTION_CONTINUE_EXECUTION(-1): 表示不相信不能执行,需要重新执行一遍,只有处理了的异常才会使用
    // 异常过滤函数通常要用到的两个函数调用
    //  - GetExceptionCode: 获取产生的异常的类型,只能在过滤表达式和异常处理块中调用
    //  - GetExceptionInformation: 获取异常的信息和异常产生时的线程环境,只能在过滤表达式中使用
    __except(FilterHandler(GetExceptionCode(), GetExceptionInformation()))
    
        // 只会在异常过滤表达式的值为 EXCEPTION_EXECUTE_HANDLER 才会调用
        printf("__except(EXCEPTION_EXECUTE_HANDLER)  ... \\n");
    
?
    return 0;

 

顶层异常处理
// 顶层异常处理(UEH): 是应用程序的最后一道防线,如果所有的 SEH 都没有能够处理异常,就会执行它
//  - UEH 通常被用于执行内存转储操作,将收集的错误信息(异常类型,线程上下文和内存)提交到服务器
//  - UEH 在 64位系统 下的调试器内是永远不会执行的,需要单独的进行运行
?
?
// 自定义的顶层异常处理函数,即使没有自定义,也会有一个默认的处理函数,且只有一个
// 它的返回值类型和 SEH 是相同的,但是缺少了 EXCEPTION_EXECUTE_HANDLER
LONG WINAPI TopLevelExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)

    printf("TopLevelExceptionHandler(): %08X\\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
?
    // 假设产生的是一个整数除零异常,尝试对异常进行处理,并返回继续执行
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
    
        ExceptionInfo->ContextRecord->Ecx = 1;
        return EXCEPTION_CONTINUE_EXECUTION;    // 0
    
?
    // 否则其它的异常不做处理,向上传递
    return EXCEPTION_CONTINUE_SEARCH;           // 1

?
?
int main()

    // 顶层异常处理的设置依赖于一个函数
    SetUnhandledExceptionFilter(TopLevelExceptionHandler);
?
    __try
    
        // 产生除零异常
        __asm mov eax, 100
        __asm xor edx, edx
        __asm xor ecx, ecx
        __asm idiv ecx
    
    __except (EXCEPTION_CONTINUE_SEARCH)
    
        // 这里永远不会执行,因为不是 EXCEPTION_EXECUTE_HANDLER(1)
        printf("__except (EXCEPTION_CONTINUE_SEARCH)\\n");
?
    
?
    printf("异常处理成功!");
    system("pause");
?
    return 0;

 

向量化异常处理程序(VEH)

// 向量化异常处理程序(VEH): 用户层支持的一种机制,在 SEH 之前被执行
//  - 保存在一个全局的链表中,整个进行都可以访问到
?
?
// 自定义的 VEH 异常处理函数,它的执行位于 SEH 之前,如果 VEH 没有处理成功,才会调用 SEH
LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)

    printf("VectoredExceptionHandler(): %08X\\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
?
    // 假设产生的是一个整数除零异常,尝试对异常进行处理,并返回继续执行
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
    
        ExceptionInfo->ContextRecord->Ecx = 1;
        return EXCEPTION_CONTINUE_EXECUTION;    // 0
    
?
    // 否则其它的异常不做处理,向上传递
    return EXCEPTION_CONTINUE_SEARCH;           // 1

?
?
int main()

    // 设置一个向量化异常处理函数(VEH),参数一表示添加到异常处理函数链表的位置
    AddVectoredExceptionHandler(TRUE, VectoredExceptionHandler);
?
    __try
    
        // 产生除零异常
        __asm mov eax, 100
        __asm xor edx, edx
        __asm xor ecx, ecx
        __asm idiv ecx
    
    __except (EXCEPTION_EXECUTE_HANDLER)
    
        // 这里永远不会执行,因为不是 EXCEPTION_EXECUTE_HANDLER(1)
        printf("__except (EXCEPTION_EXECUTE_HANDLER)\\n");
?
    
?
    printf("异常处理成功!");
    system("pause");
?
    return 0;

 

 

向量化异常处理程序(VCH)

// 向量化异常处理程序(VCH): 用户层支持的一种机制,在 最后 被执行
//  - 保存在一个全局的链表中,整个进程都可以访问到,和 VEH 在同一个表中,只是标志位不同
//  - VCH 只会在异常被处理的情况下,最后被执行。
?
// VEH -> SEH -> UEH -> [VCH]
?
?
// 自定义 UEH 函数,在 SEH 之后执行
LONG WINAPI TopLevelExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)

    printf("TopLevelExceptionHandler(): %08X\\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
?
    // 假设产生的是一个整数除零异常,尝试对异常进行处理,并返回继续执行
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
    
        ExceptionInfo->ContextRecord->Ecx = 1;
        return EXCEPTION_CONTINUE_EXECUTION;
    
?
    // 否则其它的异常不做处理,向上传递
    return EXCEPTION_CONTINUE_SEARCH;

?
// 自定义的 VEH 异常处理函数,它的执行位于 SEH 之前,如果 VEH 没有处理成功,才会调用 SEH
LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)

    printf("VectoredExceptionHandler(): %08X\\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
?
    // 假设产生的是一个整数除零异常,尝试对异常进行处理,并返回继续执行
    //if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
    //
    //  ExceptionInfo->ContextRecord->Ecx = 1;
    //  return EXCEPTION_CONTINUE_EXECUTION;
    //
?
    // 否则其它的异常不做处理,向上传递
    return EXCEPTION_CONTINUE_SEARCH;

?
// 自定义的 VCH 异常处理函数,只有在异常处理成功的情况下,最后才会被调用
LONG WINAPI VectoredContinueHandler(EXCEPTION_POINTERS* ExceptionInfo)

    printf("VectoredContinueHandler(): %08X\\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
    return EXCEPTION_CONTINUE_SEARCH;

?
?
// 过滤函数: 用于根据不同的情况,返回不同类型的值
DWORD FilterHandler(DWORD ExceptionCode, PEXCEPTION_POINTERS ExceptionInfo)

    printf("FilterHandler(): %08X\\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
?
    //// 假设产生的是一个整数除零异常,尝试对异常进行处理,并返回继续执行
    //if (ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
    //
    //  ExceptionInfo->ContextRecord->Ecx = 1;
    //  return EXCEPTION_CONTINUE_EXECUTION;
    //
?
    // 否则其它的异常不做处理,向上传递
    return EXCEPTION_CONTINUE_SEARCH;

?
int main()

    // 设置一个向量化异常处理函数(VEH)
    AddVectoredExceptionHandler(TRUE, VectoredExceptionHandler);
    // 设置一个向量化异常处理函数(VCH)
    AddVectoredContinueHandler(TRUE, VectoredContinueHandler);
    // UEH 处理函数
    SetUnhandledExceptionFilter(TopLevelExceptionHandler);
?
    __try
    
        // 产生除零异常
        __asm mov eax, 100
        __asm xor edx, edx
        __asm xor ecx, ecx
        __asm idiv ecx
    
    __except (FilterHandler(GetExceptionCode(), GetExceptionInformation()))
    
        // 这里永远不会执行,因为不是 EXCEPTION_EXECUTE_HANDLER(1)
        printf("__except (EXCEPTION_EXECUTE_HANDLER)\\n");
    
?
    printf("异常处理成功!");
    system("pause");
?
    return 0;

 

 

 

以上是关于Windows调试1.WinDbg基本使用-异常基础知识的主要内容,如果未能解决你的问题,请参考以下文章

Windbg调试工具使用详解,干货满满!

通过并口进行 windbg 双击 Kernel 调试

关于windbg的认识

TroubleShooting经验总结

背水一战 Windows 10 (73) - 控件(控件基类): UIElement - 拖放的基本应用, 手动开启 UIElement 的拖放操作

Windows调试2.异常产生详细流程