Microsoft MVP성태의 닷넷 이야기
.NET Framework: 185. windbg로 확인하는 .NET CLR 메서드 [링크 복사], [링크+제목 복사],
조회: 26032
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 121  [122]  123  124  125  126  127  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
10873정성태11/25/201525432.NET Framework: 540. C# - 부동 소수 계산 왜 이렇게 나오죠? (2) [3]파일 다운로드1
10872정성태11/24/201532395.NET Framework: 539. C# - 부동 소수 계산 왜 이렇게 나오죠? (1) [1]
10871정성태11/23/201528027오류 유형: 313. SignTool Error: No certificates were found that met all the given criteria.
10870정성태11/23/201528884오류 유형: 312. 윈도우 10 TH2 (버전 1511) 업데이트가 안되는 경우 [1]
10869정성태11/23/201524631오류 유형: 311. certutil 실행 오류 - 0x80070057 [1]
10868정성태11/20/201524501제니퍼 .NET: 25. 제니퍼 닷넷 적용 사례 (5) - RestSharp 라이브러리의 CPU High 현상파일 다운로드1
10867정성태10/18/201527063.NET Framework: 538. Thread.Abort로 인해 프로세스가 종료되는 현상
10866정성태10/14/201523316.NET Framework: 537. C# - Reflection의 박싱 없이 값 형식을 다루는 방법파일 다운로드1
10865정성태10/13/201523073.NET Framework: 536. Thread.Abort의 스레드 종료 지연파일 다운로드1
10864정성태10/12/201521258.NET Framework: 535. aspnet.config 파일의 설정을 읽는 방법
10863정성태10/9/201526081.NET Framework: 534. ASP.NET 응용 프로그램이 예외로 프로세스가 종료된다면?
10862정성태10/9/201524443오류 유형: 310. 비주얼 스튜디오 - Unspecified error (Exception from HRESULT: 0x80004005 (E_FAIL))
10861정성태10/9/201529078기타: 54. 도서: 시작하세요! C# 6.0 프로그래밍: 기본 문법부터 실전 예제까지 (2)
10860정성태10/5/201526539개발 환경 구성: 277. IIS AppPool의 시작/중단에 대한 이벤트 로그 확인 방법
10859정성태10/5/201527744.NET Framework: 533. C#에서 string 형식이 primitive일까요? [6]
10858정성태10/2/201524234VS.NET IDE: 105. Visual Studio의 단위 테스트 작성 시 Fakes를 이용한 메서드 재정의 방법 [1]파일 다운로드1
10857정성태10/1/201520280VS.NET IDE: 104. Visual C++ 프로젝트의 빌드 이벤트에서 환경 변수 사용하는 방법
10856정성태9/30/201531499.NET Framework: 532. WPF DataGrid의 데이터 바인딩 시 리플렉션의 부하는 어느 정도일까요?파일 다운로드1
10855정성태9/30/201521255.NET Framework: 531. C# - XSLT 내의 javascript에 전달한 XML 노드의 타입은?
10854정성태9/30/201521777오류 유형: 309. C# - 포인터를 쓰는 경우 VerificationException이 발생한다면?
10853정성태9/21/201519388오류 유형: 308. 공백 문자를 포함한 계정명의 권한으로 Visual Studio 확장을 설치할 때 오류 발생
10852정성태9/17/201524451VC++: 92. C++ 생성자를 DLL로부터 동적 로드해 객체를 생성한다면? [2]파일 다운로드1
10851정성태9/15/201524229.NET Framework: 530. C# - 중위식을 후위식으로 변환하는 예제파일 다운로드1
10850정성태9/14/201522923.NET Framework: 529. C# - volatile 키워드로 인한 차이점을 발생시키는 예제 [1]파일 다운로드1
10849정성태9/14/201556980오류 유형: 307. CLR20r3 오류 해결을 위해 mscorlib.dll을 덮어쓸때 주의할 점 [12]
10848정성태9/8/201527336VS.NET IDE: 103. Visual Studio의 Ctrl + F5 실행 동작파일 다운로드1
... 121  [122]  123  124  125  126  127  128  129  130  131  132  133  134  135  ...