Visual Studio 로 디버깅하는 방법(Dump, pdb 파일이용)

2013. 10. 3. 16:53C++


이번에 포스팅할 내용은 Visual Studio 로 디버깅하는 방법에 대해서다.

일반적으로, 개발사에서 개발한 내용을 검수나, 배포과정 중에 생기는 오류에 대해서 대응이 취약한 경우가 많다.

보통 아래처럼 대응하게 된다.

"어떨경우에 상황이 발생하였나요? 재현이 되나요? 아이디가 뭔가요?"

그나마 재현이 되면 다행이지만, 이도저도 아니면 디버깅 하느라 시간이 줄줄 흐른다.

여기서 다룰 주제는 응용프로그램이 죽는 경우에 대해, 심플하게 디버깅 하는 방법이다.

로직은 이렇다.

1. 셈플 프로젝트에 디버깅 코드 삽입

2. 프로젝트가 뻗을경우, 먼저 그 메시지를 디버깅 코드가 검출

3. 현재 메모리를 미니(작은) 덤프로 파일에 저장

4. 덤프 파일을 Visual Studio(여기서는 2005) 에서 열어서, 오류 당시의 소스를 확인

5. 소스 수정하여 디버깅 완료



1. 셈플 프로젝트에 디버깅 코드 삽입

먼저 Crash 를 발생시킬 프로젝트를 하나 만들자. 프로젝트명은 CrashProject 이며, MFC Dialog 형태이다.

그리고 버튼을 누를 경우 Crash 가 발생하는 코드를 삽입한다.




 

void CCrashProjectDlg::OnBnClickedBtnCrash()
{
    int a = 0;
    int b = 1;
    int c = b/a;
    WCHAR szMsg[256] = {0,};
    swprintf_s(szMsg,256,_T("%d"),c);
    AfxMessageBox(szMsg);
}

참고로, c=b/a; 면 Crash 가 발생하지 않는다. 일반옵션으로 Release 모드로 컴파일 할 경우

빌더 최적화 옵션에 의해, 의미없는 문맥으로 간주되어, 어셈블러를 만들어 내지 않기 때문이다.

그리고 아래 소스를 삽입한다.


<whdump.h>
#include <windows.h>
#if _MSC_VER < 1300
#define DECLSPEC_DEPRECATED
// VC6: change this path to your Platform SDK headers
#include "M:\\dev7\\vs\\devtools\\common\\win32sdk\\include\\dbghelp.h"   // must be XP version of file
#else
// VC7: ships with updated headers
#include "dbghelp.h"
#endif

// based on dbghelp.h
typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(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
                                         );

class MiniDumper
{
private:
    static LPCTSTR m_szAppName;

    static LONG WINAPI TopLevelFilter( struct _EXCEPTION_POINTERS *pExceptionInfo );

public:
    MiniDumper( LPCTSTR szAppName );
};


<whdump.h>
#include "stdafx.h"

#include "whdump.h"

LPCTSTR MiniDumper::m_szAppName;

MiniDumper::MiniDumper( LPCTSTR szAppName )
{



    m_szAppName = szAppName ? wcsdup(szAppName) : _T("Application");

    ::SetUnhandledExceptionFilter( TopLevelFilter );
}

LONG MiniDumper::TopLevelFilter( struct _EXCEPTION_POINTERS *pExceptionInfo )
{
    LONG retval = EXCEPTION_CONTINUE_SEARCH;
    HWND hParent = NULL;      // find a better value for your app

    // firstly see if dbghelp.dll is around and has the function we need
    // look next to the EXE first, as the one in System32 might be old
    // (e.g. Windows 2000)
    HMODULE hDll = NULL;
    WCHAR szDbgHelpPath[_MAX_PATH];

    if (GetModuleFileName( NULL, szDbgHelpPath, _MAX_PATH ))
    {
        WCHAR *pSlash = _tcsrchr( szDbgHelpPath, '\\' );
        if (pSlash)
        {
            _tcscpy( pSlash+1, _T("DBGHELP.DLL") );
            hDll = ::LoadLibrary( szDbgHelpPath );
        }
    }

    if (hDll==NULL)
    {
        // load any version we can
        hDll = ::LoadLibrary( _T("DBGHELP.DLL") );
    }

    LPCTSTR szResult = NULL;

    if (hDll)
    {
       MINIDUMPWRITEDUMP pDump = (MINIDUMPWRITEDUMP)::GetProcAddress( hDll, "MiniDumpWriteDump" );
        if (pDump)
        {
            WCHAR szDumpPath[_MAX_PATH];
            WCHAR szScratch [_MAX_PATH];

            // work out a good place for the dump file
            if (!GetTempPath( _MAX_PATH, szDumpPath ))
                _tcscpy( szDumpPath, _T("c:\\temp\\") );

            _tcscat( szDumpPath, m_szAppName );
            _tcscat( szDumpPath, _T(".dmp") );

            // ask the user if they want to save a dump file
            if (::MessageBox( NULL, _T("Something bad happened in your program, would you like to save a diagnostic file?"), m_szAppName, MB_YESNO )==IDYES)
            {
                // create the file
               HANDLE hFile = ::CreateFile( szDumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL, NULL );

                if (hFile!=INVALID_HANDLE_VALUE)
                {
                    _MINIDUMP_EXCEPTION_INFORMATION ExInfo;

                    ExInfo.ThreadId = ::GetCurrentThreadId();
                    ExInfo.ExceptionPointers = pExceptionInfo;
                    ExInfo.ClientPointers = NULL;

                    // write the dump
                   BOOL bOK = pDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ExInfo, NULL, NULL );
                    if (bOK)
                    {
                        swprintf( szScratch, _T("Saved dump file to '%s'"), szDumpPath );
                        szResult = szScratch;
                        retval = EXCEPTION_EXECUTE_HANDLER;
                    }
                    else
                    {
                        swprintf( szScratch, _T("Failed to save dump file to '%s' (error %d)"), szDumpPath, GetLastError() );
                        szResult = szScratch;
                    }
                    ::CloseHandle(hFile);
                }
                else
                {
                    swprintf( szScratch, _T("Failed to create dump file '%s' (error %d)"), szDumpPath, GetLastError() );
                    szResult = szScratch;
                }
            }
        }
        else
        {
            szResult = _T("DBGHELP.DLL too old");
        }
    }
    else
    {
        szResult = _T("DBGHELP.DLL not found");
    }

    if (szResult)
        ::MessageBox( NULL, szResult, m_szAppName, MB_OK );

    return retval;
}


적당한 위치에 위 코드를 초기화 한다.

#include "whdump.h"

...

BOOL CCrashProjectApp::InitInstance()
{
...
    MiniDumper mDump( _T("MiniDump") );
...


타겟 PC에서 Crash를 테스트하는것이 좋으나, 내 PC에서 해도 상관없다.

여기서는 XP에서 Release (static mfc) 로 컴파일 하여 Vista에서 테스트 하였다.



 


Crash! 를 누르게 되면, Crash 를 Application에서 먼저 후킹하여, 처리하게 된다.

여기서는 정상적인 과정임을 보여주기 위해, 메시지 박스를 띄운다. 예를 선택 



 

저장 폴더를 보여주게 된다. 이것도 소스에서 메시지 박스를 띄운 부분이다.





해당 경로로 가면 아래와 같이 미니 덤프 파일이 보이게 된다. 이것을 가지고 작업 PC로 복사하자.



Visual Studio 를 새로 열어서, dump 파일을 오픈한다. 파일/열기->프로젝트/솔류션



기호파일이 필요하다. 확장자가 pdb인 파일인데, 기본옵션으로 빌더하게 될 경우, 실행파일과 동일 폴더에

생성된다. ms 제품의 pdb 파일은, 심볼서버를 지정하면 알아서 내려 받는다. 내려받은 기호파일을 저장할 


 
E:\Project\CrashProject\Release
http://msdl.microsoft.com/download/symbols
E:\Project\home\symbol




 


이제 디버깅 시작 버튼을 누르면 아래와 같이 정확하게 소스 위치를 보여주게 된다.

만일, 소스의 위치를 Visual Studio 가 찾을수 없는 상태라면 소스의 위치를 요구하게 된다. 프로젝트 폴더를

지정하면 된다.



이제, 프로젝트 개발후, 배포할 경우, 배포 소스파일의 최종본을 유지해두자!!

그리고, 해당 덤프파일을 가져오는 방법은, 여러가지 상황에 맞는 방법이 있을 것이므로, 상황에 맞게 하면

되겠습니다.