步骤执行发布代码/事后调试(VS / C ++)

时间:2009-10-22 22:10:44

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

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

我问这个问题,因为将WinDbg crashdump文件加载到Visual Studio中会带来相同的堆栈和变量局部视图作为步执行。有没有办法改善crashdump分析体验,除非重新编译没有优化的应用程序?

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

4 个答案:

答案 0 :(得分:3)

是 - 如果您有构建的.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文档等等。如果我遗漏了归因,我的意思是没有害处。但是,我不相信任何上述代码都是由我自己创建的。

答案 1 :(得分:2)

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

一般来说:

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

答案 2 :(得分:1)

至少不是IA64转储...

除了完整的转储和私有符号之外,你真的没有什么可做的。现代编译器在您的代码中有一个字段日,几乎无法识别,特别是如果您添加LTCG之类的内容。

我发现有两件事有用:

  1. 走上筹码,直到你得到一个关于'这个'真正指向的好锚。大多数情况下,当您处于对象方法框架中时,由于注册表优化,“此”不可靠。通常有几个调用堆栈,你得到一个具有正确地址的对象,你可以导航,成员引用的成员引用,直到你的崩溃点并且具有正确的'this'值

  2. uf(Windbg的unassembly函数命令)。这个小助手可以以比普通的解集视图更易于管理的形式列出函数dissasembly。因为它遵循跳转和代码重新排列,更容易遵循uf输出的逻辑。

答案 3 :(得分:0)

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

然后你必须知道,由于优化,代码可能会重新排序,因此调试看起来有点不稳定。还有一些中间变量可能已被优化掉了。一般来说,数据的行为和可见性可能会有一些限制。

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