2008/12/17 Assembly code에 대한 고찰 (jump 명령어 그리고, pointer 연산)
2008/11/26 [Troubleshooting Tool] Gflags.exe 2008/11/25 [Troubleshooting Tool] Windbg 2008/11/21 [Troubleshooting Tool] Debug Diagnostic Tool 2008/11/04 NTDLL!KiUserExceptionDispatcher 아래는 x module의 ttt 라는 함수에 대한 disassemble code이다. 아래를 보고 몇 가지 특징을 잡아 보자. x!ttt: 77f82f35 8b4c2408 mov ecx,dword ptr [esp+8] 77f82f39 8b442404 mov eax,dword ptr [esp+4] 77f82f3d 56 push esi 77f82f3e 668b11 mov dx,word ptr [ecx] 77f82f41 8d7002 lea esi,[eax+2] 77f82f44 668910 mov word ptr [eax],dx 77f82f47 eb08 jmp x!ttt+0x12 (77f82f51) x!ttt+0x19: 77f82f49 668b11 mov dx,word ptr [ecx] 77f82f4c 668916 mov word ptr [esi],dx 77f82f4f 46 inc esi 77f82f50 46 inc esi x!ttt+0x12: 77f82f51 41 inc ecx 77f82f52 41 inc ecx 77f82f53 6685d2 test dx,dx 77f82f56 75f1 jne x!ttt+0x19 (77f82f49) x!ttt+0x23: 77f82f58 5e pop esi 77f82f59 c3 ret 첫 번째는 jmp 에 의한 loop가 보인다. 상위의 노란색 block 이다. 일단, jmp 명령어에 의해서 77f82f51 로 분기한다. 그리고, jne 명령어에 의해서 77f82f49 로 분기될 수 있다. 결국, 아래 그림과 같이 looping이 된다. 77f82f47 -------> 77f82f51 -------> 77f82f56 ------+ ^ | | | +------77f82f49 <---------+ Loop에 대한 end 조건을 찾아보면, 녹색 Block 이다. 77f82f53 6685d2 test dx,dx 77f82f56 75f1 jne x!ttt+0x19 (77f82f49) test dx, dx 는 dx가 null여부를 Check 하는 알려진 명령어이다. test a,b 는 a&b와 같이 AND 연산을 의미한다. 그러므로, dx가 0x0이 아니면 결과가 0x0을 예상할 수 가 없다. 그 이후에 jne 연산을 하여 dx값이 0x0이 아닐 경우에 looping을 돌고, 0x0이면 해당 loop를 빠지는 condition으로 이뤄져 있음을 알 수 있다. 두 번째는 Frame 기반의 code가 아닌 듯 보인다. 이유는 assembly code 초기에 ebp(frame pointer) 저장 코드가 존재하지 않는 다. 또한, 초기 code를 보면, 77f82f35 8b4c2408 mov ecx,dword ptr [esp+8] 77f82f39 8b442404 mov eax,dword ptr [esp+4] Esp 기반의 data를 ecx, eax 로 전달하는 것으로 보이며, 이는 parameter의 assign 으로 보인다. 이는 kv command로도 확인된다. 이는 FPO 이다. ChildEBP RetAddr Args to Child 0006d950 0100e654 00d19fb0 0006e3b0 00c67fe0 x!ttt+0x1c (FPO: [2,0,1]) // 2는 parameters 0은 local variables 1은 cpu register (??) – push esi // 세 번째는 pointer 연산이 존재한다. 77f82f35 8b4c2408 mov ecx,dword ptr [esp+8] 77f82f39 8b442404 mov eax,dword ptr [esp+4] 77f82f3d 56 push esi 77f82f3e 668b11 mov dx,word ptr [ecx] 77f82f41 8d7002 lea esi,[eax+2] 77f82f44 668910 mov word ptr [eax],dx 상위의 노란 색을 보면, ecx 는 parameter로 전달된 address를 pointer로 갖고 있으며, 해당 pointer의 첫 번째 value 를 dx에 저장한다. 그리고, dx의 값을 eax address에 copy한다. Eax 역시 pointer이다. Debugging 할 때, 중요한 것은 data를 살피는 것인데, 상위의 code의 경우는 stack 에 존재하는 data를 유심히 살펴볼 필요가 있다. 만일, 아래의 위치에 break이 되어 있다고 가정하면, 77f82f4c 668916 mov word ptr [esi],dx 0:000> ddp esp 0006d950 00d19fb0 0042007b <---- esp 0006d954 0100e654 5914438b <---- esp+4 0006d958 00d19fb0 0042007b <---- esp+8 0006d95c 0006e3b0 0042007b <---- esp+c mov ecx,dword ptr[esp+8] 에서 해당 parameter의 처음 위치, 즉 parameter의 address는 어떻게 될까? 00d19fb0 가 아니고, esp+c, 0006e3b0 가 맞다. 이유는 중간에 push esi 라는 명령어가 존재하기 때문에 esp가 1 decrement 되었기 때문이다. 그러므로, esp+8, 00d19fb0 은 처음 eax에 mov된 parameter의 처음 address가 된다. Push/pop에 따라 esp가 변경되기 때문에 이를 유의하여 data를 check해야 한다. Loop block 안의 code는 아래와 같다. 77f82f49 668b11 mov dx,word ptr [ecx] 77f82f4c 668916 mov word ptr [esi],dx 77f82f4f 46 inc esi 77f82f50 46 inc esi x!ttt+0x12: 77f82f51 41 inc ecx 77f82f52 41 inc ecx 보면, ecx에서 esi로 dx register를 temp로 copy가 이뤄지고 있음을 알 수 있다. 그리고, inc가 두 번씩 발생하는 데, 이는 word 연산이기 때문에 그렇다. Word type data가 copy되고 pointer가 증가하기 때문에 각 pointer++은 inc 가 두 번 발생할 수 밖에 없다. 이는 아래의 명령어에서도 동일하게 추측된다. 77f82f3e 668b11 mov dx,word ptr [ecx] 77f82f41 8d7002 lea esi,[eax+2] 77f82f44 668910 mov word ptr [eax],dx lea 명령은 esi=eax+2 와 같은 pointer연산이라고 보면 되는 데, address를 +2했다는 것은 word type data를 dx에 copy 하고, 그 다음 word에 위치시키기 위해서, 즉 word 연산이기 때문에 +2가 된다는 것을 알 수 있다. 그러므로, 해당 function은 2개의 pointer를 source와 destination으로 받아서 copy하는 operation으로 보인다. 그리고, source data가 0x0일 때까지 copy가 이뤄지는 구조로 이해할 수 있다. //참고로 … Jump 명령어는 Condition code flag와 연관이 있다. 이어지는 내용 Gflags.exe는 Windows System Diagnostics 를 위해 debugging tools for windows에서 제공하는 Tool이다.
상위의 그림만 봐서는 이해하기 어려울 수도 있지만, Application에 대한 System registry의 일부 설정을 통하여 메모리누수나 handle 누수를 tracking 하거나 pageheap을 enable 하여 Heap corruption을 Troubleshooting 할 수 있다. 메모리누수에 대한 Gflags 설정.
1) Gflags에 보면, image file tab에서 “Create user mode stack trace database” 라는 option을 enable 한다. (그전에 image text box에 해당 image 이름을 입력해야 한다) Command 창에서는 “gflags –i CrashApp.exe +ust” 하고 실행하면 된다. 2) 설정이 되었는지는 레지스트리를 통해서 확인할 수 있는 데, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\CrashApp.exe 로 이동하여, key중에 REG_DWORD 로 GlobalFlag 가 존재하는 지 확인하고, 해당 값이 0x1000으로 설정되어 있으면 정상적으로 설정이 된 것이다. 3) 그리고, UDDH를 위해서 Symbol 설정을 해야 한다. Command창에서 “SET _NT_SYMBOL_PATH=C:\LeakAppSymbols;SRV*c:\symbols*http://msdl.microsoft.com/download/symbols” 하여 환경변수로 설정하고, 4) Application을 수행한 후, Leak 현상이 발생하기 전 해당 Command 창에 “umdh –p:PID > C:\start.txt” 를 수행한다. 5) 충분히 Leak 현상이 발생하고 나면, “umdh –p:PID > end.txt” 를 command창에서 한번 더 수행을 하고, 6) 마지막으로 해당 command 창에서 “umdh –d start.txt end.txt > result.txt” 를 수행하여 결과를 얻는 다. 결과 report 는 Leak이전에 잡은 Log와 이후에 잡은 Log를 통해서 차이가 발생하는 Allocation에 대한 결과를 다음과 같이 보여준다. Symbol이 맞아야 Callstack에서 Allocation이 발생하는 Code의 위치를 정확히 확인할 수 있다. /////////////// + 20480 ( 20480 - 0) 1 allocs BackTrace1516DC + 1 ( 1 - 0) BackTrace1516DC allocations ntdll!RtlTimeToElapsedTimeFields+00001963 buggy!memoryLeak+00000036 (d:\myprog\dbgt\exe\buggy\buggy.cpp, 20) buggy!wmain+000000C1 (d:\myprog\dbgt\exe\buggy\buggy.cpp, 225) buggy!wmainCRTStartup+0000016A kernel32!BaseThreadInitThunk+00000012 ntdll!RtlInitializeExceptionChain+00000063 ntdll!RtlInitializeExceptionChain+00000036 Total increase == 20480 requested + 24 overhead = 20504 ///////////// 여기서 increase 된 20480은 bytes 단위이다. Heap은 Code에서 사용하는 Memory Block으로 Application에서 메모리 할당이나 해제 시 잘못된 사용으로 인하여 Corruption이 발생할 수 있다. 일반적인 Heap corruption이 발생하는 경우는 다음과 같다. ² Buffer overrun ² Buffer underrun ² Double Free ² Free block에 대한 access ² 메모리 재사용에 대한 문제, (예를 들어, 메모리를 free하고 동일한 size를 재사용할 때, 동일한 address가 잡힐 것이라는 기대감에서 발생할 수 있는 문제. 등등 Heap Corruption 은 임의의 Heap block이 깨진 이후 다른 시점에 Allocation이나 Memory 사용시 예기치 않게 Access Violation이 발생할 수 있기 때문에 AV 순간 Crash dump를 수집한다고 해도 해당 Callstack이 문제 근원이 되는 Callstack이 아닐 가능성이 매우 크다. 이를 위해서 Gflags 는 allocation을 Check하도록 설정하여 잘못된 사용시 이를 Debugger를 통해 알려줄 수 있도록 도와준다. - Normal Pageheap ² free될 때만 allocation check ² gflags –p /enable CrashApp.exe ² High Memory usage의 process를 위해서는 “gflags.exe –p /enable lsass.exe /decommit /random 20” 를 이용할 수 있다. random 20은 20% 만 pageheap이 enable 된다는 의미. - Full Pageheap ² No access pages를 사용하여 Allocation block에 padding ² gflags –p /enable CrashApp.exe /full ² 만일, /decommit option을 이용하면, Memory를 1/2 로 줄일 수 있다. n gflags –p /enable CrashApp.exe /full /decommit n decommit 은 No access pages 대신에 reserved virtual memory zone을 이용함. Heap이 깨졌다는 것은 어떻게 알까? 먼저, AV crash dump를 확인하고, Heap corruption 여부를 확인하여 다시 Crash dump를 수집하게 된다. 간혹, 다음과 같은 Function stack에서 AV가 발생하는 것을 볼 수 있다.
Pageheap에 따른 Heap block의 구조는 아래의 문서를 참조한다. 중요한 것은 Block header나 suffix bytes 인데, 이는 Heap block이 Allocated된 것인지 Free된 것인지를 확인할 수 있는 정보가 된다. //// http://msdn.microsoft.com/en-us/library/cc266933.aspx발췌... The following diagrams show the arrangement of heap blocks. These are valid in Windows 2000 (Service Pack 1 and later), Windows XP, and later versions of Windows. Light page heap block — allocated: +-----+---------------+---+ Light page heap block — freed: +-----+---------------+---+ Full page heap block — allocated: +-----+---------+---+------- Full page heap block — freed: +-----+---------+---+------- 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 Debug Diagnostics Tool은 AD+ 와 같이 특정한 문제에 대해서 Memory Dump를 수집할 수 있는 기능을 제공한다. 여기서 특정한 문제라는 것은 Hang/Performance 문제/Memory 누수/Virtual Memory 조각화 및 다양한 Crash Issue이다. 뿐만 아니라 Crash나 Memory 관련 문제에 대해서 분석 report를 제공한다는 점에 있어서 유용하다. 먼저, Crash Rule에서 보면, 따로 설정하지 않은 1st Chance Exception에 대한 설정을 할 수 있다. 아래의 그림과 같이 Action Type으로 Log를 남기거나 Mini Dump 및 Full Dump를 수집할 수 있도록 설정가능하며, dump 생성의 수를 제한할 수 도 있다. (만일, 0라고 설정하면 제한하지 않는 다는 의미이다.) Performance를 고려하여 대부분 None이거나 간혹 Log 정도를 남기게 된다.
그리고, 임의의 1st Exception에 대한 Dump 수집을 설정할 수 있다. (이 말은 기본적으로 아무 설정을 하지 않았을 때, 2nd chance exception에 대한 Crash Dump가 기본적으로 수집된다) 이는 상위 그림의 Advances Settings에 Exceptions 버튼을 Click하여 설정한다.
상위의 그림은 좌측의 Exception Code에 AV를 Click 하여 1st Chance AV가 발생했을 때 자료를 어떻게 수집할 지 설정하는 방법이다. 앞서서 설명한 것처럼 Action Type은 Log나 Mini 및 Full User Dump를 수집할 수 있도록 설정할 수 있다. 특이한 경우는 CLR Exception의 경우이다.
보면, .NET Exception Type 이라는 부분이 존재한다. 여기에 Exception 정보를 추가하면, 해당하는 Exception 에 대해서 Full User Dump를 수집하도록 설정된다. Advanced Settings에 또 한가지는 Break point 를 설정하여 Dump를 수집할 수 있다는 것이다. 다음의 그림을 보자.
기본적으로 list에 Kernel32!ExitProcess나 몇 가지가 추가되어 있다. 일반적으로 2nd Chance Exception으로 인하여 Process terminated 되는 경우에 Crash Dump를 수집하므로, Kernel32!ExitProcess를 list에서 선택하고 Action Type을 Full user Dump로 설정하여 Crash Rule 을 완성한다. 하지만, 외에 원하는 부분에 BP를 설정하고 Dump를 수집하거나 Log를 남기고 싶을 경우에는 상위의 그림과 같이 Breakpoint Expression 부분에 원하는 address를 설정하여 추가시킬 수도 있다. 그리고, Heap이 Corruption되는 증상에 대해서 일반적으로 Pageheap을 Enabled 하고 Crash Dump를 수집하게 되는 데, Debug Diagnostic Tool에서는 Advanced Settings에 PageHeap Flags… 라는 부분에서 PageHeap을 설정할 수 있다.
Debug Diagnostic Tool은 Process의 Memory Leak 현상을 Detection 하기 위해서 Leaktrack.dll을 injection 시켜서 Leak을 tracking 할 수 있는 기능이 존재한다.
Processes Tab을 확인해 보면, Process ID에 따른 Process list 정보를 알 수 있는 데, Leak으로 의심이 되는 Process를 선택하여 오른쪽 마우스를 Click 하면, Monitor For Leaks 라는 Menu를 확인할 수 있다. 이는 앞서서 설명한 Leaktrack.dll을 해당 Process에 Injection 시키는 역할을 한다. 이후에 Private Bytes의 증가를 유심히 확인하여 충분히 메모리가 지속적으로 증가하는 패턴이 확인되면, Create Full Userdump menu 를 통해 메모리 dump를 수집할 수 있다. 간혹 Memory Leak으로 인하여 Out Of Memory Exception이나 Crash를 동반하는 경우가 있으므로, Crash Rule에 Kernal32!ExitProcess 에 Full user dump와 함께 leaktrack을 걸어 놓는 것도 좋을 듯 하다. ** 메모리 Leak은? Return 되지 않는 resource 로 인하여 process가 점유하고 있는 메모리가 지속적으로 증가하는 현상을 말한다. Logic에 따라서 Application이 메모리를 보유하고 사용할 수도 있으므로, 이와는 구분 되어야 한다. 이 경우, 시스템 resource가 부족하여 시스템의 전반적인 performance에 영향을 줄 수 있으며, 간혹 Hang 증상이 발생할 수 있다. 메모리 Leak을 판단하기 위해서는 메모리의 증가하는 패턴이 꾸준히 발생하기 때문에 성능로그의 Private Bytes 나 Virtual Bytes 그리고, Working Set의 데이터 패턴을 보고 Leak 여부를 판단해야만 한다. ******* Debug Diagnostic Tool은 다음과 같은 메모리 할당에 대한 Tracking이 가능하다. 1) NT Heap allocator 1. HeapAlloc 2. LocalAlloc 3. GlobalAlloc 2) Virtual Memory allocator 1. VirtualAlloc 3) C Run time memory allocator 1. malloc 2. calloc 3. operator new 4) BSTR allocator 1. SysAllocString 5) OLE/COM allocator 1. CoTaskMemAlloc, IMalloc interface Debug Diagnostic Tool이 제공하는 기능 중에 Crash Dump 및 Memory Dump 분석하여 reporting을 출력해주는 기능을 가지고 있다.
그림에서처럼 Advanced Analysis Tab에는 Crash나 memory analysis를 선택할 수 있다. 분석에 앞서서 Symbol을 정확하게 맞춰주는 것이 필요한데, tools menu의 Options and Settings … 를 통해서 설정할 수 있다.
Symbol Search path For analysis 에는 default로 Microsoft Public Symbol이(http://msdl.microsoft.com/download/symbols, 그리고, C:\symcache directory를 local machine의 symbol cache로 사용하도록 ) 설정되어 있다. 그리고, 필요한 symbol path를 Symbol Search Path for Debugging에 위치시켜야만 Symbol이 맞지 않아서 잘못된 reporting이 출력되는 것을 방지할 수 있다. Debug Diag는 다음과 같은 Memory leak 관련 report 를 출력한다.
보면, Analysis summary에서 눈에 띄게 Memory를 보유하고 있는 Function Call을 Check할 수 있다. 상위 report에서는 asp.dll나 VBScript.dll 과 관련한 Callstack에서 메모리를 보유하고 있음을 보여주고 있는 데, 해당 Function을 Click 하면, 어떤 Callstack에 메모리 할당이 이뤄졌는지 다음과 같이 reporting 하고 있음을 확인할 수 있다. 그림에서는 안 나타나 있지만, sample callstack 정보와 더불어 단위 Allocation size와 Allocation Count에 따른 unfreed memory에 대한 Top 10 list of allocations 정보를 출력해 줌으로써 Allocation pattern을 알 수 있다.
그리고, 다음은 Application에서 사용하고 있는 Virtual Memory에 대한 정보이다. 아래 그림에서도 알 수 있지만, Application 전반적으로 Reserved/Commited 그리고 Free 영역에 대한 사용정도를 알 수 있다. 그리고, Virtual Memory Details에서는 Application 에서 VirtualAlloc이 얼마나 되는 지, Threads가 사용한 Memory 또는 Native Heap으로 사용된 메모리가 많은 지에 대한 정보를 제공한다.
Heap Memory의 경우는 다음과 같이 따로 정보를 제공한다. 각 Heap의 종류에 따라(예를 들어, Application에서 필요에 따라 Heap을 따로 관리하는 경우가 많은 데, default Heap 외에 crt heap이나 odbc나 oledb가 사용하는 mpheap 등이 존재할 수 있다.) 어느정도의 memory가 reserved 되어 있는 지, commit 되어 있는 지 보고해주며, 또한 Heap block에 따른 Address 정보나 세부적으로 memory의 할당여부를 확인할 수 있다. 이러한 Heap block의 정보를 가지고 의심이 나는 Heap 영역에 어떤 Data가 존재하는 지를 Memory Dump에서 Windbg를 통해 확인해 볼 수 있기 때문에 유용하다.
Application에 문제가 발생하여 오류나 Exception정보가 Popup되는 경우도 있지만, 그렇지 않고 Process가 죽어버리는 경우도 생각보다 많다. 아래의 Crash dump의 경우는 2nd chance exception 이 발생하지 않고 오류로 인한 Kernel32!ExitProcess가 호출되는 경우인데, 이러한 경우가 대표적으로 Exception 이 중간에 씹힌 경우라고 하겠다. ChildEBP RetAddr Args to Child 0f28e810 7928b829 80131506 0f28ec94 0f28eca4 KERNEL32!ExitProcess 0f28e820 7928b8f8 0e1a9420 00000040 0f28e920 mscorsvr!FailFast+0x114 0f28e834 7c006f08 0f28e85c 00000000 0f28e85c mscorsvr!FailFast+0x198 0f28e85c 77fbb272 0f28e920 0f28ec94 0f28e934 msvcr70!_except_handler3+0x4b 0f28e880 77facc28 0f28e920 0f28ec94 0f28e934 NTDLL!ExecuteHandler+0x26 0f28e908 77f91bc6 0f28e920 0f28e934 0f28e920 NTDLL!RtlDispatchException+0x76 0f28e908 77e7bcb1 0f28e920 0f28e934 0f28e920 NTDLL!KiUserExceptionDispatcher+0xe 0f28ec58 7928b8ce e0434f4d 00000001 00000000 KERNEL32!RaiseException+0x56 0f28eca4 7928b969 00000040 003611ec 79230fcd mscorsvr!FailFast+0x16e 0f28ecb0 79230fcd 00000001 0f28edd0 0f28f128 mscorsvr!FatalInternalError+0xd 0f28ecd4 791cf1ad 0f28edd0 0f28f128 0f28edec mscorsvr!GetPrevSEHRecord+0x65d 0f28ecec 7928f651 0f28edd0 0f28f128 0f28edec mscorsvr!COMPlusFrameHandler+0x41 0f28ed0c 77fbb272 0f28edd0 0f28f128 0f28edec mscorsvr!ContextTransitionFrameHandler+0x35 0f28ed30 77facc28 0f28edd0 0f28f128 0f28edec NTDLL!ExecuteHandler+0x26 0f28edb8 77f91bc6 0f28edd0 0f28edec 0f28edd0 NTDLL!RtlDispatchException+0x76 0f28edb8 792d4797 0f28edd0 0f28edec 0f28edd0 NTDLL!KiUserExceptionDispatcher+0xe 0f28f0d4 792d463d 0e1a9420 0f28f1c8 0018c4b8 mscorsvr!ComCallMLStubCache::CompileMLStub+0x5d7 0f28f114 791d6920 0f28f178 791f1c58 0f28f1c8 mscorsvr!ComCallMLStubCache::CompileMLStub+0x1af 0f28f15c 792d4755 0018c4b8 792d4605 0f28f178 mscorsvr!Thread::DoADCallBack+0x5c 0f28f18c 003ba1fd 0e1a9420 0f28f1c8 0f28f1f4 mscorsvr!ComCallMLStubCache::CompileMLStub+0x595 경험 많은 사람이라면 해당 Dump는 mscorsvr!ComCallMLStubCache::CompileMLStub에서부터 문제가 발생했을 것이라고 예상한다. 그 이후에 Callstack은 Exception이 handling되고 씹히고 FailFast 되고 … 하지만, 사실 여기서 주목해야 하는 부분은 NTDLL!KiUserExceptionDispatcher 이다. 이는 Exception Context정보를 가지고 있기 때문인데, 2nd parameter가 바로 그것이다. 상위의 Callstack은 2개의 NTDLL!KiUserExceptionDispatcher이 존재한다. 0:018> dc 0f28e934 l2 0f28e934 0001003f 00000000 ?....... 1003f 는 Context Structure의 ContextFlags 이므로 Context structure임을 확인할 수 있다. 그리고, 해당 Context로 이동하면 CLR Exception이 발생했음을 알 수 있다. 0:018> .cxr 0f28e934 eax=0f28ec08 ebx=0e1a9420 ecx=00000000 edx=003611ec esi=00000000 edi=0f28edec eip=77e7bcb1 esp=0f28ec00 ebp=0f28ec58 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 KERNEL32!RaiseException+0x56: 77e7bcb1 5f pop edi 0:018> kb ChildEBP RetAddr Args to Child 0f28ec58 7928b8ce e0434f4d 00000001 00000000 KERNEL32!RaiseException+0x56 0f28eca4 7928b969 00000040 003611ec 79230fcd mscorsvr!FailFast+0x16e 0f28ecb0 79230fcd 00000001 0f28edd0 0f28f128 mscorsvr!FatalInternalError+0xd 0f28ecd4 791cf1ad 0f28edd0 0f28f128 0f28edec mscorsvr!GetPrevSEHRecord+0x65d 0f28ecec 7928f651 0f28edd0 0f28f128 0f28edec mscorsvr!COMPlusFrameHandler+0x41 0f28ed0c 77fbb272 0f28edd0 0f28f128 0f28edec mscorsvr!ContextTransitionFrameHandler+0x35 0f28ed30 77facc28 0f28edd0 0f28f128 0f28edec NTDLL!ExecuteHandler+0x26 0f28edb8 77f91bc6 0f28edd0 0f28edec 0f28edd0 NTDLL!RtlDispatchException+0x76 0f28edb8 792d4797 0f28edd0 0f28edec 0f28edd0 NTDLL!KiUserExceptionDispatcher+0xe 0f28f0d4 792d463d 0e1a9420 0f28f1c8 0018c4b8 mscorsvr!ComCallMLStubCache::CompileMLStub+0x5d7 0f28f114 791d6920 0f28f178 791f1c58 0f28f1c8 mscorsvr!ComCallMLStubCache::CompileMLStub+0x1af 0f28f15c 792d4755 0018c4b8 792d4605 0f28f178 mscorsvr!Thread::DoADCallBack+0x5c 0f28f18c 003ba1fd 0e1a9420 0f28f1c8 0f28f1f4 mscorsvr!ComCallMLStubCache::CompileMLStub+0x595 WARNING: Frame IP not in any known module. Following frames may be wrong. 0f28f204 6b3ae86a 00000000 11cf8e78 11cf8e7c 0x3ba1fd 0f28f25c 6b3b010e 00000000 00000001 00138750 comsvcs!CJITActivation::DeactivateAndReleaseObject+0x86 0f28f2bc 7cf573f0 11cf8e7c 0e14f3a8 0e14f3a8 comsvcs!CJITActivation::DestroyStub+0x3be 0f28f2d8 7cf5db7e 0e14f3a8 00000000 0e1ffef0 OLE32!CObjectContext::DestroyIdentity+0x47 0f28f2ec 7cf51d0e 00001000 0e1eab48 0f28f388 OLE32!CIDObject::SetZombie+0x78 0f28f308 7cf51c55 00000000 7cf52804 0e1ffef0 OLE32!CStdWrapper::PrepareForDestruction+0xb4 0f28f310 7cf52804 0e1ffef0 7cf037a0 00000000 OLE32!PrepareWrapperForDestruction+0xb 그리고, 다시 0:018> dc 0f28edec l2 0f28edec 0001003f 00000000 ?....... 0:018> .cxr 0f28edec eax=00000000 ebx=00000000 ecx=00000007 edx=0017aadc esi=0dc2ee80 edi=01309780 eip=792d4797 esp=0f28f0b8 ebp=0f28f0d4 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00010202 mscorsvr!ComCallMLStubCache::CompileMLStub+0x5d7: 792d4797 8b00 mov eax,dword ptr [eax] ds:0023:00000000=???????? 보통, .Frame 10 해서 해당 line으로 가서 확인하는 것과 해당 Context로 이동해서 확인하는 것은 차이가 있다. Context로 이동한다는 것은 해당 문제가 발생한 시점의 Register 정보 등을 확인할 수 있지만 line으로 이동하면 local variables나 parameters정보외에는 ... 부족하다. 그리고, kv command를 사용하여 Callstack을 확인하는 것에 익숙해지는 것이 좋다. Kv command는 Context 정보, Calling Conv 및 FPO 정보 등을 표시해 주기 때문에 유용하다. # ChildEBP RetAddr Args to Child 00 0f28e810 7928b829 80131506 0f28ec94 0f28eca4 KERNEL32!ExitProcess (FPO: [Non-Fpo]) 01 0f28e820 7928b8f8 0e1a9420 00000040 0f28e920 mscorsvr!FailFast+0x114 (FPO: [Non-Fpo]) 02 0f28e834 7c006f08 0f28e85c 00000000 0f28e85c mscorsvr!FailFast+0x198 (FPO: [Non-Fpo]) 03 0f28e85c 77fbb272 0f28e920 0f28ec94 0f28e934 msvcr70!_except_handler3+0x4b (FPO: [Uses EBP] [3,0,7]) (CONV: cdecl) 04 0f28e880 77facc28 0f28e920 0f28ec94 0f28e934 NTDLL!ExecuteHandler+0x26 05 0f28e908 77f91bc6 0f28e920 0f28e934 0f28e920 NTDLL!RtlDispatchException+0x76 (FPO: [Non-Fpo]) 06 0f28e908 77e7bcb1 0f28e920 0f28e934 0f28e920 NTDLL!KiUserExceptionDispatcher+0xe (FPO: [2,0,0]) (CONTEXT @ 0f28e934) 07 0f28ec58 7928b8ce e0434f4d 00000001 00000000 KERNEL32!RaiseException+0x56 (FPO: [Non-Fpo]) 08 0f28eca4 7928b969 00000040 003611ec 79230fcd mscorsvr!FailFast+0x16e (FPO: [Non-Fpo]) 09 0f28ecb0 79230fcd 00000001 0f28edd0 0f28f128 mscorsvr!FatalInternalError+0xd (FPO: [0,0,0]) 0a 0f28ecd4 791cf1ad 0f28edd0 0f28f128 0f28edec mscorsvr!GetPrevSEHRecord+0x65d (FPO: [Non-Fpo]) 0b 0f28ecec 7928f651 0f28edd0 0f28f128 0f28edec mscorsvr!COMPlusFrameHandler+0x41 (FPO: [Non-Fpo]) 0c 0f28ed0c 77fbb272 0f28edd0 0f28f128 0f28edec mscorsvr!ContextTransitionFrameHandler+0x35 (FPO: [Non-Fpo]) 0d 0f28ed30 77facc28 0f28edd0 0f28f128 0f28edec NTDLL!ExecuteHandler+0x26 0e 0f28edb8 77f91bc6 0f28edd0 0f28edec 0f28edd0 NTDLL!RtlDispatchException+0x76 (FPO: [Non-Fpo]) 0f 0f28edb8 792d4797 0f28edd0 0f28edec 0f28edd0 NTDLL!KiUserExceptionDispatcher+0xe (FPO: [2,0,0]) (CONTEXT @ 0f28edec) 10 0f28f0d4 792d463d 0e1a9420 0f28f1c8 0018c4b8 mscorsvr!ComCallMLStubCache::CompileMLStub+0x5d7 (FPO: [Non-Fpo]) 11 0f28f114 791d6920 0f28f178 791f1c58 0f28f1c8 mscorsvr!ComCallMLStubCache::CompileMLStub+0x1af (FPO: [Non-Fpo]) 12 0f28f15c 792d4755 0018c4b8 792d4605 0f28f178 mscorsvr!Thread::DoADCallBack+0x5c (FPO: [Non-Fpo]) 13 0f28f18c 003ba1fd 0e1a9420 0f28f1c8 0f28f1f4 mscorsvr!ComCallMLStubCache::CompileMLStub+0x595 (FPO: [Non-Fpo])
|
카테고리
이글루링크
최근 등록된 덧글
그러세요
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 이글루 파인더
| |||||||||