블로그는 나의 힘!
[ Programing ]/C++2022. 1. 21. 13:27

# 개요 : 
프로그램을 실행하다 보면 예상하지 못한 곳에서 충돌이 발생해 프로그램 다운되는 현상이 발생한다.
이런 충돌을 해결하는 방법에는 여러가지가 있겠지만 Minidump 기능에 대해 알아 보자.



# 장점 : 
1. 정확한 충돌 위치를 확인 할 수 있다.
   Minidump기능을 작성한 응용 프로그램에 넣어두면 프로그램이 크래쉬를 일으킨다면
   정확한 충돌 위치를 찾을 수 있다. 추가적으로 CallStack 정보 까지 알아 볼 수 있다.

2. 충돌시 변수 값들이 확인 가능하다.
   프로그램이 크래쉬가 났다면 보통 무엇으로 인해서 Kill 되었는지 매우 궁금 할 것이다. 
   보통 충돌 위치만 알고 어떤 값으로 인해서 죽었는지 궁금한 경우가 많은데 
   Minidump를 사용하면 모든 메모리 정보를 저장할 수도 있고 
   그 함수 안에 있는 로컬 변수에 값들을 확인 할 수 있다.

3. 소스가 간결해진다.
   보통 크래쉬 위치를 알기 위해서 Try Catch를 사용하는데 
   이것 또한 여러 소스에 코딩을 할려면 여간 귀찮은 일이 아니다. 
   Minidump는 제일 처음에 한번만 등록을 하면 더이상 소스코드 부분에 신경을 쓸 필요가 없다.

 


# 단계진행방향 : 
1. SetUnhandledExceptionFilter로 충돌 위치 정보얻기

2. MiniDumpWirteDump로 덤프 파일(dmp) 생성

3. 덤프 파일의 종류

4. 생성된 덤프파일 디버깅

5. Minidump기능 확장
   1) FTP로 덤프파일 관리
   2) Memory Snapshot
   3) 추가 내용 기록

 


# 단계세부설명 :

1. SetUnhandledExceptionFilter로 충돌 위치 정보얻기
   SetUnhandledExceptionFilter 함수는 예외가 발생할 때 호출되어 CallBack 함수를 지정(등록)한다.
   쉽게 말해 예외처리를 하지 않은 부분에서 예외가 발생했을 경우 
   이 함수가 지정한 곳으로 크래쉬 정보들이 들어오게 되는 것을 의미한다.
   원래 설정된 값을 담아 프로그램 종료 시 복구 용도로 쓰인다.

   사용법
   SetUnhandledExceptionFilter(TopLevelFilter);


 

2. MiniDumpWirteDump로 덤프 파일(dmp) 생성
   먼저 MiniDump의 기능을 사용하기 위해서는 DBGHELP.DLL 파일이 필요하다.
   Debugging Tools for Windows에 포함되어 있다. 
   MiniDumpWriteDump기능을 사용하기 위해선 DBGHELP.DLL 5.1 이후 부터 지원을 받을 수 있다.
   MiniDumpWirteDump를 실행시키면 MiniDump파일을 생성하게 된다.

   사용법
   HANDLE hProcess = GetCurrentProcess();
   DWORD dwProcessID = GetCurrentProcessId();
   DWORD dwThreadID = GetCurrentThreadId();

   MINIDUMP_EXCEPTION_INFORMATION sExceptionInfo;
   sExceptionInfo.ThreadId = dwThreadID;
   sExceptionInfo.ExceptionPointers = pException;
   sExceptionInfo.ClientPointers = FALSE;

   if( pMiniDumpWriteDump( hProcess, dwProcessID, hFile, MiniDumpNormal, 
                                    &sExceptionInfo, NULL, NULL) )
   {
      lResult = EXCEPTION_EXECUTE_HANDLER;
   }



 

3. 덤프 파일 종류
   덤프 파일을 만들때에도 여러 가지 종류로 나누어져 생성 할 수 있다. 
   위의 사용법에서 4번째 인자에 그 종류가 들어가게 된다. 

   주로 2가지를 자주 사용하는데 
   MiniDumpNormal : 함수의 로컬레지스터 값을 포함해서 덤프를 한다.
   MiniDumpWithFullMemory : 전체 메모리 값을 포함하여 덤프를 하게 된다.



 

4. 생성된 덤프 파일 디버깅
   덤프 파일(dmp)이 생성이 되면 더블 클릭만으로 덤프파일을  VS .NET로 열수 있다. 
   덤프파일을 열고 실행(F5)을 하면 정확한 충돌 위치의 코드로 이동하여 크래쉬를 알 수 있다.

   조사식에 죽었을 때 당시 로컬(지역) 변수를 찍어 값도 확인 할 수 있다.
   하지만 전역 변수값은 확인이 어려울 수도 있는데 
   그건 minidump를 Normal 형식으로 덤프 했기 때문에 로컬 변수에 대해 값을 저장하기 때문이다. 
   FullMemory 방식으로 메모리를 저장하면 덤프 용량은 커지지만 예외처리가 발생했을 때 
   당시의 메모리 값들을 전부 저장하게 된다. 
   그리고 오른쪽에 호출 스택에서 어떤 경로를 통해 프로그램이 크래쉬 되었는지 확인 할 수 있다.
 




5. Minidump기능 확장
   1) FTP로 덤프파일 관리
      덤프 파일이 생성이 되면 보통 사용자 컴퓨터에 정보가 남게 된다. 
      이 정보를 받아야 디버그를 할 수 있기 때문에 어떤 수단을 통해서든 받아야 할 것이다. 
      메일을 통해서든 이동식 디크스를 통해서든 받을 수 있지만 가장 사용자들에게 쉬운 방법은 
      Window에서 사용 하는 방식처럼 사용자의 동의를 구하고 파일을 FTP로 전송을 하면 되겠다.
      
   2) Memory Snapshot
      가짜 충돌 상황을 발생시켜 덤프파일을 생성시키는 기능이다. 

      예를들어
      서버 프로그램 서비스 도중 메모리 값에 문제가 생겼을 경우 
      파일 로그나 코드나 유저 정보들을 보고 문제점을 해결 해야 한다.
      하지만 찾기가 매우 어렵다면 디버깅을 하고 싶은 충동을 느끼게 되는데 
      Memory Snapshot을 활용 
      가짜 예외처리 상황(문제점 재현 시나리오)을 만들어 덤프 파일을 생성하게 한다. 
      그러면 서버는 브레이크를 걸지 않고 서버 상태를 점검 할 수 있게 된다. 
      단점은 서버가 사용하는 메모리 용량이 파일 행태로 다 복사를 해야 하기 때문에 
      덤프 생성 시간이 소요되게 될 것이다.
 
   3) 추가 내용 기록
      pMiniDumpWriteDump의 6번째 인자를 보면 
      MINIDUMP_USER_STREAM_INFORMATION 이라는 정보를 입력 할 수 있다. 
      구조체 이름 그대로 유저정보를 입력 할 수 있다. 
      여기에 사용자 동의를 구하고 사용자 컴퓨터의 메모리 상태라던지 그래픽카드 정보를 
      추가적으로 입력하면 하드웨어에 따른 컴퓨터 오류를 보다 쉽게 해결 할 수 있을 것이다. 

      여기에 화면 리스토어로 리소스 복구가 일어났을 경우와 
      사용자의 각각의 이동경로 히스토리로 기억해서 어떤 경로를 통해서 이동하다가 
      프로그램 오류를 일으켰는지와 마지막 받은 패킷 메세지 번호를 기억하여 어떤 패킷을 받고 
      그 패킷 처리 후 죽었다는 정보를 추가적으로 남겨서 사용자의 여러 정황에 대해서 
      상세하게 파악하는데 많은 도움을 받는다.




# Sample

// >>>>> 1. MiniDump.h
#pragma once

class CMiniDump
{
public:
     static BOOL Begin();
     static BOOL End();
};

// >>>>> 2. MiniDump.cpp
#include <iostream>
#include <windows.h>
#include <tchar.h>

using namespace std;

#include <DbgHelp.h>

// 에러 메시지 출력용 함수
VOID printLastError(DWORD dwError = GetLastError())
{
     TCHAR* lpOSMsg;
     FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                    FORMAT_MESSAGE_FROM_SYSTEM |
                    FORMAT_MESSAGE_IGNORE_INSERTS, 

                    NULL, 
                    dwError, 
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
                    (LPTSTR)&lpOSMsg, 
                    0, 
                    NULL);

     _tprintf(_T("[ERROR] [%d] %s\n"), dwError, lpOSMsg);
     LocalFree(lpOSMsg);
}


LPTOP_LEVEL_EXCEPTION_FILTER PreviousExceptionFilter = NULL;

// Unhandled Exception 발생시 호출 될 CallBack 함수 원형
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 );

// Unhandled Exception 발생시 호출될 콜백 함수
LONG WINAPI UnHandledExceptionFilter(struct _EXCEPTOIN_POINTERS* exceptionInfo)
{
     // 반드시 로딩
     HMODULE DllHandle = LoadLibrary(_T("DBGHELP.DLL"));
     if (NULL == DllHandle)
     {
          printLastError();
          return EXCEPTION_CONTINUE_SEARCH;
     }

     // DBGHELP.DLL에서 MiniDumpWriteDump를 불러와 Dump라고 정의하며 이걸로 덤프 파일을 생성.
     MINIDUMPWRITEDUMP Dump = (MINIDUMPWRITEDUMP)GetProcAddress(DllHandle,"MiniDumpWriteDump");
     if (NULL == Dump)
     {
          printLastError();
          return EXCEPTION_CONTINUE_SEARCH;
     }

     SYSTEMTIME SystemTime;
     GetLocalTime(&SystemTime); // 현재시간 획득

     TCHAR DumpPath[MAX_PATH] = { 0, };

     // 덤프 파일 이름 설정
     _sntprintf_s(DumpPath, MAX_PATH, _TRUNCATE, _T("%d-%d-%d %d_%d_%d.dmp"),
          SystemTime.wYear,
          SystemTime.wMonth,
          SystemTime.wDay,
          SystemTime.wHour,
          SystemTime.wMinute,
          SystemTime.wSecond);

     // 덤프 파일 생성
     HANDLE FileHandle = CreateFile(DumpPath, 
                                        GENERIC_WRITE, 
                                        FILE_SHARE_WRITE, 
                                        NULL, 
                                        CREATE_ALWAYS, 
                                        FILE_ATTRIBUTE_NORMAL, 
                                        NULL);

     if (INVALID_HANDLE_VALUE == FileHandle)
     {
          printLastError();
          return EXCEPTION_CONTINUE_SEARCH;
     }

     // MiniDump 예외 정보 저장 구조체
     _MINIDUMP_EXCEPTION_INFORMATION MiniDumpExceptionInfo;
     MiniDumpExceptionInfo.ThreadId = GetCurrentThreadId();
     MiniDumpExceptionInfo.ExceptionPointers = (PEXCEPTION_POINTERS)exceptionInfo;
     MiniDumpExceptionInfo.ClientPointers = NULL;

     // typedef BOOL(WINAPI *MINIDUMPWRITEDUMP)(HANDLE, DWORD, HANDLE, TYPE, Param, Param, Param)
     // 현재 프로세스에 대한 덤프 기록을 실행
     BOOL bSuccess = Dump(GetCurrentProcess(),
                              GetCurrentProcessId(),
                              FileHandle,    // 덤프를 기록할 파일 핸들
                              MiniDumpNormal// MINIDUMP_TYPE
                              &MiniDumpExceptionInfo,  // MiniDump 예외 정보
                              NULL,
                              NULL);

     CloseHandle(FileHandle);

     // 덤프 기록 성공시 수행
     return (TRUE == bSuccess)? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
}

// UnHandled Exception 발생시 덤프 만들기 위해 사용 정의한 UnHandledExceptionFilter() 호출되도록 설정
// 프로그램 시작 부분에서 호출하도록 한다.
BOOL CMiniDump::BeginDump()
{
     // 잘못된 연산 메시지 창이 나오지 않도록 설정
     SetErrorMode(SEM_FAILCRITICALERRORS);

     // Unhandled Exception 발생시 호출될 CallBack 함수 등록
     // PreviousExceptionFilter는 원래 설정을 담아 프로그램 종료시 복구 용도로 쓰인다.
     PreviousExceptionFilter
        SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)UnHandledExceptionFilter);


     return TRUE;
}

// 원래 설정으로 복구하여 덤프가 종료 되도록 한다.
// 프로그램 종료 부분에서 호출 하도록 한다.
BOOL CMiniDump::EndDump()
{
     // 프로그램 종료 전에 원래 설정으로 복구
     SetUnhandledExceptionFilter(PreviousExceptionFilter);
     return TRUE;
}



// >>>>> 3. 사용 예제
class CTestCls 
{
public:
     BOOL isOpened;
};

int main()
{
     setlocale(LC_ALL, ""); // 한글 출력 설정

     CMiniDump::BeginDump();

     CTestCls* testCls = new CTestCls();
     testCls = NULL;
     testCls->isOpened = FALSE; // Unhandled Exception

     CMiniDump::EndDump();
     return 0;
}​

/*
덤프 파일 분석 방법 : 
 0. 덤프 파일이 생기면
 1. 덤프파일을 Visual Studio로 연다.
 2. F5 로 디버그를 시작하면 덤프 파일 솔루션을 저장하라고 나온다.
 3. 덤프 파일 솔루션을 저장하면 덤프가 발생한 지점의 소스가 표시된다.
 
 ※ 주의 : 실행 파일을 만들때 생성된 PDB 파일과 같이 두어야 분석 가능합니다.
*/





출처 :
MiniDump (미니 덤프 생성하기) (tistory.com)

 

MiniDump (미니 덤프 생성하기)

출처 : http://dblab.co.kr/294 미니 덤프 프로그램이 Unhandled Exception으로 종료될때 원인 분석을 위해 덤프 파일을 생성 하는 방법입니다. DEBUG 모드로 컴파일된 실행파일(.exe) 실행시 덤프파일이 생성

yamoe.tistory.com

Minidump :: ::: 책 읽는 프로그래머 ::: (tistory.com)

 

Minidump

- 작성자:고리(goli81@naver.com) - Minidump 개요: 프로그램을 실행하다 보면 예상하지 못한 곳에서 충돌이 발생해 프로그램이 다운되는 현상이 발생한다. 이런 충돌을 해결하는 방법에는 여러가지가

microsoft.tistory.com

 

Posted by Mister_Q