[Troubleshooting Tool] Windbg

Windbg는 그야 말로 Debugger Tool이다. 메모리 덤프를 수집할 수도 있으며, 수집된 Dump를 분석하는 데 도움이 되는 Tool이다.

 

먼저, 메모리 덤프 수집을 위해서는 다음과 같은 방법이 존재한다.

 

AeDebug Dr. Wastn 대신에 다음과 같이 WinDbg를 설정할 수 있다.

 

windbg -p %ld -e %ld -g

 

그리고, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options Application 이름을 key로 추가하고, 문자열 “debugger” REG_SZ 역시 추가, Windbg.exe path를 입력하여, 아래 그림 참고, Application 수행 시 Windbg attach 할 수 있다. 이 경우는 Service Application의 수행 시 문제가 발생하는 경우에 유용하다.

Windbg의 메뉴에는 Open Executable… 을 제공하는 데, 이는 Windbg를 통해서 Application launch 시킬 수 있는 메뉴이다. 또한 실행 중인 Process Windbg Attach 할 수 있는 Attach to a Process… 메뉴도 존재한다.

일단, Debugger Process Attach가 되면, 아래와 같은 화면을 Open된다. 익숙하지 않을 수 있겠지만, 하단 부분을 보면 Command 창이 존재하는 데, command 창에 명령어를 입력함으로써 Dump의 분석이나 수집이 가능하다.

일반적으로 command에서 ‘g’명령어를 입력하면, 해당 Application 2nd chance exception이 발생할 때, debug break이 되어 windbg에 알려준다. 이 경우에 ‘.dump’ command를 이용하여 Dump를 수집할 수 있다.

 

.dump /ma

 

MiniOption

Effect

a

Creates a minidump with all optional additions. The /ma option is equivalent to /mfFhut — it adds full memory data, handle data, unloaded module information, basic memory information, and thread time information to the minidump.

 

/a option mini dump를 수집할 때의 option인데, 만일 /ma 와 같이 수집한다면, mini dump에 추가적으로 full memory data 가 포함된다. 가장 흔한 option이며, /f option (Full Dump)보다 더 많은 정보를 가지고 있다.

 

AV(Access Violation)와 같은 명백한 2nd chance exception외에 C++ EH(E06D7363) 이나 CLR Exception(E0434F4D) 와 같은 1st Chance Exception에 대한 Dump를 수집하기 위해선 해당 Exception이 발생했을 때, debug break되도록 해야 할 필요가 있다. 이 경우에는 sxe command를 이용한다. 예를 들어,

 

sxe eh

sxe clr

 

또는 exception code를 직접입력 할 수도 있다.

 

sxe E06D7363

sxe E0434F4D

 

그리고, 이러한 설정을 다시 disable 하기 위해서‘sxd’를 이용한다.

 

물론, windbg debugger 이기 때문에 bp(break point)를 설정할 수 있다. 그러므로, 다음과 같이 conditional break point를 걸고, dump를 수집할 수도 있다.

 

bp mscorwks!RaiseTheException "j (poi(poi(poi(poi(esp+4))+8)+48) = 02000092)'.dump /ma /u OOM.dmp;g''gc' "

 

bp 다음의 mscorwks!RaiseTheException  address가 위치할 자리이다. Address 대신에 이렇게 function name을 사용할 수 있는 것은 적절한 symbol link되어 있기 때문인데, 처음 File menu에서 보았지만, symbol path를 설정할 수 있는 menu가 존재한다. Command를 통해서는 다음과 같이 설정할 수 있다.

 

.sympath+ srv*c:\pubsymbols*http://msdl.microsoft.com/download/symbols

.reload

 

적절한 symbolload되어 있는 것은 다음과 같이 확인할 수 있다. 일단, lm command를 확인하면, 아래와 같은 module list를 확인할 수 있다. Deferred 는 아직 symbolload되지 않았다는 의미이다.

 

0:001> lm

start    end        module name

00400000 00414000   CrashApp   (deferred)            

10000000 10018000   ItClient   (deferred)            

10200000 10287000   MSVCR71D   (deferred)            

6eb70000 6ec15000   IMETIP     (deferred)            

6ec50000 6ec72000   IMKRAPI    (deferred)            

6ec80000 6ecac000   IMJKAPI    (deferred)

. . . . . . . . . .

 

.reload /f command를 이용하여 강제로 load 시킨 후에 lm vm command를 이용하여 확인할 수 있다. 만일, 존재하지 않으면, no symbols, 그리고 맞지 않으면 m(ismatch) 이라고 표시되는 것을 알 수 있다. 외에 C (Checksum이 맞지 않는 경우), T(Timestamp 가 맞지 않는 경우) 로 표시되기도 한다. 정상적인 경우는 private 이나 public 이란 표시와 함께 Loaded symbol image file에 정확한 pdb file path를 확인할 수 있다.

 

0:000> .reload /f CrashApp.exe

0:000> lm vmCrashApp  

start    end        module name

00400000 00414000   CrashApp   (no symbols)          

    Loaded symbol image file: C:\APPS\CrashApp\Debug\CrashApp.exe

    Image path: C: \APPS\CrashApp\Debug\CrashApp.exe

    Image name: CrashApp.exe

    Timestamp:        Fri May 20 03:45:17 2005 (428CDEBD)

    CheckSum:         000239A4

    ImageSize:        00014000

    File version:     1.0.0.1

    Product version:  1.0.0.1

    File flags:       1 (Mask 3F) Debug

    File OS:          4 Unknown Win32

    File type:        1.0 App

    File date:        00000000.00000000

    Translations:     0409.04b0

    ProductName:      CrashApp Application

    InternalName:     CrashApp

    OriginalFilename: CrashApp.EXE

    ProductVersion:   1, 0, 0, 1

    FileVersion:      1, 0, 0, 1

    FileDescription:  CrashApp MFC Application

    LegalCopyright:   Copyright (C) 2001

 

Conditional break point 는 주로 (꼭 그런 것은 아니지만) function return value나 전달되는 parameter의 값을 이용하여 설정하는 경우가 많이 있다. 그러므로, 이러한 값들이 WinDbg를 통해서 어떻게 보여지는 지 아는 것이 중요하다. 다음은 이와 관련하여 stack walking을 살펴보고자 한다.

0:000> r

eax=00931b70 ebx=7ffd5000 ecx=00000001 edx=00931c10 esi=00000000 edi=00000000

eip=00401010 esp=0012ff30 ebp=0012ff34 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

Stackwalker!func1:

00401010 55              push    ebp

 

r register 를 확인하는 command 이다. 보면, eax .. 등등과 eip, esp 그리고, ebp의 정보를 확인할 수 있다. 현재, break이 된 위치는 오른쪽 source code에도 나와있지만, func1 을 호출하는 시점이다. Source code를 보면, func1에서 func2로 호출이 되는 구조인데, func2 2개의 parameter를 전달하고 있다. 이 경우에 사용하는 자료구조가 stack이다. Stack push/pop operation에 의해 function call에 필요한 local variablesparameters를 저장하거나 전달한다. 이러한 구조에서 stack의 모양은 어떻게 변할까?

Raw stack을 확인할 수 있는 명령어, dds 를 이용하여 local variablesparameters에 의해 사용되는 stack의 구조를 살펴보자. 현재, esp0012ff30 이다.

 

0:000> dds esp

0012ff30  00401008 Stackwalker!main+0x8

0012ff34  0012ff80

0012ff38  00401187 Stackwalker!__tmainCRTStartup+0x117

 

여기서 0012ff30 address가 가지고 있는 00401008값은 무엇일까? 일단, uf 명령어를 통해서 func1을 호출하는 main funcation에 대한 assembly를 살펴보자

 

0:000> uf Stackwalker!main

Stackwalker!main

   11 00401000 55              push    ebp

   11 00401001 8bec            mov     ebp,esp

   12 00401003 e808000000      call    Stackwalker!func1 (00401010)

   13 00401008 33c0            xor     eax,eax

   13 0040100a 5d              pop     ebp

   13 0040100b c3              ret

 

그러므로, func1을 호출하고 난 다음의 address , return address stack에 저장한다. 결국, call 명령과 동시에 return address stack에 저장되고, stack pointer는 감소된다. (stack push 동작에 의해 high address에서 low address로 이동한다.)

그리고 나서, ebp stack에 저장한다. 그리고, 현재의 esp ebp에 저장하여 새로운 Stack frame을 생성한다고 보면 된다.

 

0012ff2c  0012ff34   < ---- 현재의 esp:0012ff2c 에는 old ebp address를 저장.

0012ff30  00401008 Stackwalker!main+0x8

0012ff34  0012ff80

0012ff38  00401187 Stackwalker!__tmainCRTStartup+0x117

 

ebp stack chain 형태로 연결이 되어 있고, 이 값들을 통해서 stack walking이 가능한 것이다. 이를 기반으로 callstack을 재 구성할 수 있다. Kb command ebp 기반의 callstack을 보여준다.

 

0:000> kb

ChildEBP RetAddr  Args to Child             

0012ff2c 00401008 0012ff80 00401187 00000001 Stackwalker!func1+0x4

0012ff34 00401187 00000001 00931b70 00931c10 Stackwalker!main+0x8

0012ff80 0040105f 0012ff94 75834911 7ffd5000 Stackwalker!__tmainCRTStartup+0x117

0012ff88 75834911 7ffd5000 0012ffd4 76f2e4b6 Stackwalker!mainCRTStartup+0xf

0012ff94 76f2e4b6 7ffd5000 7692139b 00000000 kernel32!BaseThreadInitThunk+0xe

0012ffd4 76f2e489 00401050 7ffd5000 00000000 ntdll!__RtlUserThreadStart+0x23

0012ffec 00000000 00401050 7ffd5000 00000000 ntdll!_RtlUserThreadStart+0x1b

 

그리고 나서 source code를 보면, local variable int y 0 assign 한다. 이를 assembly code에서 살펴보면,

 

0:000> u Stackwalker!func1 eip+1

Stackwalker!func1

00401010 55              push    ebp

00401011 8bec            mov     ebp,esp

00401013 51              push    ecx

00401014 c745fc00000000  mov     dword ptr [ebp-4],0

 

Int y에 해당하는 address ebp-4 로 표현되고 있다. Local variable ebp-4, -8, -c 형태로 stack이 감소하면서 할당된다.

 

0:000> dds esp

0012ff28  00000000

0012ff2c  0012ff34   < ----- ebp

0012ff30  00401008 Stackwalker!main+0x8

0012ff34  0012ff80

0012ff38  00401187 Stackwalker!__tmainCRTStartup+0x117

 

그 다음은 func2를 호출해야 하는 데, parameters 2개가 필요한 것을 볼 수 있다. 이를 위해서 먼저 stack 2개의 parameters push 하는 동작을 한다. Assembly를 보면, 다음과 같다.

 

18 0040101b 6a0a            push    0Ah  ; 두번째 parameter: 10

   18 0040101d 8d45fc          lea     eax,[ebp-4]

   18 00401020 50              push    eax ; 첫번째 parameter : y ßlocal variable

   18 00401021 e80a000000      call    Stackwalker!func2 (00401030)

 

첫번째 parameter pointer인 것을 알 수 있는 데, address를 넘기기 위해서 ebp-4에 대한 address eax copy하고 eax stack에 저장하는 operation을 취하고 있다. 그리고, stack을 살펴보면, 다음과 같다.

 

0:000> dds esp

0012ff1c  00401026 Stackwalker!func1+0x16 < --- function call에 의한 ret address

0012ff20  0012ff28   < ---- ebp-4였던 local variablepointer, 첫번째 parameter

0012ff24  0000000a  < ---- push 0ah에 해당하는 두번째 parameter

0012ff28  00000000  < ---- ebp-4

0012ff2c  0012ff34    < ---- ebp

0012ff30  00401008 Stackwalker!main+0x8

0012ff34  0012ff80

0012ff38  00401187 Stackwalker!__tmainCRTStartup+0x117

 

Source code를 보면, func2에서는 첫번째 parameter에 두번째 parameter의 연산이 존재하는 데, stack의 구조상 parameter func2 ebp보다 positive 위치에 존재할 수 밖에 없다.

 

0:000> uf Stackwalker!func2

Stackwalker!func2

   21 00401030 55              push    ebp

   21 00401031 8bec            mov     ebp,esp

   22 00401033 8b4508          mov     eax,dword ptr [ebp+8] << 현재, esp의 위치

   22 00401036 8b08            mov     ecx,dword ptr [eax]

   22 00401038 034d0c          add     ecx,dword ptr [ebp+0Ch]

   22 0040103b 8b5508          mov     edx,dword ptr [ebp+8]

   22 0040103e 890a            mov     dword ptr [edx],ecx

   24 00401040 5d              pop     ebp

   24 00401041 c3              ret

 

Assembly code에서 ebp+81st parameter이다. 그리고, ebp+c 2nd parameter임을 알 수 있다. 이것은 아래의 raw stack을 통해서 확인이 가능하다.

 

0:000> dds esp

0012ff18  0012ff2c                        < ---- ebp : old ebp

0012ff1c  00401026 Stackwalker!func1+0x16 < ---- ebp+4 : ret address

0012ff20  0012ff28                        < ---- ebp+8 : 1st parameter

0012ff24  0000000a                       < ---- ebp+c : 2nd parameter

0012ff28  00000000

0012ff2c  0012ff34

0012ff30  00401008 Stackwalker!main+0x8

 

이후, 여기서 ret으로 하고 return address로 돌아갔을 때의 raw stack과 비교하여 살펴보면, Func2에 대한 stackclear된 것을 다음과 같이 확인할 수 있다.

 

0:000> dds esp

0012ff20  0012ff28

0012ff24  0000000a

0012ff28  0000000a

0012ff2c  0012ff34

0012ff30  00401008 Stackwalker!main+0x8

0012ff34  0012ff80

0012ff38  00401187 Stackwalker!__tmainCRTStartup+0x117

이외에도 Windbg를 통해서 Data를 확인하기 위한 몇 가지 유용한 command 로는 다음이 있다.

dv command 는 현 Framelocal variables에 대한 정보를 출력한다.

 

0:000> dv /V

0012ff20 @ebp+0x08               x = 0x0012ff28

0012ff24 @ebp+0x0c             val = 10

 

dc command를 이용하여 해당 address에 존재하는 data를 확인할 수 있다.

 

0:000> dc 0x0012ff28 l1

0012ff28  0000000a                             ....

0:000> ?a

Evaluate expression: 10 = 0000000a

 

knLnframe numberCallstack에 표시하는 데, .frame # command를 이용하여 해당 frame으로 이동 할 수 있으며, 그러므로, 해당 framelocal variables도 쉽게 확인할 수 있다.

 

0:000> knL

 # ChildEBP RetAddr 

00 0012ff18 00401026 Stackwalker!func2+0x10

01 0012ff2c 00401008 Stackwalker!func1+0x16

02 0012ff34 00401187 Stackwalker!main+0x8

03 0012ff80 0040105f Stackwalker!__tmainCRTStartup+0x117

04 0012ff88 75834911 Stackwalker!mainCRTStartup+0xf

05 0012ff94 76f2e4b6 kernel32!BaseThreadInitThunk+0xe

06 0012ffd4 76f2e489 ntdll!__RtlUserThreadStart+0x23

07 0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b

 

0:000> .frame 1

01 0012ff2c 00401008 Stackwalker!func1+0x16

0:000> dv /i /V

prv local  0012ff28 @ebp-0x04               y = 10

by 강세윤 | 2008/11/25 16:28 | Windows debugging | 트랙백 | 덧글(0)
트랙백 주소 : http://byung.egloos.com/tb/4750412
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]

:         :

:

비공개 덧글

< 이전페이지 다음페이지 >