Microsoft MVP성태의 닷넷 이야기
.NET Framework: 890. 상황별 GetFunctionPointer 반환값 정리 - x64 [링크 복사], [링크+제목 복사]
조회: 270
글쓴 사람
홈페이지
첨부 파일

상황별 GetFunctionPointer 반환값 정리 - x64

지난 글에서는,

상황별 GetFunctionPointer 반환값 정리
; https://www.sysnet.pe.kr/2/0/1027

x86을 대상으로만 했었는데, 이번에는 .NET 4.8 / x64 / Debug 빌드로 실행한 결과를 정리해 보겠습니다.


EXE 어셈블리의 Main 메서드

class Program
{
    static void Main(string[] args)
    {
        ShowMainFunc();
    }

    static void ShowMainFunc()
    {
        MethodBase func = typeof(Program).GetMethod("Main", BindingFlags.Static | BindingFlags.NonPublic);
        IntPtr pMain = func.MethodHandle.GetFunctionPointer();
        OutputFunctionAddress("Main", pMain);
    }

    private static void OutputFunctionAddress(string title, IntPtr pAddr)
    {
        Console.WriteLine($"{title} == {pAddr.ToInt64():x}");
    }
}

x86과 결과와 다르지 않습니다. 특이한 점이 있다면 "Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기" 글에서의 PreStub 과정 없이 곧바로 Fixup Precode에서 Native Code를 호출하는 jmp 문으로 패치된다는 점입니다. (Main 함수야말로 단 한번 실행되는 경우가 대부분일 것이므로 굳이 공을 들여 최적화를 안 해도 될 텐데 말이죠. ^^)

또한 MethodDesc 위치의 +8 바이트 위치에 저장한 기계어 코드의 주소도 GetFunctionPointer의 값과 일치합니다.


어셈블리에 정의된 메서드의 JIT 이전

class Program
{
    static void Main(string[] args)
    {
        ShowTestFunc("Test1 - Before JITting", "Test1");
        Console.ReadLine();

        Program pg = new Program();
        pg.Test1();
    }

    private void Test1()
    {
        Console.WriteLine("Test1 called!");
    }

    static void ShowTestFunc(string text, string methodName)
    {
        MethodBase func = typeof(Program).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
        IntPtr pFunc = func.MethodHandle.GetFunctionPointer();
        OutputFunctionAddress(text, pFunc);
    }

    private static void OutputFunctionAddress(string title, IntPtr pAddr)
    {
        Console.WriteLine($"{title} == {pAddr.ToInt64():x}");
    }
}

/* 출력 결과
Test1 - Before JITting == 7ffe48310488
*/

이에 대해서도 "Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기" 글에서 살펴봤습니다. GetFunctionPointer가 반환한 주소는 "Fixup Precode"를 가리키고 실제 호출하려고 할 때의 call 코드의 변위도 정확히 GetFunctionPointer의 반환 주소를 가리킵니다. (따라서 x86의 call 주솟값과는 다르다는 차이점이 있습니다.)

00007ffe`48310912 e871fbffff      call    00007ffe`48310488 (Program.Test1(), mdToken: 0000000006000002)


어셈블리에 정의된 메서드의 JIT 이후

static void Main(string[] args)
{
    ShowTestFunc("Test1 - Before JITting", "Test1");

    Program pg = new Program();
    pg.Test1();

    ShowTestFunc("Test1 - After JITting", "Test1");
}

/* 출력 결과
Test1 - Before JITting == 7ffe48300488
Test1 called!
Test1 - After JITting == 7ffe48300c10
*/

이번에도 "Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기" 글의 내용과 일치합니다.

0:000> !name2ee ConsoleApp1.exe!Program.Test1 
Module:      00007ffe481f4148
Assembly:    ConsoleApp1.exe
Token:       0000000006000002
MethodDesc:  00007ffe481f5a10
Name:        Program.Test1()
JITTED Code Address: 00007ffe48300c10

0:000> dq 00007ffe481f5a10 L2
00007ffe`481f5a10  00080006`21020002 00007ffe`48300c10

정리해 보면, Jit 전에는 Fixup Precode의 위치를 GetFunctionPointer 메서드가 반환하지만, 일단 JIT가 되면 GetFunctionPointer는 해당 메서드의 Body가 컴파일된 메서드의 주솟값을 반환합니다.


NGen 된 BCL(Base Class Library) 메서드

public static void ShowCreateCommandAddress()
{
    MethodBase func = typeof(SqlConnection).GetMethod("CreateCommand", BindingFlags.Instance | BindingFlags.Public);

    IntPtr pOld = func.MethodHandle.GetFunctionPointer();
    OutputFunctionAddress("CreateCommand", pOld);
}

/* 출력 결과
CreateCommand == 7ffe48320510
*/

GetFunctionPointer가 반환한 값(7ffe48320510)은 특이하게 Fixup Precode의 위치입니다. (게다가 method desc을 구하는 인덱스가 모두 0, 0입니다.)

0:000> !u 7ffe48320510
Unmanaged code
00007ffe`48320510 e80b40545f      call    clr!PrecodeFixupThunk (00007ffe`a7864520)
00007ffe`48320515 5e              pop     rsi
00007ffe`48320516 0000            add     byte ptr [rax],al
00007ffe`48320518 081d9d72fe7f    or      byte ptr [00007ffe`c83077bb],bl

0:000> !ip2md  7ffe48320510 // 왜냐하면 fixup precode의 위치이므로.
Failed to request MethodData, not in JIT code range

하지만, 해당 메서드의 MethodDesc 값에는 정확히 기계어로 번역된 주솟값을 가지고 있습니다.

0:000> !name2ee System.Data.dll!System.Data.SqlClient.SqlConnection.CreateCommand
Module:      00007ffe72961000
Assembly:    System.Data.dll
Token:       0000000006001a69
MethodDesc:  00007ffe729d1d08
Name:        System.Data.SqlClient.SqlConnection.CreateCommand()
JITTED Code Address: 00007ffe72ea4410

0:000> dq 00007ffe729d1d08 L2
00007ffe`729d1d08  00006453`3b7c9a69 00000000`004d2700

0:000> ? 00007ffe`729d1d10 + 004d2700
Evaluate expression: 140730826376208 = 00007ffe`72ea4410

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




이 외에 재미있는 거 하나 더 언급해 보면 Visual Studio에서 F5 디버깅을 시작했을 때의 JIT 컴파일이 다소 특별하다는 점입니다. 실제로 그 상태에서 실행해 GetFunctionPointer로 출력된 주소의 값을 JIT 컴파일 전/후에 따라 각각 살펴보면,

// GetFunctionPointer의 반환 값 == 00007FFE48330490

[JIT 전]
00007FFE48330490 E8 8B 40 53 5F       call        00007FFEA7864520  
00007FFE48330495 5E                   pop         rsi  

[JIT 후]
00007FFE48330490 E9 CB 06 00 00       jmp         00007FFE48330B60  
00007FFE48330495 5F                   pop         rdi  

Tiered Compilation 동작을 무시하고 Fixup Precode의 call 코드가 곧바로 Native Code로의 jump 문으로 패치되는 것을 볼 수 있습니다. 게다가 Visual Studio의 디버깅 상태가 아니라면 GetFunctionPointer는 위에서처럼 Fixup Precode의 위치를 가리키지 않고 IL 코드가 번역된 Native Code의 주소를 가리켰다는 점이 다릅니다.




한 마디로, 상황 별로 GetFunctionPointer가 반환하는 값이 달라 이에 대해 어떤 고정적인 동작을 가정하고 코딩해서는 안 됩니다.




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

[연관 글]





[최초 등록일: ]
[최종 수정일: 2/13/2020 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 




1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12181정성태3/11/2020443기타: 76. 재현 가능한 최소한의 예제 프로젝트란? - 두 번째 예제파일 다운로드1
12180정성태3/10/2020213오류 유형: 600. "Docker Desktop for Windows" - EXPOSE 포트가 LISTENING 되지 않는 문제
12179정성태3/10/2020297개발 환경 구성: 481. docker - PostgreSQL 컨테이너 실행
12178정성태3/10/2020230개발 환경 구성: 480. Linux 운영체제의 docker를 위한 tcp 바인딩 추가
12177정성태3/9/2020287개발 환경 구성: 479. docker - MySQL 컨테이너 실행
12176정성태3/9/2020198개발 환경 구성: 478. 파일의 (sha256 등의) 해시 값(checksum) 확인하는 방법
12175정성태3/8/2020335개발 환경 구성: 477. "Docker Desktop for Windows"의 "Linux Container" 모드를 위한 tcp 바인딩 추가
12174정성태3/8/2020419개발 환경 구성: 476. DockerDesktopVM의 파일 시스템 접근 [1]
12173정성태3/8/2020405개발 환경 구성: 475. docker - SQL Server 2019 컨테이너 실행 [1]
12172정성태3/8/2020434개발 환경 구성: 474. docker - container에서 root 권한 명령어 실행(sudo)
12171정성태3/6/2020399VS.NET IDE: 143. Visual Studio - ASP.NET Core Web Application의 "Enable Docker Support" 옵션으로 달라지는 점
12170정성태3/6/2020288오류 유형: 599. "Docker Desktop is switching..." 메시지와 DockerDesktopVM CPU 소비 현상
12169정성태3/5/2020508개발 환경 구성: 473. Windows nanoserver에 대한 docker pull의 태그 사용
12168정성태3/8/2020487개발 환경 구성: 472. 윈도우 환경에서의 dockerd.exe("Docker Engine" 서비스)가 Linux의 것과 다른 점
12167정성태3/5/2020255개발 환경 구성: 471. C# - 닷넷 응용 프로그램에서 DB2 Express-C 데이터베이스 사용 (3) - ibmcom/db2express-c 컨테이너 사용
12166정성태3/14/2020306개발 환경 구성: 470. Windows Server 컨테이너 - DockerMsftProvider 모듈을 이용한 docker 설치
12165정성태3/2/2020321.NET Framework: 900. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 세 번째 이야기(Monitor.Enter 후킹)파일 다운로드1
12164정성태2/29/2020289오류 유형: 598. Surface Pro 6 - Windows Hello Face Software Device가 인식이 안 되는 문제
12163정성태2/27/2020338.NET Framework: 899. 익명 함수를 가리키는 delegate 필드에 대한 직렬화 문제
12162정성태2/28/2020407디버깅 기술: 166. C#에서 만든 COM 객체를 C/C++로 P/Invoke Interop 시 메모리 누수(Memory Leak) 발생파일 다운로드2
12161정성태2/26/2020195오류 유형: 597. manifest - The value "x64" of attribute "processorArchitecture" in element "assemblyIdentity" is invalid.
12160정성태2/26/2020269개발 환경 구성: 469. Reg-free COM 개체 사용을 위한 manifest 파일 생성 도구 - COMRegFreeManifest
12159정성태2/26/2020186오류 유형: 596. Visual Studio - The project needs to include ATL support
12158정성태2/26/2020296디버깅 기술: 165. C# - Marshal.GetIUnknownForObject/GetIDispatchForObject 사용 시 메모리 누수(Memory Leak) 발생파일 다운로드1
12157정성태2/27/2020314디버깅 기술: 164. C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법파일 다운로드1
12156정성태2/25/2020228오류 유형: 595. LINK : warning LNK4098: defaultlib 'nafxcw.lib' conflicts with use of other libs; use /NODEFAULTLIB:library
1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...