성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>windbg 분석 사례 - cpu 100% 현상 (1)</h1> <p> CPU High 현상이 발생했습니다. 테스트 응용 프로그램 중 불규칙하게 재현되는 것이라서 CPU High 현상이 나온 EXE 프로세스가 살아 있는 동안 최대한 정보를 수집했습니다.<br /> <br /> 우선, Process Explorer를 통해 Thread ID == 7824가 (아마도) 무한 루프에 빠져 CPU를 점유하는 현상이 보이는 것을 확인했습니다. (2vCPU였으므로 50%면 무한 루프!)<br /> <br /> <img alt='windbg_cpu_high_1.png' src='/SysWebRes/bbs/windbg_cpu_high_1.png' /><br /> <br /> 이제 작업 관리자를 이용해 해당 프로세스의 풀 덤프를 뜨고, "C:\Users\[계정]\AppData\Local\Temp\...dmp"에 저장된 덤프 파일을 간단하게 <a target='tab' href='http://www.microsoft.com/en-us/download/details.aspx?id=42933'>Debug Diagnostic Tool v2 Update 1</a> 도구를 이용해 분석해 보면 다음과 같이 Top 5 스레드 시간이 출력되어 단독범 소행임을 알 수 있습니다. (windbg에서는 !runaway 명령어로 CPU 소비 시간을 알 수 있습니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Top 5 Threads by CPU time Note - Times include both user mode and kernel mode for each thread Thread ID: 24 <span style='color: blue; font-weight: bold'>Total CPU Time: 2 day(s) 09:41:55.936</span> Entry Point for Thread: mscorwks!Thread::intermediateThreadProc Thread ID: 15 Total CPU Time: 00:01:39.593 Entry Point for Thread: mscorwks!Thread::intermediateThreadProc Thread ID: 9 Total CPU Time: 00:00:20.937 Entry Point for Thread: mscorwks!Thread::intermediateThreadProc Thread ID: 7 Total CPU Time: 00:00:16.436 Entry Point for Thread: mscorwks!Thread::intermediateThreadProc Thread ID: 8 Total CPU Time: 00:00:13.999 Entry Point for Thread: mscorwks!Thread::intermediateThreadProc </pre> <br /> Debug Diagnostic Tool 도구가 생성하는 리포트 웹 페이지에는 위에서 "Thread ID: 24"는 링크로 제공됩니다. 따라서 그것을 클릭하면 상세 정보로 넘어가는데요. 대충 아래와 같이 나왔습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Thread 24 - System ID 7824 Entry point mscorwks!Thread::intermediateThreadProc Create time 2014-12-17 오전 12:40:30 Time spent in user mode 2 Days 08:34:37.515 Time spent in kernel mode 0 Days 01:07:18.421 This thread is not fully resolved and may or may not be a problem. Further analysis of these threads may be required. .NET Call Stack Function <span style='color: blue; font-weight: bold'>MyApp.util.IntBoundSet.put(Int32)+1c8</span> MyApp.AgentText.addServiceName(Int32, System.String)+32 System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(System.Runtime.Remoting.Channels.IServerChannelSinkStack, System.Runtime.Remoting.Messaging.IMessage, System.Runtime.Remoting.Channels.ITransportHeaders, System.IO.Stream, System.Runtime.Remoting.Messaging.IMessage ByRef, System.Runtime.Remoting.Channels.ITransportHeaders ByRef, System.IO.Stream ByRef)+8a2 System.Runtime.Remoting.Channels.Ipc.IpcServerTransportSink.ServiceRequest(System.Object)+176 System.Runtime.Remoting.Channels.SocketHandler.ProcessRequestNow()+34 System.Runtime.Remoting.Channels.RequestQueue.ProcessNextRequest(System.Runtime.Remoting.Channels.SocketHandler)+19 System.Runtime.Remoting.Channels.SocketHandler.BeginReadMessageCallback(System.IAsyncResult)+b8 System.Runtime.Remoting.Channels.Ipc.IpcPort.AsyncFSCallback(UInt32, UInt32, System.Threading.NativeOverlapped*)+50 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32, UInt32, System.Threading.NativeOverlapped*)+7c </pre> <br /> 보시는 바와 같이 IntBoundSet 클래스의 put 메서드를 실행 중에 있습니다. 사실 이 정보만으로는 cpu high 현상이 왜 발생하는지 알 수 없습니다. 그래서 <a target='tab' href='http://www.microsoft.com/en-us/download/details.aspx?id=42933'>Debug Diagnostic Tool v2 Update 1</a> 도구에서는 덤프를 여러 개 떠서 비교 분석하는 방법을 제공하는데요. 그렇긴 해도 역시나 정확한 반복 영역이나 왜 그런 현상이 발생했는지는 알 수 없습니다. 반복 덤프된 것을 통해 IntBoundSet.put 메서드가 항상 상위에 있다는 점을 통해 put 메서드 내부에서만 무한 루프가 있을 수 있다는 (100% 확실이 아닌) 가능성을 확인하는 정도에 불과합니다.<br /> <br /> 여기까지만 확인하고 (현실에서는 불가능한 경우가 많지만, 시간 관계상) 이제 실행 중인 EXE 프로세스에 windbg로 attach를 시켜서 확인해 봤습니다. SOS를 로드하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:172> <span style='color: blue; font-weight: bold'>.loadby sos mscorwks</span> </pre> <br /> 문제가 있는 스레드의 ID(0x1e90 == 7824)를 확인하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:172> <span style='color: blue; font-weight: bold'>!threads</span> ThreadCount: 169 UnstartedThread: 0 BackgroundThread: 168 PendingThread: 0 DeadThread: 0 Hosted Runtime: no PreEmptive GC Alloc Lock ID OSID ThreadOBJ State GC Context Domain Count APT Exception 0 1 14d4 00664a78 a020 Enabled 00000000:00000000 005e6f08 0 MTA ...[생략]... <span style='color: blue; font-weight: bold'>24 17 1e90 05841320 880b220 Disabled 00000000:00000000 005e6f08 1 MTA (Threadpool Completion Port)</span> ...[생략]... 170 a9 1348 05b4fba0 880b220 Enabled 0169eb58:016a09fc 005e6f08 0 MTA (Threadpool Completion Port) </pre> <br /> 문맥 전환을 한 다음,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:172> <span style='color: blue; font-weight: bold'>~24s</span> eax=00000000 ebx=00000000 ecx=00009c47 edx=00000000 esi=014e5f3c edi=07cbecd0 eip=0508a92a esp=07cbec94 ebp=07cbece0 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 0508a92a 0f95c0 setne al </pre> <br /> 콜스택으로 해당 스레드가 맞는지 확인합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:024> <span style='color: blue; font-weight: bold'>!clrstack</span> OS Thread Id: 0x1e90 (24) ESP EIP <span style='color: blue; font-weight: bold'>07cbec94 0508a92a MyApp.util.IntBoundSet.put(Int32)</span> 07cbece8 05068042 MyApp.AgentText.addServiceName(Int32, System.String) 07cbeeec 04afc9ea System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(System.Runtime.Remoting.Channels.IServerChannelSinkStack, System.Runtime.Remoting.Messaging.IMessage, System.Runtime.Remoting.Channels.ITransportHeaders, System.IO.Stream, System.Runtime.Remoting.Messaging.IMessage ByRef, System.Runtime.Remoting.Channels.ITransportHeaders ByRef, System.IO.Stream ByRef) 07cbef90 050ec3fe System.Runtime.Remoting.Channels.Ipc.IpcServerTransportSink.ServiceRequest(System.Object) 07cbefec 04afa8dc System.Runtime.Remoting.Channels.SocketHandler.ProcessRequestNow() 07cbf01c 04afa7b1 System.Runtime.Remoting.Channels.RequestQueue.ProcessNextRequest(System.Runtime.Remoting.Channels.SocketHandler) 07cbf024 04afa3c0 System.Runtime.Remoting.Channels.SocketHandler.BeginReadMessageCallback(System.IAsyncResult) 07cbf054 050ec198 System.Runtime.Remoting.Channels.Ipc.IpcPort.AsyncFSCallback(UInt32, UInt32, System.Threading.NativeOverlapped*) 07cbf06c 04af7f54 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32, UInt32, System.Threading.NativeOverlapped*) 07cbf210 6e801b4c [GCFrame: 07cbf210] </pre> <br /> PDB 파일이 있는 경우, 소스 코드의 라인 정보까지 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:021> <span style='color: blue; font-weight: bold'>!clrstack</span> OS Thread Id: 0x1120 (21) Child SP IP Call Site 00000000204fe0f8 0000000076fb05fa [RedirectedThreadFrame: 00000000204fe0f8] 00000000204fe190 000007ff007e8882 MyApp.util.IntBoundSet.put(Int32) <span style='color: blue; font-weight: bold'>[d:\TestApp\IntBoundSet.cs @ 74]</span> 00000000204fe2a0 000007ff007fdd50 MyApp.AgentText.addServiceName(Int32, System.String) [d:\TestApp\AgentText.cs @ 76] 00000000204fe650 000007ff006bec3e System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(System.Runtime.Remoting.Channels.IServerChannelSinkStack, System.Runtime.Remoting.Messaging.IMessage, System.Runtime.Remoting.Channels.ITransportHeaders, System.IO.Stream, System.Runtime.Remoting.Messaging.IMessage ByRef, System.Runtime.Remoting.Channels.ITransportHeaders ByRef, System.IO.Stream ByRef) 00000000204fe740 000007ff00a14303 System.Runtime.Remoting.Channels.Ipc.IpcServerTransportSink.ServiceRequest(System.Object) 00000000204fe840 000007ff006bbf93 System.Runtime.Remoting.Channels.SocketHandler.ProcessRequestNow() 00000000204fe880 000007ff006bb66e System.Runtime.Remoting.Channels.SocketHandler.BeginReadMessageCallback(System.IAsyncResult) 00000000204fe8e0 000007ff006b7226 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32, UInt32, System.Threading.NativeOverlapped*) 00000000204feb80 000007fef8d9c9e4 [GCFrame: 00000000204feb80] 00000000204fed50 000007fef8d9c9e4 [DebuggerU2MCatchHandlerFrame: 00000000204fed50] </pre> <br /> 이처럼 PDB 파일이 있으면 디버깅이 매우 쉽습니다. F10/F11 키를 눌러 Step Over/Step Into 기능으로 기계어 코드를 해당 스레드에서 한 단계씩 실행하면서 소스 코드 위치를 확인하면 어떤 코드에서 무한 루프가 형성되는지 알 수 있습니다. 좀 더 문제를 자세하게 분석해 볼까요? ^^ 이제 put 메서드의 MethodDesc 식별자와 기계어로 번역된 어셈블리의 주소를 알아내고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:024> <span style='color: blue; font-weight: bold'>!name2ee MyApp!MyApp.util.IntBoundSet.put</span> Module: 04a5f3dc (MyApp.dll) Token: 0x06001241 <span style='color: blue; font-weight: bold'>MethodDesc: 04ccbe74</span> Name: MyApp.util.IntBoundSet.put(Int32) <span style='color: blue; font-weight: bold'>JITTED Code Address: 0508a788</span> </pre> <br /> 이를 통해 IL 코드 정보를 얻을 수 있지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:024> <span style='color: blue; font-weight: bold'>!dumpil 04ccbe74</span> ilAddr = 6ba5f404 IL_0000: nop IL_0001: ldarg.0 IL_0002: dup IL_0003: stloc.s VAR OR ARG 5 IL_0005: call System.Threading.Monitor::Enter IL_000a: nop .try { IL_000b: nop IL_000c: ldarg.0 IL_000d: ldfld MyApp.util.IntBoundSet::table IL_0012: stloc.0 ...[생략]... IL_01a7: add IL_01a8: stfld MyApp.util.IntBoundSet::count IL_01ad: ldc.i4.1 IL_01ae: stloc.s VAR OR ARG 4 IL_01b0: leave.s IL_01bb } // end .try .finally { IL_01b2: ldloc.s VAR OR ARG 5 IL_01b4: call System.Threading.Monitor::Exit IL_01b9: nop IL_01ba: endfinally } // end .finally IL_01bb: nop IL_01bc: ldloc.s VAR OR ARG 4 IL_01be: ret </pre> <br /> 사실 IL 코드 정보가 그다지 도움이 되진 않습니다. 왜냐하면 실행은 기계어를 바탕으로 하기 때문에 IL 코드는 소스코드를 가지고 있지 않을 때 문제 분석을 위해 필요한 정도일 뿐입니다. (물론, 소스코드가 없을 때는 유용합니다.) 중요한 것은 기계어 정보인데, "JITTED Code Address: 0508a788" 값을 이용해 다음과 같이 구할 수 있습니다. <br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:024> <span style='color: blue; font-weight: bold'>!U 0508a788</span> Normal JIT generated code MyApp.util.IntBoundSet.put(Int32) Begin 0508a788, size 353 >>> 0508a788 55 push ebp 0508a789 8bec mov ebp,esp 0508a78b 57 push edi 0508a78c 56 push esi 0508a78d 53 push ebx 0508a78e 83ec40 sub esp,40h 0508a791 8bf1 mov esi,ecx 0508a793 8d7db8 lea edi,[ebp-48h] 0508a796 b90e000000 mov ecx,0Eh 0508a79b 33c0 xor eax,eax 0508a79d f3ab rep stos dword ptr es:[edi] 0508a79f 8bce mov ecx,esi 0508a7a1 33c0 xor eax,eax 0508a7a3 8945e8 mov dword ptr [ebp-18h],eax 0508a7a6 894dcc mov dword ptr [ebp-34h],ecx 0508a7a9 8955dc mov dword ptr [ebp-24h],edx 0508a7ac 833d94f5a50400 cmp dword ptr ds:[4A5F594h],0 0508a7b3 7405 je 0508a7ba 0508a7b5 e8cf609f69 call mscorwks!JIT_DbgIsJustMyCode (6ea80889) 0508a7ba 33d2 xor edx,edx 0508a7bc 8955c4 mov dword ptr [ebp-3Ch],edx 0508a7bf c745d000000000 mov dword ptr [ebp-30h],0 0508a7c6 33d2 xor edx,edx 0508a7c8 8955b8 mov dword ptr [ebp-48h],edx 0508a7cb 33d2 xor edx,edx 0508a7cd 8955c8 mov dword ptr [ebp-38h],edx 0508a7d0 33d2 xor edx,edx 0508a7d2 8955d8 mov dword ptr [ebp-28h],edx 0508a7d5 33d2 xor edx,edx 0508a7d7 8955c0 mov dword ptr [ebp-40h],edx 0508a7da 33d2 xor edx,edx 0508a7dc 8955bc mov dword ptr [ebp-44h],edx 0508a7df c745d400000000 mov dword ptr [ebp-2Ch],0 0508a7e6 90 nop 0508a7e7 8b45cc mov eax,dword ptr [ebp-34h] 0508a7ea 8945bc mov dword ptr [ebp-44h],eax 0508a7ed 8b4dbc mov ecx,dword ptr [ebp-44h] 0508a7f0 e8c0837769 call mscorwks!JIT_MonEnterWorker (6e802bb5) 0508a7f5 90 nop 0508a7f6 90 nop 0508a7f7 8b45cc mov eax,dword ptr [ebp-34h] 0508a7fa 8b4004 mov eax,dword ptr [eax+4] 0508a7fd 8945c8 mov dword ptr [ebp-38h],eax 0508a800 90 nop 0508a801 e938010000 jmp 0508a93e ...[생략]... </pre> <br /> 무한 루프이기 때문에 F10/F11키를 누르면서 !U 출력 결과와 함께 비교하며 실행하다 보면 무한 반복이 시작되는 코드 지점을 그리 어렵지 않게 찾아낼 수 있습니다. 이렇게 해서 일단 기계어 상에서 반복 루프 지점을 알아냈는데, 그렇다면 과연 소스 코드상에서는 어느 지점에 해당하는 것일까요?<br /> <br /> 제가 테스트 하던 당시의 MyApp.util.IntBoundSet.put 코드는 다음과 같습니다. (라인 수를 줄이기 위해 if 문 블록 코드를 가능한 없앴습니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public bool put(int key) { lock (this) { Entry[] tab = table; int index; Entry e, prev; while (count >= capacity) { index = (tail.key & 0x7fffffff) % tab.Length; e = tab[index]; prev = null; for (; e != null; e = e.hash_next) { if (e == tail) { if (prev != null) prev.hash_next = e.hash_next; else tab[index] = e.hash_next; if (tail.link_next == null) head = tail = null; else tail = tail.link_next; count--; e.hash_next = e.link_next = null; break; } prev = e; } } index = (key & 0x7fffffff) % tab.Length; for (e = tab[index]; e != null; e = e.hash_next) { if (e.key == key) return false; } e = new Entry(); e.key = key; e.hash_next = tab[index]; tab[index] = e; if (head == null) head = tail = e; else { head.link_next = e; head = e; } count++; return true; } } </pre> <br /> 어떤가요? 기계어 코드만 보면서 소스 코드의 위치를 알 수 있을까요? ^^ 어쨌든, 제 경우에는 기계어의 0508a806 지점이 최초 무한 루프의 시작지점이었습니다. 그럼, 거기부터 시작해 볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0508a806 90 nop 0508a807 8b45cc mov eax,dword ptr [ebp-34h] </pre> <br /> 우선 [ebp-34h]에 어떤 값이 들어있는지 확인해야 하는데요. 닷넷 프로그램이기 때문에 대개의 경우 !dumpobject 명령을 내려보면 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:024> <span style='color: blue; font-weight: bold'>!do [ebp-34h]</span> Name: MyApp.util.IntBoundSet MethodTable: 04ccbea4 EEClass: 04e57534 Size: 28(0x1c) bytes (C:\Windows\assembly\GAC_MSIL\MyApp\5.0.5.0__cc862a9be44088eb\MyApp.dll) Fields: MT Field Offset Type VT Attr Value Name 001b84e4 4000fc6 4 System.Object[] 0 instance 0257a518 table 005bedf0 4000fc7 10 System.Int32 1 instance 20000 count 005bedf0 4000fc8 14 System.Int32 1 instance 20000 capacity 04ccc030 4000fc9 8 ...IntBoundSet+Entry 0 instance 01538510 tail 04ccc030 4000fca c ...IntBoundSet+Entry 0 instance 015e70a8 head </pre> <br /> 이 명령이 좋은 것은, 해당 인스턴스의 Offset 위치에 대한 필드의 이름 뿐만 아니라 그것에 대한 메모리 상의 값(Value)도 알 수 있다는 점입니다. 어쨌든, [ebp-34h] 값은 MyApp.util.IntBoundSet이기 때문에 일단 this 포인터 값이라고 보면 됩니다. 정리해 보면 현재 eax 값에 this 포인터가 있는 것입니다. F10 키를 눌러 연 이은 기계어 코드를 실행하면서 분석을 계속해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0508a80a 8b4008 mov eax,dword ptr [eax+8] ; IntBoundSet.tail (0x01538510 == MyApp.util.IntBoundSet+Entry) </pre> <br /> eax에는 this포인터가 들어가 있고 거기에 +8 옵셋위치로 넘어갔으니 "!do [ebp-34h]"로 출력된 MyApp.util.IntBoundSet의 구조를 보면 "Offset" 컬럼의 값이 8에 해당하는 필드가 "tail"임을 알 수 있습니다. 따라서, 위의 코드는 eax 레지스터에 this.tail 값을 대입하게 됩니다. tail은 IntBoundSet 클래스의 내부 클래스로 이렇게 정의되어 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public sealed class IntBoundSet { //...[생략]... public sealed class Entry { public int key; public Entry link_next, hash_next; } //...[생략]... } </pre> <br /> 역시 !do 명령으로 분석해 보면 다음과 같이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:024> <span style='color: blue; font-weight: bold'>!do [eax+8]</span> Name: MyApp.util.IntBoundSet+Entry MethodTable: 04ccc030 EEClass: 04e576c8 Size: 20(0x14) bytes (C:\Windows\assembly\GAC_MSIL\MyApp\5.0.5.0__cc862a9be44088eb\MyApp.dll) Fields: MT Field Offset Type VT Attr Value Name 005bedf0 4000fcb c System.Int32 1 instance 494085374 key 04ccc030 4000fcc 4 ...IntBoundSet+Entry 0 instance 01538524 link_next 04ccc030 4000fcd 8 ...IntBoundSet+Entry 0 instance 00000000 hash_next </pre> <br /> 대충 어떤 식으로 분석하는 지 이제 감이 오시죠? ^^ 그리하여 다음의 코드는 쉽게 이해가 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0508a80d 8b400c mov eax,dword ptr [eax+0Ch] ; MyApp.util.IntBoundSet+Entry.key 0508a810 25ffffff7f and eax,7FFFFFFFh ; // (tail.key & 0x7fffffff) </pre> <br /> eax에 있는 값이 this.tail이고, 거기에 다시 offset 값으로 0c를 더했으니 "MyApp.util.IntBoundSet+Entry"의 구조에 보면 그것이 "key" 필드임을 알 수 있습니다. 결과적으로 eax에는 this.tail.key값이 들어갑니다. 그리곤 eax와 7FFFFFFFh 값으로 and 연산을 하니... 오호~~~ 이 정도만 해도 소스 코드에서 유사한 라인이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public bool put(int key) { lock (this) { Entry[] tab = table; int index; Entry e, prev; while (count >= capacity) { index = <span style='color: blue; font-weight: bold'>(tail.key & 0x7fffffff)</span> % tab.Length; e = tab[index]; prev = null; ...[생략]... } </pre> <br /> 대략 이런 식으로 분석하면 무한 루프의 끝 지점도 찾아낼 수 있고, 아울러 각 변수들의 값도 확인할 수 있으므로 도대체 어떤 상태에서 무한 루프가 발생하는 것인지 그 원인을 밝힐 수 있습니다.<br /> <br /> 그 외에 도움이 될 수 있는 것으로는 !clrstack에서 "-p" 옵션을 이용해 스레드 스택 프레임마다 전달된 인자의 값을 확인하는 방법이 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:024> <span style='color: blue; font-weight: bold'>!clrstack -p</span> OS Thread Id: 0x1e90 (24) ESP EIP 07cbec94 0508a821 MyApp.util.IntBoundSet.put(Int32) PARAMETERS: this = 0x014e4f30 key = 0x77b96530 <span style='color: blue; font-weight: bold'>07cbece8 05068042 MyApp.AgentText.addServiceName(Int32, System.String) PARAMETERS: hash = 0x77b96530 value = 0x015e7454</span> 07cbeeec 04afc9ea System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(System.Runtime.Remoting.Channels.IServerChannelSinkStack, System.Runtime.Remoting.Messaging.IMessage, System.Runtime.Remoting.Channels.ITransportHeaders, System.IO.Stream, System.Runtime.Remoting.Messaging.IMessage ByRef, System.Runtime.Remoting.Channels.ITransportHeaders ByRef, System.IO.Stream ByRef) 07cbef90 050ec3fe System.Runtime.Remoting.Channels.Ipc.IpcServerTransportSink.ServiceRequest(System.Object) PARAMETERS: this = 0x014d1250 state = <no data> 07cbefec 04afa8dc System.Runtime.Remoting.Channels.SocketHandler.ProcessRequestNow() PARAMETERS: this = 0x015b3acc 07cbf01c 04afa7b1 System.Runtime.Remoting.Channels.RequestQueue.ProcessNextRequest(System.Runtime.Remoting.Channels.SocketHandler) PARAMETERS: this = <no data> sh = <no data> 07cbf024 04afa3c0 System.Runtime.Remoting.Channels.SocketHandler.BeginReadMessageCallback(System.IAsyncResult) PARAMETERS: this = <no data> ar = <no data> 07cbf054 050ec198 System.Runtime.Remoting.Channels.Ipc.IpcPort.AsyncFSCallback(UInt32, UInt32, System.Threading.NativeOverlapped*) PARAMETERS: errorCode = <no data> numBytes = <no data> pOverlapped = <no data> 07cbf06c 04af7f54 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32, UInt32, System.Threading.NativeOverlapped*) PARAMETERS: errorCode = <no data> numBytes = <no data> pOVERLAP = <no data> 07cbf210 6e801b4c [GCFrame: 07cbf210] </pre> <br /> 위의 출력 결과에서 2번째 addServiceName 메서드에 전달된 인자는 this가 없는 것으로 봐서 static 메서드입니다. hash 인자는 int32 값이므로 더 검사할 필요가 없고, System.String 타입의 value(0x015e7454)는 역시 !dumpobject 명령어로 조사할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:024> <span style='color: blue; font-weight: bold'>!do 0x015e7454</span> Name: System.String MethodTable: 001b914c EEClass: 004b2824 Size: 110(0x6e) bytes (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll) <span style='color: blue; font-weight: bold'>String: ThisIsTestThisIsTestThisIsTestThisIsTestThis</span> Fields: MT Field Offset Type VT Attr Value Name 005bedf0 4000096 4 System.Int32 1 instance 47 m_arrayLength 005bedf0 4000097 8 System.Int32 1 instance <span style='color: blue; font-weight: bold'>44 m_stringLength</span> 001b9870 4000098 c System.Char 1 instance 54 m_firstChar 001b914c 4000099 10 System.String 0 shared static Empty >> Domain:Value 005e6f08:014b1700 << 005b4620 400009a 14 System.Char[] 0 shared static WhitespaceChars >> Domain:Value 005e6f08:014b1714 << </pre> <br /> 이 정도만 알고 계시면, 향후 cpu 100% 현상을 보이게 될 여러분의 EXE 프로세스를 가볍게 요리하실 수 있을 것입니다. ^^<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3669
(왼쪽의 숫자를 입력해야 합니다.)