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

비밀번호

댓글 작성자
 




... 16  17  18  19  [20]  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13153정성태11/2/20227363.NET Framework: 2062. C# - 코드로 재현하는 소켓 상태(SYN_SENT, SYN_RECV)
13152정성태11/1/20225957.NET Framework: 2061. ASP.NET Core - DI로 추가한 클래스의 초기화 방법 [1]
13151정성태10/31/20226138C/C++: 161. Windows 11 환경에서 raw socket 테스트하는 방법파일 다운로드1
13150정성태10/30/20226049C/C++: 160. Visual Studio 2022로 빌드한 C++ 프로그램을 위한 다른 PC에서 실행하는 방법
13149정성태10/27/20226038오류 유형: 825. C# - CLR ETW 이벤트 수신이 GCHeapStats_V1/V2에 대해 안 되는 문제파일 다운로드1
13148정성태10/26/20225960오류 유형: 824. msbuild 에러 - error NETSDK1005: Assets file '...\project.assets.json' doesn't have a target for 'net5.0'. Ensure that restore has run and that you have included 'net5.0' in the TargetFramew
13147정성태10/25/20225047오류 유형: 823. Visual Studio 2022 - Unable to attach to CoreCLR. The debugger's protocol is incompatible with the debuggee.
13146정성태10/24/20225895.NET Framework: 2060. C# - Java의 Xmx와 유사한 힙 메모리 최댓값 제어 옵션 HeapHardLimit
13145정성태10/21/20226224오류 유형: 822. db2 - Password validation for user db2inst1 failed with rc = -2146500508
13144정성태10/20/20226118.NET Framework: 2059. ClrMD를 이용해 윈도우 환경의 메모리 덤프로부터 닷넷 모듈을 추출하는 방법파일 다운로드1
13143정성태10/19/20226622오류 유형: 821. windbg/sos - Error code - 0x000021BE
13142정성태10/18/20225981도서: 시작하세요! C# 12 프로그래밍
13141정성태10/17/20227227.NET Framework: 2058. [in,out] 배열을 C#에서 C/C++로 넘기는 방법 - 세 번째 이야기파일 다운로드1
13140정성태10/11/20226571C/C++: 159. C/C++ - 리눅스 환경에서 u16string 문자열을 출력하는 방법 [2]
13139정성태10/9/20226240.NET Framework: 2057. 리눅스 환경의 .NET Core 3/5+ 메모리 덤프로부터 모든 닷넷 모듈을 추출하는 방법파일 다운로드1
13138정성태10/8/20227647.NET Framework: 2056. C# - await 비동기 호출을 기대한 메서드가 동기로 호출되었을 때의 부작용 [1]
13137정성태10/8/20225931.NET Framework: 2055. 리눅스 환경의 .NET Core 3/5+ 메모리 덤프로부터 닷넷 모듈을 추출하는 방법
13136정성태10/7/20226513.NET Framework: 2054. .NET Core/5+ SDK 설치 없이 dotnet-dump 사용하는 방법
13135정성태10/5/20226782.NET Framework: 2053. 리눅스 환경의 .NET Core 3/5+ 메모리 덤프를 분석하는 방법 - 두 번째 이야기
13134정성태10/4/20225462오류 유형: 820. There is a problem with AMD Radeon RX 5600 XT device. For more information, search for 'graphics device driver error code 31'
13133정성태10/4/20225847Windows: 211. Windows - (commit이 아닌) reserved 메모리 사용량 확인 방법 [1]
13132정성태10/3/20225735스크립트: 42. 파이썬 - latexify-py 패키지 소개 - 함수를 mathjax 식으로 표현
13131정성태10/3/20228570.NET Framework: 2052. C# - Windows Forms의 데이터 바인딩 지원(DataBinding, DataSource) [2]파일 다운로드1
13130정성태9/28/20225430.NET Framework: 2051. .NET Core/5+ - 에러 로깅을 위한 Middleware가 동작하지 않는 경우파일 다운로드1
13129정성태9/27/20225731.NET Framework: 2050. .NET Core를 IIS에서 호스팅하는 경우 .NET Framework CLR이 함께 로드되는 환경
13128정성태9/23/20228418C/C++: 158. Visual C++ - IDL 구문 중 "unsigned long"을 인식하지 못하는 #import파일 다운로드1
... 16  17  18  19  [20]  21  22  23  24  25  26  27  28  29  30  ...