Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)
(시리즈 글이 3개 있습니다.)
디버깅 기술: 92. windbg - C# Monitor Lock을 획득하고 있는 스레드 찾는 방법
; https://www.sysnet.pe.kr/2/0/11268

닷넷: 2214. windbg - Monitor.Enter의 thin lock과 fat lock
; https://www.sysnet.pe.kr/2/0/13552

닷넷: 2215. windbg - thin/fat lock 없이 동작하는 Monitor.Wait + Pulse
; https://www.sysnet.pe.kr/2/0/13553




windbg - C# Monitor Lock을 획득하고 있는 스레드 찾는 방법

이에 대해서는 다음의 글에 방법이 잘 나옵니다.

Analyzing Monitor-Based Deadlocks with SOS.dll
; http://blogs.microsoft.co.il/sasha/2009/10/17/analyzing-monitor-based-deadlocks-with-sosdll/

그런데 직접 실습을 해보니 약간 달라진 면이 있어서 기록을 남깁니다.




우선, 재현을 위해 간단한 테스트 프로그램을 다음과 같이 만들어 볼 수 있습니다.

using System;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            object _objLock1 = new object();
            object _objLock2 = new object();

            Thread t1 = new Thread(threadFunc1);
            t1.IsBackground = true;
            t1.Start(_objLock1);

            Thread t2 = new Thread(threadFunc2);
            t2.IsBackground = true;
            t2.Start(_objLock2);

            lock (_objLock1)
                lock (_objLock2)
                {
                    Console.WriteLine("Enter...");
                    Console.ReadLine();
                    Console.WriteLine("Exit...");
                }
        }

        private static void threadFunc1(object obj)
        {
            lock (obj)
            {
                Console.WriteLine("threadFunc1");
            }
        }

        private static void threadFunc2(object obj)
        {
            lock (obj)
            {
                Console.WriteLine("threadFunc2");
            }
        }
    }
}

결국, Main 스레드가 2개의 lock을 이미 소유한 상태에서 threadFunc1과 threadFunc2의 실행이 hang 상태가 된 것을 표현한 것입니다.

위의 프로그램을 실행하고 작업 관리자를 이용해 덤프를 남긴 후 windbg로 읽어들입니다. 당연히 ".loadby sos clr"로 sos 확장을 로드하고 다음과 같이 현재 열려 있는 lock의 수를 확인할 수 있습니다.

0:000> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner
    3 0000027ecd80ece8            3         1 0000027ecd7b2b00 7744   0   0000027ecf3a3ea8 System.Object
    4 0000027ecd80ed38            3         1 0000027ecd7b2b00 7744   0   0000027ecf3a3ec0 System.Object
-----------------------------
Total           4
CCW             0
RCW             0
ComClassFactory 0
Free            0

보는 바와 같이 총 2개의 lock이 사용 중이고, 현재 그것을 점유하고 있는 "Owing Thread"는 0000027ecd7b2b00으로 나옵니다. 이에 해당하는 스레드 정보는 !threads 명령어로 확인할 수 있습니다.

0:000> !threads
ThreadCount:      4
UnstartedThread:  0
BackgroundThread: 3
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                        Lock  
       ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1 7744 0000027ecd7b2b00    2a020 Preemptive  0000027ECF3A9FA8:0000027ECF3A9FD0 0000027ecd783180 3     MTA 
   5    2 44c0 0000027ecd7dbe40    2b220 Preemptive  0000000000000000:0000000000000000 0000027ecd783180 0     MTA (Finalizer) 
   6    3 7d7c 0000027ecd80e470  202b220 Preemptive  0000027ECF3A6018:0000027ECF3A7FD0 0000027ecd783180 0     MTA 
   7    4 1338 0000027ecd814970  202b220 Preemptive  0000000000000000:0000000000000000 0000027ecd783180 0     MTA 

0번 스레드인데 당연히 Main 메서드를 실행 중일 테니 굳이 확인할 필요도 없어 보입니다.

나머지 중에 5번 스레드는 Finalizer고 6번과 7번이 우리가 생성했던 스레드였을 텐데 이 중에서 6번을 먼저 살펴보겠습니다.

0:000> ~6s
ntdll!NtWaitForMultipleObjects+0x14:
00007fff`499f5ef4 c3              ret

0:006> !clrstack
OS Thread Id: 0x7d7c (6)
        Child SP               IP Call Site
000000e3b82ff0f8 00007fff499f5ef4 [GCFrame: 000000e3b82ff0f8] 
000000e3b82ff238 00007fff499f5ef4 [GCFrame: 000000e3b82ff238] 
000000e3b82ff278 00007fff499f5ef4 [HelperMethodFrame_1OBJ: 000000e3b82ff278] System.Threading.Monitor.Enter(System.Object)
000000e3b82ff370 00007ffed33f0775 ConsoleApp1.Program.threadFunc1(System.Object) [E:\ConsoleApp1\ConsoleApp1\Program.cs @ 32]
000000e3b82ff3c0 00007fff2f06ca72 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
000000e3b82ff490 00007fff2f06c904 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
000000e3b82ff4c0 00007fff2f06c8c2 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
000000e3b82ff510 00007fff2fa40ffc System.Threading.ThreadHelper.ThreadStart(System.Object)
000000e3b82ff768 00007fff329f6753 [GCFrame: 000000e3b82ff768] 
000000e3b82ffab8 00007fff329f6753 [DebuggerU2MCatchHandlerFrame: 000000e3b82ffab8] 

콜스택 상단에 Monitor.Enter가 있으므로 현재 lock을 대기 중임을 알 수 있습니다. 그렇다면 어떤 object를 대상으로 lock을 건 것일까요? 이를 위해 native 레벨의 callstack을 확인해 보면 단서를 찾을 수 있습니다.

0:006> kv
 # Child-SP          RetAddr           : Args to Child                                                           : Call Site
00 000000e3`b82feb38 00007fff`4677dd20 : 00000099`00000000 00000000`00000010 00000002`00000002 00007fff`00000010 : ntdll!NtWaitForMultipleObjects+0x14
01 000000e3`b82feb40 00007fff`32bb401e : ffffffff`fffffffe 00007fff`00000000 000000e3`b82ff0f8 00000000`00000000 : KERNELBASE!WaitForMultipleObjectsEx+0xf0
02 000000e3`b82fee40 00007fff`32bb3eb0 : 00000000`00000001 00000000`00000000 0000027e`cd80e470 00000000`ffffffff : clr!WaitForMultipleObjectsEx_SO_TOLERANT+0x62
03 000000e3`b82feea0 00007fff`32bb3ca9 : 0000027e`cd80e470 00000000`00000001 00000000`00000000 00007ffe`00000000 : clr!Thread::DoAppropriateWaitWorker+0x1e4
04 000000e3`b82fefa0 00007fff`32bb42b9 : 00000000`00000000 00007fff`00000001 0000027e`cd80e470 00007fff`329ff1af : clr!Thread::DoAppropriateWait+0x7d
05 000000e3`b82ff020 00007fff`32bb458e : 0000027e`cd80ece8 000000e3`b82ff3b0 00000000`00000000 0000027e`cd80e470 : clr!CLREventBase::WaitEx+0xc4
06 000000e3`b82ff0b0 00007fff`32bb44a2 : 0000027e`cd80ece8 0000027e`cd80ece8 00007ffe`00000000 0000027e`cf3a3ea8 : clr!AwareLock::EnterEpilogHelper+0xca
07 000000e3`b82ff170 00007fff`32bb41b2 : 0000027e`cd80e470 0000027e`cd80e470 00000000`00000000 0000027e`cd80ece8 : clr!AwareLock::EnterEpilog+0x62
08 000000e3`b82ff1d0 00007ffe`d33f0775 : 0000027e`cf3a3fa0 000000e3`b82ff3a8 0000027e`cf3a4080 00000000`00000000 : clr!JITutil_MonEnterWorker+0xe2
09 000000e3`b82ff370 00007fff`2f06ca72 : 0000027e`cf3a3ea8 0000027e`cf3a3ea8 000000e3`b82ff3f0 00000000`00000000 : 0x00007ffe`d33f0775
0a 000000e3`b82ff3c0 00007fff`2f06c904 : 0000027e`cf3a4080 0000027e`cf3a3fa0 0000027e`cf3a3f78 00000000`00000000 : mscorlib_ni+0x4dca72
0b 000000e3`b82ff490 00007fff`2f06c8c2 : 0000027e`cf3a3f78 00000000`00000000 00000000`cf3a3ea8 00000000`00000000 : mscorlib_ni+0x4dc904
0c 000000e3`b82ff4c0 00007fff`2fa40ffc : 000000e3`b82ff668 000000e3`b82ff668 000000e3`b82ff570 00000000`00000000 : mscorlib_ni+0x4dc8c2
0d 000000e3`b82ff510 00007fff`329f6753 : 0000027e`cf3a3fe0 0000027e`cf3a3ea8 00000000`00000000 00000000`00000000 : mscorlib_ni+0xeb0ffc
0e 000000e3`b82ff550 00007fff`329f6649 : 00000000`00000001 00007fff`32b3f8e3 000000e3`b82ff828 00000000`00000000 : clr!CallDescrWorkerInternal+0x83
0f 000000e3`b82ff590 00007fff`329f732d : 00000000`00000002 000000e3`b82ff748 000000e3`b82ff768 000000e3`b82ff828 : clr!CallDescrWorkerWithHandler+0x4e
10 000000e3`b82ff5d0 00007fff`32b096e9 : 000000e3`b82ffbe0 000000e3`b82ffb20 00007fff`2f28e720 000000e3`b82ffb20 : clr!MethodDescCallSite::CallTargetWorker+0xf8
11 000000e3`b82ff6d0 00007fff`329f7c9d : 00000000`00001f80 00007fff`32b095e0 000000e3`b82ffb20 ffffffff`ffffffff : clr!ThreadNative::KickOffThread_Worker+0x109
12 000000e3`b82ff930 00007fff`329f7c18 : 000000e3`b82ffb20 00007fff`329f6051 0000027e`00000000 00000000`00000018 : clr!Frame::Push+0x59
13 000000e3`b82ff970 00007fff`329f7b56 : 00000000`00000001 00000000`00000000 00000000`00000000 00000000`00000000 : clr!MarshalInfo::SetupArgumentSizes+0x234
14 000000e3`b82ffa70 00007fff`329f7cdf : ffffffff`ffffffff 0000027e`cd80e470 0000027e`cd80e470 0000027e`cd806190 : clr!MarshalInfo::SetupArgumentSizes+0x15d
15 000000e3`b82ffb00 00007fff`32b095cb : 0000027e`cd80e470 000000e3`00000001 0000027e`cd806190 00000000`00000003 : clr!MarshalInfo::SetupArgumentSizes+0xe3
16 000000e3`b82ffb60 00007fff`32b31c3f : 0000027e`cd806190 0000027e`cd80e470 000000e3`b82ffbb8 00000000`00000000 : clr!ThreadNative::KickOffThread+0xdb
17 000000e3`b82ffc30 00007fff`47152774 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : clr!Thread::intermediateThreadProc+0x86
18 000000e3`b82ffd70 00007fff`499c0d51 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0x14
19 000000e3`b82ffda0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21

콜스택에 보면 clr!CLREventBase::WaitEx 호출이 보이는데 그것의 첫 번째 인자(0000027e`cd80ece8) 값이 바로 syncblock 값과 일치하게 나옵니다.

0:000> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner
    3 0000027ecd80ece8            3         1 0000027ecd7b2b00 7744   0   0000027ecf3a3ea8 System.Object
    4 0000027ecd80ed38            3         1 0000027ecd7b2b00 7744   0   0000027ecf3a3ec0 System.Object

즉, 6번 스레드는 0000027ecd80ece8에 해당하는 lock을 대기하고 있는 중이며 그 lock을 현재 소유하고 있는 스레드는 0000027ecd7b2b00인 0번 스레드임이 밝혀집니다. 이 정도면... ^^ 실전에서 충분히 추적할만한 단서가 되겠지요.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/14/2017]

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

비밀번호

댓글 작성자
 




... [46]  47  48  49  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12480정성태1/6/202112302.NET Framework: 999. C# - ArrayPool<T>와 MemoryPool<T> 소개파일 다운로드1
12479정성태1/6/20219708.NET Framework: 998. C# - OWIN 예제 프로젝트 만들기
12478정성태1/5/202111320.NET Framework: 997. C# - ArrayPool<T> 소개파일 다운로드1
12477정성태1/5/202113695기타: 79. github 코드 검색 방법 [1]
12476정성태1/5/202110380.NET Framework: 996. C# - 닷넷 코어에서 다른 스레드의 callstack을 구하는 방법파일 다운로드1
12475정성태1/5/202112960.NET Framework: 995. C# - Span<T>와 Memory<T> [1]파일 다운로드1
12474정성태1/4/202110476.NET Framework: 994. C# - (.NET Core 2.2부터 가능한) 프로세스 내부에서 CLR ETW 이벤트 수신 [1]파일 다운로드1
12473정성태1/4/20219275.NET Framework: 993. .NET 런타임에 따라 달라지는 정적 필드의 초기화 유무 [1]파일 다운로드1
12472정성태1/3/20219553디버깅 기술: 178. windbg - 디버그 시작 시 스크립트 실행
12471정성태1/1/202110026.NET Framework: 992. C# - .NET Core 3.0 이상부터 제공하는 runtimeOptions의 rollForward 옵션 [1]
12470정성태12/30/202010207.NET Framework: 991. .NET 5 응용 프로그램에서 WinRT API 호출 [1]파일 다운로드1
12469정성태12/30/202013794.NET Framework: 990. C# - SendInput Win32 API를 이용한 가상 키보드/마우스 [1]파일 다운로드1
12468정성태12/30/202010412Windows: 186. CMD Shell의 "Defaults"와 "Properties"에서 폰트 정보가 다른 문제 [1]
12467정성태12/29/202010353.NET Framework: 989. HttpContextAccessor를 통해 이해하는 AsyncLocal<T> [1]파일 다운로드1
12466정성태12/29/20208318.NET Framework: 988. C# - 지연 실행이 꼭 필요한 상황이 아니라면 singleton 패턴에서 DCLP보다는 static 초기화를 권장파일 다운로드1
12465정성태12/29/202011435.NET Framework: 987. .NET Profiler - FunctionID와 연관된 ClassID를 구할 수 없는 문제
12464정성태12/29/202010304.NET Framework: 986. pptfont.exe - PPT 파일에 숨겨진 폰트 설정을 일괄 삭제
12463정성태12/29/20209360개발 환경 구성: 520. RDP(mstsc.exe)의 다중 모니터 옵션 /multimon, /span
12462정성태12/27/202010971디버깅 기술: 177. windbg - (ASP.NET 환경에서 유용한) netext 확장
12461정성태12/21/202011795.NET Framework: 985. .NET 코드 리뷰 팁 [3]
12460정성태12/18/20209493기타: 78. 도서 소개 - C#으로 배우는 암호학
12459정성태12/16/20209884Linux: 35. C# - 리눅스 환경에서 클라이언트 소켓의 ephemeral port 재사용파일 다운로드1
12458정성태12/16/20209369오류 유형: 694. C# - Task.Start 메서드 호출 시 "System.InvalidOperationException: 'Start may not be called on a task that has completed.'" 예외 발생 [1]
12457정성태12/15/20208958Windows: 185. C# - Windows 10/2019부터 추가된 SIO_TCP_INFO파일 다운로드1
12456정성태12/15/20209247VS.NET IDE: 156. Visual Studio - "Migrate packages.config to PackageReference"
12455정성태12/15/20208741오류 유형: 693. DLL 로딩 시 0x800704ec - This Program is Blocked by Group Policy
... [46]  47  48  49  50  51  52  53  54  55  56  57  58  59  60  ...