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

비밀번호

댓글 작성자
 




... 46  47  48  49  50  51  52  53  54  55  [56]  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12540정성태2/17/202118352.NET Framework: 1024. C# - Win32 API에 대한 P/Invoke를 대신하는 Microsoft.Windows.CsWin32 패키지
12539정성태2/16/202118256Windows: 189. WM_TIMER의 동작 방식 개요파일 다운로드1
12538정성태2/15/202118728.NET Framework: 1023. C# - GC 힙이 아닌 Native 힙에 인스턴스 생성 - 0SuperComicLib.LowLevel 라이브러리 소개 [2]
12537정성태2/11/202119381.NET Framework: 1022. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! - 두 번째 이야기 [2]
12536정성태2/9/202118240개발 환경 구성: 542. BDP(Bandwidth-delay product)와 TCP Receive Window
12535정성태2/9/202117338개발 환경 구성: 541. Wireshark로 확인하는 LSO(Large Send Offload), RSC(Receive Segment Coalescing) 옵션
12534정성태2/8/202117859개발 환경 구성: 540. Wireshark + C/C++로 확인하는 TCP 연결에서의 closesocket 동작 [1]파일 다운로드1
12533정성태2/8/202116864개발 환경 구성: 539. Wireshark + C/C++로 확인하는 TCP 연결에서의 shutdown 동작파일 다운로드1
12532정성태2/6/202118045개발 환경 구성: 538. Wireshark + C#으로 확인하는 ReceiveBufferSize(SO_RCVBUF), SendBufferSize(SO_SNDBUF) [3]
12531정성태2/5/202116814개발 환경 구성: 537. Wireshark + C#으로 확인하는 PSH flag와 Nagle 알고리듬파일 다운로드1
12530정성태2/4/202120633개발 환경 구성: 536. Wireshark + C#으로 확인하는 TCP 통신의 Receive Window
12529정성태2/4/202118521개발 환경 구성: 535. Wireshark + C#으로 확인하는 TCP 통신의 MIN RTO [1]
12528정성태2/1/202118135개발 환경 구성: 534. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 윈도우 환경
12527정성태2/1/202118230개발 환경 구성: 533. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 리눅스 환경파일 다운로드1
12526정성태2/1/202115058개발 환경 구성: 532. Azure Devops의 파이프라인 빌드 시 snk 파일 다루는 방법 - Secure file
12525정성태2/1/202113987개발 환경 구성: 531. Azure Devops - 파이프라인 실행 시 빌드 이벤트를 생략하는 방법
12524정성태1/31/202115156개발 환경 구성: 530. 기존 github 프로젝트를 Azure Devops의 빌드 Pipeline에 연결하는 방법 [1]
12523정성태1/31/202116127개발 환경 구성: 529. 기존 github 프로젝트를 Azure Devops의 Board에 연결하는 방법
12522정성태1/31/202118315개발 환경 구성: 528. 오라클 클라우드의 리눅스 VM - 9000 MTU Jumbo Frame 테스트
12521정성태1/31/202117348개발 환경 구성: 527. 이더넷(Ethernet) 환경의 TCP 통신에서 MSS(Maximum Segment Size) 확인 [1]
12520정성태1/30/202116113개발 환경 구성: 526. 오라클 클라우드의 VM에 ping ICMP 여는 방법
12519정성태1/30/202114854개발 환경 구성: 525. 오라클 클라우드의 VM을 외부에서 접근하기 위해 포트 여는 방법
12518정성태1/30/202132966Linux: 37. Ubuntu에 Wireshark 설치 [2]
12517정성태1/30/202120658Linux: 36. 윈도우 클라이언트에서 X2Go를 이용한 원격 리눅스의 GUI 접속 - 우분투 20.04
12516정성태1/29/202117121Windows: 188. Windows - TCP default template 설정 방법
12515정성태1/28/202118790웹: 41. Microsoft Edge - localhost에 대해 http 접근 시 무조건 https로 바뀌는 문제 [3]
... 46  47  48  49  50  51  52  53  54  55  [56]  57  58  59  60  ...