성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <div style='font-family: 맑은 고딕, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>상황별 GetFunctionPointer 반환값 정리 - x86</div> <br /> 우선, 이 글을 읽기 전에 다음의 글을 읽어보시면 도움이 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 ; <a target='_tab' href='http://www.sysnet.pe.kr/2/0/942'>http://www.sysnet.pe.kr/2/0/942</a> Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 ; <a target='_tab' href='http://www.sysnet.pe.kr/2/0/1023'>http://www.sysnet.pe.kr/2/0/1023</a> </pre> <br /> 간단히 설명하자면, RuntimeMethodHandle 타입에서 제공되는 GetFunctionPointer 메서드를 이용하면 해당 함수의 주소를 알 수가 있습니다. 예를 들어, 아래와 같은 식입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > === 현재 호출 중인 함수의 주소를 알고 싶다면? ==== void Test() { StackFrame st = new StackFrame(0); IntPtr ptr = st.GetMethod().<b style='COLOR: blue'>MethodHandle.GetFunctionPointer</b>(); } === 특정 메서드의 함수 주소를 알고 싶다면? ==== MethodBase func = typeof([...Type...]).GetMethod("[...MethodName...]", ...{예: BindingFlags.Static | BindingFlags.NonPublic }...); IntPtr pMain = func.<b style='COLOR: blue'>MethodHandle.GetFunctionPointer</b>(); </pre> <br /> 하지만, 이 값은 다양한 상황에서 약간의 차이를 보여주는데 개별 경우의 수에 따라 어떻게 다른지 알아보는 것이 이번 글의 목적입니다.<br /> <br /> 이후 설명하는 내용은 별다른 이야기가 없는 경우 기본적으로 다음과 같은 환경임을 가정합니다.<br /> <br /> <ul> <li>NET 4.0 Console Application</li> <li>Debug Build</li> <li>체크 해제 - Enable the Visual Studio hosting process</li> <li>체크 - Enable unmanaged code debugging</li> <li>심벌 서버 지정 - 마이크로소프트 공용 PDB 심벌 파일 배포 서버</li> </ul> <br /> <hr style='width: 50%' /><br /> <br /> <div style='font-size: 12pt; font-family: 맑은 고딕, Consolas; color: #2211AA; text-align: left; font-weight: bold'>EXE 어셈블리의 Main 메서드</div><br /> <br /> Main 메서드는 진입 함수이기 때문에 GetFunctionPointer를 이용하여 JIT 컴파일 이전 단계의 값을 알아낼 수는 없습니다. 따라서 JIT 이후의 값만 테스트 해볼 수 있는데요. Main 메서드에 대한 GetFunctionPointer 출력 결과는 실제 JIT 컴파일 된 기계어 코드의 주소가 출력되는 아주 표준적인 동작을 보여줍니다. 확인 과정은 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > static void Main(string[] args) { ShowMainFunc(); } static void ShowMainFunc() { MethodBase func = typeof(Program).GetMethod("Main", BindingFlags.Static | BindingFlags.NonPublic); IntPtr pMain = func.<b style='COLOR: blue'>MethodHandle.GetFunctionPointer</b>(); ClassLibrary1.Class1.OutputFunctionAddress("Main", pMain); } === 출력 결과 === Main Function == <b style='COLOR: blue'>68d850</b> </pre> <br /> "Immediate Window"에서 sos.dll을 이용하여 확인해 보면 정확히 일치하는 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!load "C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\SOS.dll"</b> extension c:\windows\microsoft.net\framework\v4.0.30319\sos.dll loaded <b style='COLOR: blue'>!name2ee ConsoleApplication1.exe!ConsoleApplication1.Program.Main</b> PDB symbol for clr.dll not loaded Module: 00152e9c Assembly: ConsoleApplication1.exe Token: 1eb2701706000001 MethodDesc: 001533f0 Name: ConsoleApplication1.Program.Main(System.String[]) JITTED Code Address: <b style='COLOR: blue'>0068d850</b> </pre> <br /> <hr style='width: 50%' /><br /> <br /> <div style='font-size: 12pt; font-family: 맑은 고딕, Consolas; color: #2211AA; text-align: left; font-weight: bold'>같은 어셈블리에 정의된 메서드의 JIT 이전</div><br /> <br /> 아래와 같이 테스트 코드를 작성하고, GetFunctionPointer 값을 확인해 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > static void Main(string[] args) { Program pg = new Program(); ShowTestFunc("Test1 - Before JITting", "Test1"); 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(); ClassLibrary1.Class1.OutputFunctionAddress(text, pFunc); } === 출력 결과 === Test1 - Before JITting Function == <b style='COLOR: blue'>2ac070</b> </pre> <br /> 반면에, .NET Disassembly 창에서 값을 확인해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > 22: pg.Test1(); 00000053 8B 4D F8 mov ecx,dword ptr [ebp-8] 00000056 39 09 cmp dword ptr [ecx],ecx 00000058 FF 15 04 34 2A 00 call dword ptr <b style='COLOR: blue'>ds:[002A3404h]</b> 0000005e 90 nop </pre> <br /> <img alt='getfunctionpointer_output_1.png' src='/SysWebRes/bbs/getfunctionpointer_output_1.png' /><br /> <br /> 0x2ac015 값인데 0x2ac070과는 5B 값만큼의 차이가 납니다. 즉, JIT 되기 이전의 메서드에 대해서는 GetFunctionPointer가 가리키는 값이 Stub 주솟값이 아니라는 것을 알 수 있습니다.<br /> <br /> <br /> <br /> <hr style='width: 50%' /><br /> <br /> <div style='font-size: 12pt; font-family: 맑은 고딕, Consolas; color: #2211AA; text-align: left; font-weight: bold'>같은 어셈블리에 정의된 메서드의 JIT 이후</div><br /> <br /> 이 경우에는 Main 메서드에서 확인한 것처럼 GetFunctionPointer 반환값과 JIT 컴파일된 메서드의 주솟값이 일치합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <div style='font-size: 12pt; font-family: 맑은 고딕, Consolas; color: #2211AA; text-align: left; font-weight: bold'>다른 어셈블리에 정의된 메서드의 JIT 이전</div><br /> <br /> 이 경우에도 "같은 어셈블리에 정의된 메서드의 JIT 이전"에서 살펴본 대로 결과가 다르게 나옵니다. 단지 그때는 0x5B만큼의 차이가 났지만 이번에는 0x33만큼의 차이가 납니다. 사실 0x5B, 0x33이라는 절대적인 값의 차이는 의미가 없습니다. 왜냐하면 해당 클래스에 정의된 몇 번째의 메서드를 비교했느냐에도 값이 달라질 수 있기 때문에, 단지 의미가 있다면 어쨌든 'offset 값이 다르다' 는 정도가 될 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <div style='font-size: 12pt; font-family: 맑은 고딕, Consolas; color: #2211AA; text-align: left; font-weight: bold'>다른 어셈블리에 정의된 메서드의 JIT 이후</div><br /> <br /> 이 경우에도 Main 메서드에서 확인한 것처럼 GetFunctionPointer 반환값과 JIT 컴파일 된 메서드의 주소값이 일치합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <div style='font-size: 12pt; font-family: 맑은 고딕, Consolas; color: #2211AA; text-align: left; font-weight: bold'>NGen 된 BCL(Base Class Library) 메서드 </div><br /><br /> <br /> 다음은, 예제 코드와 결과입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; 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 Function == <b style='COLOR: blue'>5114fd34</b> </pre> <br /> 위의 코드에 사용된 SqlConnection 타입을 담은 System.Data 어셈블리가 NGen 된 이미지 버전으로 사용되었음을 어떻게 알 수 있을까요? Visual Studio 의 디버그 상태에서 "Ctrl + D, M"으로 Modules 창을 띄우면 해당 어셈블리에 대해서 System.Data.ni.dll이 목록에 포함되어 있다는 사실로 알 수 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='getfunctionpointer_output_2.png' src='/SysWebRes/bbs/getfunctionpointer_output_2.png' /><br /> <br /> 재미있는 점은, 특별히 DLL 로딩 주소의 충돌이 발생하지 않는 한 이미 기계어 코드로 출력된 상태이기 때문에, EXE 프로그램을 재실행하는 경우에도 JIT 컴파일 된 주솟값이 바뀌지 않습니다.<br /> <br /> 참고로, 이번 경우에는 지난 이야기에서도 한번 다룬 적이 있는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > Visual Studio의 .NET Disassembly 창의 call 호출에 사용되는 주소의 의미는? ; <a target='_tab' href='http://www.sysnet.pe.kr/2/0/1019'>http://www.sysnet.pe.kr/2/0/1019</a> </pre> <br /> 그때도 언급했지만, GetFunctionPointer 반환값(위에서는 0x5114fd34)은 메서드의 JIT 컴파일된 주소값이 아닙니다. 이에 대해서 다시 한번 sos.dll을 이용해서 알아보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!load "C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\SOS.dll"</b> extension c:\windows\microsoft.net\framework\v4.0.30319\sos.dll loaded <b style='COLOR: blue'>!name2ee System.Data.dll!System.Data.SqlClient.SqlConnection.CreateCommand</b> PDB symbol for clr.dll not loaded Module: 510b1000 Assembly: System.Data.dll Token: 4ce43204060024c9 MethodDesc: 510bbb38 Name: System.Data.SqlClient.SqlConnection.CreateCommand() <b style='COLOR: blue'>JITTED Code Address: 51459370</b> </pre> <br /> 위의 결과에서처럼 실제 "System.Data.SqlClient.SqlConnection.CreateCommand" 메서드의 body가 JIT 컴파일 되어 기계어로 출력된 주소는 "0x51459370"이고, 역어셈블을 해보면 본래의 메서드임을 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!u 51459370</b> <b style='COLOR: blue'>preJIT generated code System.Data.SqlClient.SqlConnection.CreateCommand()</b> Begin 51459370, size 1f >>> 51459370 57 push edi 51459371 56 push esi 51459372 8BF9 mov edi,ecx 51459374 B9B0FC1851 mov ecx,5118FCB0h (MT: System.Data.SqlClient.SqlCommand) 51459379 E81AF2CEFF call 51148598 (?gpViaConns@@3PAPAVVia@@A) 5145937E 8BF0 mov esi,eax 51459380 57 push edi 51459381 8BCE mov ecx,esi 51459383 33D2 xor edx,edx 51459385 E86205CFFF call 511498EC (System.Data.SqlClient.SqlCommand..ctor(System.String, System.Data.SqlClient.SqlConnection), mdToken: 060023e2) 5145938A 8BC6 mov eax,esi 5145938C 5E pop esi 5145938D 5F pop edi 5145938E C3 ret </pre> <br /> 그렇다고 GetFunctionPointer 반환값(위에서는 0x5114fd34)이 전혀 이상한 값이 나온 것은 아닙니다. 왜냐하면 역어셈블을 해보면 다음과 같이 jmp 구문이 나오기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!u 5114fd34</b> Unmanaged code 5114FD34 B838BB0B51 mov eax,510BBB38h 5114FD39 90 nop 5114FD3A E8418AFFFF call 51148780 5114FD3F E92C963000 <b style='COLOR: blue'>jmp 51459370</b> 5114FD44 B854BB0B51 mov eax,510BBB54h 5114FD49 90 nop 5114FD4A E8318AFFFF call 51148780 5114FD4F E9248AFFFF jmp 51148778 5114FD54 B8F0BB0B51 mov eax,510BBBF0h 5114FD59 90 nop </pre> <br /> 실제로 코드에서 SqlConnection.CreateCommand를 사용한 경우, 그 호출을 포함한 메서드가 JIT 컴파일 될 때 기록되는 call에 사용되는 주소는 GetFunctionPointer 반환값과 일치합니다. 즉, JIT 컴파일 된 메서드의 기계어 코드에 대한 절대 주솟값을 직접 코드에서 다루지 않고 경유하는 지점을 별도로 두어 사용하는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <div style='font-size: 12pt; font-family: 맑은 고딕, Consolas; color: #2211AA; text-align: left; font-weight: bold'>NGen 되지 않은 BCL메서드의 JIT 이전 (또는, NGen 없이 GAC에 등록만 된 어셈블리)</div><br /> <br /> NGen 되지 않은 BCL 함수라? 그게 어떻게 가능할까요? 바로 .NET Profiler를 이용하면 가능합니다. .NET Profiler의 옵션 중에서 COR_PRF_USE_PROFILE_IMAGES를 지정하게 되면 NGen 모듈을 사용하지 않는다고 이전에 설명을 했었지요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > 닷넷 프로파일러 - IL 코드 재작성 ; <a target='_tab' href='http://www.sysnet.pe.kr/2/0/767'>http://www.sysnet.pe.kr/2/0/767</a> </pre> <br /> 그렇게 해서 동일하게 SqlConnection.CreateCommand 메서드의 JIT 단계 이전에 GetFunctionPointer 값을 출력해 보니 0x30ce70이 나왔습니다. 물론, sos.dll로 확인해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!name2ee System.Data.dll!System.Data.SqlClient.SqlConnection.CreateCommand</b> PDB symbol for clr.dll not loaded Module: 00303bc0 Assembly: System.Data.dll Token: 2a469854060024c9 MethodDesc: 00b499a4 Name: System.Data.SqlClient.SqlConnection.CreateCommand() <b style='COLOR: blue'>Not JITTED yet.</b> Use !bpmd -md 00b499a4 to break on run. </pre> <br /> 정말로 JIT된 상태가 아니라고 나옵니다. 그런데, GetFunctionPointer 반환값(0x30ce70)이 재미있습니다. 역어셈블을 해보면 다음과 같이 정확한 stub 코드의 주솟값임을 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > !u 0x30ce70 Unmanaged code 0030CE70 B8A499B400 mov eax,0B499A4h 0030CE75 90 nop 0030CE76 E865620970 call 703A30E0 <b style='COLOR: blue'>0030CE7B E9D039FAFF jmp 002B0850</b> 0030CE80 0000 add byte ptr [eax],al 0030CE82 0000 add byte ptr [eax],al 0030CE84 0000 add byte ptr [eax],al 0030CE86 0000 add byte ptr [eax],al 0030CE88 0000 add byte ptr [eax],al 0030CE8A 0000 add byte ptr [eax],al </pre> <br /> JIT 전 상태이니, 실제로 SqlConnection.CreateCommand를 호출하는 코드를 .NET Disassembly로 확인해 보면 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > private static void CallFunc() { SqlConnection con = new SqlConnection(); 38: con.CreateCommand(); 00000035 8B 4D FC mov ecx,dword ptr [ebp-4] 00000038 39 09 cmp dword ptr [ecx],ecx <b style='COLOR: blue'>0000003a E8 41 13 76 FD call FD761380</b> <b style='COLOR: blue'>0000003f</b> 90 nop </pre> <br /> 오호~~~ call FF가 아니라 call E8 기계어 코드에 직접적인 변위값을 받고 있습니다. FD761380 주소가 어디인지 계산하기 위해서는 예전에 살펴봤던 그 방법을 사용해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > Visual Studio의 .NET Disassembly 창의 call 호출에 사용되는 주소의 의미는? ; <a target='_tab' href='http://www.sysnet.pe.kr/2/0/1019'>http://www.sysnet.pe.kr/2/0/1019</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > !name2ee ClassLibrary1.dll!ClassLibrary1.Class1.CallFunc<b style='COLOR: blue'> </b>Module: 003034f4 Assembly: ClassLibrary1.dll Token: 2a449f8406000003 MethodDesc: 003039d8 Name: ClassLibrary1.Class1.CallFunc() <b style='COLOR: blue'>JITTED Code Address: 02babaf0</b> <b style='COLOR: blue'>!u 02babaf0</b> Normal JIT generated code ClassLibrary1.Class1.CallFunc() Begin 02babaf0, size 45 >>> 02BABAF0 55 push ebp 02BABAF1 8BEC mov ebp,esp 02BABAF3 83EC08 sub esp,8 02BABAF6 833D9437300000 cmp dword ptr ds:[00303794h],0 02BABAFD 7405 je 02BABB04 02BABAFF E817A9AA6D call 7065641B (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE) 02BABB04 33D2 xor edx,edx 02BABB06 8955FC mov dword ptr [ebp-4],edx 02BABB09 90 nop 02BABB0A B9E09CB400 mov ecx,0B49CE0h (MT: System.Data.SqlClient.SqlConnection) 02BABB0F E83E72816D call 703C2D52 (JitHelp: CORINFO_HELP_NEW_CROSSCONTEXT) 02BABB14 8945F8 mov dword ptr [ebp-8],eax 02BABB17 8B4DF8 mov ecx,dword ptr [ebp-8] 02BABB1A E8795EE5FF call 02A01998 (System.Data.SqlClient.SqlConnection..ctor(), mdToken: 060024ec) 02BABB1F 8B45F8 mov eax,dword ptr [ebp-8] 02BABB22 8945FC mov dword ptr [ebp-4],eax 02BABB25 8B4DFC mov ecx,dword ptr [ebp-4] 02BABB28 3909 cmp dword ptr [ecx],ecx <b style='COLOR: blue'>02BABB2A E8411376FD call 0030CE70</b> (System.Data.SqlClient.SqlConnection.CreateCommand(), mdToken: 060024c9) <b style='COLOR: blue'>02BABB2F</b> 90 nop 02BABB30 90 nop 02BABB31 8BE5 mov esp,ebp 02BABB33 5D pop ebp 02BABB34 C3 ret </pre> <br /> 정말 맞는지 그때처럼 계산해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > 02BABB2F + FD761380 ==> 4byte값만 취하면 ==> 0x0030ceaf - (.NET Disassembly 창 변위) 0x3f == 0030CE70 </pre> <br /> 정확하군요. ^^ 게다가 0x30ce70 값은 GetFunctionPointer로 반환받은 값과 동일합니다.<br /> <br /> 정리해 보면, NGen되지 않은 BCL 메서드의 경우 GetFunctionPointer 값은 Stub 코드 위치를 정확하게 가리키고 있다는 차이가 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <div style='font-size: 12pt; font-family: 맑은 고딕, Consolas; color: #2211AA; text-align: left; font-weight: bold'>NGen 되지 않은 BCL 메서드의 JIT 이후</div><br /> <br /> 이것 또한 재미있습니다. NGen되기 이전에 GetFunctionPointer 값이 0030CE70이었는데, NGen 후에도 그 값은 그대로 변하지 않았습니다. 물론, JIT 컴파일은 되었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!name2ee System.Data.dll!System.Data.SqlClient.SqlConnection.CreateCommand</b> Module: 00303bc0 Assembly: System.Data.dll Token: 2d289f9c060024c9 MethodDesc: 00b499a4 Name: System.Data.SqlClient.SqlConnection.CreateCommand() <b style='COLOR: blue'>JITTED Code Address: 05f1c280</b> </pre> <br /> 더욱 재미있는 점이 하나 있다면, JIT 이전/이후의 값에 대한 패치를 "데이터"영역이 아닌, 코드 영역에서 직접 수정한다는 것입니다.<br /> <br /> 이 때문에 JIT 이전의 GetFunctionPointer 값으로 반환된 stub 코드의 역어셈블 값에서 jmp 코드의 오퍼랜드 값이 "JITTED Code Address"로 바뀌었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!u 0x30ce70</b> Unmanaged code 0030CE70 B8A499B400 mov eax,0B499A4h 0030CE75 90 nop 0030CE76 E865620970 call 703A30E0 0030CE7B E900F4C005 jmp <b style='COLOR: blue'>05F1C280</b> <== JIT 이전값: jmp <b style='COLOR: blue'>002B0850</b> 0030CE80 00B000EB7CB0 add byte ptr [eax+B07CEB00h],dh 0030CE86 03EB add ebp,ebx 0030CE88 78B0 js 0030CE3A 0030CE8A 06 push es 0030CE8B EB74 jmp 0030CF01 0030CE8D B009 mov al,9 </pre> <br /> <hr style='width: 50%' /><br /> <br /> 이 외에 더 알아봐야 할 경우의 수가 있을까요? (있다면, 다시 추가하면 될 것이고.)<br /> <br /> 이러한 일련의 검사를 통해 알 수 있는 중요한 사실은, 적어도 JIT 컴파일 이후의 GetFunctionPointer 반환값은 안전하게 해당 메서드를 call 할 수 있는 주소를 포함하고 있다는 점입니다. (너무 당연한 결론이죠? ^^;)<br /> <br /> 혹시 아래의 글 기억나세요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 ; <a target='_tab' href='http://www.sysnet.pe.kr/2/0/942'>http://www.sysnet.pe.kr/2/0/942</a> </pre> <br /> 원래의 "Runtime Method Replacer" 소스 코드에서는 JIT 컴파일 된 함수의 주소를 (.NET 버전별로 바뀔 가능성이 있는) offset 연산을 통해 계산되었지만, 제가 개선한 소스 코드에서는 그 부분을 GetFunctionPointer 반환값으로 대체했기 때문에 .NET 버전에 상관없이 안정적으로 동작하는 소스 코드가 얻어진 것입니다.<br /> <br /> 자, 여기서 '메서드 치환'에 대해서 잠깐 생각해 보면, 이를 위해서는 '치환되어질 원본 메서드의 주소'값과 '치환될 새로운 메서드의 주소'값을 알아야만 가능합니다.<br /> <br /> 여전히 "Runtime Method Replacer"는 '치환되어질 원본 메서드의 주소'값에 대해서는 불안정한 offset 값 계산을 하고 있으며 그 때문에 다양한 메서드의 변종(? - 예를 들어 Ngen된 BCL 함수의 경우)에 대해서는 치환하지 못하는 단점이 있습니다.<br /> <br /> 사실, 제가 이번 글에서 시도했던 작업의 목표는 GetFunctionPointer 반환값의 정합성을 확인하기 위한 것은 아니었고, 어떤 경우에서든지 안정적으로 '치환되어질 원본 메서드의 주소' 값을 찾는 것이 목표였는데 아쉽게도 달성하지 못한 것 같습니다. 대신에 ^^ 정리해 놓은 것이 아까워서 이렇게 다른 제목으로 글을 쓰게 된 것입니다.<br /> <br /> 혹시나 닷넷 메서드 가로채기에 관심있으신 분들은 위의 글을 읽어보시고 저와 같은 시행착오를 겪지 않기를 바랍니다. ^^<br /> <br /><br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1096
(왼쪽의 숫자를 입력해야 합니다.)