Microsoft MVP성태의 닷넷 이야기
.NET Framework: 185. windbg로 확인하는 .NET CLR 메서드 [링크 복사], [링크+제목 복사],
조회: 26056
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 3개 있습니다.)
windbg로 확인하는 .NET CLR 메서드

windbg와 sos 명령어 실습 겸 CLR 메서드에 대한 JIT 컴파일 여부를 확인해 볼 텐데요. 대략 다음의 글에 대한 실습 과정이라고 해야겠지요. ^^

Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects
; https://learn.microsoft.com/en-us/archive/msdn-magazine/2005/may/net-framework-internals-how-the-clr-creates-runtime-objects

실습을 위해 다음과 같이 간단한 코드를 만들어서 실행시키고,

using System;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            MyMethod();
            Console.ReadLine();
        }

        static void MyMethod()
        {
            Console.WriteLine("MyMethod");
        }

        static void MyMethod2()
        {
            Console.WriteLine("MyMethod2");
        }
    }
}

멈춘 그 시점에 windbg로 연결하고 다음과 같이 sos.dll을 로딩시켜 줍니다. (여기서는 x64 / .NET 3.5 환경에서 테스트합니다.)

0:003> .load "C:\\Windows\\Microsoft.NET\\Framework64\\v2.0.50727\\SOS.dll"

SOS.dll (SOS Debugging Extension)
; https://learn.microsoft.com/en-us/dotnet/framework/tools/sos-dll-sos-debugging-extension

우선, 기계어로 번역되지 않은 MyMethod2 먼저 확인해 보겠습니다.

0:003> !name2ee ConsoleApplication1.exe!ConsoleApplication1.Program.MyMethod2   
Module: 000007ff000433d0 (ConsoleApplication1.exe)
Token: 0x0000000006000003
MethodDesc: 000007ff000439a8
Name: ConsoleApplication1.Program.MyMethod2()
Not JITTED yet. Use !bpmd -md 000007ff000439a8 to break on run.

0:003> !dumpmd 000007ff000439a8
Method Name: ConsoleApplication1.Program.MyMethod2()
Class: 000007ff00182210
MethodTable: 000007ff000439d0
mdToken: 06000003
Module: 000007ff000433d0
IsJitted: no
CodeAddr: ffffffffffffffff

JIT 컴파일링이 된 MyMethod를 확인해 보면 다음과 같습니다.

0:003> !name2ee ConsoleApplication1.exe!ConsoleApplication1.Program.MyMethod
Module: 000007ff000433d0 (ConsoleApplication1.exe)
Token: 0x0000000006000002
MethodDesc: 000007ff00043998
Name: ConsoleApplication1.Program.MyMethod()
JITTED Code Address: 000007ff00190180

0:003> !dumpmd 000007ff00043998
Method Name: ConsoleApplication1.Program.MyMethod()
Class: 000007ff00182210
MethodTable: 000007ff000439d0
mdToken: 06000002
Module: 000007ff000433d0
IsJitted: yes
CodeAddr: 000007ff00190180

JIT 컴파일된 기계어 주소 "CodeAddr: 000007ff00190180"에 대해서 "u" 명령어를 내려서 확인하는 것도 가능합니다.

0:003> u 000007ff00190180 Lf  // 또는 !u [MethodDesc 값]
000007ff`00190180 4883ec28        sub     rsp,28h
000007ff`00190184 90              nop
000007ff`00190185 48b8c8360400ff070000 mov rax,7FF000436C8h
000007ff`0019018f 8b00            mov     eax,dword ptr [rax]
000007ff`00190191 85c0            test    eax,eax
000007ff`00190193 7405            je      000007ff`0019019a
000007ff`00190195 e846b29ef1      call    mscorwks!JIT_DbgIsJustMyCode (000007fe`f1b7b3e0)
000007ff`0019019a 90              nop
000007ff`0019019b 48b950306e1200000000 mov rcx,126E3050h
000007ff`001901a5 488b09          mov     rcx,qword ptr [rcx]
000007ff`001901a8 e8b3908ef0      call    mscorlib_ni+0x319260 (000007fe`f0a79260)  // Console.WriteLine
000007ff`001901ad 90              nop
000007ff`001901ae eb00            jmp     000007ff`001901b0
000007ff`001901b0 4883c428        add     rsp,28h
000007ff`001901b4 f3c3            rep ret

IL 코드도 확인해 볼까요?

0:003> !dumpil 000007ff00043998
ilAddr = 0000000000332065
IL_0000: nop 
IL_0001: ldstr "MyMethod"
IL_0006: call System.Console::WriteLine 
IL_000b: nop 
IL_000c: ret 

보시는 것처럼 ilAddr == 0000000000332065 주소에 MyMethod의 Body가 확인됩니다. 풀어볼까요? ^^

0:003> db 0000000000332065 Le
00000000`00332065  36 00 72 01 00 00 70 28-11 00 00 0a 00 2a        6.r...p(.....*

0x36 == Tiny header
    typedef struct IMAGE_COR_ILMETHOD_TINY
    {
        BYTE Flags_CodeSize;
    } IMAGE_COR_ILMETHOD_TINY;
    
    0x36 == 00110110b
    하위 2bit 10b == CorILMethod_TinyFormat
    상위 6bit 001101b == 0xd IL 코드 크기 (즉, 이후 13byte들은 실행되어야 할 IL코드)
        
0x00 == nop
0x72 == ldstr
0x01 0x00 0x00 0x70 == [0x70000001 문자열 token]
0x28 == call
0x11 0x00 0x00 0x0a == [0x0a000011 메서드 token]
0x00 == nop
0x2a == ret




JIT 컴파일 되기 전과 후를 windbg의 MethodDesc 테이블을 확인해 보면 알 수 있습니다.


0:003> !DumpMT -MD 000007ff000439d0
EEClass: 000007ff00182210
Module: 000007ff000433d0
Name: ConsoleApplication1.Program
mdToken: 02000002  (D:\...[경로생략]...\ConsoleApplication1.exe)
BaseSize: 0x18
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 9
--------------------------------------
MethodDesc Table
           Entry       MethodDesc      JIT Name
000007fef0a4abe0 000007fef07ce828   PreJIT System.Object.ToString()
000007fef0a52560 000007fef07ce830   PreJIT System.Object.Equals(System.Object)
000007fef0a4bc70 000007fef07ce870   PreJIT System.Object.GetHashCode()
000007fef0afe5f0 000007fef07ce8a0   PreJIT System.Object.Finalize()
000007ff0004c040 000007ff000439c8     NONE ConsoleApplication1.Program..ctor()
000007ff00190120 000007ff00043988      JIT ConsoleApplication1.Program.Main(System.String[])
000007ff00190180 000007ff00043998      JIT ConsoleApplication1.Program.MyMethod()
000007ff0004c030 000007ff000439a8     NONE ConsoleApplication1.Program.MyMethod2()
000007ff001901e0 000007ff000439b8      JIT ConsoleApplication1.Program.ShowMethodDesc()

Entry 값이 "000007ff0004c030"이므로, IL 코드에서 이 함수를 부를 때 "000007ff0004c030" 주소에 있는 코드를 실행하게 됩니다. 어떤 코드인지는 감이 잡히시죠?

0:003> u 000007ff0004c030 
000007ff`0004c030 e8bbea8bf1      call    mscorwks!PrecodeFixupThunk (000007fe`f190aaf0)
000007ff`0004c035 cc              int     3
000007ff`0004c036 0402            add     al,2
000007ff`0004c038 e8b3ea8bf1      call    mscorwks!PrecodeFixupThunk (000007fe`f190aaf0)
000007ff`0004c03d cc              int     3
000007ff`0004c03e 06              ???
000007ff`0004c03f 01e8            add     eax,ebp
000007ff`0004c041 ab              stos    dword ptr [rdi]

물론, JIT된 이후에는 Entry 값이 교체되는데, 그것이 바로 실제 기계어로 번역된 코드의 주솟값입니다.

재미있게도, 위의 확인 과정은 Reflection으로도 가능합니다.

위에서 name2ee를 통해서 "MethodDesc" 값을 확인해 보았는데요. 코드에서는 다음과 같이 MethodInfo.MethodHandle.Value 값이 MethodDesc 값과 동일하다는 것을 확인할 수 있습니다.

MethodInfo methodInfo = typeof(Program).GetMethod("MyMethod", 
        System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);

IntPtr methodDesc = methodInfo.MethodHandle.Value;

기계어로 번역되기 전과 후의 주소는 다음과 같이 확인해 볼 수 있습니다.

MethodInfo methodInfo = ...;
IntPtr pAddress = methodInfo.MethodHandle.GetFunctionPointer();
Console.WriteLine("Stub or Jitted Address == " + pAddress.ToString("x"));

JIT 컴파일되기 전과 후에 각각 MethodHandle.GetFunctionPointer() 값을 구하면 서로 다르다는 것을 확인할 수 있고 그 값을 windbg의 MethodDesc 테이블에 있는 Entry 값과 비교해보면 동일한 것을 알 수 있습니다.

첨부한 솔루션은 이를 확인해 본 간단한 소스 코드입니다.



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

[연관 글]






[최초 등록일: ]
[최종 수정일: 6/28/2023]

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)
10949정성태4/28/201619862.NET Framework: 575. SharedDomain과 JIT 컴파일파일 다운로드1
10948정성태4/28/201623810.NET Framework: 574. .NET - 눈으로 확인하는 SharedDomain의 동작 방식 [3]파일 다운로드1
10947정성태4/27/201621664.NET Framework: 573. .NET CLR4 보안 모델 - 4. CLR4 보안 모델에서의 조건부 APTCA 역할파일 다운로드1
10946정성태4/26/201624506VS.NET IDE: 106. Visual Studio 2015 확장 - INI 파일을 위한 사용자 정의 포맷 기능 (Syntax Highlighting)파일 다운로드1
10945정성태4/26/201618238오류 유형: 327. VSIX 프로젝트 빌드 시 The "VsTemplatePaths" task could not be loaded from the assembly 오류 발생
10944정성태4/22/201619496디버깅 기술: 80. windbg - 풀 덤프 파일로부터 텍스트 파일의 내용을 찾는 방법
10943정성태4/22/201624352디버깅 기술: 79. windbg - 풀 덤프 파일로부터 .NET DLL을 추출/저장하는 방법 [1]
10942정성태4/19/201619657디버깅 기술: 78. windbg 사례 - .NET 예외가 발생한 시점의 오류 분석 [1]
10941정성태4/19/201619565오류 유형: 326. Error MSB8020 - The build tools for v120_xp (Platform Toolset = 'v120_xp') cannot be found.
10940정성태4/18/201622815Windows: 116. 프로세스 풀 덤프 시간을 줄여 주는 Process Reflection [3]
10939정성태4/18/201623864.NET Framework: 572. .NET APM 비동기 호출의 Begin...과 End... 조합 [3]파일 다운로드1
10938정성태4/13/201623428오류 유형: 325. 파일 삭제 시 오류 - Error 0x80070091: The directory is not empty.
10937정성태4/13/201631650Windows: 115. UEFI 모드로 윈도우 10 설치 가능한 USB 디스크 만드는 방법
10936정성태4/8/201642319Windows: 114. 삼성 센스 크로노스 7 노트북의 운영체제를 USB 디스크로 새로 설치하는 방법 [3]
10935정성태4/7/201626633웹: 32. Edge에서 Google Docs 문서 편집 시 한영 전환키가 동작 안하는 문제
10934정성태4/5/201625370디버깅 기술: 77. windbg의 콜스택 함수 인자를 쉽게 확인하는 방법 [1]
10933정성태4/5/201630975.NET Framework: 571. C# - 스레드 선호도(Thread Affinity) 지정하는 방법 [8]파일 다운로드1
10932정성태4/4/201623270VC++: 96. C/C++ 식 평가 - printf("%d %d %d\n", a, a++, a);
10931정성태3/31/201623544개발 환경 구성: 283. Hyper-V 내에 구성한 Active Directory 환경의 시간 구성 방법 [3]
10930정성태3/30/201621505.NET Framework: 570. .NET 4.5부터 추가된 CLR Profiler의 실행 시 Rejit 기능
10929정성태3/29/201631602.NET Framework: 569. ServicePointManager.DefaultConnectionLimit의 역할파일 다운로드1
10928정성태3/28/201637315.NET Framework: 568. ODP.NET의 완전한 닷넷 버전 Oracle ODP.NET, Managed Driver [2]파일 다운로드1
10927정성태3/25/201626539.NET Framework: 567. System.Net.ServicePointManager의 DefaultConnectionLimit 속성 설명
10926정성태3/24/201626065.NET Framework: 566. openssl의 PKCS#1 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 [10]파일 다운로드1
10925정성태3/24/201620377.NET Framework: 565. C# - Rabin-Miller 소수 생성 방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 - 두 번째 이야기파일 다운로드1
10924정성태3/22/201621009오류 유형: 324. Visual Studio에서 Azure 클라우드 서비스 생성 시 Failed to initialize the PowerShell host 에러 발생
... 106  107  108  109  110  111  112  113  114  115  116  117  118  [119]  120  ...