'소프트웨어/Etc.'에 해당되는 글 2건

이 포스트에서는 WinDbg를 사용해 Application 메모리 덤프 (말하자면, 유저 메모리덤프)를 분석하는 방법을 설명합니다. WinDbg를 사용해본 적이 없는 초보자를 대상으로 하는 글이며, 메모리덤프 작성하는 방법을 모르시는 분은 아래의 글을 먼저 읽어 주세요.

http://kuaaan.tistory.com/213


우선, Null 포인터에 쓰기를 시도하여 Access Violation을 일으키는 샘플 프로그램을 하나 작성해 보겠습니다.

  1. #include "stdafx.h"  
  2.   
  3. #include <windows.h>  
  4.   
  5. void funcC(INT x, INT y, INT z)  
  6. {  
  7.     LPINT   pInt = NULL;  
  8.     *(pInt) = x + y + z;    // Crash!!  
  9. }  
  10.   
  11. void    funcB(INT c, INT d, INT e)  
  12. {  
  13.     funcC(c, d, e);  
  14. }  
  15.   
  16. void    funcA(INT a, INT b, INT c)  
  17. {  
  18.     funcB(a, b, c);  
  19. }  
  20.   
  21. int _tmain(int argc, _TCHAR* argv[])  
  22. {  
  23.     funcA(1, 2, 3);  
  24.   
  25.     return 0;  
  26. }  



위와 같은 프로그램을 실행시키면 "funcC" 함수에서 Access Violation을 발생시키고 비정상 종료되겠지요?

이제 메모리덤프 설정을 한 후 샘플 프로그램을 실행시켜 Access Violation이 발생할 때의 메모리덤프를 얻습니다. (메모리덤프를 얻는 방법은 여기 참고)

메모리덤프를 얻었다고 치고, 분석하는 방법을 간단하게 설명해 보겠습니다.

 

1. WinDbg 설치
메모리덤프를 분석할 PC(Debugger PC)에 WinDbg를 다운로드하고 설치합니다. 이부분은 생략.

2. pdb 파일 확보
디버깅할 모듈의 디버그심볼 (pdb 파일)을 확보합니다.
디버그심볼이란, 메모리의 어느 번지가 어느 함수 시작점이고, 어느 번지가 어느함수 몇번째줄이고, 어느 번지가 어떤 변수가 저장된 지점이라는 등 디버깅하는데 필요한 정보를 저장하고 있는 파일입니다. 우리가 디버그모드로 빌드한 것을 디버깅할 수 있는것도 사실은 디버그 심볼이 있기 때문에 가능한 일이지요.
메모리덤프를 분석하기 위한 관건은 바로 디버그심볼을 잘 관리하는 것입니다. 디버그심볼을 얻기 위한 설정은 아래의 포스트를 참고하세요.

http://kuaaan.tistory.com/104


 3. WinDbg 실행
WinDbg를 실행합니다. 만약 Vista 이후의 OS라면 관리자 권한으로 실행해야 합니다.

4. Dump File Open
"File" 메뉴에서 "Open Crash Dump"를 선택하여 덤프파일을 Open니다. 그냥 덤프파일을 Drag & Drop해도 됩니다.


메모리덤프를 오픈하면 다음과 같은 초기화면이 열립니다.




5. pdb 경로 설정
디버거가 디버그 심볼을 인식할 수 있도록 pdb 경로를 설정합니다.


심볼 경로를 설정하는 방법은 인터넷에 잘 나와있습니다만 예를 들면 아래와 같이 합니다.

 

SRV*D:\Symbol\WebSymbol*http://msdl.microsoft.com/download/symbols;D:\MySymbol

위의 설정은 두개의 Symbol Path를 지정합니다.

  • Microsoft Symbol Server에서 필요한 심볼을 D:\Symbol\WebSymbol에 다운로드받아 사용해라. (이 심볼은 Win API등을 분석하는데 사용됩니다.)
  • D:\MySymbol 폴더에 있는 심볼파일을 사용해라. (이 부분엔 VisualStudio에서 심볼이 생성되는 경로를 적어주면 되겠습니다.)


심볼 경로를 입력한 후 좌측 아래의 "Reload" 체크박스를 체크한 후 "OK"버튼을 클릭합니다. "Reload"를 체크하지 않았다면 다음의 WinDbg 명령으로도 대신할 수 있습니다.

.reload

만약, pdb가 메모리덤프와 동일 폴더에 존재한다면, 상기 심볼경로 설정은 하지 않아도 됩니다.

6. 메모리 덤프 분석
이제 메모리덤프를 분석할 준비가 끝났습니다.
   CallStack을 분석하거나, Memory 번지를 열어보거나, Register 값을 확인하는 등, Crash가 발생한 상황의 정보들을 분석할 수 있지만, 일단 아래와 같은 마법의 주문을 외어서 WinDbg에게 분석을 시켜봅니다.

!analyze -v

그러면 아래와 같이 WinDbg가 알아서 분석을 해줍니다.


와우, 크래쉬가 발생한 지점을 정확하게 보여주고 왜 죽었는지를 설명해주는군요. 이 "!analyze -v" 명령 한방이면 왠만한 상황의 디버깅은 끝납니다.

게다가 빌드 당시와 동일한 절대경로에 소스코드가 존재할 경우, 소스코드를 자동으로 찾아서 연결해줍니다. 따라서, 해당 모듈을 빌드한 PC에서 메모리덤프를 분석한다면, 알아서 소스코드가 연결됩니다.
만약 연결되지 않는다면 "File" > "Source File Path" 메뉴에서 아래와 같이 소스코드 경로를 설정해주어야 합니다. (소스코드 경로는 정확히 cpp파일이 있는 위치를 입력해도 되지만, Solution Dir을 입력해도 됩니다)


"!analyze -v" 명령을 이용한 자동분석으로 충분한 분석이 되지 않을 경우, 디버거의 여러가지 기능을 이용해 추가적인 분석을 진행합니다. 우선 "View" 메뉴를 눌러보면 아래와 같은 여러개의 추가 Window를 열 수 있습니다.


여기서 CallStack 을 선택하면 아래와 같이 Crash 발생시점의 CallStack 확인이 가능합니다.


위의 CallStack 창에서 특정 라인을 더블클릭하면 해당 라인의 Source Code가 연결됩니다.


만약 다른 Thread의 CallStack이 궁금하다면 "View" 메뉴에서 "Processes and Threads" 창을, 변수의 값을 확인하고 싶다면 "Locals"창을, 특정 메모리번지를 확인하고 싶다면 "Memory"창을 열면 됩니다. VisualStudio와 같은 다른 디버거 사용법과 크게 다를바 없습니다.


"!analyze -v" 실행했을 때의 레포트 예입니다.
레포트 중간중간에 파란색으로 하이퍼링크가 걸린 곳이 있는데 이부분을 클릭하면 추가적인 정보를 볼 수 있습니다.

 

FAULTING_IP:
CrashTest!funcC+17 [d:\my documents\1. test code\crashtest\crashtest\crashtest.cpp @ 11]
00407317 8901            mov     dword ptr [ecx],eax

EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 00407317 (CrashTest!funcC+0x00000017)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000001
   Parameter[1]: 00000000
Attempt to write to address 00000000

PROCESS_NAME:  CrashTest.exe

ADDITIONAL_DEBUG_TEXT: 
Use '!findthebuild' command to search for the target build information.
If the build information is available, run '!findthebuild -s ; .reload' to set symbol path and load symbols.

FAULTING_MODULE: 77b00000 ntdll

DEBUG_FLR_IMAGE_TIMESTAMP:  4c629f39

ERROR_CODE: (NTSTATUS) 0xc0000005 - 0x%08lx

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - 0x%08lx

EXCEPTION_PARAMETER1:  00000001

EXCEPTION_PARAMETER2:  00000000

WRITE_ADDRESS:  00000000

FOLLOWUP_IP:
CrashTest!funcC+17 [d:\my documents\1. test code\crashtest\crashtest\crashtest.cpp @ 11]
00407317 8901            mov     dword ptr [ecx],eax

FAULTING_THREAD:  000002f0

BUGCHECK_STR:  APPLICATION_FAULT_NULL_POINTER_READ_NULL_POINTER_WRITE_WRONG_SYMBOLS

PRIMARY_PROBLEM_CLASS:  NULL_POINTER_READ

DEFAULT_BUCKET_ID:  NULL_POINTER_READ

LAST_CONTROL_TRANSFER:  from 00407334 to 00407317

STACK_TEXT: 
0012fefc 00407334 00000001 00000002 00000003 CrashTest!funcC+0x17 [d:\my documents\1. test code\crashtest\crashtest\crashtest.cpp @ 11]
0012ff10 00407354 00000001 00000002 00000003 CrashTest!funcB+0x14 [d:\my documents\1. test code\crashtest\crashtest\crashtest.cpp @ 16]
0012ff24 0040736e 00000001 00000002 00000003 CrashTest!funcA+0x14 [d:\my documents\1. test code\crashtest\crashtest\crashtest.cpp @ 21]
0012ff38 004011d2 00000001 002122e8 00212348 CrashTest!wmain+0xe [d:\my documents\1. test code\crashtest\crashtest\crashtest.cpp @ 26]
0012ff88 77a71194 7ffd5000 0012ffd4 77b5b495 CrashTest!__tmainCRTStartup+0x15e [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 327]
WARNING: Stack unwind information not available. Following frames may be wrong.
0012ff94 77b5b495 7ffd5000 778a33a8 00000000 kernel32!BaseThreadInitThunk+0x12
0012ffd4 77b5b468 00401229 7ffd5000 00000000 ntdll!RtlInitializeExceptionChain+0x63
0012ffec 00000000 00401229 7ffd5000 00000000 ntdll!RtlInitializeExceptionChain+0x36


STACK_COMMAND:  ~0s; .ecxr ; kb

FAULTING_SOURCE_CODE: 
     7:
     8: void funcC(INT x, INT y, INT z)
     9: {
    10:     LPINT    pInt = NULL;
>    11:     *(pInt) = x + y + z;    // Crash!!
    12: }
    13:
    14: void    funcB(INT c, INT d, INT e)
    15: {
    16:     funcC(c, d, e);


SYMBOL_STACK_INDEX:  0

SYMBOL_NAME:  CrashTest!funcC+17

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: CrashTest

IMAGE_NAME:  CrashTest.exe

BUCKET_ID:  WRONG_SYMBOLS

FAILURE_BUCKET_ID:  NULL_POINTER_READ_c0000005_CrashTest.exe!funcC

WATSON_STAGEONE_URL:  http://watson.microsoft.com/StageOne/CrashTest_exe/0_0_0_0/4c629f39/CrashTest_exe/0_0_0_0/4c629f39/c0000005/00007317.htm?Retriage=1

Followup: MachineOwner
---------



다른 부분은 그냥 읽여보면 되는데요... CallStack 설명한 부분을 잠시 보겠습니다.

STACK_TEXT: 
0012fefc 00407334 00000001 00000002 00000003 CrashTest!funcC+0x17 [d:\my documents\1. test code\crashtest\crashtest\crashtest.cpp @ 11]


위의 정보의 의미는 다음과 같습니다.

  • 제일 좌측의 숫자 (0012fefc) : 해당 함수 실행 시점의 EBP 값. 이 EBP가 가리키는 위치의 메모리 주소의 값들을 4바이트씩 끊어서 나타낸 것이 다음에 나오는 4개의 16진수입니다.
  • 두번째 숫자 (00407334) : EBP가 가리키는 주소 (0012fefc) 의 4바이트 값. 해당 함수를 호출한 이전 함수의 EBP를 의미합니다.
  • 세번째 ~ 다섯번째 숫자 (00000001 00000002 00000003) :CrashTest 모듈의 funcC 함수에 전달된 파라메터를 좌측부터 세개까지 나타냅니다.
  • CrashTest!funcC+0x17 : CrashTest 모듈(exe 혹은 dll 이름)의 funcC 함수 시작점에서 0x17바이트 떨어진 지점을 의미합니다.
  • [d:\my documents\1. test code\crashtest\crashtest\crashtest.cpp @ 11] : 아시죠? crashtest.cpp의 11번째 줄을 의미합니다.



7. 비고
어떤가요? 메모리 덤프 분석 어렵지 않죠?
메모리 덤프 분석을 하는데 있어서 가장 중요한 것은 Symbol 관리입니다. 모든 릴리즈되는 모듈의 바이너리와 pdb 파일이 이력관리되어야 하죠. 가장 좋은 방법은 Symbol Store라 불리우는 심볼서버를 구성하는 방법이구요, 심볼을 팀원간에 공유할 필요성이 별로 없다면 SVN 등으로 관리하는 방법도 괜찮을 것 같습니다.

pdb 파일은 파일 이름만 달라져도 Debugger가 인식하지 못하므로 Rename해선 안됩니다. 또한, 소스코드가 동일하더라도 Build한 TimeStamp가 달라지면 pdb를 인식하지 못하므로 정확히 빌드한 산출물 Binary와 함께 만들어진 pdb는 한 짝으로 관리되어야 합니다.

심볼스토어 구성하는 방법은 아래 포스트에 잘 설명되어 있네요.

'소프트웨어 > Etc.' 카테고리의 다른 글

Application Memory Dump 분석하기 (part 1)  (0) 2016.01.19
블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,
1. "포스트모템 디버깅"과 "메모리 덤프"
"포스트 모템"이라는 말은 "사후(死後)"라는 의미입니다. 사후 세계를 믿는 종교를 "포스트모템 신앙"이라고 하죠.

"포스트모템 디버깅 (Post Mortem Debugging)" 이라는 말도 대략 비슷한 의미입니다. 디버거가 설치되어 있고 개발환경이 꾸며져 있는 PC에서 문제가 발생한다면야 별 걱정할 게 없겠지만... 그렇지 않다면 문제가 발생한 PC에서 "메모리 덤프"를 작성하여 분석가능한 개발PC로 가져와서 덤프 분석을 수행해야 합니다. 이런 작업을 "포스트모템 디버깅"이라고 합니다.

대략은 아래와 같은 순서로 진행됩니다.

1. 문제가 발생하는 PC에 Just-In-Time Debugger를 등록 (관련된 내용은 여기를 참조)
2. 오류창이 발생하는 현상을 재현하면 JIT Debugger로 등록한 디버거가 실행되면서 문제 프로세스를 자동으로 Attach한다.
3. 실행된 디버거를 사용하여 메모리 덤프를 작성한다.
4. 디버거를 개발환경이 갖추어진 PC로 가져가 분석한다.

메모리 덤프란 프로세스의 메모리를 정해진 덤프 포맷에 따라 기록한 파일입니다. 커널메모리를 덤프뜨면 "커널 메모리덤프", Application 메모리를 덤프뜨면 "Application 메모리덤프"가 됩니다. ^^
이 덤프파일을 디버거로 분석하면, (Full Dump인 경우) 오류가 발생한 시점에 해당 프로세스에 Debugger를 Attach한 것과 같은 수준의 분석이 가능합니다.

이 포스트에서는...
  "메모리덤프 분석은 커널드라이버 개발자들이나 하는 거다. Application 디버깅에는 해당 안되는 얘기일 거야"
라고 생각하시는 분들이나,
  "WinDbg 같은건 너무 어려워서 난 몰라"
라고 생각하는 분들을 위해 메모리덤프 작성하는 방법과 WinDbg를 이용해 간단하게 덤프 분석하는 방법을 다루고자 합니다.
절대 고급 기술을 다루고자 함이 아니며, 저는 그럴 능력도 안되오니 고수분께서는 그냥 조용히 나가주시면 감사하겠습니다. ^^

이번 포스트에서는 "메모리덤프 작성하는 방법"을 주로 다루고자 합니다. 작성된 메모리덤프 분석하는 방법은 다음 번 포스트에서 다루겠습니다.


2. Dr.Watson을 이용한 메모리 덤프
Dr.Watson은 XP 이전까지의 Windows에 기본적으로 탑재되어 있는 디버거(?)로서, 다음과 같은 간단한 기능을 가지고 있습니다.

  • Just-In-Time 디버거로 등록되어, 시스템의 프로세스 중에서 오류가 발생할 경우 오류가 발생한 주소와 오류코드, 콜스택 등을 파일로그로 기록
  • 오류가 발생한 프로세스의 메모리덤프를 작성

Dr. Watson으로 메모리덤프를 작성하는 경우의 장점은 다음과 같습니다.

  • 오류가 발생한 경우, 메모리 덤프를 얻기 위한 방법으로는 가장 간단한 방법임.
  • 특히 Windows XP 이전의 OS인 경우 Windows에 기본으로 포함되어 있으므로 별도의 설치가 필요 없슴.
  • Dr.Watson은 오류 발생시 자동으로 덤프를 작성하기 때문에 svchost.exe 처럼 중요 시스템프로세스에서 오류가 발생하는 경우에도 덤프를 뜰 수 있습니다. 다른 디버거를 사용하면, 디버거가 시스템 프로세스를 Attach하면 그 Process가 멈추면서 시스템이 정상적으로 동작하지 않기 때문에 Remote Debugging 등 귀찮은 방법을 좀 동원해야 합니다.

사용법은 다음과 같습니다.

1) Dr.Watson에 메모리덤프 설정
- 먼저 시작 > 실행 창에서 "drwtsn32" 명령을 실행하여 Dr.Watson을 실행


- 설정 창이 뜨면 "크래시 덤프 유형" 을 "전체"로 선택. (이때 크래시 덤프 생성 경로도 확인해둡니다.)


2) Dr.Watson을 기본디버거로 등록하기
- 시작 > 실행 창에서 "drwtsn32 -i" 명령을 실행하여 기본디버거로 등록합니다.


- 디버거 등록이 완료되면 아래와 같이 창이 뜹니다.


- 기본디버거가 등록되면 "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug" 레지스트리에 아래와 같은 값들이 등록됩니다.  (원상복구를 원하신다면 이 키의 "Debugger" 와 "Auto" Value를 삭제하시면 됩니다.)


3)오류 상황을 재현하기
- 기본디버거가 등록된 후 오류가 발생하면 아래와 같이 로그와 덤프파일이 생성됩니다. "전체덤프" 설정일 때, 덤프파일은 해당 프로세스가 사용하는 물리메모리 크기와 같은 사이즈로 생성됩니다. 따라서 좀 사이즈가 클 수도 있습니다.


- 이 덤프파일(user.dmp)을 개발환경이 갖춰진 PC에 가져가서 분석하면 됩니다. 만약 덤프파일 분석이 곤란한 상황이라면 아쉬운대로 로그파일을 이용할 수도 있는데요, 이 경우 map 파일이나 cod 파일등을 사용해 분석하면 오류가 발생한 위치와 그때의 CallStack 등을 확인할 수 있습니다. (관련 내용은 여기를 참고)

- Windows Vista 이후에는 Dr.Watson이 설치되어 있지 않기 때문에 WinDbg 등 다른 방법을 이용해야 합니다. 하지만, Windows XP의 System32 폴더에서 drwtsn32.exe 파일을 복사해온다면 Vista 이후 OS에서도 Dr.Watson을 사용할 수 있습니다.


drwtsn32.exe



2. WinDbg 를 사용하는 방법
꼭 Dr.Watson이 아니더라도 메모리 덤프 기능이 있는 디버거를 기본 디버거로 등록만 한다면 오류 발생시 메모리덤프를 뜰 수 있습니다. 여기서는 WinDbg의 예를 들어 설명합니다.
WinDbg로 메모리덤프를 작성하는 경우, Dr.Watson과 비교했을 때 다음과 같은 장점들이 있습니다.

  • WinDbg는 비교적 설치가 간단한 가벼운 디버거입니다. 따라서 Dr.Watson이 탑재되지 않은 Vista나 Win7 등에서 간단하게 다운로드하여 사용할 수 있습니다.
  • 오류가 발생하지 않는 경우 Dr.Watson은 메모리 덤프를 작성할 수 없지만, WinDbg는 디버거의 기능을 모두 갖추고 있기 때문에 오류가 발생하지 않는 경우에도 메모리 덤프 분석이 가능합니다. 예를 들어, Hang이 걸린 프로세스가 있을 때, WinDbg를 설치한 후 해당 Process를 Attach하면 간단하게 메모리덤프를 작성할 수 있습니다.
WinDbg를 이용해 덤프를 작성하는 방법은 다음과 같습니다.

1) 문제가 재현되는 PC에 WinDbg를 다운로드하여 설치 : 이부분은 설명을 생략합니다.

2) WinDbg를 기본 디버거로 등록
- 관리자 권한의 커맨드 쉘을 실행해 WinDbg 설치 위치로 이동한 후 아래와 같이 "windbg.exe -I" 명령을 실행해주면 됩니다.


- 정상적으로 기본디버거 등록이 완료되면 아래와 같은 메시지 창이 뜹니다.


- 기본디버거가 등록되면 역시 "AeDebugger" 레지스트리 키에 "Debugger" Value에 디버거 실행커맨드가 등록됩니다. 기본디버거 설정을 해제하고 싶으면 "Debugger"와 "Auto" Value를 삭제하면 됩니다.


3) 문제 현상을 재현하면 자동으로 WinDbg가 실행됨

4) 실행된 WinDbg 에서 아래와 같은 명령을 실행하여 덤프를 생성
.dump /ma c:\temp\user.dmp






3. 기타 메모리덤프 생성 방법
사실 메모리덤프 생성하는 방법은 이 외에도 많습니다.

VisualStudio로 디버깅 중에도 메모리 덤프를 뜰 수 있구요...



Vista 이후 OS에서는 작업관리자에서도 덤프를 작성할 수 있습니다.


이 외에도 Olly 등 왠만한 디버거에는 메모리덤프 기능이 있지요. 어떤 방법을 이용해도 마찬가지구요. 해당 상황에서 적합한 방법을 사용하시면 되겠습니다.

 

'소프트웨어 > Etc.' 카테고리의 다른 글

Application Memory Dump 분석하기 (part 2)  (0) 2016.01.19
블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,