성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
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 - ThreadStatic 필드 값을 조사하는 방법</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;' > windbg - 닷넷 메모리 덤프에서 정적(static) 필드 값을 조사하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11487'>https://www.sysnet.pe.kr/2/0/11487</a> </pre> <br /> 정적 필드까지는 알아봤었는데요, 그렇다면 Thread마다 고유한 (이름만 비슷할 뿐 전혀 다른 성격의 저장소인) ThreadStatic인 경우에는 어떨까요?<br /> <br /> 테스트를 위해 다음과 같이 코드를 만들고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace ConsoleApp1; internal class Program { static void Main(string[] args) { CreateObject(); GC.Collect(); GC.Collect(); Console.ReadLine(); } private static void CreateObject() { <span style='color: blue; font-weight: bold'>MyClass.TlsRef = new Program(); MyClass.TlsValue = 0xFF_EE_DD_CC_BB_AA_00_99; MyClass.StaticValue = 0x_AA_BB_CC_DD;</span> } } class MyClass { <span style='color: blue; font-weight: bold'>[ThreadStatic] public static Program? TlsRef; [ThreadStatic] public static ulong TlsValue; public static uint StaticValue;</span> } </pre> <br /> 실행한 다음, windbg로 attach 시켜 다음과 같이 static 변수를 덤프했던 경험을 살려 추적했더니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:014> <span style='color: blue; font-weight: bold'>!name2ee ConsoleApp1.dll!ConsoleApp1.MyClass</span> Module: 00007ffcba7fcf48 Assembly: ConsoleApp1.dll Token: 0000000002000007 MethodTable: 00007ffcba96cb38 <span style='color: blue; font-weight: bold'>EEClass: 00007ffcba94ed78</span> Name: ConsoleApp1.MyClass 0:014> <span style='color: blue; font-weight: bold'>!DumpClass /d 00007ffcba94ed78</span> Class Name: ConsoleApp1.MyClass mdToken: 0000000002000007 File: C:\temp\ConsoleApp1\bin\Debug\net7.0\ConsoleApp1.dll Parent Class: 00007ffcba570e98 Module: 00007ffcba7fcf48 Method Table: 00007ffcba96cb38 Vtable Slots: 4 Total Method Slots: 5 Class Attributes: 100000 NumInstanceFields: 0 NumStaticFields: 3 NumThreadStaticFields: 2 MT Field Offset Type VT Attr Value Name 00007ffcba6cfc50 4000006 40 System.UInt32 1 static <span style='color: blue; font-weight: bold'>2864434397</span> StaticValue <span style='color: blue; font-weight: bold'>00007ffcba7feff8 4000004 0 ConsoleApp1.Program 0 TLstatic TlsRef >> Thread:Value 8bcc:000001aac840df68 <<</span> <span style='color: blue; font-weight: bold'>00007ffcba6e3cf0 4000005 20 System.UInt64 1 TLstatic TlsValue >> Thread:Value 8bcc:18441921395520307353 <<</span> </pre> <br /> 오호... 이전의 static 변수 확인과 동일하게 잘 나옵니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 8bcc:000001aac840df68 ==> 8bcc OS 스레드에 속한 값 000001aac840df68 == Program 인스턴스의 GC Heap 주소 8bcc:18441921395520307353 ==> 16진수로 0xFFEEDDCCBBAA0099 </pre> <br /> <hr style='width: 50%' /><br /> <br /> 여기서 재미있는 건, TLS도 역시 GC 입장에서는 root에 해당하는데요, 따라서 TLS 변수가 값 형식을 담고 있을 때는 GC 입장에서 관심 대상이 아니지만 참조 형식을 담고 있을 때는 GC가 반드시 관여를 해야 합니다.<br /> <br /> 즉, 위와 같은 경우에 TlsRef 변수에 Program 타입의 인스턴스를 가리키게 했는데요, 그렇다면 해당 인스턴스는 스레드가 종료될 때까지는 GC가 되어서는 안 됩니다.<br /> <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:014> <span style='color: blue; font-weight: bold'>!dumpheap -type ConsoleApp1.Program</span> Address MT Size <span style='color: blue; font-weight: bold'>01aac840df68</span> 7ffcba7feff8 24 Statistics: MT Count TotalSize Class Name 7ffcba7feff8 1 24 ConsoleApp1.Program Total 1 objects, 24 bytes </pre> <br /> 편의상 1개의 개체만 만들었으므로 쉽게 판별할 수 있는데요, ^^ 이것의 root 개체가 무엇인지 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:014> <span style='color: blue; font-weight: bold'>!gcroot 01aac840df68</span> Caching GC roots, this may take a while. Subsequent runs of this command will be faster. HandleTable: <span style='color: blue; font-weight: bold'>000001aac5501358 (strong handle)</span> -> 01aac840df80 System.Object[] -> 01aac840df68 ConsoleApp1.Program Found 1 unique roots. </pre> <br /> 특이하게도 "strong handle" 성격의 <a target='tab' href='https://devblogs.microsoft.com/dotnet/gc-handles/'>HandleTable</a>에 등록돼 있습니다. <a target='tab' href='https://www.awise.us/2024/03/31/gc-handle.html'>전체 핸들</a> 테이블은 !gchandles로 확인할 수 있는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:014> <span style='color: blue; font-weight: bold'>!gchandles</span> Handle Type Object Size Data Type 000001AAC5501178 WeakShort 000001aaca811860 40 System.Buffers.TlsOverPerCoreLockedStacksArrayPool`1[[System.Byte, System.Private.CoreLib]] ...[생략]... 000001AAC55011F8 WeakShort 000001aac840c260 72 System.Threading.Thread 000001AAC5501798 WeakLong 000001aaca811168 152 System.RuntimeType+RuntimeTypeCache ...[생략]... 000001AAC55017F8 WeakLong 000001aac8409b68 64 Interop+Advapi32+EtwEnableCallback 000001AAC5501318 Strong 000001aaca812fe8 144 System.Object[] 000001AAC5501320 Strong 000001aaca808320 72 System.Threading.Thread 000001AAC5501328 Strong 000001aaca811ac0 32 System.Object[] 000001AAC5501330 Strong 000001aaca806d20 72 System.Threading.Thread 000001AAC5501338 Strong 000001aaca8067c8 72 System.Threading.Thread 000001AAC5501340 Strong 000001aaca808020 144 System.Object[] 000001AAC5501348 Strong 000001aaca806020 72 System.Threading.Thread 000001AAC5501350 Strong 000001aac5c08400 2072 System.Object[] <span style='color: blue; font-weight: bold'>000001AAC5501358 Strong 000001aac840df80 32 System.Object[]</span> 000001AAC5501360 Strong 000001aac5c04428 16344 System.Object[] 000001AAC5501368 Strong 000001aac8412fe8 144 System.Object[] 000001AAC5501370 Strong 000001aac840ea90 72 System.Threading.Thread ...[생략]... 000001AAC55015F0 Pinned 000001aaca812988 25 System.Byte[] 000001AAC55015F8 Pinned 000001aac8400200 24 System.Object 000001AAC5501BF0 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.dependenthandle'>Dependent</a> 000001aaca8121d0 456 0000000000000000 System.Buffers.TlsOverPerCoreLockedStacksArrayPool`1+ThreadLocalArray[[System.Byte, System.Private.CoreLib]][] 000001AAC5501BF8 Dependent 000001aac840bb48 456 0000000000000000 System.Buffers.TlsOverPerCoreLockedStacksArrayPool`1+ThreadLocalArray[[System.Char, System.Private.CoreLib]][] 000001AAC5501DF0 AsyncPinned 000001aaca812aa0 72 System.Threading.OverlappedData 000001AAC5501DF8 AsyncPinned 000001aaca811d50 72 System.Threading.OverlappedData Statistics: MT Count TotalSize Class Name 00007ffcba5793b8 1 24 System.Object ...[생략]... 00007ffcba69aa68 14 36792 System.Object[] Total 60 objects Handles: Strong Handles: 27 Pinned Handles: 2 Async Pinned Handles: 2 Weak Long Handles: 12 Weak Short Handles: 15 Dependent Handles: 2 </pre> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ... GCHandle type. Only 4 types are exposed this way: Normal, Pinned, Weak and WeakTrackResurrection ... Weak and WeakTrackResurrection types are internally called short and long weak handles </pre> <br /> 보는 바와 같이 Strong 타입의 유형으로 000001AAC5501358 위치에 System.Object[] 인스턴스로 000001aac840df80 값을 담고 있습니다. 따라서 (만약 gcroot 명령어가 없었다고 해도) 모든 GC Handle Table의 Strong 유형에 해당하는 값을 대상으로 다음과 같은 식으로 조사하다 보면 운이 좋게 우리가 찾고 있는 개체를 참조하고 있는 항목을 발견할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:014> <span style='color: blue; font-weight: bold'>dq 000001AAC5501358 L1</span> 000001aa`c5501358 000001aa`c840df80 0:014> <span style='color: blue; font-weight: bold'>!DumpArray /d 000001aa`c840df80</span> Name: System.Object[] MethodTable: 00007ffcba69aa68 EEClass: 00007ffcba69a9d0 Size: 32(0x20) bytes Array: Rank 1, Number of elements 1, Type CLASS Element Methodtable: 00007ffcba5793b8 [0] <span style='color: blue; font-weight: bold'>000001aac840df68</span> 0:014> <span style='color: blue; font-weight: bold'>!do 000001aac840df68</span> Name: ConsoleApp1.Program MethodTable: 00007ffcba7feff8 EEClass: 00007ffcba7dfd98 Tracked Type: false Size: 24(0x18) bytes File: C:\temp\ConsoleApp1\bin\Debug\net7.0\ConsoleApp1.dll Fields: None </pre> <br /> <hr style='width: 50%' /><br /> <br /> 여기서 또 재미있는 것은, GC Handle Table의 위치입니다. !gchandles의 결과로 출력되는 항목 중 가장 첫 번째와 마지막의 주솟값은,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:014> <span style='color: blue; font-weight: bold'>!gchandles</span> Handle Type Object Size Data Type 000001AAC5501178 WeakShort 000001aaca811860 40 System.Buffers.TlsOverPerCoreLockedStacksArrayPool`1[[System.Byte, System.Private.CoreLib]] ...[생략]... 000001AAC5501DF8 AsyncPinned 000001aaca811d50 72 System.Threading.OverlappedData </pre> <br /> 000001AAC5501178 ~ 000001AAC5501DF8 범위인데, 이 영역은 GC Heap의 어떤 정보에도 속하지 않습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:014> <span style='color: blue; font-weight: bold'>!eeheap</span> Loader Heap: ---------------------------------------- System Domain: 7ffd1a585f70 LowFrequencyHeap: 7ffcbaa10000(10000:a000) ...[생략]... 7ffcba570000(3000:1000) Size: 0x10a000 (1089536) bytes total, 0x3000 (12288) bytes wasted. HighFrequencyHeap: 7ffcbaa20000(10000:f000) ...[생략]... (1265664) bytes total, 0x3000 (12288) bytes wasted. StubHeap: 7ffcba57d000(3000:1000) Size: 0x1000 (4096) bytes total. IndirectionCellHeap: 7ffcba580000(6000:1000) Size: 0x1000 (4096) bytes total. LookupHeap: 7ffcba58f000(4000:1000) Size: 0x1000 (4096) bytes total. ResolveHeap: 7ffcba5c4000(57000:1000) Size: 0x1000 (4096) bytes total. DispatchHeap: 7ffcba593000(31000:1000) Size: 0x1000 (4096) bytes total. CacheEntryHeap: 7ffcba586000(9000:1000) Size: 0x1000 (4096) bytes total. Total size: Size: 0x245000 (2379776) bytes total, 0x6000 (24576) bytes wasted. ---------------------------------------- Domain 1: 01aac3a8d640 No unique loader heaps found. ---------------------------------------- JIT Manager: 01aac3a8fcd0 LoaderCodeHeap: 7ffcba730000(80000:a000) Size: 0xa000 (40960) bytes total. Total size: Size: 0xa000 (40960) bytes total. ---------------------------------------- ======================================== Number of GC Heaps: 1 ---------------------------------------- Small object heap segment begin allocated committed allocated size committed size generation 0: 01ead984f950 01aaca800020 01aaca814fe8 01aaca821000 0x14fc8 (85960) 0x21000 (135168) generation 1: 01ead984f1c0 01aac7c00020 01aac7c00020 01aac7c11000 0x11000 (69632) generation 2: 01ead984f320 01aac8400020 01aac84132e0 01aac8421000 0x132c0 (78528) 0x21000 (135168) Large object heap segment begin allocated committed allocated size committed size 01ead984f3d0 01aac8800020 01aac8800020 01aac8801000 0x1000 (4096) Pinned object heap segment begin allocated committed allocated size committed size 01ead984ec40 01aac5c00020 01aac5c08c18 01aac5c11000 0x8bf8 (35832) 0x11000 (69632) ------------------------------ GC Allocated Heap Size: Size: 0x30e80 (200320) bytes. GC Committed Heap Size: Size: 0x65000 (413696) bytes. Total bytes consumed by CLR: 0x2b4000 (2834432) </pre> <br /> 출력 결과에 따라 짐작해 보면, 가장 앞서 있는 "Pinned object heap" 영역이 01aac5c00020이라고 나오므로 (GC Handle의 범위인) 000001AAC5501178 ~ 000001AAC5501DF8은 그것보다 앞선 영역에 특별하게 할당돼 있는 것입니다.<br /> <br /> 암튼, GC도 참... 어지간히 꽤나 많은 일을 하는 것 같습니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 마지막으로, 위의 경우에는 운이 좋았는지 DumpClass의 결과에 ThreadStatic 변수의 내용이 함께 출력되었지만, 이게 언제나 가능한 것은 아닙니다. 일례로, 예제를 다음과 같은 식으로 바꿨더니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace ConsoleApp1; internal class Program { static void Main(string[] args) { MyClass.Obj = new Program(); MyClass.N1 = 1; MyClass.N2 = 2; MyClass.N3 = 3; MyClass.N4 = 4; MyClass.N5 = 5; MyClass.N6 = 6; MyClass.N7 = 7; Console.ReadLine(); } } class MyClass { [ThreadStatic] public static Program? Obj; [ThreadStatic] public static int N1; [ThreadStatic] public static long N2; [ThreadStatic] public static int N3; [ThreadStatic] public static int N4; [ThreadStatic] public static int N5; [ThreadStatic] public static int N6; [ThreadStatic] public static int N7; } </pre> <br /> 아래와 같은 식으로 windbg가 출력을 했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:015> !DumpClass /d 00007ffcba94edd8 Class Name: ConsoleApp1.MyClass mdToken: 0000000002000007 File: C:\temp\ConsoleApp1\bin\Debug\net7.0\ConsoleApp1.dll Parent Class: 00007ffcba580e98 Module: 00007ffcba80cf48 Method Table: 00007ffcba976b28 Vtable Slots: 4 Total Method Slots: 5 Class Attributes: 100000 NumInstanceFields: 0 NumStaticFields: 8 NumThreadStaticFields: 8 MT Field Offset Type VT Attr Value Name 00007ffcba80ef98 4000004 0 ConsoleApp1.Program 0 TLstatic Obj >> Thread:Value << 00007ffcba6ae8d0 4000005 28 System.Int32 1 TLstatic N1 >> Thread:Value << 00007ffcba6f2058 4000006 20 System.Int64 1 TLstatic N2 >> Thread:Value << 00007ffcba6ae8d0 4000007 2c System.Int32 1 TLstatic N3 >> Thread:Value << 00007ffcba6ae8d0 4000008 30 System.Int32 1 TLstatic N4 >> Thread:Value << 00007ffcba6ae8d0 4000009 34 System.Int32 1 TLstatic N5 >> Thread:Value << 00007ffcba6ae8d0 400000a 38 System.Int32 1 TLstatic N6 >> Thread:Value << 00007ffcba6ae8d0 400000b 3c System.Int32 1 TLstatic N7 >> Thread:Value << </pre> <br /> 보다시피 ">> Thread:Value <<" 문자열만 출력될 뿐 실제 값은 나오지 않고 있는 것입니다. 이와 관련해 검색해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Inspecting thread static value for generic class failed in SOS ; <a target='tab' href='https://github.com/dotnet/diagnostics/issues/818'>https://github.com/dotnet/diagnostics/issues/818</a> </pre> <br /> 제네릭 유형인 경우에도 값이 안 나온다는 정도의 이슈만 나옵니다. 뭔가, ThreadStatic 값을 가져오는 코드가 그다지 매끄럽게 작성된 것은 아닌 듯합니다.<br /> <br /> 따라서, 아마도 실제 응용 프로그램의 덤프에서 ThreadStatic 값을 확인할 수 있는 경우는 쉽지 않을 것입니다. 그래도 값 형식이 아닌, 참조 형식인 경우라면 그나마 Handle Table을 뒤져 보거나, gcroot를 이용해 유추할 수 있는 정도로 유추는 할 수 있습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1486
(왼쪽의 숫자를 입력해야 합니다.)