성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
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'>상황별 GetFunctionPointer 반환값 정리 - x64</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;' > 상황별 GetFunctionPointer 반환값 정리 - x86 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1027'>https://www.sysnet.pe.kr/2/0/1027</a> </pre> <br /> x86을 대상으로만 했었는데, 이번에는 .NET 4.8 / x64 / Debug 빌드로 실행한 결과를 정리해 보겠습니다.<br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>EXE 어셈블리의 Main 메서드</div> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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}"); } } </pre> <br /> x86과 결과와 다르지 않습니다. 특이한 점이 있다면 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/12133'>Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기</a>" 글에서의 PreStub 과정 없이 곧바로 Fixup Precode에서 Native Code를 호출하는 jmp 문으로 패치된다는 점입니다. (Main 함수야말로 단 한번 실행되는 경우가 대부분일 것이므로 굳이 공을 들여 최적화를 안 해도 될 텐데 말이죠. ^^)<br /> <br /> 또한 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12142#md_func_ptr'>MethodDesc 위치의 +8 바이트 위치에 저장한 기계어 코드의 주소</a>도 GetFunctionPointer의 값과 일치합니다.<br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>어셈블리에 정의된 메서드의 JIT 이전</div> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 */ </pre> <br /> 이에 대해서도 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/12133'>Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기</a>" 글에서 살펴봤습니다. GetFunctionPointer가 반환한 주소는 "Fixup Precode"를 가리키고 실제 호출하려고 할 때의 call 코드의 변위도 정확히 GetFunctionPointer의 반환 주소를 가리킵니다. (따라서 x86의 call 주솟값과는 다르다는 차이점이 있습니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 00007ffe`48310912 e871fbffff call 00007ffe`48310488 (Program.Test1(), mdToken: 0000000006000002) </pre> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>어셈블리에 정의된 메서드의 JIT 이후</div> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 */ </pre> <br /> 이번에도 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/12133'>Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기</a>" 글의 내용과 일치합니다.<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'>!name2ee ConsoleApp1.exe!Program.Test1</span> Module: 00007ffe481f4148 Assembly: ConsoleApp1.exe Token: 0000000006000002 MethodDesc: <span style='color: blue; font-weight: bold'>00007ffe481f5a10</span> Name: Program.Test1() JITTED Code Address: <span style='color: blue; font-weight: bold'>00007ffe48300c10</span> 0:000> <span style='color: blue; font-weight: bold'>dq 00007ffe481f5a10 L2</span> 00007ffe`481f5a10 00080006`21020002 <span style='color: blue; font-weight: bold'>00007ffe`48300c10</span> </pre> <br /> 정리해 보면, Jit 전에는 Fixup Precode의 위치를 GetFunctionPointer 메서드가 반환하지만, 일단 JIT가 되면 GetFunctionPointer는 해당 메서드의 Body가 컴파일된 메서드의 주솟값을 반환합니다.<br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>NGen 된 BCL(Base Class Library) 메서드</div> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static void ShowCreateCommandAddress() { MethodBase func = typeof(SqlConnection).GetMethod("CreateCommand", BindingFlags.Instance | BindingFlags.Public); IntPtr pOld = func.MethodHandle.GetFunctionPointer(); OutputFunctionAddress("CreateCommand", pOld); } /* 출력 결과 CreateCommand == 7ffe48320510 */ </pre> <br /> GetFunctionPointer가 반환한 값(7ffe48320510)은 특이하게 Fixup Precode의 위치입니다. (게다가 method desc을 구하는 인덱스가 모두 0, 0입니다.)<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'>!u 7ffe48320510</span> 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> <span style='color: blue; font-weight: bold'>!ip2md 7ffe48320510</span> // 왜냐하면 fixup precode의 위치이므로. Failed to request MethodData, not in JIT code range </pre> <br /> 하지만, 해당 메서드의 MethodDesc 값에는 정확히 기계어로 번역된 주솟값을 가지고 있습니다.<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'>!name2ee System.Data.dll!System.Data.SqlClient.SqlConnection.CreateCommand</span> Module: 00007ffe72961000 Assembly: System.Data.dll Token: 0000000006001a69 MethodDesc: 00007ffe729d1d08 Name: System.Data.SqlClient.SqlConnection.CreateCommand() JITTED Code Address: <span style='color: blue; font-weight: bold'>00007ffe72ea4410</span> 0:000> <span style='color: blue; font-weight: bold'>dq 00007ffe729d1d08 L2</span> 00007ffe`729d1d08 00006453`3b7c9a69 <span style='color: blue; font-weight: bold'>00000000`004d2700</span> 0:000> <span style='color: blue; font-weight: bold'>? 00007ffe`729d1d10 + 004d2700</span> Evaluate expression: 140730826376208 = <span style='color: blue; font-weight: bold'>00007ffe`72ea4410</span> </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1552&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 외에 재미있는 거 하나 더 언급해 보면 Visual Studio에서 F5 디버깅을 시작했을 때의 JIT 컴파일이 다소 특별하다는 점입니다. 실제로 그 상태에서 실행해 GetFunctionPointer로 출력된 주소의 값을 JIT 컴파일 전/후에 따라 각각 살펴보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // GetFunctionPointer의 반환 값 == 00007FFE48330490 [JIT 전] 00007FFE48330490 E8 8B 40 53 5F <span style='color: blue; font-weight: bold'>call</span> 00007FFEA7864520 00007FFE48330495 5E pop rsi [JIT 후] 00007FFE48330490 E9 CB 06 00 00 <span style='color: blue; font-weight: bold'>jmp</span> 00007FFE48330B60 00007FFE48330495 5F pop rdi </pre> <br /> Tiered Compilation 동작을 무시하고 Fixup Precode의 call 코드가 곧바로 Native Code로의 jump 문으로 패치되는 것을 볼 수 있습니다. 게다가 Visual Studio의 디버깅 상태가 아니라면 GetFunctionPointer는 위에서처럼 Fixup Precode의 위치를 가리키지 않고 IL 코드가 번역된 Native Code의 주소를 가리켰다는 점이 다릅니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 한마디로, 상황별로 GetFunctionPointer가 반환하는 값이 달라 이에 대해 어떤 고정적인 동작을 가정하고 코딩해서는 안 됩니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1728
(왼쪽의 숫자를 입력해야 합니다.)