|
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
/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 적절한 symbol이 load되어 있는 것은 다음과 같이 확인할 수 있다. 일단, lm command를 확인하면, 아래와 같은 module list를 확인할 수 있다. Deferred 는 아직 symbol이 load되지 않았다는 의미이다. 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 variables나 parameters를 저장하거나 전달한다. 이러한 구조에서 stack의 모양은 어떻게 변할까? Raw stack을 확인할 수 있는 명령어, dds 를 이용하여 local variables나 parameters에 의해 사용되는 stack의 구조를 살펴보자. 현재, esp는 0012ff30 이다. 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 variable의 pointer, 첫번째 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+8은 1st 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에 대한 stack이 clear된 것을 다음과 같이 확인할 수 있다. 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 는 현 Frame의 local 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 knL의 n은 frame number를 Callstack에 표시하는 데, .frame # command를 이용하여 해당 frame으로 이동 할 수 있으며, 그러므로, 해당 frame의 local 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 강세윤 at 12/15 오늘 많이 헤매다..알게 .. by youna at 12/14 글 잘 읽었습니다 . 전 .. by 위시 at 11/26 어렷다 by klhk at 11/09 dhjjgbem by kl at 11/09 17번부터 어떻게 접는지.. by tykim0131 at 10/28 ATL이나 MFC를 이용하.. by 김명신 at 09/24 복원되었군요.. 제 RSS.. by 강세윤 at 09/24 허걱, 하고 있는 것으로.. by 강세윤 at 09/15 RSS 주소 서비스는 안 .. by 정성태 at 09/15 이글루 파인더
| |||||||