Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)

windbg - GC Heap에서 .NET 타입에 대한 배열을 찾는 방법

.NET 입장에서, 배열은 결국 System.Array를 상속받은 개체로 다뤄집니다. 지난번 이야기에서 "System.Char[]" 개체가 "dumpheap -stat" 명령으로 확인되었던 것을 볼 수 있었는데요.

StringBuilder에서의 OutOfMemoryException 오류 원인 분석
; https://www.sysnet.pe.kr/2/0/1171

Primitive 타입에 대한 배열은 그렇다 치고, 기타 타입들에 대한 배열은 "dumpheap -stat"에서 직접적으로 보이지 않습니다. 이에 대한 이해를 돕기 위해 예제를 다뤄볼까요?

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Program[] pgList = new Program[0];
            Thread.Sleep(-1);
        }
    }
}

위와 같은 코드로 작성된 프로그램을 windbg로 로드하고 Thread.Sleep까지 진행 시킨 후, Heap을 살펴보면 다음과 같은 결과를 얻을 수 있습니다.

0:004> .loadby sos clr

0:004>  !dumpheap -stat
total 0 objects
Statistics:
      MT    Count    TotalSize Class Name
72fd5f10        1           12 System.Security.HostSecurityManager
72fd5938        1           12 System.Collections.Generic.ObjectEqualityComparer`1[[System.Type, mscorlib]]
72fd4b44        1           12 System.Security.Permissions.ReflectionPermission
72fd4a60        1           12 System.Security.Permissions.FileDialogPermission
72fd4980        1           12 System.Security.PolicyManager
72fd4898        1           12 System.Security.SecurityDocument
72fd27fc        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]]
72fd5ec8        1           16 System.Security.Policy.Evidence+EvidenceLockHolder
72fd4bf4        1           16 System.Security.Permissions.UIPermission
72fd4640        1           16 System.Security.Util.Parser
... [생략] ...
72fcf92c      469        15652 System.String
72f86ba8       67        19148 System.Object[]
Total 784 objects

예상대로라면, Count == 1, TotalSize == 12 조건을 만족하는 목록 중에서 "ConsoleApplication1.Program[]"인 항목이 나와야 하는데 없는 것입니다. 왜 그럴까요?

아쉽게도 (네, 아쉽습니다.) Primitive 타입을 제외하고는 모든 타입에 대해서 "System.Object[]"로 통털어서 출력이 됩니다. 그래서, 다음과 같이 부가적인 검사를 해야 합니다.

0:004> !dumpheap -mt 72f86ba8       
 Address       MT     Size
027d18e4 72f86ba8       88     
027d5578 72f86ba8       56     
027d55d0 72f86ba8       20     
027d55e4 72f86ba8       32     
027d5604 72f86ba8       20     
... [생략] ...
037d1010 72f86ba8     4096     
037d2020 72f86ba8      528     
037d2240 72f86ba8     4096     
total 0 objects
Statistics:
      MT    Count    TotalSize Class Name
72f86ba8       67        19148 System.Object[]
Total 67 objects

그런데, 과연 67개의 인스턴스 중에서 어떤 것이 Program[]의 개체일까요? (크기를 정확하게 알고 있다면 대상이 좀 더 축소가 되겠지만) 현실적으로는 방법이 없습니다. 67개의 개체 모두 일일이 "!do" 명령어를 내려서 확인해야 합니다. 물론, 위의 경우에는 작은 프로그램이니 그나마 쉽게(?) 찾을 수 있겠지만 대형 프로그램의 경우에는 어림도 없는 작업입니다.

대신 그때그때 상황에 맞게 지름길을 선택하는 정도가 최선일 텐데요. 가령 예를 들어서, 위와 같은 예제 코드에서 pgList 인스턴스가 아직 스택에 존재할 거라는 정보가 있기 때문에 이를 찾아나서면 됩니다.

우선, "~*e!CLRStack" 명령어를 이용해서 pgList를 보유하고 있는 메서드를 어떤 스레드에서 실행하고 있는지 확인해야 하는데,

0:004> ~*e!CLRStack
OS Thread Id: 0x2490 (0)
Child SP IP       Call Site
003af0dc 7762fd81 [HelperMethodFrame: 003af0dc] System.Threading.Thread.SleepInternal(Int32)
003af128 72f03409 System.Threading.Thread.Sleep(Int32)
003af12c 004b00e9 ConsoleApplication1.Program.Main(System.String[]) [D:\...\Program.cs @ 17]
003af398 73b421bb [GCFrame: 003af398] 
OS Thread Id: 0x1dd8 (1)
Unable to walk the managed stack. The current thread is likely not a managed thread. You can run !threads to get a list of managed threads in the process
OS Thread Id: 0x2c2c (2)
Child SP IP       Call Site
00fdfdd8 7763014d [DebuggerU2MCatchHandlerFrame: 00fdfdd8] 
OS Thread Id: 0x312c (3)
Unable to walk the managed stack. The current thread is likely not a managed thread. You can run !threads to get a list of managed threads in the process
OS Thread Id: 0x2ee8 (4)
Unable to walk the managed stack. The current thread is likely not a managed thread. You can run !threads to get a list of managed threads in the process

총 5개의 스레드가 실행되는 중에 우리가 원하는 메서드(Main)를 실행 중인 것은 0번 인덱스의 0x2490 스레드임을 알 수 있습니다. 또한, 현재 windbg의 스레드 문맥은 "0:004>" 프롬프트에서 알려주듯이 4번 인덱스의 스레드이므로 이후의 명령어를 진행하기에 앞서 스레드 전환을 해줍니다.

0:004> ~0s
eax=00000000 ebx=00000001 ecx=00000000 edx=00000000 esi=003aeff8 edi=00000000
eip=7762fd81 esp=003aefb4 ebp=003af01c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!NtDelayExecution+0x15:
7762fd81 83c404          add     esp,4

위와 같이 "~[스레드 인덱스 번호]s" 명령어 이외에, windbg의 "View" / "Processes and Threads (Alt+9)" 메뉴를 이용하여 GUI 윈도우에서 마우스를 이용하여 전환하는 것도 가능합니다.

how_to_find_type_array_mt_1.png

그다음, 현재 선택된 스레드 문맥에 포함된 stack object들을 모두 나열시키면,

0:000> !dso
OS Thread Id: 0x2490 (0)
ESP/REG  Object   Name
003AF09C 027dbf80 System.Object[]    (System.String[])
003AF12C 027dbf90 System.Object[]    (ConsoleApplication1.Program[])
003AF130 027dbf90 System.Object[]    (ConsoleApplication1.Program[])
003AF134 027dbf90 System.Object[]    (ConsoleApplication1.Program[])
003AF140 027dbf80 System.Object[]    (System.String[])
003AF214 027dbf80 System.Object[]    (System.String[])
003AF3BC 027dbf80 System.Object[]    (System.String[])
003AF3F0 027dbf80 System.Object[]    (System.String[])

Name 필드에 친절하게도 어떤 타입의 배열인지 알려주기 때문에 쉽게 Object 주소를 알 수 있습니다. 이후의 Method Table이나 EEClass를 찾는 것은 목적에 따라 실행해 주시면 되겠고. ^^

0:000> !do 027dbf90 
Name:        System.Object[]
MethodTable: 72f86ba8
EEClass:     72d09688
Size:        20(0x14) bytes
Array:       Rank 1, Number of elements 1, Type CLASS
Element Type:ConsoleApplication1.Program
Fields:
None

0:000> !dumpmt 72f86ba8
EEClass:      72d09688
Module:       72cb1000
Name:         System.Object[]
mdToken:      02000000
File:         C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
BaseSize:        0x10
ComponentSize:   0x4
Slots in VTable: 28
Number of IFaces in IFaceMap: 6

0:000> !dumpclass 72d09688
Class Name:      System.Object[]
mdToken:         02000000
File:            C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Parent Class:    72d08ad8
Module:          72cb1000
Method Table:    72f86ba8
Vtable Slots:    18
Total Method Slots:  1c
Class Attributes:    2101  
Transparency:        Transparent
NumInstanceFields:   0
NumStaticFields:     0




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 9/28/2024]

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)
11149정성태2/21/201722906오류 유형: 378. A 64-bit test cannot run in a 32-bit process. Specify platform as X64 to force test run in X64 mode on X64 machine.
11148정성태2/20/201721900.NET Framework: 644. AppDomain에 대한 단위 테스트 시 알아야 할 사항
11147정성태2/19/201721126오류 유형: 377. Windows 10에서 Fake 어셈블리를 생성하는 경우 빌드 시 The type or namespace name '...' does not exist in the namespace 컴파일 오류 발생
11146정성태2/19/201719752오류 유형: 376. Error VSP1033: The file '...' does not contain a recognized executable image. [2]
11145정성태2/16/201721241.NET Framework: 643. 작업자 프로세스(w3wp.exe)가 재시작되는 시점을 알 수 있는 방법 - 두 번째 이야기 [4]파일 다운로드1
11144정성태2/6/201724598.NET Framework: 642. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (부록 1) - CallingConvention.StdCall, CallingConvention.Cdecl에 상관없이 왜 호출이 잘 될까요?파일 다운로드1
11143정성태2/5/201722034.NET Framework: 641. [Out] 형식의 int * 인자를 가진 함수에 대한 P/Invoke 호출 방법파일 다운로드1
11142정성태2/5/201730020.NET Framework: 640. 닷넷 - 배열 크기의 한계 [2]파일 다운로드1
11141정성태1/31/201724280.NET Framework: 639. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (4) - CLR JIT 컴파일러의 P/Invoke 호출 규약 [1]파일 다운로드1
11140정성태1/27/201720056.NET Framework: 638. RSAParameters와 RSA파일 다운로드1
11139정성태1/22/201722737.NET Framework: 637. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (3) - x64 환경의 __fastcall과 Name mangling [1]파일 다운로드1
11138정성태1/20/201721018VS.NET IDE: 113. 프로젝트 생성 시부터 "Enable the Visual Studio hosting process" 옵션을 끄는 방법 - 두 번째 이야기 [3]
11137정성태1/20/201719737Windows: 135. AD에 참여한 컴퓨터로 RDP 연결 시 배경 화면을 못 바꾸는 정책
11136정성태1/20/201718897오류 유형: 375. Hyper-V 내에 구성한 Active Directory 환경의 시간 구성 방법 - 두 번째 이야기
11135정성태1/20/201719925Windows: 134. Windows Server 2016의 작업 표시줄에 있는 시계가 사라졌다면? [1]
11134정성태1/20/201727357.NET Framework: 636. System.Threading.Timer를 이용해 타이머 작업을 할 때 유의할 점 [5]파일 다운로드1
11133정성태1/20/201723495.NET Framework: 635. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (2) - x86 환경의 __fastcall [1]파일 다운로드1
11132정성태1/19/201734975.NET Framework: 634. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (1) - x86 환경에서의 __cdecl, __stdcall에 대한 Name mangling [1]파일 다운로드1
11131정성태1/13/201723886.NET Framework: 633. C# - IL 코드 분석을 위한 팁 [2]
11130정성태1/11/201724412.NET Framework: 632. x86 실행 환경에서 SECURITY_ATTRIBUTES 구조체를 CreateEvent에 전달할 때 예외 발생파일 다운로드1
11129정성태1/11/201728769.NET Framework: 631. async/await에 대한 "There Is No Thread" 글의 부가 설명 [9]파일 다운로드1
11128정성태1/9/201723192.NET Framework: 630. C# - Interlocked.CompareExchange 사용 예제 [3]파일 다운로드1
11127정성태1/8/201722730기타: 63. (개발자를 위한) Visual Studio의 "with MSDN" 라이선스 설명
11126정성태1/7/201727494기타: 62. Edge 웹 브라우저의 즐겨찾기(Favorites)를 편집/백업/복원하는 방법 [1]파일 다운로드1
11125정성태1/7/201724304개발 환경 구성: 310. IIS - appcmd.exe를 이용해 특정 페이지에 클라이언트 측 인증서를 제출하도록 설정하는 방법
11124정성태1/4/201727777개발 환경 구성: 309. 3년짜리 유효 기간을 제공하는 StartSSL [2]
... 106  107  108  109  110  [111]  112  113  114  115  116  117  118  119  120  ...