성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# 9.0의 Function pointer를 이용한 함수 주소 구하는 방법</h1> <p> 그러고 보니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 9.0 - (6) Function pointers ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12374'>https://www.sysnet.pe.kr/2/0/12374</a> </pre> <br /> 신규 문법은 unmanaged, managed 메서드에 대해 모두 함수 포인터를 지원하는데, 그것 자체가 포인터이므로 사용하기도 간단합니다. 살짝 테스트를 해볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Reflection; class Program { static void Main(string[] args) { // JIT 완료를 위해! Program.CallMyMethod(); unsafe { <span style='color: blue; font-weight: bold'>delegate*<void> func = &Program.CallMyMethod;</span> Console.WriteLine("function pointer: " + <span style='color: blue; font-weight: bold'>new IntPtr(func)</span>.ToInt64().ToString("x")); } } public static void CallMyMethod() { Console.WriteLine("CallMyMethod"); } } </pre> <br /> 위의 프로그램을 비주얼 스튜디오에서 F5 디버깅으로 확인하면, func 주소에 해당하는 코드가 다음과 같이 jmp를 가리키고 있는 것을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 01210448 E9 23 06 00 00 jmp Program.CallMyMethod() (01210A70h) 0121044D 5F pop edi </pre> <br /> 재미있는 것은, 위의 프로그램을 F5 디버깅이 아닌 (Ctrl + F5로) 그냥 실행했을 경우엔 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12133#fixup_precode'>_PrecodeFixupThunk</a>의 호출 코드를 가리킨다는 점입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 </pre> <br /> 예전에 위의 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12133#fixup_precode'>thunk 호출에 대해 확인하는 방법</a>을 살펴본 적이 있었는데요, 그것에 따라 계산해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > rax == 02A80465 r10 == 0, r11 == 0 MethodDesc == [rax + r10 * 4 + 3] == [02A80468] == 01094d6c </pre> <br /> 얻은 값(01094d6c)을 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12116'>windbg의 dumpmd</a>로 보면 Program.CallMyMethod를 가리킵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!DumpMD 01094d6c</span> Method Name: Program.CallMyMethod() Class: 010912f8 MethodTable: 01094d80 mdToken: 06000003 Module: 01094044 <span style='color: blue; font-weight: bold'>IsJitted: yes CodeAddr: 02a80a50</span> Transparency: Critical </pre> <br /> 당연히 (이전에 Program.CallMyMethod 호출을 했으므로) CodeAddr의 위치는 기계어로 번역된 함수의 처음 부분에 해당합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12407'>JIT_DbgIsJustMyCode</a> (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 </pre> <br /> 그러니까, 함수 포인터의 호출은 일반 메서드의 접근과는 별도로 또 다른 call site의 역할을 하도록 처리한 것 같습니다. 따라서 precode fixup을 호출한다고 해서 함수 포인터의 속도에 영향이 있는 것은 아닙니다. 왜냐하면 마찬가지로 함수 포인터의 호출 역시 처음 한 번만 precode fixup 단계를 거칠 뿐 이후에는 jmp로 바뀌기 때문입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 정리해 보면, 함수 포인터를 통한 닷넷 메서드의 주소를 (F5, Ctrl+F5 실행 방식에 상관없이) 가져오고 싶다면 아쉽게도 함수 포인터가 한 번은 실행되었어야 가능합니다. 그런 경우라면, 다음과 같은 식의 코드로 간단하게 처리할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 { <span style='color: blue; font-weight: bold'>delegate*<void> func = &Program.CallMyMethod; func();</span> IntPtr ptrFunc = <span style='color: blue; font-weight: bold'>ReadJmpPointer32</span>(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"); } } </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1656&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> 시간 되시면 다음의 글도 한 번 보시고. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 상황별 GetFunctionPointer 반환값 정리 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1027'>https://www.sysnet.pe.kr/2/0/1027</a> 상황별 GetFunctionPointer 반환값 정리 - x64 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12143'>https://www.sysnet.pe.kr/2/0/12143</a> C++의 inline asm 사용을 .NET으로 포팅하는 방법 - 두 번째 이야기 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/10889'>https://www.sysnet.pe.kr/2/0/10889</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1177
(왼쪽의 숫자를 입력해야 합니다.)