代码之家  ›  专栏  ›  技术社区  ›  tomash

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

  •  3
  • tomash  · 技术社区  · 16 年前

    是否有必要执行释放代码?我注意到一些代码行被省略了,例如一些方法调用。此外,变量预览不显示某些变量,也不显示其他变量的无效(非真实)值,因此这一切都非常误导人。

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

    Windows,VisualStudio 2005,非托管C++

    4 回复  |  直到 16 年前
        1
  •  2
  •   moonshadow    16 年前

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

    一般来说:

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

    是的-如果您有用于构建的.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."
    );
    

    上面需要我写的小型垃圾车类,如下:

    #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;
        }
    }
    

    我为这不是一个简单的解决方案而道歉。工具箱库的其他部分存在依赖关系。但我认为,要想让您对如何从代码中自动构建“捕获崩溃迷你转储”有一个正确的想法,还需要很长的路要走,然后您可以将代码与.dsp文件结合起来,使开发周期成为正常的一部分,这样当.dmp出现时,您可以使用保存的.pdb启动调试程序。您的发布版本(不分发!)您可以很容易地调试崩溃条件。

    上面的代码是许多不同来源的混合体-调试书籍中的代码片段、msdn文档等。如果我遗漏了属性,我的意思是没有伤害。但是,我不相信上面的任何代码是由我自己以外的任何人创建的。

        3
  •  1
  •   Remus Rusanu    16 年前

    至少不是ia64转储…

    除了拥有完整的转储和私有符号,你真的没有什么可以做的。现代编译器有一天会处理您的代码,几乎无法识别,特别是如果您添加了类似 LTCG .

    我发现有两件事有用:

    1. 沿着堆栈向上走,直到找到一个很好的锚定“这”真正指向什么。大多数情况下,当您处于对象方法框架“this”中时,由于注册表选项的原因,它是不可靠的。通常有几个调用堆栈,您会得到一个具有正确地址的对象,并且您可以导航,一个成员引用一个成员引用,直到您的崩溃点,并具有正确的“this”值。

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

        4
  •  0
  •   jdehaan    16 年前

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

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

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