zoukankan      html  css  js  c++  java
  • Windows 程序 dump 崩溃调试

    Windows 程序捕获崩溃异常 生成dump

    概述

    事情的起因是,有个同事开发的程序,交付的版本程序,会偶尔随机崩溃了。

    悲催的是没有输出log,也没有输出dump文件。

    我建议他给程序代码加个异常捕获,在崩溃时生成dump,方便找出问题点。

    隔了一天之后,短暂交流,发现他没有这个开发经验,我只好披挂上阵了。

    开动

    查阅MSDN文档,和stackoverlfow.com的相关文章,可知

    SetUnhandledExceptionFilter 可以捕获触发系统崩溃的异常

    风风火火开始写代码

        void exceptionHandler(PEXCEPTION_POINTERS excpInfo)
        {
            // your code to handle the exception. Ideally it should
            // marshal the exception for processing to some other
            // thread and wait for the thread to complete the job
            std::unique_lock<std::mutex> lk(g_handlerLock);
            generateMiniDump(nullptr, excpInfo);
        }
    
        LONG WINAPI unhandledException(PEXCEPTION_POINTERS excpInfo = nullptr)
        {
            DebugBreak();
    
            if (excpInfo == nullptr)
            {
                __try // Generate exception to get proper context in dump
                {
                    RaiseException(EXCEPTION_BREAKPOINT, 0, 0, nullptr);
                }
                __except (exceptionHandler(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER)
                {
                }
            }
            else
            {
                exceptionHandler(excpInfo);
            }
    
            return 0;
        }
    
        SetUnhandledExceptionFilter(unhandledException);

    测试

    在main函数入口,设置异常处理函数SetUnhandledExceptionFilter。

    异常处理函数负责捕获异常,调用MiniDumpWriteDump生成dump文件,供开发者使用Windbg调试

    编译运行

    Access Volation C000005错误可以顺利捕获

    令人费解的是,

    abort,数组越界,虚函数调用异常等均无法捕获

    系统把这些异常给拦截了,并给出了程序崩溃的提示窗口

    改进

    为了捕获这些异常并生成dump文件,必须要把系统拦截的那一层给禁止掉

    1. 禁止系统弹出崩溃窗口,该窗口提示非常渣,对开发者和用户都不友好

      SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);

    可以模仿腾讯的QQ程序,专门开发一个对用户界面友好Bug Report的程序,
    在程序崩溃时转存储dump文件时,运行该程序提示用户有用的信息。

    2. 注册异常捕获函数

      SetUnhandledExceptionFilter(unhandledException);

    当异常发生时,系统会跳进我们的unhandleException回调中

    在该回调函数中,我们可以弹出Bug Report这样的子进程,并存储异常dump文件

    3. 拦截C Runtime的异常处理

      _set_invalid_parameter_handler(invalidParameter);
      _set_purecall_handler(pureVirtualCall);
      signal(SIGABRT, sigAbortHandler);
      _set_abort_behavior(0, 0);

    这些异常处理只是简单的调用unhandleException函数

    4. 开启系统的程序崩溃请求

    Vista之后,微软加了一个特性

    程序崩溃时,默认不交给程序崩溃处理

    而是使用一个莫名其妙的机制,不让程序进入崩溃环节

    搞得用户懵逼,开发者也让代码无法进入崩溃异常处理

        void EnableCrashingOnCrashes()
        {
            typedef BOOL(WINAPI *tGetPolicy)(LPDWORD lpFlags);
            typedef BOOL(WINAPI *tSetPolicy)(DWORD dwFlags);
            const DWORD EXCEPTION_SWALLOWING = 0x1;
    
            HMODULE kernel32 = LoadLibraryA("kernel32.dll");
            tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32,
                "GetProcessUserModeExceptionPolicy");
            tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32,
                "SetProcessUserModeExceptionPolicy");
            if (pGetPolicy && pSetPolicy)
            {
                DWORD dwFlags;
                if (pGetPolicy(&dwFlags))
                {
                    // Turn off the filter 
                    pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING);
                }
            }
        }

    5. 断了系统SetUnhandledExceptionFilter的后路

    C Runtime等异常,运行时库会调用SetUnhandledExceptionFilter向系统注册一个NULL

    从而使得我们之前注册的回调失效

    真是无语(ˉ▽ˉ;)...

    在这里,需要hook掉SetUnhandledExceptionFilter,在我们注册完回调之后,让它默认不做任何处理

    用到Windows核心编程这本书里面,Jeffrey Richter开发的CAPIHook这个模块

        void PreventSetUnhandledExceptionFilter()
        {
            CAPIHook apiHook("kernel32.dll",
                "SetUnhandledExceptionFilter",
                (PROC)ExceptionFilterHookProc);
        }

    其中ExceptionFilterHookProc这个函数是个空函数,无需做多余操作,直接renturn null即可

    6. 完整流程

        void setExceptionHandlers()
        {
            if (!IsDebuggerPresent() && !g_isHandlerSet)
            {
                g_isHandlerSet = true;
    
                SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
                SetUnhandledExceptionFilter(unhandledException);
                _set_invalid_parameter_handler(invalidParameter);
                _set_purecall_handler(pureVirtualCall);
                signal(SIGABRT, sigAbortHandler);
                _set_abort_behavior(0, 0);
    
                EnableCrashingOnCrashes();
                PreventSetUnhandledExceptionFilter();
            }
        }

    在VS或者Windbg中调试时,我们就没有必要生成dump文件了

    IsDebuggerPresent这个系统API会帮助我们判断我们是否在调试环境中

    总结

    即使看起来这么简单的一个功能,也是需要挺多细节处理的。

  • 相关阅读:
    基于ADO的远程Oracle连接
    oracle中的定时任务
    关于C++ const 变量
    堆排序和选择排序
    插入排序
    多线程的两种启动方式
    多尺度变换去噪的阈值选择
    jstree
    JS中call、apply、bind使用指南,带部分原理。
    六. JavaScript时间日期格式化
  • 原文地址:https://www.cnblogs.com/jojodru/p/9618416.html
Copyright © 2011-2022 走看看