성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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 - (x64에서의 인자 값 추적을 이용한) Thread.Abort 시 대상이 되는 스레드를 식별하는 방법</h1> <p> 예전 글이 생각나는군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Thread.Abort 호출의 hang 현상 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1617'>http://www.sysnet.pe.kr/2/0/1617</a> </pre> <br /> 최근에 저 현상을 또다시 겪었습니다. 메모리 덤프를 떠서 확인한 결과 Thread.Abort 호출이 반환되지 않는 경우였습니다. 이 글에서는 해당 메모리 덤프를 분석한 결과를 공유합니다. ^^<br /> <br /> 그나저나, Thread.Abort로 종료시키려고 했던 대상 스레드가 궁금했습니다. 우선 Thread.Abort를 호출한 코드의 콜 스택은 이랬습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:045> <span style='color: blue; font-weight: bold'>!clrstack</span> OS Thread Id: 0x18b4 (45) Child SP IP Call Site 0000000020dad5d8 000000007725c2ea [HelperMethodFrame_1OBJ: 0000000020dad5d8] System.Threading.Thread.AbortInternal() <span style='color: blue; font-weight: bold'>0000000020dad6f0</span> 000007ff0326a26a <span style='color: blue; font-weight: bold'>System.Threading.Thread.Abort()</span> 0000000020dad730 000007ff0326a196 WebApp.Library.CallStack.CancelThread() 0000000020dad790 000007ff027c7c7b WebApp.Library.CallStack.CheckThread(WebApp.Library.State, System.Action, Int32) 0000000020dad7e0 000007ff027c7b6f WebApp.Library.CallStack.CheckValue(Int32) 0000000020dad850 000007ff027bb59c WebApp.BaseLib.Dumper.monitorFunc() 0000000020dad8b0 000007ff025fa678 System.Threading.ExecutionContext.runTryCode(System.Object) 0000000020dadfd8 000007fef885c9e4 [HelperMethodFrame_PROTECTOBJ: 0000000020dadfd8] System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object) 0000000020dae100 000007ff007de3f6 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) 0000000020dae160 000007ff025f8fab System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) 0000000020dae1b0 000007ff026bbb3d System.Threading.ThreadHelper.ThreadStart() 0000000020dae608 000007fef885c9e4 [GCFrame: 0000000020dae608] 0000000020dae9f0 000007fef885c9e4 [DebuggerU2MCatchHandlerFrame: 0000000020dae9f0] 0000000020daebc8 000007fef885c9e4 [ContextTransitionFrame: 0000000020daebc8] 0000000020daedb0 000007fef885c9e4 [DebuggerU2MCatchHandlerFrame: 0000000020daedb0] </pre> <br /> Abort 메서드 호출이 다음과 같은 식이기 때문에,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Thread t1 = new Thread(threadFunc); // ...[생략]... t1.Abort(); </pre> <br /> (C# 코드 상으로는 안 보이는) Abort 메서드의 첫 번째 인자, 즉 x64의 경우 rcx에 들어온 인자가 t1 스레드를 가리키게 되지만, 아쉽게도 위의 호출 스택은 .NET Managed 호출 스택만 보여주기 때문에 rcx 인자 값을 살펴봐서는 안됩니다. 왜냐하면 실제로 다음과 같이 Native 함수까지 포함한 호출 스택을 보면 현재 CPU의 rcx 레지스터 값은 Abort 메서드를 호출한 시점의 rcx 값과는 전혀 무관한 값으로 채워져 있을 가능성이 크기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:045> <span style='color: blue; font-weight: bold'>kv</span> # Child-SP RetAddr : Args to Child : Call Site 00 00000000`20dacfa8 000007fe`fd1d1430 : 00000000`00000000 00000000`00000001 00000000`1aefe390 00000000`ffffffff : ntdll!NtWaitForMultipleObjects+0xa 01 00000000`20dacfb0 00000000`770116e3 : 00000000`20dad0e8 00000000`20dad0e0 00000000`00000000 00000000`00077bd0 : KERNELBASE!WaitForMultipleObjectsEx+0xe8 02 00000000`20dad0b0 000007fe`f89ef8e1 : 00000000`00000000 00000000`00000000 00000000`1aefd570 00000000`00000001 : kernel32!WaitForMultipleObjectsExImplementation+0xb3 03 00000000`20dad140 000007fe`f89ef6ba : 00000000`00000001 00000000`20dad448 00000000`00000001 00000000`20dad448 : clr!WaitForMultipleObjectsEx_SO_TOLERANT+0x91 04 00000000`20dad1d0 000007fe`f89ef545 : 00000000`00000000 00000000`20dad2b9 00000000`1aefd570 00000000`2027c040 : clr!Thread::DoAppropriateAptStateWait+0x56 05 00000000`20dad210 000007fe`f89ef63b : 00000000`1aefd570 00000000`00000001 00000000`20dad448 00000000`00000000 : clr!Thread::DoAppropriateWaitWorker+0x1b1 06 00000000`20dad310 000007fe`f896ff6a : 00000000`00000001 000007fe`00000001 00000000`00b3bf50 000007fe`f8978031 : clr!Thread::DoAppropriateWait+0x73 07 00000000`20dad390 000007fe`f8c1bae6 : 00000000`00000000 00000000`1aefe390 00000000`1aefe390 00000000`00000d14 : clr!Thread::JoinEx+0xa6 08 00000000`20dad430 000007fe`f8a29cc3 : 00000001`1fab1220 000007fe`00000000 00000000`00000001 00000000`00000000 : clr! ?? ::FNODOBFM::`string'+0xa2d89 09 00000000`20dad540 000007ff`0326a26a : 00000000`20dadae8 00000001`5fb259d8 00000000`00000000 00000000`00000000 : clr!ThreadNative::Abort+0x14b <span style='color: blue; font-weight: bold'>0a 00000000`20dad6f0 000007ff`0326a196 : 00000001`1fab1220 00000001`1fab0ca8 00000001`605cfa88 00000000`00000fa0 : 0x000007ff`0326a26a</span> 0b 00000000`20dad730 000007ff`027c7c7b : 00000001`1fab0c68 00000001`1fab0ca8 00000001`605cfa88 00000000`00000fa0 : 0x000007ff`0326a196 0c 00000000`20dad790 000007ff`027c7b6f : 00000001`1fab0c68 00000001`1fab0ca8 00000001`605cfa88 00000001`00000fa0 : 0x000007ff`027c7c7b 0d 00000000`20dad7e0 000007ff`027bb59c : 00000001`1fab0c68 00000000`00000fa0 00000000`20dad1d0 00000000`00000001 : 0x000007ff`027c7b6f 0e 00000000`20dad850 000007ff`025fa678 : 00000001`1fab07c8 00000001`1fab0960 00000000`00000000 00000000`00000000 : 0x000007ff`027bb59c 0f 00000000`20dad8b0 000007fe`f885c9e4 : 00000001`2fc130d0 00000001`5fb259d8 00000000`00000000 00000000`00000000 : 0x000007ff`025fa678 ..[생략]... 28 00000000`20dafa00 00000000`7723a561 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0xd 29 00000000`20dafa30 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x1d </pre> <br /> 어쨌든 중요한 것은 System.Threading.Thread.Abort가 호출된 시점의 rcx 값을 알아내야 한다는 것입니다.<br /> <br /> 가만 보니, clr!ThreadNative::Abort 아래에 있는 항목(Call Site: 0x000007ff`0326a26a)을 보면 Child-SP 값이 00000000`20dad6f0라고 나오고 이 값은 !clrstack의 System.Threading.Thread.Abort와 일치합니다. 그럼, 일단 Abort 메서드부터 시작해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:048> <span style='color: blue; font-weight: bold'>!U /d 000007ff01c5d7fa</span> Normal JIT generated code System.Threading.Thread.Abort() Begin 000007ff01c5d7d0, size 31 000007ff`01c5d7d0 53 push rbx 000007ff`01c5d7d1 4883ec30 sub rsp,30h 000007ff`01c5d7d5 488bd9 <span style='color: blue; font-weight: bold'>mov rbx,rcx</span> 000007ff`01c5d7d8 48c744242000000000 mov qword ptr [rsp+20h],0 000007ff`01c5d7e1 488d542420 lea rdx,[rsp+20h] 000007ff`01c5d7e6 488d0db33559fe lea rcx,[000007ff`001f0da0] 000007ff`01c5d7ed e8de8105f8 call clr!JIT_Security_Prolog (000007fe`f9cb59d0) 000007ff`01c5d7f2 488bcb <span style='color: blue; font-weight: bold'>mov rcx,rbx</span> 000007ff`01c5d7f5 e87ec321f8 <span style='color: blue; font-weight: bold'>call clr!ThreadNative::Abort (000007fe`f9e79b78)</span> >>> 000007ff`01c5d7fa 90 nop 000007ff`01c5d7fb 4883c430 add rsp,30h 000007ff`01c5d7ff 5b pop rbx 000007ff`01c5d800 c3 ret </pre> <br /> System.Threading.Thread.Abort의 부모 메서드가 전달한 첫 번째 인자(rcx)를 잠시 rbx에 보관 후 다시 rcx를 이용해 clr!ThreadNative::Abort에 전달하고 있습니다. 즉, 위의 머신 코드에서는 첫 번째 인자에 대한 값이 Thread Stack에 전혀 남아 있지 않습니다. 그렇다면 이쯤에서 우리는 선택을 해야 합니다. 이후의 분석을 rcx를 전달한 부모 쪽으로 가야 할지, rcx를 넘기며 호출한 자식 쪽으로 가야 할지에 대해서입니다.<br /> <br /> 달리 말하면, 부모 호출 쪽으로 가게 되면 managed 코드이고, 자식 호출 쪽으로 가게 되면 native가 되니 당연히 분석의 난이도를 봤을 때 선택은 부모 쪽이 낫습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:048> <span style='color: blue; font-weight: bold'>!U /d 000007ff01c5d726</span> Normal JIT generated code <span style='color: blue; font-weight: bold'>WebApp.Library.CallStack.CancelThread()</span> Begin 000007ff01c5d6e0, size bd 000007ff`01c5d6e0 48894c2408 <span style='color: blue; font-weight: bold'>mov qword ptr [rsp+8],rcx</span> 000007ff`01c5d6e5 55 push rbp 000007ff`01c5d6e6 4883ec50 <span style='color: blue; font-weight: bold'>sub rsp,50h</span> 000007ff`01c5d6ea 488d6c2420 <span style='color: blue; font-weight: bold'>lea rbp,[rsp+20h]</span> 000007ff`01c5d6ef 48896500 mov qword ptr [rbp],rsp 000007ff`01c5d6f3 48b830597600ff070000 mov rax,7FF00765930h 000007ff`01c5d6fd 8b00 mov eax,dword ptr [rax] 000007ff`01c5d6ff 85c0 test eax,eax 000007ff`01c5d701 7405 je 000007ff`01c5d708 000007ff`01c5d703 e858ce47f8 call clr!JIT_DbgIsJustMyCode (000007fe`fa0da560) 000007ff`01c5d708 90 nop 000007ff`01c5d709 90 nop 000007ff`01c5d70a 488b4540 mov rax,qword ptr [rbp+40h] 000007ff`01c5d70e 488b4028 mov rax,qword ptr [rax+28h] 000007ff`01c5d712 48894508 mov qword ptr [rbp+8],rax 000007ff`01c5d716 488b4508 mov rax,qword ptr [rbp+8] 000007ff`01c5d71a 803800 cmp byte ptr [rax],0 000007ff`01c5d71d 488b4d08 <span style='color: blue; font-weight: bold'>mov rcx,qword ptr [rbp+8]</span> 000007ff`01c5d721 e8224658fe call 000007ff`001e1d48 (System.Threading.Thread.Abort(), mdToken: 0000004BCC77C220) >>> 000007ff`01c5d726 90 nop 000007ff`01c5d727 90 nop 000007ff`01c5d728 eb00 jmp 000007ff`01c5d72a 000007ff`01c5d72a 488b4540 mov rax,qword ptr [rbp+40h] 000007ff`01c5d72e 488b4030 mov rax,qword ptr [rax+30h] </pre> <br /> 오~~~ 보는 바와 같이 부모 쪽 닷넷 메서드의 JIT 컴파일 결과에서는 rcx에 전달한 스레드 인스턴스를 스레드 스택(rbp + 8)에 보관하고 있습니다. 그럼 게임 끝났군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > windbg - x64 SOS 확장의 !clrstack 명령어가 출력하는 Child SP 값의 의미 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11349'>http://www.sysnet.pe.kr/2/0/11349</a> </pre> <br /> 위의 글에 따라 clrstack 명령에서 구했던 부모 메서드의 Child SP 값에서 시작할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Child SP IP Call Site <span style='color: blue; font-weight: bold'>0000000020dad730</span> 000007ff0326a196 WebApp.Library.CallStack.CancelThread() </pre> <br /> 0000000020dad730 값은 프롤로그 코드를 지난, 즉 sub rsp, 50h가 반영된 값입니다. 그렇다면 "lea rbp, rsp + 20h"의 값을 금방 계산할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > rbp = 0000000020dad730 + 20h = 0000000020dad750h </pre> <br /> 그리고 그 rbp 위치의 +8에 위치한 값을 !do 명령어로 덤프하면 그것이 바로 Thread.Abort가 종료하려는 스레드가 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:045> <span style='color: blue; font-weight: bold'>dq 0000000020dad750 + 8 L1</span> 00000000`20dad758 <span style='color: blue; font-weight: bold'>00000001`1fab1220</span> 0:045> <span style='color: blue; font-weight: bold'>!do 00000001`1fab1220</span> Name: System.Threading.Thread MethodTable: 000007ff001e1780 EEClass: 000007ff001c9cc8 Size: 88(0x58) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields: MT Field Offset Type VT Attr Value Name 000007ff00252860 400079b 8 ....Contexts.Context 0 instance 000000015fa91238 m_Context 000007ff003f0930 400079c 10 ....ExecutionContext 0 instance 000000011fab1518 m_ExecutionContext 000007ff001ad478 400079d 18 System.String 0 instance <span style='color: blue; font-weight: bold'>000000011fab1458 m_Name</span> 000007ff001e0530 400079e 20 System.Delegate 0 instance 0000000000000000 m_Delegate 000007ff001f5780 400079f 28 ...ation.CultureInfo 0 instance 0000000000000000 m_CurrentCulture 000007ff001f5780 40007a0 30 ...ation.CultureInfo 0 instance 0000000000000000 m_CurrentUICulture 000007ff001a57f8 40007a1 38 System.Object 0 instance 0000000000000000 m_ThreadStartArg 000007ff00259f70 40007a2 40 System.IntPtr 1 instance 1aefe390 DONT_USE_InternalThread 000007ff001f3518 40007a3 48 System.Int32 1 instance 2 m_Priority 000007ff001f3518 40007a4 4c System.Int32 1 instance <span style='color: blue; font-weight: bold'>27 m_ManagedThreadId</span> 000007ff006273f8 40007a5 318 ...LocalDataStoreMgr 0 shared static s_LocalDataStoreMgr >> Domain:Value 0000000002837030:NotInit 00000000186d9260:NotInit << 000007ff01dfab40 40007a6 10 ...alDataStoreHolder 0 shared TLstatic s_LocalDataStore >> Thread:Value << </pre> <br /> 게다가 운이 좋군요. m_Name에 값이 있으니 다음과 같이 스레드 객체에 할당했던 이름을 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:045> <span style='color: blue; font-weight: bold'>!DumpObj /d 000000011fab1458</span> Name: System.String MethodTable: 000007ff001ad478 EEClass: 000007ff001c8588 Size: 92(0x5c) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll String: <span style='color: blue; font-weight: bold'>TEST-MY-THREAD</span> Fields: MT Field Offset Type VT Attr Value Name 000007ff001f3518 4000103 8 System.Int32 1 instance 33 m_stringLength 000007ff001ae438 4000104 c System.Char 1 instance 41 m_firstChar 000007ff001ad478 4000105 10 System.String 0 shared static Empty >> Domain:Value 0000000002837030:000000019fa60978 00000000186d9260:000000019fa60978 << </pre> <br /> 저것만 알아도 소스 코드에서 어떤 스레드가 대상인지 금방 식별할 수 있습니다. 그다음 흥미 있는 정보는 m_ManagedThreadId == 27입니다. 16진수로 0x1b이고 !threads 명령을 통해 디버거 내의 스레드 ID를 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:045> <span style='color: blue; font-weight: bold'>!threads</span> ThreadCount: 101 UnstartedThread: 0 BackgroundThread: 73 PendingThread: 0 DeadThread: 28 Hosted Runtime: no PreEmptive Lock ID OSID ThreadOBJ State GC GC Alloc Context Domain Count APT Exception 16 1 16d0 0000000002878150 8220 Enabled 0000000000000000:0000000000000000 0000000002837030 0 Ukn 34 2 b4c 000000000289e970 b220 Enabled 0000000000000000:0000000000000000 0000000002837030 0 MTA (Finalizer) 36 3 21c0 0000000017b67ca0 100a220 Enabled 0000000000000000:0000000000000000 0000000002837030 0 MTA (Threadpool Worker) 37 4 27c8 0000000018753930 1220 Enabled 0000000000000000:0000000000000000 0000000002837030 0 Ukn 38 11 1b40 000000001aef9cf0 b220 Enabled 0000000000000000:0000000000000000 00000000186d9260 0 MTA 39 12 1be4 000000001aefa400 200b220 Enabled 00000001500568b0:0000000150057d28 00000000186d9260 0 MTA 40 13 25d0 000000001aefab10 200b220 Enabled 00000001b21dc148:00000001b21dd1a8 00000000186d9260 0 MTA 41 14 10e0 000000001aefb220 b220 Enabled 00000001405b48c8:00000001405b48f0 00000000186d9260 0 MTA 42 15 1508 000000001aefb930 200b220 Enabled 0000000000000000:0000000000000000 00000000186d9260 0 MTA 43 16 a40 000000001aefc040 1000220 Enabled 0000000000000000:0000000000000000 0000000002837030 0 Ukn (Threadpool Worker) 44 17 e94 000000001aefc750 8009220 Enabled 0000000000000000:0000000000000000 0000000002837030 0 MTA (Threadpool Completion Port) 45 19 18b4 000000001aefd570 200b220 Enabled 00000001605cfac8:00000001605d17b0 00000000186d9260 0 MTA 46 1a a94 000000001aefdc80 200b220 Enabled 0000000000000000:0000000000000000 00000000186d9260 0 MTA <span style='color: blue; font-weight: bold'>47 1b</span> 9d0 000000001aefe390 b221 Enabled 0000000120c94e38:0000000120c94e50 00000000186d9260 1 MTA 48 1c 2320 000000001aefeaa0 200b220 Enabled 0000000000000000:0000000000000000 00000000186d9260 0 MTA ...[생략]... </pre> <br /> 따라서 문맥을 변경해서 Abort 대상이 되었던 스레드가 어떤 작업을 수행하는 상태였는지 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:045> <span style='color: blue; font-weight: bold'>~47s</span> ntdll!ZwWaitForSingleObject+0xa: 00000000`7725bd7a c3 ret 0:047> <span style='color: blue; font-weight: bold'>!clrstack</span> OS Thread Id: 0x9d0 (47) Child SP IP Call Site 0000000020e2d5b8 000000007725bd7a [HelperMethodFrame: 0000000020e2d5b8] 0000000020e2d6d0 000007ff0298739d System.Diagnostics.StackFrameHelper.GetMethodBase(Int32) 0000000020e2d720 000007ff02f78dbc System.Diagnostics.StackTrace.CaptureStackTrace(Int32, Boolean, System.Threading.Thread, System.Exception) <span style='color: blue; font-weight: bold'>0000000020e2d780 000007ff02f78cb1 System.Diagnostics.StackTrace..ctor(System.Threading.Thread, Boolean)</span> 0000000020e2d7d0 000007ff02f7898b WebApp.BaseLib.Dumper.GetCallStack(System.Threading.Thread) 0000000020e2d890 000007ff027c8036 WebApp.Library.CallStack.DumpStack() 0000000020e2da50 000007ff027bc2f5 WebApp.Library.CallStack.DoCallStack(System.Object) 0000000020e2dac0 000007ff025fa678 System.Threading.ExecutionContext.runTryCode(System.Object) 0000000020e2e1e8 000007fef885c9e4 [HelperMethodFrame_PROTECTOBJ: 0000000020e2e1e8] System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object) 0000000020e2e310 000007ff007de3f6 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) 0000000020e2e370 000007ff025f8fab System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) 0000000020e2e3c0 000007ff025f8b8d System.Threading.ThreadHelper.ThreadStart(System.Object) 0000000020e2e828 000007fef885c9e4 [GCFrame: 0000000020e2e828] 0000000020e2ec10 000007fef885c9e4 [DebuggerU2MCatchHandlerFrame: 0000000020e2ec10] 0000000020e2ede8 000007fef885c9e4 [ContextTransitionFrame: 0000000020e2ede8] 0000000020e2efd0 000007fef885c9e4 [DebuggerU2MCatchHandlerFrame: 0000000020e2efd0] </pre> <br /> 보니까, 특정 스레드의 호출 스택을 구하는 스레드입니다. 그렇다면 또 그 스레드는 어떤 것일지 추적해 보겠습니다. StackTrace 타입의 생성자에 첫 번째 인자(rcx)로 System.Threading.Thread 객체가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:047> <span style='color: blue; font-weight: bold'>!U /d 000007ff02f78cb1</span> Normal JIT generated code <span style='color: blue; font-weight: bold'>System.Diagnostics.StackTrace..ctor(System.Threading.Thread, Boolean)</span> Begin 000007ff02f78c70, size 4a 000007ff`02f78c70 53 push rbx 000007ff`02f78c71 56 push rsi 000007ff`02f78c72 57 push rdi 000007ff`02f78c73 4883ec30 sub rsp,30h 000007ff`02f78c77 410fb6f8 movzx edi,r8b 000007ff`02f78c7b 488bf2 mov rsi,rdx 000007ff`02f78c7e 488bd9 <span style='color: blue; font-weight: bold'>mov rbx,rcx</span> 000007ff`02f78c81 488bcb mov rcx,rbx 000007ff`02f78c84 e8e77728fd call 000007ff`00200470 (System.Object..ctor(), mdToken: 000000644E0FBCC0) 000007ff`02f78c89 c7431000000000 mov dword ptr [rbx+10h],0 000007ff`02f78c90 c7431400000000 mov dword ptr [rbx+14h],0 000007ff`02f78c97 48c744242000000000 mov qword ptr [rsp+20h],0 000007ff`02f78ca0 4c8bce mov r9,rsi 000007ff`02f78ca3 440fb6c7 movzx r8d,dil 000007ff`02f78ca7 33d2 xor edx,edx 000007ff`02f78ca9 488bcb <span style='color: blue; font-weight: bold'>mov rcx,rbx</span> 000007ff`02f78cac e8f70e7bfd call 000007ff`00729ba8 (System.Diagnostics.StackTrace.CaptureStackTrace(Int32, Boolean, System.Threading.Thread, System.Exception), mdToken: 000000644E0FBCC0) >>> 000007ff`02f78cb1 90 nop 000007ff`02f78cb2 4883c430 add rsp,30h 000007ff`02f78cb6 5f pop rdi 000007ff`02f78cb7 5e pop rsi 000007ff`02f78cb8 5b pop rbx 000007ff`02f78cb9 c3 ret </pre> <br /> 역시 이번에도 스레드 스택에는 rcx의 족적이 없습니다. 여기서도 다시 부모 함수로 가보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:047> <span style='color: blue; font-weight: bold'>!U /d 000007ff02f7898b</span> Normal JIT generated code <span style='color: blue; font-weight: bold'>WebApp.BaseLib.Dumper.GetCallStack(System.Threading.Thread)</span> Begin 000007ff02f787b0, size 349 000007ff`02f787b0 4889542410 mov qword ptr [rsp+10h],rdx 000007ff`02f787b5 48894c2408 mov qword ptr [rsp+8],rcx 000007ff`02f787ba 55 push rbp 000007ff`02f787bb 4881ecb0000000 <span style='color: blue; font-weight: bold'>sub rsp,0B0h</span> 000007ff`02f787c2 488d6c2420 <span style='color: blue; font-weight: bold'>lea rbp,[rsp+20h]</span> 000007ff`02f787c7 48896500 mov qword ptr [rbp],rsp 000007ff`02f787cb 33c0 xor eax,eax 000007ff`02f787cd 48894528 mov qword ptr [rbp+28h],rax 000007ff`02f787d1 48894510 mov qword ptr [rbp+10h],rax 000007ff`02f787d5 33c0 xor eax,eax 000007ff`02f787d7 884531 mov byte ptr [rbp+31h],al ...[생략]... 000007ff`02f7896c 48894560 mov qword ptr [rbp+60h],rax 000007ff`02f78970 488b4560 mov rax,qword ptr [rbp+60h] 000007ff`02f78974 48894568 mov qword ptr [rbp+68h],rax 000007ff`02f78978 41b001 mov r8b,1 000007ff`02f7897b 488b95a8000000 mov <span style='color: blue; font-weight: bold'>rdx,qword ptr [rbp+0A8h]</span> 000007ff`02f78982 488b4d68 <span style='color: blue; font-weight: bold'>mov rcx,qword ptr [rbp+68h]</span> 000007ff`02f78986 e805127bfd call 000007ff`00729b90 (System.Diagnostics.StackTrace..ctor(System.Threading.Thread, Boolean), mdToken: 000000644E0FBCC0) >>> 000007ff`02f7898b 4c8b5d68 mov r11,qword ptr [rbp+68h] </pre> <br /> System.Diagnostics.StackTrace..ctor 생성자를 호출하는데, 이것의 첫 번째 인자(rcx)는 this 포인터 값이라는 것에 유의해야 합니다. 따라서 우리가 원하는 생성자의 (C# 코드 상의) 첫 번째 인자(System.Threading.Thread)는 실제로는 두 번째 인자(rdx)로 전달됩니다.<br /> <br /> 즉, [rbp+0A8h] 위치에 우리가 원하는 Thread 인스턴스의 값이 보관되어 있습니다. 따라서 이번에도 역시 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/11349'>windbg - x64 SOS 확장의 !clrstack 명령어가 출력하는 Child SP 값의 의미</a>" 글에서 설명했던 방법에 따라, 현재 메서드의 Child SP로부터 시작해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Child SP IP Call Site <span style='color: blue; font-weight: bold'>0000000020e2d7d0</span> 000007ff02f7898b WebApp.BaseLib.Dumper.GetCallStack(System.Threading.Thread) </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:047> <span style='color: blue; font-weight: bold'>? 0000000020e2d7d0 + 20h</span> Evaluate expression: 551737328 = <span style='color: blue; font-weight: bold'>00000000`20e2d7f0</span> 0:047> <span style='color: blue; font-weight: bold'>dq 00000000`20e2d7f0 + 0a8h L1</span> 00000000`20e2d898 <span style='color: blue; font-weight: bold'>00000001`8fe88670</span> 0:047> <span style='color: blue; font-weight: bold'>!do 00000001`8fe88670</span> Name: System.Threading.Thread MethodTable: 000007ff001e1780 EEClass: 000007ff001c9cc8 Size: 88(0x58) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields: MT Field Offset Type VT Attr Value Name 000007ff00252860 400079b 8 ....Contexts.Context 0 instance 0000000181994a40 m_Context 000007ff003f0930 400079c 10 ....ExecutionContext 0 instance 000000017ff9c8f0 m_ExecutionContext 000007ff001ad478 400079d 18 System.String 0 instance <span style='color: blue; font-weight: bold'>0000000000000000 m_Name</span> 000007ff001e0530 400079e 20 System.Delegate 0 instance 0000000000000000 m_Delegate 000007ff001f5780 400079f 28 ...ation.CultureInfo 0 instance 0000000000000000 m_CurrentCulture 000007ff001f5780 40007a0 30 ...ation.CultureInfo 0 instance 0000000000000000 m_CurrentUICulture 000007ff001a57f8 40007a1 38 System.Object 0 instance 0000000000000000 m_ThreadStartArg 000007ff00259f70 40007a2 40 System.IntPtr 1 instance 1ff7bcf0 DONT_USE_InternalThread 000007ff001f3518 40007a3 48 System.Int32 1 instance 2 m_Priority 000007ff001f3518 40007a4 4c System.Int32 1 instance <span style='color: blue; font-weight: bold'>35 m_ManagedThreadId</span> 000007ff006273f8 40007a5 318 ...LocalDataStoreMgr 0 shared static s_LocalDataStoreMgr >> Domain:Value 0000000002837030:NotInit 00000000186d9260:NotInit << 000007ff01dfab40 40007a6 10 ...alDataStoreHolder 0 shared TLstatic s_LocalDataStore >> Thread:Value << ThinLock owner 1b (0000000000000000), Recursive 0 </pre> <br /> 아쉽지만 이번에는 m_Name 값이 없고 m_ManagedThreadId(35 == 0x23)를 통해 디버거 내의 해당 스레드의 식별자를 찾을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:047> <span style='color: blue; font-weight: bold'>!threads</span> ThreadCount: 101 UnstartedThread: 0 BackgroundThread: 73 PendingThread: 0 DeadThread: 28 Hosted Runtime: no PreEmptive Lock ID OSID ThreadOBJ State GC GC Alloc Context Domain Count APT Exception 16 1 16d0 0000000002878150 8220 Enabled 0000000000000000:0000000000000000 0000000002837030 0 Ukn ..[생략]... <span style='color: blue; font-weight: bold'>66 23</span> 162c 000000001ff7bcf0 <span style='color: blue; font-weight: bold'>1089224</span> Enabled 0000000181c4b510:0000000181c4b528 00000000186d9260 1 MTA (Threadpool Worker) XXXX 3d 000000002012ccc0 1019820 Enabled 0000000000000000:0000000000000000 0000000002837030 0 MTA (Threadpool Worker) </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:047> <span style='color: blue; font-weight: bold'>~66s</span> ntdll!ZwWaitForSingleObject+0xa: 00000000`7725bd7a c3 ret 0:066> <span style='color: blue; font-weight: bold'>!threadstate 1089224</span> User Suspend Pending Legal to Join Background CLR Owns In Multi Threaded Apartment Sync Suspended Thread Pool Worker Thread 0:066> <span style='color: blue; font-weight: bold'>!clrstack</span> OS Thread Id: 0x162c (66) Child SP IP Call Site ...[생략]... System.Web.Hosting.UnsafeIISMethods.MgdIndicateCompletion(IntPtr, System.Web.RequestNotificationStatus ByRef) 0000000022e2f530 000007ff0256bf57 DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr, System.Web.RequestNotificationStatus ByRef) 0000000022e2f600 000007ff024e8551 System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr, IntPtr, IntPtr, Int32) 0000000022e2f740 000007ff024e8142 System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr, IntPtr, IntPtr, Int32) 0000000022e2f790 000007ff024e7b21 DomainNeutralILStubClass.IL_STUB_ReversePInvoke(Int64, Int64, Int64, Int32) 0000000022e2f9f8 000007fef897c2db [ContextTransitionFrame: 0000000022e2f9f8] </pre> <br /> <hr style='width: 50%' /><br /> <br /> 정리해 보면, 닷넷 응용 프로그램에서 인자 값 추적은 부모 쪽으로 가는 것이 더 좋다는 점입니다. 왜냐하면 사용자 코드를 포함한 managed 쪽은 실행 시 컴파일(JIT)되는 구조이기 때문에 성능적인 이슈로 인해 아무리 릴리스 빌드라고 해도 최적화를 그다지 심하게 할 수는 없기 때문입니다. 반면, 네이티브 함수로 내려가면 대개의 경우 최적화가 잘 된 상태로 머신 코드가 배열되기 때문에 분석도 어렵고 rbp 프레임 구조를 띄지 않는 경우가 많습니다.<br /> <br /> 마지막으로, 인자 추적을 위한 예제로 이 글을 사용하긴 했지만 어쨌든 중요한 점은 Thread.Abort가 hang에 걸릴 수 있다는 점을 염두에 두어야 합니다. (그 이유는 나중에 한번 더 다루겠습니다. ^^)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1754
(왼쪽의 숫자를 입력해야 합니다.)