Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 3개 있습니다.)
디버깅 기술: 93. windbg - 풀 덤프에서 .NET 스레드의 상태를 알아내는 방법
; https://www.sysnet.pe.kr/2/0/11269

디버깅 기술: 114. windbg - !threads 출력 결과로부터 닷넷 관리 스레드(System.Threading.Thread) 객체를 구하는 방법
; https://www.sysnet.pe.kr/2/0/11476

.NET Framework: 790. .NET Thread 상태가 Cooperative일 때 GC hang 현상 재현 방법
; https://www.sysnet.pe.kr/2/0/11634




windbg - 풀 덤프에서 .NET 스레드의 상태를 알아내는 방법

System.Threading.Thread 타입은 Suspend와 Resume 메서드를 제공해 스레드의 일시 정지/재개를 할 수 있습니다.

그렇다면 그 상태를 어떻게 풀 덤프에서 알 수 있을까요? 어렵게 생각하지 말고 ^^ 그냥 간단하게 상황을 재현해 테스트해보면 됩니다. 이를 위해 다음과 같이 CPU를 소비하는 코드를 만들어 Running 상태의 스레드를 재현하고,

while (true)
{
    for (int i = 0; i < Int32.MaxValue; i ++)
    {
        sum += i;
    }
    System.Diagnostics.Trace.WriteLine(sum.ToString());
}

실행 후 풀 덤프를 떠서 windbg로 로딩합니다.

우선, CPU를 소비한 스레드를 파악하고,

0:000> !runaway
 User Mode Time
  Thread       Time
   6:8684      0 days 0:00:15.375
   0:33cc      0 days 0:00:00.031
   5:77bc      0 days 0:00:00.015
   4:6cbc      0 days 0:00:00.000
   3:44bc      0 days 0:00:00.000
   2:5960      0 days 0:00:00.000
   1:723c      0 days 0:00:00.000

이에 대한 스레드 정보를 sos로 확인합니다.

0:000> .loadby sos clr

0:000> !threads
ThreadCount:      3
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                        Lock  
       ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1 33cc 0000000000a24380    2a020 Preemptive  00000000029A7E18:00000000029A7FD0 0000000000a06000 1     MTA 
   5    2 77bc 0000000000a4f6d0    2b220 Preemptive  0000000000000000:0000000000000000 0000000000a06000 0     MTA (Finalizer) 
   6    3 8684 0000000000a80d10    2b020 Cooperative 00000000029D5228:00000000029D5FD0 0000000000a06000 0     MTA 

6번 스레드이고, State 값이 2b020으로 나옵니다. 따라서 !ThreadState로 조회하면 스레드의 현재 상태를 알 수 있습니다.

0:000> !ThreadState 2b020
    Legal to Join
    CLR Owns
    CoInitialized
    In Multi Threaded Apartment
    Fully initialized

딱히 Running이라는 단어는 없는데 그냥 저 상태 값이면 Running이라고 보면 됩니다.

그렇다면 Thread.Suspend 했을 때는 어떨까요? 닷넷에서 호출 후 역시 덤프를 뜨고 확인해 보면 다음과 같은 출력값이 나옵니다.

0:000> !ThreadState ab024
    User Suspend Pending
    Legal to Join
    CLR Owns
    CoInitialized
    In Multi Threaded Apartment
    Fully initialized
    Sync Suspended

또는, 다른 스레드에 의해 점유된 lock을 획득하기 위해 Monitor.Enter로 진입한 스레드의 상태는 다음과 같습니다.

0:382> !ThreadState 3009220
    Legal to Join
    Background
    CLR Owns
    In Multi Threaded Apartment
    Thread Pool Worker Thread
    Interruptible

참고로, ThreadState 값은 주소가 아닌 Enum Flags 값이기 때문에, 프로세스 실행 단위로 변하지 않으므로 2b020, ab024가 저런 상태이구나... 라고 외워도 무방합니다. 각각의 플래그 값은 다음의 소스 코드에서 찾아볼 수 있습니다.

crummel/dotnet_coreclr
; https://github.com/crummel/dotnet_coreclr/blob/master/src/ToolBox/SOS/Strike/strike.cpp

static const struct ThreadStateTable ThreadStates[] = 
{ 
    {0x1, "Thread Abort Requested"}, 
    {0x2, "GC Suspend Pending"}, 
    {0x4, "User Suspend Pending"}, 
    {0x8, "Debug Suspend Pending"}, 
    {0x10, "GC On Transitions"}, 
    {0x20, "Legal to Join"}, 
    {0x40, "Yield Requested"}, 
    {0x80, "Hijacked by the GC"}, 
    {0x100, "Blocking GC for Stack Overflow"}, 
    {0x200, "Background"}, 
    {0x400, "Unstarted"}, 
    {0x800, "Dead"}, 
    {0x1000, "CLR Owns"}, 
    {0x2000, "CoInitialized"}, 
    {0x4000, "In Single Threaded Apartment"}, 
    {0x8000, "In Multi Threaded Apartment"}, 
    {0x10000, "Reported Dead"}, 
    {0x20000, "Fully initialized"}, 
    {0x40000, "Task Reset"}, 
    {0x80000, "Sync Suspended"}, 
    {0x100000, "Debug Will Sync"}, 
    {0x200000, "Stack Crawl Needed"}, 
    {0x400000, "Suspend Unstarted"}, 
    {0x800000, "Aborted"}, 
    {0x1000000, "Thread Pool Worker Thread"}, 
    {0x2000000, "Interruptible"}, 
    {0x4000000, "Interrupted"}, 
    {0x8000000, "Completion Port Thread"}, 
    {0x10000000, "Abort Initiated"}, 
    {0x20000000, "Finalized"}, 
    {0x40000000, "Failed to Start"}, 
    {0x80000000, "Detached"}, 
}; 




그런데, 혹시 System.Threading.Thread의 ThreadState 값은 어떨까요?

ThreadState Enumeration
; https://docs.microsoft.com/en-us/dotnet/api/system.threading.threadstate

[ComVisible(true)]
[Flags]
public enum ThreadState
{
    Running = 0,
    StopRequested = 1,
    SuspendRequested = 2,
    Background = 4,
    Unstarted = 8,
    Stopped = 16,
    WaitSleepJoin = 32,
    Suspended = 64,
    AbortRequested = 128,
    Aborted = 256
}

값이 다른 걸로 봐서는 아마도 windbg의 ThreadState와는 다른 기준을 갖는 듯합니다.

이를 알기 위해서는 결국 해당 스레드를 실행하고 있는 System.Threading.Thread 객체를 알아내야 합니다. 현재, 이에 대한 직접적인 방법은 없지만 !dumpheap을 통해 찾아들어갈 여지는 있습니다.

WinDbg/SOS: How to correlate managed threads from !threads command with System.Threading.Thread instances
; https://stackoverflow.com/questions/4616584/windbg-sos-how-to-correlate-managed-threads-from-threads-command-with-system-t

0:006> !dumpheap -type System.Threading.Thread
         Address               MT     Size
00000000029a12c8 00007fff2f296eb8      160     
00000000029a1368 00007fff2f296eb8      160     
00000000029a3f00 00007fff2f2a2b20       64     
00000000029a3f40 00007fff2f297d18       96     
00000000029a3fa0 00007fff2f285618       40     
00000000029a4008 00007fff2f2a2b20       64     
00000000029a4048 00007fff2f297d18       96     

Statistics:
              MT    Count    TotalSize Class Name
00007fff2f285618        1           40 System.Threading.ThreadHelper
00007fff2f2a2b20        2          128 System.Threading.ThreadStart
00007fff2f297d18        2          192 System.Threading.Thread
00007fff2f296eb8        2          320 System.Threading.ThreadAbortException
Total 7 objects

이 중에서 Address의 값으로 덤프를 해 보면,

0:006> !DumpObj /d 00000000029a3f40
Name:        System.Threading.Thread
MethodTable: 00007fff2f297d18
EEClass:     00007fff2ec5ace0
Size:        96(0x60) 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
00007fff2f22c378  40018ef        8 ....Contexts.Context  0 instance 00000000029d2ae0 m_Context
00007fff2f2aec38  40018f0       10 ....ExecutionContext  0 instance 0000000000000000 m_ExecutionContext
00007fff2f296938  40018f1       18        System.String  0 instance 00000000029a3e60 m_Name
00007fff2f298d98  40018f2       20      System.Delegate  0 instance 0000000000000000 m_Delegate
00007fff2f29b5f0  40018f3       28 ...ation.CultureInfo  0 instance 0000000000000000 m_CurrentCulture
00007fff2f29b5f0  40018f4       30 ...ation.CultureInfo  0 instance 0000000000000000 m_CurrentUICulture
00007fff2f296f18  40018f5       38        System.Object  0 instance 0000000000000000 m_ThreadStartArg
00007fff2f2afbb0  40018f6       40        System.IntPtr  1 instance           a80d10 DONT_USE_InternalThread
00007fff2f299278  40018f7       48         System.Int32  1 instance                2 m_Priority
00007fff2f299278  40018f8       4c         System.Int32  1 instance                3 m_ManagedThreadId
00007fff2f2a1f18  40018f9       50       System.Boolean  1 instance                1 m_ExecutionContextBelongsToOuterScope
00007fff2f290448  40018fa      db0 ...LocalDataStoreMgr  0   shared           static s_LocalDataStoreMgr
                                 >> Domain:Value  0000000000a06000:NotInit  <<
00007fff2fdb4570  40018fc      db8 ...eInfo, mscorlib]]  0   shared           static s_asyncLocalCurrentCulture
                                 >> Domain:Value  0000000000a06000:NotInit  <<
00007fff2fdb4570  40018fd      dc0 ...eInfo, mscorlib]]  0   shared           static s_asyncLocalCurrentUICulture
                                 >> Domain:Value  0000000000a06000:NotInit  <<
00007fff2f28f3f8  40018fb       18 ...alDataStoreHolder  0   shared         TLstatic s_LocalDataStore
    >> Thread:Value <<

m_ManagedThreadId와 DONT_USE_InternalThread(참고 1, 2, 3, 4, 5) 값을 발견할 수 있습니다. DONT_USE_InternalThread 값은 !threads 명령어로 출력된 ThreadOBJ와 값이 같습니다.

스레드가 많을 경우 일치하는 m_ManagedThreadId를 이런 식으로 하나씩 눌러가며 찾으려면 피곤해지는데요. 이를 위해 "WinDbg/SOS: How to correlate managed threads from !threads command with System.Threading.Thread instances" 글에 좋은 매크로가 소개되어 있습니다.

0:006> .foreach ($t {!dumpheap -mt 00007fff2f297d18 -short}) {  .printf " Thread Obj ${$t} and the Thread Id is %N \n",poi(${$t}+4c) }

제 경우에 출력 결과는 이렇고,

Thread Obj 00000000029a3f40 and the Thread Id is 0000000100000003 
Thread Obj 00000000029a4048 and the Thread Id is 0000000000000001 

따라서 1번 스레드의 System.Threading.Thread.ThreadState 값을 원한다면 다음과 같이 명령을 내리면 됩니다.

0:006> !do 00000000029a4048 
Name:        System.Threading.Thread
MethodTable: 00007fff2f297d18
EEClass:     00007fff2ec5ace0
Size:        96(0x60) 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
00007fff2f22c378  40018ef        8 ....Contexts.Context  0 instance 0000000000000000 m_Context
00007fff2f2aec38  40018f0       10 ....ExecutionContext  0 instance 0000000000000000 m_ExecutionContext
00007fff2f296938  40018f1       18        System.String  0 instance 0000000000000000 m_Name
00007fff2f298d98  40018f2       20      System.Delegate  0 instance 0000000000000000 m_Delegate
00007fff2f29b5f0  40018f3       28 ...ation.CultureInfo  0 instance 0000000000000000 m_CurrentCulture
00007fff2f29b5f0  40018f4       30 ...ation.CultureInfo  0 instance 0000000000000000 m_CurrentUICulture
00007fff2f296f18  40018f5       38        System.Object  0 instance 0000000000000000 m_ThreadStartArg
00007fff2f2afbb0  40018f6       40        System.IntPtr  1 instance           a24380 DONT_USE_InternalThread
00007fff2f299278  40018f7       48         System.Int32  1 instance                2 m_Priority
00007fff2f299278  40018f8       4c         System.Int32  1 instance                1 m_ManagedThreadId
00007fff2f2a1f18  40018f9       50       System.Boolean  1 instance                0 m_ExecutionContextBelongsToOuterScope
00007fff2f290448  40018fa      db0 ...LocalDataStoreMgr  0   shared           static s_LocalDataStoreMgr
                                 >> Domain:Value  0000000000a06000:NotInit  <<
00007fff2fdb4570  40018fc      db8 ...eInfo, mscorlib]]  0   shared           static s_asyncLocalCurrentCulture
                                 >> Domain:Value  0000000000a06000:NotInit  <<
00007fff2fdb4570  40018fd      dc0 ...eInfo, mscorlib]]  0   shared           static s_asyncLocalCurrentUICulture
                                 >> Domain:Value  0000000000a06000:NotInit  <<
00007fff2f28f3f8  40018fb       18 ...alDataStoreHolder  0   shared         TLstatic s_LocalDataStore
    >> Thread:Value <<

그런데, 이상하군요. System.Threading.Thread 타입에는 ThreadState를 나타내는 필드가 없습니다. .NET Reflector 등을 통해 확인해 보면 ThreadState는 다음의 InternalCall로 연결된 것을 볼 수 있습니다.

[MethodImpl(MethodImplOptions.InternalCall), SecurityCritical]
private extern int GetThreadStateNative();

그리고, CoreCLR로부터 이 정보를 찾을 수 있습니다.

coreclr / src / vm / comsynchronizable.cpp 
; https://github.com/dotnet/coreclr/blob/master/src/vm/comsynchronizable.cpp

FCIMPL1(INT32, ThreadNative::GetThreadState, ThreadBaseObject* pThisUNSAFE) 
{ 
    FCALL_CONTRACT; 


    INT32               res = 0; 
    Thread::ThreadState state; 


    if (pThisUNSAFE==NULL) 
        FCThrowRes(kNullReferenceException, W("NullReference_This")); 


    // validate the thread.  Failure here implies that the thread was finalized 
    // and then resurrected. 
    Thread  *thread = pThisUNSAFE->GetInternal(); 


    if (!thread) 
        FCThrowEx(kThreadStateException, IDS_EE_THREAD_CANNOT_GET, NULL, NULL, NULL); 


    HELPER_METHOD_FRAME_BEGIN_RET_0(); 


    // grab a snapshot 
    state = thread->GetSnapshotState(); 


    if (state & Thread::TS_Background) 
        res |= ThreadBackground; 


    if (state & Thread::TS_Unstarted) 
        res |= ThreadUnstarted; 


    // Don't report a StopRequested if the thread has actually stopped. 
    if (state & Thread::TS_Dead) 
    { 
        if (state & Thread::TS_Aborted) 
            res |= ThreadAborted; 
        else 
            res |= ThreadStopped; 
    } 
    else 
    { 
        if (state & Thread::TS_AbortRequested) 
            res |= ThreadAbortRequested; 
    } 


    if (state & Thread::TS_Interruptible) 
        res |= ThreadWaitSleepJoin; 


    // CoreCLR does not support user-requested thread suspension 
    _ASSERTE(!(state & Thread::TS_UserSuspendPending)); 


    HELPER_METHOD_POLL(); 
    HELPER_METHOD_FRAME_END(); 


    return res; 
} 
FCIMPLEND 

즉, 내부 thread->GetSnapshotState() 함수가 반환한 enum ThreadState 형식의 state 값을 이용해 새롭게 System.Threading.Thread의 ThreadState 값을 구성해 반환해 주는 것입니다. 그리고 enum ThreadState 값은 이전에 windbg의 ThreadState로 보았던 바로 그 내용들과 일치합니다.

clrmd / src / Microsoft.Diagnostics.Runtime / ClrThread.cs 
; https://github.com/Microsoft/clrmd/blob/master/src/Microsoft.Diagnostics.Runtime/ClrThread.cs

enum ThreadState
{
    TS_Unknown                = 0x00000000,    // threads are initialized this way

    TS_StopRequested          = 0x00000001,    // process stop at next opportunity
    TS_GCSuspendPending       = 0x00000002,    // waiting to get to safe spot for GC
    TS_UserSuspendPending     = 0x00000004,    // user suspension at next opportunity
    TS_DebugSuspendPending    = 0x00000008,    // Is the debugger suspending threads?
    TS_GCOnTransitions        = 0x00000010,    // Force a GC on stub transitions (GCStress only)

    TS_LegalToJoin            = 0x00000020,    // Is it now legal to attempt a Join()
    TS_Hijacked               = 0x00000080,    // Return address has been hijacked

    TS_Background             = 0x00000200,    // Thread is a background thread
    TS_Unstarted              = 0x00000400,    // Thread has never been started
    TS_Dead                   = 0x00000800,    // Thread is dead

    TS_WeOwn                  = 0x00001000,    // Exposed object initiated this thread
    TS_CoInitialized          = 0x00002000,    // CoInitialize has been called for this thread
    TS_InSTA                  = 0x00004000,    // Thread hosts an STA
    TS_InMTA                  = 0x00008000,    // Thread is part of the MTA

    // Some bits that only have meaning for reporting the state to clients.
    TS_ReportDead             = 0x00010000,    // in WaitForOtherThreads()

    TS_SyncSuspended          = 0x00080000,    // Suspended via WaitSuspendEvent
    TS_DebugWillSync          = 0x00100000,    // Debugger will wait for this thread to sync
    TS_RedirectingEntryPoint  = 0x00200000,    // Redirecting entrypoint. Do not call managed entrypoint when set 

    TS_SuspendUnstarted       = 0x00400000,    // latch a user suspension on an unstarted thread

    TS_ThreadPoolThread       = 0x00800000,    // is this a threadpool thread?
    TS_TPWorkerThread         = 0x01000000,    // is this a threadpool worker thread? (if not, it is a threadpool completionport thread)

    TS_Interruptible          = 0x02000000,    // sitting in a Sleep(), Wait(), Join()
    TS_Interrupted            = 0x04000000,    // was awakened by an interrupt APC

    TS_AbortRequested         = 0x08000000,    // same as TS_StopRequested in order to trip the thread
    TS_AbortInitiated         = 0x10000000,    // set when abort is begun
    TS_UserStopRequested      = 0x20000000,    // set when a user stop is requested. This is different from TS_StopRequested
    TS_GuardPageGone          = 0x40000000,    // stack overflow, not yet reset.
    TS_Detached               = 0x80000000,    // Thread was detached by DllMain
}

정리해 보면 다음과 같은 규칙들로 정해집니다.

if (TS_Background) |= ThreadBackground
if (TS_Unstarted) |= ThreadUnstarted

if (TS_Dead) 
{
    if (TS_Aborted) |= ThreadAborted
    else |= ThreadStopped
}
else
{
    if (TS_AbortRequested) |= ThreadAbortRequested
}

if (TS_Interruptible) |= ThreadWaitSleepJoin




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/13/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 106  107  108  109  110  111  112  113  114  115  [116]  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11059정성태10/11/201628891.NET Framework: 609. WPF - 다중 스레드 환경에서 데이터 바인딩의 INotifyPropertyChanged.PropertyChanged에 대한 배려 [1]파일 다운로드1
11058정성태10/8/201624010개발 환경 구성: 303. Windows 10 Bash Shell - 한글 환경을 영문으로 바꾸고 싶다면?
11057정성태10/8/201618539오류 유형: 358. Windows 10 bash shell - sudo: unable to resolve host ...
11056정성태10/8/201622339개발 환경 구성: 302. Windows 10 bash shell 시작 시 [...] packages can be updated.
11055정성태10/8/201623352.NET Framework: 608. double 값을 구할 때는 반드시 피연산자를 double로 형변환! [6]
11054정성태10/5/201627371개발 환경 구성: 301. "Let's Encrypt" SSL 인증서를 Azure Cloud Services(classic)에 업데이트하는 방법
11053정성태10/5/201622010.NET Framework: 607. C# try/catch/finally의 IL 코드 표현
11052정성태9/27/201635939개발 환경 구성: 300. C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 [7]파일 다운로드1
11051정성태9/25/201623109개발 환경 구성: 299. docker - c:\programdata\docker\windowsfilter 폴더 정리하는 방법파일 다운로드1
11050정성태9/24/201627943VC++: 101. 반올림하지 않고 double 변수 값 출력하는 방법 [3]
11049정성태9/24/201622333오류 유형: 357. 윈도우 백업 시 오류 - 0x81000037
11048정성태9/24/201623402VC++: 100. 전역 변수 유형별 실행 파일 크기 차이점
11047정성태9/21/201627317기타: 61. algospot.com - 양자화(Quantization) 문제 [2]파일 다운로드1
11046정성태9/15/201629003개발 환경 구성: 298. Windows 10 - bash 실행 시 시작 디렉터리 자동 변경
11045정성태9/15/201621652Windows: 119. Windows 10 - bash 명령어 창을 실행했는데 바로 닫히는 경우
11044정성태9/15/201621864VS.NET IDE: 112. Visual Studio 확장 - 편집 화면 내에서 링크를 누르면 외부 웹 브라우저에서 열기
11043정성태9/15/201622575.NET Framework: 606. .NET 스레드 콜 스택 덤프 (7) - ClrMD(Microsoft.Diagnostics.Runtime)를 이용한 방법 [1]파일 다운로드1
11042정성태9/14/201620652오류 유형: 356. Unknown custom metadata item kind: 6
11041정성태9/10/201620769.NET Framework: 605. CLR4 보안 - yield 구문 내에서 SecurityCritical 메서드 사용 불가 - 2번째 이야기
11040정성태9/10/201628242.NET Framework: 604. C# Windows Forms - Drag & Drop 예제 코드 [2]파일 다운로드1
11039정성태9/9/201624074오류 유형: 355. Visual Studio 빌드 오류 - error CS0122: '__ComObject' is inaccessible due to its protection level
11038정성태9/9/201626698VC++: 99. 서로 다른 프로세스에서 WM_DROPFILES 메시지를 전송하는 방법파일 다운로드1
11037정성태9/8/201629904.NET Framework: 603. socket - shutdown 호출이 필요한 사례파일 다운로드1
11036정성태8/29/201625697개발 환경 구성: 297. 소스 코드가 없는 닷넷 어셈블리를 디버깅할 때 지역 변숫값을 확인하는 방법
11035정성태8/29/201621600오류 유형: 354. .NET Reflector - PDB 생성 화면에서 "Clear Store"를 하면 "Index and length must refer to a location within the string" 예외 발생
11034정성태8/25/201625595개발 환경 구성: 296. .NET Core 프로젝트를 NuGet Gallery에 배포하는 방법 [2]
... 106  107  108  109  110  111  112  113  114  115  [116]  117  118  119  120  ...