Пошаговое выполнение кода выпуска / посмертная отладка (VS / C ++) - PullRequest
3 голосов
/ 23 октября 2009

Есть ли смысл пошагово выполнять код релиза? Я заметил, что некоторые строки кода опущены, то есть некоторые вызовы методов. Кроме того, предварительный просмотр переменных не показывает некоторые переменные и показывает недопустимые (не реальные) значения для некоторых других, поэтому все это вводит в заблуждение.

Я задаю этот вопрос, потому что загрузка файла сбоя WinDbg в Visual Studio приводит к тому же частичному представлению стека и переменных, что и пошаговое выполнение. Есть ли способ улучшить опыт анализа сбоев, кроме перекомпиляции приложения без оптимизации?

Windows, Visual Studio 2005, неуправляемый C ++

Ответы [ 4 ]

3 голосов
/ 23 октября 2009

Да - если у вас есть .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 и т. Д. И т. Д. И т. Д. Если я не упомянул атрибуцию, я не имею в виду никакого вреда. Однако я не верю, что какой-либо из приведенных выше кодов в значительной степени создан кем-то, кроме меня.

2 голосов
/ 23 октября 2009

Перекомпилируйте только интересующий вас файл без оптимизаций:)

В целом:

  • Переключение в режим разборки с чередованием. Пошаговое выполнение разборки позволит вам перейти к вызовам функций, которые в противном случае были бы пропущены, и сделать встроенный код более очевидным.
  • Ищите альтернативные способы получения значений в переменных, которые отладчик не может вам показать напрямую. Если они были переданы в качестве аргументов, посмотрите на стек вызовов - вы часто обнаружите, что они видны в вызывающей программе. Если они были получены через геттеры от какого-либо объекта, изучите этот объект; взгляните на сборку, сгенерированную кодом, который вычисляет их, чтобы определить, где они хранились; и т. д. Если ничего не помогает и отключение оптимизации / добавление printf () в достаточной степени искажает время, чтобы повлиять на отладку, добавьте фиктивную глобальную переменную и установите для нее значение интереса при входе в раздел интереса.
1 голос
/ 23 октября 2009

По крайней мере, это не дамп IA64 ...

На самом деле вы мало что можете сделать, кроме полного дампа и личных символов. Современные компиляторы имеют день поля с вашим кодом и едва узнаваемы, особенно если вы добавляете что-то вроде LTCG .

Есть две вещи, которые я нашел полезными:

  1. Поднимайтесь по стеку, пока не получите хорошую привязку к тому, на что действительно указывает «это». В большинстве случаев, когда вы находитесь в фрейме метода объекта «this» ненадежен из-за опций реестра. Обычно при нескольких вызовах стека вы получаете объект, который имеет правильный адрес, и вы можете перемещаться, ссылка на элемент по ссылке на элемент, до точки сбоя и иметь правильное значение для 'this'

  2. uf (команда функции разборки Windbg). Этот маленький помощник может перечислить функцию disasembly в более управляемой форме, чем в обычном представлении disasembly. Поскольку он выполняет переходы и реорганизацию кода, легче следовать логике вывода uf. * ​​1015 *

0 голосов
/ 23 октября 2009

Самое главное - иметь файлы символов (* .pdb). Вы можете сгенерировать их для релизных сборок, по умолчанию они не активны.

Тогда вы должны знать, что из-за оптимизаций код может быть переупорядочен, поэтому отладка может выглядеть немного прерывисто. Также некоторые промежуточные переменные могли быть оптимизированы. Вообще говоря, поведение и видимость данных могут иметь некоторые ограничения.

В Visual Studio C ++ 2008 вы можете автоматически отлаживать файлы * .dmp. Я считаю, что это также работает для VS 2005. Боюсь, что для старых компиляторов вам придется использовать WinDbg ... (Также укажите, конечно, файлы * .pdb для WinDbg, в противном случае информация будет весьма ограниченной)

...