【问题标题】:Step execution of release code / post-mortem debugging (VS/C++)发布代码的分步执行/事后调试(VS/C++)
【发布时间】:2009-10-22 22:10:43
【问题描述】:

分步执行发布代码有什么意义吗?我注意到省略了一些代码行,即一些方法调用。此外,变量预览不会显示某些变量,并且会显示其他一些变量的无效(非真实)值,因此这一切都非常具有误导性。

我在问这个问题,因为将 WinDbg 故障转储文件加载到 Visual Studio 会带来与步进执行相同的堆栈和变量部分视图。除了在没有优化的情况下重新编译应用程序之外,还有什么方法可以改善故障转储分析体验?

Windows、Visual Studio 2005、非托管 C++

【问题讨论】:

    标签: c++ visual-studio debugging postmortem-debugging


    【解决方案1】:

    是的 - 如果您有用于构建的 .pdb 和来自崩溃的 .dmp 文件,那么您可以在确切的故障点打开调试器,并检查您的应用在该点的状态。

    正如一些人所指出的 - 一些变量将被优化掉,但如果你有一点创造性/好奇,你会找到获得这些值的方法。

    您可以为您的代码构建一个根崩溃处理程序,以自动生成一个 .dmp 文件,该文件适用于所有 Windows 风格(假设您正在创建一个 Windows 应用程序),使用类似以下内容:

    // capture the unhandled exception hook - we will create a mini dump for ourselves
    // NOTE: according to docs, if a debugger is present, this API won't succeed (ie. debug builds ignore this)
    MiniDumper::Install(
        true,
        filename,
        "Please send a copy of this file, along with a brief description of the problem, to [insert your email address here] so that we might fix this issue."
    );
    

    上面需要我写的 MiniDumper 类,下面:

    #pragma once
    #include <dbghelp.h>
    #include "DynamicLinkLibrary.h"
    #include "FileName.h"
    
    //////////////////////////////////////////////////////////////////////////
    // MiniDumper
    //
    //  Provides a mechanism whereby an application will generate its own mini dump file anytime
    //  it throws an unhandled exception (or at the client's request - see GenerateMiniDump, below).
    //
    //  Warning: the C-runtime will NOT invoke our unhandled handler if you are running a debugger
    //  due to the way that the SetUnhandledExceptionFilter() API works (q.v.)
    //
    //  To use this facility, simply call MiniDumper::Install - for example, during CWinApp initialization.
    //
    //  Once this has been installed, all current and future threads in this process will be covered.
    //  This is unlike the StructuredException and CRTInvalidParameter classes, which must be installed for
    //  for each thread for which you wish to use their services.
    //
    class MiniDumper
    {
    public:
        // install the mini dumper (and optionally, hook the unhandled exception filter chain)
        // @param filename is the mini dump filename to use (please include a path)
        // @return success or failure
        // NOTE: we can be called more than once to change our options (unhook unhandled, change the filename)
        static bool Install(bool bHookUnhandledExceptionFilter, const CFilename & filenameMiniDump, const CString & strCustomizedMessage, DWORD dwMiniDumpType = MiniDumpNormal)
        {
            return GetSingleton().Initialize(bHookUnhandledExceptionFilter, filenameMiniDump, strCustomizedMessage, dwMiniDumpType); 
        }
    
        // returns true if we've been initialized (but doesn't indicate if we have hooked the unhandled exception filter or not)
        static bool IsInitialized() { return g_bInstalled; }
    
        // returns true if we've been setup to intercept unhandled exceptions
        static bool IsUnhandledExceptionHooked() { return g_bInstalled && GetSingleton().m_bHookedUnhandledExceptionFilter; }
    
        // returns the filename we've been configured to write to if we're requested to generate a mini dump
        static CFilename GetMiniDumpFilename() { return g_bInstalled ? GetSingleton().m_filenameMiniDump : ""; }
    
        // you may use this wherever you have a valid EXCEPTION_POINTERS in order to generate a mini dump of whatever exception just occurred
        // use the GetExceptionInformation() intrinsic to obtain the EXCEPTION_POINTERS in an __except(filter) context
        // returns success or failure
        // DO NOT hand the result of GenerateMiniDump to your __except(filter) - instead use a proper disposition value (q.v. __except)
        // NOTE: you *must* have already installed MiniDumper or this will only error
        static bool GenerateMiniDump(EXCEPTION_POINTERS * pExceptionPointers);
    
    private:
    
        // based on dbghelp.h
        typedef BOOL (WINAPI * MINIDUMPWRITEDUMP_FUNC_PTR)(
            HANDLE hProcess, 
            DWORD dwPid, 
            HANDLE hFile, 
            MINIDUMP_TYPE DumpType,
            CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
            CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
            CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
            );
    
        // data we need to pass to our mini dump thread
        struct ExceptionThreadData
        {
            ExceptionThreadData(EXCEPTION_POINTERS * exceptionPointers, bool bUnhandled, DWORD threadID = ::GetCurrentThreadId())
                : pExceptionPointers(exceptionPointers)
                , dwThreadID(threadID)
                , bUnhandledException(bUnhandled)
            {
            }
    
            EXCEPTION_POINTERS *    pExceptionPointers;
            DWORD                   dwThreadID;
            bool                    bUnhandledException;
        };
    
        // our unhandled exception filter (called automatically by the run time if we've been installed to do so)
        static LONG CALLBACK UnhandledExceptionFilter(EXCEPTION_POINTERS * pExceptionPointers);
    
        // creates a new thread in which to generate our mini dump (so we don't run out of stack)
        static bool ExecuteMiniDumpThread(EXCEPTION_POINTERS * pExceptionPointers, bool bUnhandledException);
    
        // thread entry point for generating a mini dump file
        static DWORD WINAPI MiniDumpThreadProc(LPVOID lpParam);
    
        // obtains the one and only instance
        static MiniDumper & GetSingleton();
    
        // flag to indicate if we're installed or not
        static bool g_bInstalled;
    
        // create us
        MiniDumper() 
            : m_pPreviousFilter(NULL)
            , m_pWriteMiniDumpFunction(NULL)
            , m_bHookedUnhandledExceptionFilter(false)
        {
        }
    
        // install our unhandled exception filter
        bool Initialize(bool bHookUnhandledExceptionFilter, const CFilename & filenameMiniDump, const CString & strCustomizedMessage, DWORD dwMiniDumpType);
    
        // generates a mini dump file
        bool GenerateMiniDumpFile(ExceptionThreadData * pData);
    
        // handle an unhandled exception
        bool HandleUnhandledException(ExceptionThreadData * pData);
    
        bool                            m_bHookedUnhandledExceptionFilter;
        CFilename                       m_filenameMiniDump;
        CString                         m_strCustomizedMessage;
        DWORD                           m_dwMiniDumpType;
        MINIDUMPWRITEDUMP_FUNC_PTR      m_pWriteMiniDumpFunction;
        LPTOP_LEVEL_EXCEPTION_FILTER    m_pPreviousFilter;
    };
    

    及其实现:

    #include "StdAfx.h"
    #include "MiniDumper.h"
    
    using namespace Toolbox;
    
    //////////////////////////////////////////////////////////////////////////
    // Static Members
    
    bool MiniDumper::g_bInstalled = false;
    
    // returns true if we were able to create a mini dump for this exception
    bool MiniDumper::GenerateMiniDump(EXCEPTION_POINTERS * pExceptionPointers)
    {
        // obtain the mini dump in a new thread context (which will have its own stack)
        return ExecuteMiniDumpThread(pExceptionPointers, false);
    }
    
    // this is called from the run time if we were installed to hook the unhandled exception filter
    LONG CALLBACK MiniDumper::UnhandledExceptionFilter(EXCEPTION_POINTERS * pExceptionPointers)
    {
        // attempt to generate the mini dump (use a separate thread to ensure this one is frozen & we have a fresh stack to work with)
        ExecuteMiniDumpThread(pExceptionPointers, true);
    
        // terminate this process, now
        ::TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF);
    
        // carry on as normal (we should never get here due to TerminateProcess, above)
        return EXCEPTION_CONTINUE_SEARCH;
    }
    
    bool MiniDumper::ExecuteMiniDumpThread(EXCEPTION_POINTERS * pExceptionPointers, bool bUnhandledException)
    {
        // because this may have been created by a stack overflow
        // we may be very very low on stack space
        // so we'll create a new, temporary stack to work with until we fix this situation
        ExceptionThreadData data(pExceptionPointers, bUnhandledException);
        DWORD dwScratch;
        HANDLE hMiniDumpThread = ::CreateThread(NULL, 0, MiniDumpThreadProc, &data, 0, &dwScratch);
        if (hMiniDumpThread)
        {
            VERIFY(::WaitForSingleObject(hMiniDumpThread, INFINITE) == WAIT_OBJECT_0);
            VERIFY(::GetExitCodeThread(hMiniDumpThread, &dwScratch));
            VERIFY(::CloseHandle(hMiniDumpThread));
            return AsBool(dwScratch);
        }
    
        return false;
    }
    
    DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam) 
    {
        // retrieve our exception context from our creator
        ExceptionThreadData * pData = (ExceptionThreadData *)lpParam;
    
        // generate the actual mini dump file in this thread context - with our own stack
        if (pData->bUnhandledException)
            return GetSingleton().HandleUnhandledException(pData);
        else
            return GetSingleton().GenerateMiniDumpFile(pData);
    }
    
    bool MiniDumper::HandleUnhandledException(ExceptionThreadData * pData)
    {
        // generate the actual mini dump file first - hopefully we get this even if the following errors
        const bool bMiniDumpSucceeded = GenerateMiniDumpFile(pData);
    
        // try to inform the user of what's happened
        CString strMessage = FString("An Unhandled Exception occurred in %s\n\nUnfortunately, this requires that the application be terminated.", CFilename::GetModuleFilename());
    
        // create the mini dump file
        if (bMiniDumpSucceeded)
        {
            // let user know about the mini dump
            strMessage.AppendFormat("\n\nOn a higher note, we have saved some diagnostic information in %s", m_filenameMiniDump.c_str());
        }
    
        // append any custom message(s)
        if (!IsEmpty(m_strCustomizedMessage))
            strMessage.AppendFormat("\n\n%s", m_strCustomizedMessage);
    
        // cap it off with an apology
        strMessage.Append("\n\nThis application must be terminated now.  All unsaved data will be lost.  We are deeply sorry for the inconvenience.");
    
        // let the user know that things have gone terribly wrong
        ::MessageBox(GetAppWindow(), strMessage, "Internal Error - Unhandled Exception", MB_ICONERROR);
    
        // indicate success or not
        return bMiniDumpSucceeded;
    }
    
    //////////////////////////////////////////////////////////////////////////
    // Instance Members
    
    MiniDumper & MiniDumper::GetSingleton() 
    {
        static std::auto_ptr<MiniDumper> g_pSingleton(new MiniDumper);
        return *g_pSingleton.get(); 
    }
    
    bool MiniDumper::Initialize(bool bHookUnhandledExceptionFilter, const CFilename & filenameMiniDump, const CString & strCustomizedMessage, DWORD dwMiniDumpType)
    {
        // check if we need to link to the the mini dump function
        if (!m_pWriteMiniDumpFunction)
        {
            try
            {
                // attempt to load the debug helper DLL
                DynamicLinkLibrary dll("DBGHelp.dll", true);
    
                // get the function address we need
                m_pWriteMiniDumpFunction = (MINIDUMPWRITEDUMP_FUNC_PTR)dll.GetProcAddress("MiniDumpWriteDump", false);
            }
            catch (CCustomException &)
            {
                // we failed to load the dll, or the function didn't exist
                // either way, m_pWriteMiniDumpFunction will be NULL
                ASSERT(m_pWriteMiniDumpFunction == NULL);
    
                // there is nothing functional about the mini dumper if we have no mini dump function pointer
                return false;
            }
        }
    
        // record the filename to write our mini dumps to (NOTE: we don't do error checking on the filename provided!)
        if (!IsEmpty(filenameMiniDump))
            m_filenameMiniDump = filenameMiniDump;
    
        // record the custom message to tell the user on an unhandled exception
        m_strCustomizedMessage = strCustomizedMessage;
    
        // check if they're updating the unhandled filter chain
        if (bHookUnhandledExceptionFilter && !m_bHookedUnhandledExceptionFilter)
        {
            // we need to hook the unhandled exception filter chain
            m_pPreviousFilter = ::SetUnhandledExceptionFilter(&MiniDumper::UnhandledExceptionFilter);
        }
        else if (!bHookUnhandledExceptionFilter && m_bHookedUnhandledExceptionFilter)
        {
            // we need to un-hook the unhandled exception filter chain
            VERIFY(&MiniDumper::UnhandledExceptionFilter == ::SetUnhandledExceptionFilter(m_pPreviousFilter));
        }
    
        // set type of mini dump to generate
        m_dwMiniDumpType = dwMiniDumpType;
    
        // record that we've been installed
        g_bInstalled = true;
    
        // if we got here, we must have been successful
        return true;
    }
    
    bool MiniDumper::GenerateMiniDumpFile(ExceptionThreadData * pData)
    {
        // NOTE: we don't check this before now because this allows us to generate an exception in a different thread context (rather than an exception while processing an exception in the main thread)
        ASSERT(g_bInstalled);
        if (!g_bInstalled)
            return false;
    
        HANDLE hFile = ::CreateFile(m_filenameMiniDump.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile == INVALID_HANDLE_VALUE)
        {
            // indicate failure
            return false;
        }
        else
        {
            // NOTE: don't use exception_info - its a #define!!!
            Initialized<_MINIDUMP_EXCEPTION_INFORMATION> ex_info;
            ex_info.ThreadId = pData->dwThreadID;
            ex_info.ExceptionPointers = pData->pExceptionPointers;
    
            // generate our mini dump
            bool bStatus = FALSE != ((*m_pWriteMiniDumpFunction)(GetCurrentProcess(), GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)m_dwMiniDumpType, &ex_info, NULL, NULL));
    
            // close the mini dump file
            ::CloseHandle(hFile);
    
            return bStatus;
        }
    }
    

    对于这不是一个简单的解决方案,我深表歉意。我的 Toolbox 库的其他部分存在依赖关系。但我认为这将大大有助于为您提供有关如何从您的代码中自动内置“捕获崩溃小型转储”的正确想法,然后您可以将其与您可以制作的 .dsp 文件结合起来您的开发周期的正常部分 - 因此当 .dmp 进入时 - 您可以使用从发布版本中保存的 .pdb 启动调试器(您不分发!)并且您可以完全调试崩溃条件很容易。

    上面的代码是许多不同来源的混合体——来自调试书籍、MSDN 文档等的代码 sn-ps。如果我遗漏了归属,我没有恶意。但是,我不相信上述任何代码都是由除我自己之外的任何人创建的。

    【讨论】:

    • 不完全回答我的问题,但非常有价值的一段代码。谢谢!
    • 您正在创建一个线程,而崩溃正在进行中。这本身被认为是危险的,而且由于线程创建需要加载器锁,如果涉及多个锁,这很容易死锁。如果您提前创建线程,则可以解决此问题。也只是一件小事,按照惯例,您应该使用异常代码退出,而不是常量 -1。
    • 我错过了另一件事:MinidumpWriteDump 需要同步。如果一个线程紧接着另一个线程崩溃,则可能有多个线程正在执行MinidumpWriteDump。所有 dbghelp 函数都是single threaded
    【解决方案2】:

    只重新编译感兴趣的文件而不进行优化:)

    一般:

    • 切换到交错反汇编模式。单步执行反汇编将使您能够单步执行原本会被跳过的函数调用,并使内联代码更加明显。
    • 寻找获取调试器无法直接显示的变量值的替代方法。如果它们是作为参数传入的,请查找调用堆栈 - 您经常会发现它们在调用者中可见。如果它们是通过某个对象的 getter 检索到的,请检查该对象;浏览由计算它们的代码生成的程序集,以找出它们的存储位置;等等。如果所有其他方法都失败并且禁用优化/添加 printf() 会充分扭曲时序以影响调试,请添加一个虚拟全局变量并将其设置为感兴趣的部分进入时的感兴趣值。

    【讨论】:

      【解决方案3】:

      至少不是 IA64 转储...

      除了拥有完整的转储和私有符号之外,您确实无能为力。现代编译器对您的代码有很大的兴趣,并且几乎无法识别,特别是如果您添加了LTCG 之类的内容。

      我发现有两件事很有用:

      1. 沿着堆栈向上走,直到您对“this”真正指向的内容有一个很好的把握。大多数时候,由于注册表优化,当您处于对象方法框架中时,“this”是不可靠的。通常多次调用堆栈,您会得到一个具有正确地址的对象,并且您可以通过成员引用逐个成员引用进行导航,直到您的崩溃点并具有正确的 'this' 值

      2. uf(Windbg 的反汇编函数命令)。这个小助手可以以比普通的反汇编视图更易于管理的形式列出功能反汇编。因为跟随跳转和代码重排,更容易遵循uf输出的逻辑。

      【讨论】:

        【解决方案4】:

        最重要的是要有符号文件(*.pdb)。您可以为发布版本生成它们,默认情况下它们是不活动的。

        那么您必须知道,由于优化,代码可能会重新排序,因此调试可能看起来有点生涩。此外,一些中间变量可能已经被优化掉了。一般来说,数据的行为和可见性可能会有一些限制。

        使用 Visual Studio C++ 2008,您可以自动调试 *.dmp 文件。我相信它也适用于 VS 2005。对于较旧的编译器,恐怕您必须使用 WinDbg...(当然还要为 WinDbg 指定 *.pdb 文件,否则信息将非常有限)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-06-29
          • 2011-03-19
          • 2021-05-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多