Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

C# 9.0의 Function pointer를 이용한 함수 주소 구하는 방법

그러고 보니,

C# 9.0 - (6) Function pointers
; https://www.sysnet.pe.kr/2/0/12374

신규 문법은 unmanaged, managed 메서드에 대해 모두 함수 포인터를 지원하는데, 그것 자체가 포인터이므로 사용하기도 간단합니다. 살짝 테스트를 해볼까요? ^^

using System;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
        // JIT 완료를 위해!
        Program.CallMyMethod();

        unsafe
        {
            delegate*<void> func = &Program.CallMyMethod;
            Console.WriteLine("function pointer: " + new IntPtr(func).ToInt64().ToString("x"));
        }
    }

    public static void CallMyMethod()
    {
        Console.WriteLine("CallMyMethod");
    }
}

위의 프로그램을 비주얼 스튜디오에서 F5 디버깅으로 확인하면, func 주소에 해당하는 코드가 다음과 같이 jmp를 가리키고 있는 것을 볼 수 있습니다.

01210448 E9 23 06 00 00       jmp         Program.CallMyMethod() (01210A70h)  
0121044D 5F                   pop         edi  

재미있는 것은, 위의 프로그램을 F5 디버깅이 아닌 (Ctrl + F5로) 그냥 실행했을 경우엔 _PrecodeFixupThunk의 호출 코드를 가리킨다는 점입니다.

02A80460 E8 AB EC 4B 6F       call        _PrecodeFixupThunk@0 (71F3F110h)  
02A80465 5E                   pop         esi  
02A80466 00 00                add         byte ptr [eax],al  
02A80468 6C                   ins         byte ptr es:[edi],dx  
02A80469 4D                   dec         ebp  
02A8046A 09 01                or          dword ptr [ecx],eax  
02A8046C 00 00                add         byte ptr [eax],al  
02A8046E 00 00                add         byte ptr [eax],al  

예전에 위의 thunk 호출에 대해 확인하는 방법을 살펴본 적이 있었는데요, 그것에 따라 계산해,

rax == 02A80465
r10 == 0, r11 == 0
MethodDesc == [rax + r10 * 4 + 3] == [02A80468] == 01094d6c

얻은 값(01094d6c)을 windbg의 dumpmd로 보면 Program.CallMyMethod를 가리킵니다.

0:000> !DumpMD  01094d6c
Method Name:  Program.CallMyMethod()
Class:        010912f8
MethodTable:  01094d80
mdToken:      06000003
Module:       01094044
IsJitted:     yes
CodeAddr:     02a80a50
Transparency: Critical

당연히 (이전에 Program.CallMyMethod 호출을 했으므로) CodeAddr의 위치는 기계어로 번역된 함수의 처음 부분에 해당합니다.

    59:     public static void CallMyMethod()
    60:     {
02A80A50 55                   push        ebp  
02A80A51 8B EC                mov         ebp,esp  
02A80A53 83 3D F0 42 09 01 00 cmp         dword ptr ds:[10942F0h],0  
02A80A5A 74 05                je          Program.CallMyMethod()+011h (02A80A61h)  
02A80A5C E8 1F F3 85 6F       call        JIT_DbgIsJustMyCode (722DFD80h)  
02A80A61 90                   nop  
    61:         Console.WriteLine("CallMyMethod");
02A80A62 8B 0D 44 23 BA 03    mov         ecx,dword ptr ds:[3BA2344h]  
02A80A68 E8 F7 44 D2 6D       call        System.Console.WriteLine(System.String) (707A4F64h)  
02A80A6D 90                   nop  
    62:     }
02A80A6E 90                   nop  
02A80A6F 5D                   pop         ebp  
02A80A70 C3                   ret  

그러니까, 함수 포인터의 호출은 일반 메서드의 접근과는 별도로 또 다른 call site의 역할을 하도록 처리한 것 같습니다. 따라서 precode fixup을 호출한다고 해서 함수 포인터의 속도에 영향이 있는 것은 아닙니다. 왜냐하면 마찬가지로 함수 포인터의 호출 역시 처음 한 번만 precode fixup 단계를 거칠 뿐 이후에는 jmp로 바뀌기 때문입니다.




정리해 보면, 함수 포인터를 통한 닷넷 메서드의 주소를 (F5, Ctrl+F5 실행 방식에 상관없이) 가져오고 싶다면 아쉽게도 함수 포인터가 한 번은 실행되었어야 가능합니다. 그런 경우라면, 다음과 같은 식의 코드로 간단하게 처리할 수 있습니다.

using System;
using System.Reflection;
using System.Runtime.CompilerServices;

class Program
{
    static void Main(string[] args)
    {
        // MethodHandle.GetFunctionPointer() 값과 비교를 위해!
        MethodInfo mi = typeof(Program).GetMethod("CallMyMethod",
           System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
        RuntimeHelpers.PrepareMethod(mi.MethodHandle);

        unsafe
        {
            delegate*<void> func = &Program.CallMyMethod;
            func();

            IntPtr ptrFunc = ReadJmpPointer32(func, 0xe9);

            Console.WriteLine("function pointer: " + new IntPtr(func).ToInt64().ToString("x"));
            Console.WriteLine("function pointer target: " + ptrFunc.ToInt64().ToString("x"));
            Console.WriteLine("MethodInfo: " + mi.MethodHandle.GetFunctionPointer().ToInt64().ToString("x"));

            Console.ReadLine();
        }
    }

    private static unsafe IntPtr ReadJmpPointer32(delegate*<void> func, byte jmpCode)
    {
        IntPtr ptr = new IntPtr(func);

        byte* pBuf = (byte*)ptr;
        if (*pBuf != jmpCode)
        {
            return IntPtr.Zero;
        }

        if (IntPtr.Size == 4)
        {
            int pos = pBuf[1] | (pBuf[2] << 8) | (pBuf[3] << 16) | pBuf[4] << 24;
            return IntPtr.Add(ptr, pos + /* jmp 5bytes */ 5);
        }

        throw new ApplicationException("x64 - Not supported yet");
    }

    public static void CallMyMethod()
    {
        Console.WriteLine("CallMyMethod");
    }
}

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

시간 되시면 다음의 글도 한 번 보시고. ^^

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

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

C++의 inline asm 사용을 .NET으로 포팅하는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/10889




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

[연관 글]


donaricano-btn



[최초 등록일: ]
[최종 수정일: 11/12/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)
12505정성태1/23/202133.NET Framework: 1018. .NET Core Kestrel 호스팅 - Web API 추가파일 다운로드1
12504정성태1/23/202194.NET Framework: 1017. .NET 5에서의 네트워크 라이브러리 개선 (2) - HTTP/2, HTTP/3 관련
12503정성태1/21/202124오류 유형: 696. C# - HttpClient: Requesting HTTP version 2.0 with version policy RequestVersionExact while HTTP/2 is not enabled.
12502정성태1/21/202195.NET Framework: 1016. .NET Core HttpClient의 HTTP/2 지원파일 다운로드1
12501정성태1/21/202166.NET Framework: 1015. .NET 5부터 HTTP/1.1, 2.0 선택을 위한 HttpVersionPolicy 동작 방식파일 다운로드1
12500정성태1/21/202154.NET Framework: 1014. ASP.NET Core(Kestrel)의 HTTP/2 지원 여부파일 다운로드1
12499정성태1/23/202159.NET Framework: 1013. .NET Core Kestrel 호스팅 - 포트 변경, non-localhost 접속 지원 및 https 등의 설정 변경파일 다운로드1
12498정성태1/23/202149.NET Framework: 1012. .NET Core Kestrel 호스팅 - 비주얼 스튜디오의 Kestrel/IIS Express 프로파일 설정
12497정성태1/20/202142.NET Framework: 1011. C# - OWIN Web API 예제 프로젝트파일 다운로드1
12496정성태1/19/202181.NET Framework: 1010. .NET Core 콘솔 프로젝트에서 Kestrel 호스팅 방법
12495정성태1/20/202179웹: 40. IIS의 HTTP/2 지원 여부 - h2, h2c [1]
12494정성태1/19/2021131개발 환경 구성: 522. WSL2 인스턴스와 호스트 측의 Hyper-V에 운영 중인 VM과 네트워크 연결을 하는 방법 [1]
12493정성태1/18/2021142.NET Framework: 1009. .NET 5에서의 네트워크 라이브러리 개선 (1) - HTTP 관련 [1]파일 다운로드1
12492정성태1/17/202130오류 유형: 695. ASP.NET 0x80131620 Failed to bind to address
12491정성태1/18/2021138.NET Framework: 1008. 배열을 반환하는 C# COM 개체의 메서드를 C++에서 사용 시 메모리 누수 현상 [1]파일 다운로드1
12490정성태1/15/2021142.NET Framework: 1007. C# - foreach에서 열거 변수의 타입을 var로 쓰면 object로 추론하는 문제 [1]파일 다운로드1
12489정성태1/13/202186.NET Framework: 1006. C# - DB에 저장한 텍스트의 (이모티콘을 비롯해) 유니코드 문자가 '?'로 보인다면?
12488정성태1/13/2021121.NET Framework: 1005. C# - string 타입은 shallow copy일까요? deep copy일까요?파일 다운로드1
12487정성태1/13/2021104.NET Framework: 1004. C# - GC Heap에 위치한 참조 개체의 주소를 알아내는 방법파일 다운로드1
12486정성태1/12/2021201.NET Framework: 1003. x64 환경에서 참조형의 기본 메모리 소비는 얼마나 될까요?
12485정성태1/11/2021139Graphics: 38. C# - OpenCvSharp.VideoWriter에 BMP 파일을 1초씩 출력하는 예제파일 다운로드1
12484정성태1/9/2021115.NET Framework: 1002. C# - ReadOnlySequence<T> 소개파일 다운로드1
12483정성태1/8/2021155개발 환경 구성: 521. dotPeek - 훌륭한 역어셈블 소스 코드 생성 도구
12482정성태1/8/202160.NET Framework: 1001. C# - 제네릭 타입/메서드에서 사용 시 경우에 따라 CS8377 컴파일 에러
12481정성태1/7/202181.NET Framework: 1000. C# - CS8344 컴파일 에러: ref struct 타입의 사용 제한 메서드파일 다운로드1
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...