성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 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...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
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'>닷넷에서 EIP/RIP 레지스터 값을 구하는 방법</h1> <p> C++ x64에서 RIP 레지스터 값을 구하는 가장 쉬운 방법은 RtlCaptureContext Win32 API를 사용하는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { CONTEXT ctx; ZeroMemory(&ctx, sizeof(CONTEXT)); ctx.ContextFlags = CONTEXT_FULL; RtlCaptureContext(&ctx); // ctx.Rip 필드에 다음 코드인 'return 0'의 "xor eax, eax" 명령어를 가리키는 주소를 담고 있음. return 0; } </pre> <br /> 아쉽게도 이 방법은 닷넷에서 써 먹을 수 없습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [DllImport("Kernel32.dll", SetLastError = true)] extern static void RtlCaptureContext(ref byte[] contextRecord); static void Function() { byte [] contents = new byte [1232]; // 1232 == sizeof(CONTEXT) byte [] length = BitConverter.GetBytes((int)1048587); // CONTEXT_FULL == 1048587; Array.Copy(length, 0, contents, 8 * 6, 4); // ContextFlags의 옵셋 위치 값: 48 RtlCaptureContext(ref contents); } </pre> <br /> 위와 같이 실행하면 RtlCaptureContext 명령어 실행에서 프로그램이 비정상 종료합니다. 심지어 해당 닷넷 프로젝트의 "Debug" / "Enable native code debugging"을 켜고 작업하게 되면 exe 프로세스가 살아 있는 희한한 현상마저 겪게 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그래서 다른 방법을 생각해 보았습니다. 혹시 어셈블리의 도움을 받으면 되지 않을까 싶었는데요. 아쉽게도 x86/x64 모두 eip/rip 레지스터를 직접 접근하는 것은 어셈블리 코드에서 허용되지 않았습니다.<br /> <br /> 물론, 우회 방법이 있습니다. ret 시에 스택에 보관된 반환 주소를 활용하는 것입니다. 즉, 다음과 같이 만들면 [rsp]에 보관된 리턴 주소를 얻을 수 있으므로 해당 코드가 실행된 곳의 (E/R)IP 주소를 구할 수 있는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .code <span style='color: blue; font-weight: bold'>GetIpRegisterValue64</span> PROC <span style='color: blue; font-weight: bold'>mov rax, [rsp]</span> ret GetIpRegisterValue64 ENDP END </pre> <br /> <a target='tab' href='http://www.sysnet.pe.kr/2/0/1759'>이를 컴파일하고 생성된 .obj 파일을 C/C++ 프로젝트와 결합</a>해 결과를 확인할 수 있습니다. 32비트의 상황까지 고려해 다음과 같이 만들고 디버그 모드에서 확인해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "stdafx.h" #if <span style='color: blue; font-weight: bold'>_M_AMD64</span> <span style='color: blue; font-weight: bold'>extern "C" __int64 GetIpRegisterValue64();</span> #else <span style='color: blue; font-weight: bold'>__declspec(naked) DWORD GetIpRegisterValue32()</span> { __asm { <span style='color: blue; font-weight: bold'>mov eax, [esp]</span> ret } } #endif #pragma warning(default : 4035) int _tmain(int argc, _TCHAR* argv[]) { #if _M_AMD64 __int64 ipRegister = GetIpRegisterValue64(); printf("0x%016llX\n", ipRegister); #else int ipRegister = GetIpRegisterValue32(); printf("0x%X\n", ipRegister); #endif return 0; } </pre> <br /> 예상했던 대로 호출된 부근의 IP 레지스터 값을 얻을 수 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='get_ip_register_1.png' src='/SysWebRes/bbs/get_ip_register_1.png' /><br /> <br /> 오호... 그런대로 효과가 있습니다. ^^<br /> <br /> 참고로, 다음과 같이 LABEL을 사용한 방법도 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Walking the stack of the current thread ; <a target='tab' href='http://jpassing.com/2008/03/12/walking-the-stack-of-the-current-thread/'>http://jpassing.com/2008/03/12/walking-the-stack-of-the-current-thread/</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CONTEXT Context; ZeroMemory( &Context, sizeof( CONTEXT ) ); Context.ContextFlags = CONTEXT_CONTROL; __asm { <span style='color: blue; font-weight: bold'>Label:</span> mov [Context.Ebp], ebp; mov [Context.Esp], esp; <span style='color: blue; font-weight: bold'>mov eax, [Label]; mov [Context.Eip], eax;</span> } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 반환 주소를 이용한 IP 주소를 얻는 방법을 닷넷에 적용해 보면 어떨까요? inline-assembly를 .NET으로 포팅하는 방법에 대해서는 지난 글에서 이미 설명드렸습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C++의 inline asm 사용을 .NET으로 포팅하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1267'>http://www.sysnet.pe.kr/2/0/1267</a> </pre> <br /> 이를 위해 Visual Studio 디스어셈블리 창에서 x86/x64 각각에 대해 기계어 코드를 구하면 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ========= x86 ========= 00FA3DB0 <span style='color: blue; font-weight: bold'>8B 04 24</span> mov eax,dword ptr [esp] 00FA3DB3 <span style='color: blue; font-weight: bold'>C3</span> ret ========= x64 ========= 00007FF7457B13E0 <span style='color: blue; font-weight: bold'>48 8B 04 24</span> mov rax,qword ptr [rsp] 00007FF7457B13E4 <span style='color: blue; font-weight: bold'>C3</span> ret </pre> <br /> 다음의 코드는 위의 기계어를 그대로 .NET Delegate에 연결해 주고 있습니다.<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.Runtime.InteropServices; namespace ConsoleApplication2 { class Program { private readonly static byte[] x86GetIp = { <span style='color: blue; font-weight: bold'>0x8b, 0x04, 0x24, // mov eax, dword ptr [esp] 0xc3, // ret</span> }; private readonly static byte[] x64GetIp = { <span style='color: blue; font-weight: bold'>0x48, 0x8b, 0x04, 0x24, // mov rax, qword ptr [rsp] 0xc3, // ret</span> }; [Flags()] private enum AllocationType : uint { COMMIT = 0x1000, RESERVE = 0x2000, RESET = 0x80000, LARGE_PAGES = 0x20000000, PHYSICAL = 0x400000, TOP_DOWN = 0x100000, WRITE_WATCH = 0x200000 } [Flags()] public enum MemoryProtection : uint { EXECUTE = 0x10, EXECUTE_READ = 0x20, EXECUTE_READWRITE = 0x40, EXECUTE_WRITECOPY = 0x80, NOACCESS = 0x01, READONLY = 0x02, READWRITE = 0x04, WRITECOPY = 0x08, GUARD_Modifierflag = 0x100, NOCACHE_Modifierflag = 0x200, WRITECOMBINE_Modifierflag = 0x400 } [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect); [DllImport("kernel32")] private static extern bool VirtualFree(IntPtr lpAddress, UInt32 dwSize, UInt32 dwFreeType); [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)] private delegate int GetIPRegister32(); [UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)] private delegate long GetIPRegister64(); static void Main(string[] args) { TestIt(); } private static void TestIt() { IntPtr _codePointer; GetIPRegister32 _x86Call; GetIPRegister64 _x64Call; byte[] codeBytes = x86GetIp; if (IntPtr.Size == 8) { codeBytes = x64GetIp; } _codePointer = VirtualAlloc(IntPtr.Zero, new UIntPtr((uint)codeBytes.Length), AllocationType.COMMIT | AllocationType.RESERVE, MemoryProtection.EXECUTE_READWRITE ); Marshal.Copy(codeBytes, 0, _codePointer, codeBytes.Length); if (IntPtr.Size == 4) { <span style='color: blue; font-weight: bold'>_x86Call = (GetIPRegister32)Marshal.GetDelegateForFunctionPointer( _codePointer, typeof(GetIPRegister32)); Console.WriteLine(_x86Call());</span> } else { <span style='color: blue; font-weight: bold'>_x64Call = (GetIPRegister64)Marshal.GetDelegateForFunctionPointer( _codePointer, typeof(GetIPRegister64)); long result = _x64Call(); Console.WriteLine(result);</span> } if (_codePointer != IntPtr.Zero) { VirtualFree(_codePointer, 0, 0x8000); _codePointer = IntPtr.Zero; } } } } </pre> <br /> 처음엔 이렇게 해주면 실행한 곳의 IP 주소를 구할 수 있을 거라 생각했는데... 아쉽게도 delegate가 별도의 클래스라는 점을 간과하고 있었습니다.<br /> <br /> 실제로 windbg를 붙여서 디버깅하기 쉽게 Main과 TestIt 메서드에 다음과 같이 코드를 추가해 두고,<br /> <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) { <span style='color: blue; font-weight: bold'> Console.WriteLine("attach debugger"); Console.ReadLine();</span> // ... [생략] ... TestIt(); } private static void TestIt() { // ... [생략] ... if (IntPtr.Size == 4) { // ... [생략] ... } else { // ... [생략] ... <span style='color: blue; font-weight: bold'> Console.WriteLine("watch this"); Console.ReadLine();</span> long result = _x64Call(); Console.WriteLine(result); } // ... [생략] ... } </pre> <br /> 실행한 다음 "attach debugger" 메시지가 출력되었을 때 windbg를 실행시켜 붙인 다음 아래와 같은 과정을 통해 실제 _x64Call이 일어날 시점의 메서드 깊이까지 추적해 보았습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:003> <span style='color: blue; font-weight: bold'>.loadby sos clr</span> 0:003> <span style='color: blue; font-weight: bold'>!clrstack</span> OS Thread Id: 0x134c (3) Unable to walk the managed stack. The current thread is likely not a managed thread. You can run !threads to get a list of managed threads in the process Failed to start stack walk: 80070057 0:003> <span style='color: blue; font-weight: bold'>!threads</span> ThreadCount: 2 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 2a8c 0000000000fd37b0 2a020 Preemptive 0000000002E366D0:0000000002E37FD0 0000000000fc78c0 1 MTA 2 2 1d3c 0000000000fdfa50 2b220 Preemptive 0000000000000000:0000000000000000 0000000000fc78c0 0 MTA (Finalizer) 0:001> <span style='color: blue; font-weight: bold'>~0s</span> ntdll!NtReadFile+0xa: 00007ffb`1482ad6a c3 ret 0:000> <span style='color: blue; font-weight: bold'>!clrstack</span> OS Thread Id: 0x2a8c (0) Child SP IP Call Site 0000000000e8eaa0 00007ffb1482ad6a [InlinedCallFrame: 0000000000e8eaa0] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 0000000000e8eaa0 00007ffb00aa6c92 [InlinedCallFrame: 0000000000e8eaa0] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 0000000000e8ea60 00007ffb00aa6c92 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 0000000000e8eb60 00007ffb013bc983 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef) 0000000000e8ebe0 00007ffb013bc7f8 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) 0000000000e8ec50 00007ffb00a06e23 System.IO.StreamReader.ReadBuffer() 0000000000e8eca0 00007ffb00a509f7 System.IO.StreamReader.ReadLine() 0000000000e8ecf0 00007ffb013c574d System.IO.TextReader+SyncTextReader.ReadLine() 0000000000e8ed50 00007ffaa27001a7 ConsoleApplication2.Program.Main(System.String[]) [c:\...\ConsoleApplication2\Program.cs @ 68] 0000000000e8f060 00007ffb01d80453 [GCFrame: 0000000000e8f060] </pre> <br /> 보시는 바와 같이 현재 Console.ReadLine에 실행이 멈춰 있습니다. 이제 TestIt 메서드에 BreakPoint를 걸고 실행을 계속하면 TestIt 메서드 진입점에서 멈춥니다.<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'>!bpmd ConsoleApplication2 ConsoleApplication2.Program.TestIt</span> Found 1 methods in module 00007ffaa25e2fc8... MethodDesc = 00007ffaa25e4110 Adding pending breakpoints.. 0:000> <span style='color: blue; font-weight: bold'>g</span> (2d00.2a8c): CLR notification exception - code e0444143 (first chance) JITTED ConsoleApplication2!ConsoleApplication2.Program.TestIt() Setting breakpoint: bp 00007FFAA270020D [ConsoleApplication2.Program.TestIt()] Breakpoint 0 hit <span style='color: blue; font-weight: bold'>00007ffa`a270020d</span> 90 nop </pre> <br /> 다시 'g' 명령을 내려서 계속 실행하면 _x64Call() 호출 바로 전의 Console.ReadLine까지 실행되는데, 거기서 디스어셈블리 창을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 00007ffa`a26f03a5 90 nop 00007ffa`a26f03a6 e875c8b55e call mscorlib_ni+0xcfcc20 (00007ffb`0124cc20) 00007ffa`a26f03ab 4889842498000000 mov qword ptr [rsp+98h],rax ss:00000000`006be9c8=60ea6b0000000000 00007ffa`a26f03b3 90 nop 00007ffa`a26f03b4 488b4c2430 mov rcx,qword ptr [rsp+30h] 00007ffa`a26f03b9 488b4908 mov rcx,qword ptr [rcx+8] 00007ffa`a26f03bd 488b442430 mov rax,qword ptr [rsp+30h] <span style='color: blue; font-weight: bold'>00007ffa`a26f03c2 ff5018 call qword ptr [rax+18h]</span> 00007ffa`a26f03c5 48898424a0000000 mov qword ptr [rsp+0A0h],rax 00007ffa`a26f03cd 488b8424a0000000 mov rax,qword ptr [rsp+0A0h] 00007ffa`a26f03d5 4889442440 mov qword ptr [rsp+40h],rax </pre> <br /> 내부에서 델리게이트 타입의 내부 함수를 호출하는 단계로 넘어갑니다. 이 안으로 진입해서 디버깅을 계속해 보면, 다음과 같이 실제로 우리가 만든 기계어 코드로의 호출이 발견됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 00007ffa`a26f0630 48894c2408 mov qword ptr [rsp+8],rcx ss:00000000`006be930=b066580200000000 00007ffa`a26f0635 55 push rbp 00007ffa`a26f0636 53 push rbx 00007ffa`a26f0637 56 push rsi 00007ffa`a26f0638 57 push rdi 00007ffa`a26f0639 4154 push r12 00007ffa`a26f063b 4155 push r13 00007ffa`a26f063d 4156 push r14 00007ffa`a26f063f 4157 push r15 00007ffa`a26f0641 4881eca8000000 sub rsp,0A8h 00007ffa`a26f0648 488d6c2420 lea rbp,[rsp+20h] 00007ffa`a26f064d 4c895560 mov qword ptr [rbp+60h],r10 00007ffa`a26f0651 c7450000000000 mov dword ptr [rbp],0 00007ffa`a26f0658 48c7450800000000 mov qword ptr [rbp+8],0 00007ffa`a26f0660 48c7451000000000 mov qword ptr [rbp+10h],0 00007ffa`a26f0668 488b4560 mov rax,qword ptr [rbp+60h] 00007ffa`a26f066c 48894518 mov qword ptr [rbp+18h],rax 00007ffa`a26f0670 488b5518 mov rdx,qword ptr [rbp+18h] 00007ffa`a26f0674 488d4d28 lea rcx,[rbp+28h] 00007ffa`a26f0678 e81b43645f call clr!JIT_InitPInvokeFrame (00007ffb`01d34998) 00007ffa`a26f067d 48894568 mov qword ptr [rbp+68h],rax ... 00007ffa`a26f06b9 488b4d70 mov rcx,qword ptr [rbp+70h] 00007ffa`a26f06bd 488b4518 mov rax,qword ptr [rbp+18h] 00007ffa`a26f06c1 48894538 mov qword ptr [rbp+38h],rax 00007ffa`a26f06c5 488d0514000000 lea rax,[00007ffa`a26f06e0] 00007ffa`a26f06cc 48894550 mov qword ptr [rbp+50h],rax 00007ffa`a26f06d0 488b4568 mov rax,qword ptr [rbp+68h] 00007ffa`a26f06d4 c7400c00000000 mov dword ptr [rax+0Ch],0 00007ffa`a26f06db 4533db xor r11d,r11d <span style='color: blue; font-weight: bold'>00007ffa`a26f06de ffd1 call rcx</span> 00000000`00a3fffe 0000 add byte ptr [rax],al 00000000`00a40000 488b0424 mov rax,qword ptr [rsp] ss:00000000`006be838=e0066fa2fa7f0000 00000000`00a40004 c3 ret 00000000`00a40005 0000 add byte ptr [rax],al </pre> <br /> 결국, 우리가 얻게 되는 IP 주소는 닷넷 델리게이트 타입의 Invoke 함수가 위치한 코드의 주소일 뿐입니다. (업데이트: <a targe='tab' href='https://www.sysnet.pe.kr/2/0/13172#use_func_ptr'>이러한 불일치를 C# 9.0의 함수 포인터를 이용하면 해결</a>할 수 있습니다.)<br /> <br /> <hr style='width: 50%' /><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='http://www.sysnet.pe.kr/2/0/1027'>http://www.sysnet.pe.kr/2/0/1027</a> </pre> <br /> 그렇습니다. 우리는 GetFunctionPointer 메서드를 통해 함수의 진입점 주소를 알 수 있습니다.<br /> <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) { foreach (MethodInfo mi in typeof(Program).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)) { if (mi.Name == "TestIt") { RuntimeHelpers.PrepareMethod(mi.MethodHandle); Console.WriteLine(mi.MethodHandle.GetFunctionPointer().ToString("x")); // TestIt 메서드의 진입점 주소 } } TestIt(); } </pre> <br /> 위의 코드 실행 결과로 출력된 값이 해당 메서드의 주소인지 windbg를 통해 확인해 볼 수 있습니다. 일단, 출력 결과는 "00007ffaa26e03c0" 값이었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:003> <span style='color: blue; font-weight: bold'>.loadby sos clr</span> 0:003> <span style='color: blue; font-weight: bold'>!bpmd ConsoleApplication2 ConsoleApplication2.Program.TestIt</span> Found 1 methods in module 00007ffaa25c2fc8... MethodDesc = 00007ffaa25c4110 Adding pending breakpoints... 0:003> <span style='color: blue; font-weight: bold'>g</span> (31d8.3268): CLR notification exception - code e0444143 (first chance) JITTED ConsoleApplication2!ConsoleApplication2.Program.TestIt() Setting breakpoint: bp 00007FFAA26E03FD [ConsoleApplication2.Program.TestIt()] Breakpoint 0 hit <span style='color: blue; font-weight: bold'>00007ffa`a26e03fd</span> 90 nop </pre> <br /> 오호~~~ 그런데 값이 일치하지 않는군요. 화면의 GetFunctionPointer 출력값은 00007ffa`a26e03c0이지만, BP가 걸린 곳은 00007ffa`a26e03fd입니다. 차이값은 61(= 00000000`0000003d) 바이트 정도입니다. name2ee 명령어를 통해 확인해도 역시 GetFunctionPointer 값과 일치합니다.<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> !name2ee ConsoleApplication2.exe!ConsoleApplication2.Program.TestIt Module: 00007ffaa25c2fc8 Assembly: ConsoleApplication2.exe Token: 0000000006000004 MethodDesc: 00007ffaa25c4110 Name: ConsoleApplication2.Program.TestIt() <span style='color: blue; font-weight: bold'>JITTED Code Address: 00007ffaa26e03c0</span> </pre> <br /> 혹시나 싶어 00007ffaa26e03c0 주소를 역어셈블하면 다음과 같이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>00007ffa`a26e03c0 4881ecc8000000 sub rsp,0C8h</span> 00007ffa`a26e03c7 33c0 xor eax,eax 00007ffa`a26e03c9 4889442438 mov qword ptr [rsp+38h],rax 00007ffa`a26e03ce 4889442430 mov qword ptr [rsp+30h],rax 00007ffa`a26e03d3 4889442428 mov qword ptr [rsp+28h],rax 00007ffa`a26e03d8 33c0 xor eax,eax 00007ffa`a26e03da 88442448 mov byte ptr [rsp+48h],al 00007ffa`a26e03de 4889442440 mov qword ptr [rsp+40h],rax 00007ffa`a26e03e3 4889442420 mov qword ptr [rsp+20h],rax 00007ffa`a26e03e8 48b838345ca2fa7f0000 mov rax,7FFAA25C3438h 00007ffa`a26e03f2 8b00 mov eax,dword ptr [rax] 00007ffa`a26e03f4 85c0 test eax,eax 00007ffa`a26e03f6 7405 je 00007ffa`a26e03fd 00007ffa`a26e03f8 e8f335b15f call clr!JIT_DbgIsJustMyCode (00007ffb`021f39f0) <span style='color: blue; font-weight: bold'>00007ffa`a26e03fd 90 nop</span> 00007ffa`a26e03fe 48b87857431200000000 mov rax,12435778h 00007ffa`a26e0408 488b00 mov rax,qword ptr [rax] 00007ffa`a26e040b 4889442438 mov qword ptr [rsp+38h],rax 00007ffa`a26e0410 e8fb5bb35e call mscorlib_ni+0xcc6010 (00007ffb`01216010) 00007ffa`a26e0415 89442458 mov dword ptr [rsp+58h],eax 00007ffa`a26e0419 33c0 xor eax,eax </pre> <br /> 아하... 보니까 "JITTED Code Address: 00007ffaa26e03c0" 값은 실제 함수의 진입점을 가리키는 반면 디버거가 BP로 멈춘 지점은 함수의 prolog 부분이 모두 실행된 다음의 위치를 가리킨 것입니다.<br /> <br /> 자... 그럼 이것으로 GetFunctionPointer 메서드가 반환한 값을 믿을 수 있게 되었습니다.<br /> <br /> 문제는, 특정 명령어 위치의 IP 주소를 어떻게 구하느냐입니다. 이를 위해 StackFrame 타입을 이용할 수 있습니다. StackFrame에는 해당 타입이 생성된 시기의 (E/R)IP 옵셋값을 담고 있기 때문입니다.<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.Diagnostics; class Program { static void Function() { StackFrame st = new StackFrame(0, true); IntPtr funcAddress = st.GetMethod().MethodHandle.<span style='color: blue; font-weight: bold'>GetFunctionPointer</span>(); int offset = st.<span style='color: blue; font-weight: bold'>GetNativeOffset</span>(); Console.WriteLine("func address: " + funcAddress.ToInt64().ToString("x")); Console.WriteLine("offset: " + offset.ToString("x")); } static void Main(string[] args) { Console.WriteLine("attach debugger"); Console.ReadLine(); Function(); } } </pre> <br /> 따라서 GetFunctionPointer 반환값과 GetNativeOffset 값을 합치면 그 명령어가 수행된 IP 주소를 구할 수 있습니다. 실제로 위의 프로그램을 실행시켜서 windbg로 붙여 보면 다음과 같은 디스어셈블 코드를 보게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>00007ffa`a26d00f0 4881ecb8000000 sub rsp,0B8h</span> 00007ffa`a26d00f7 33c0 xor eax,eax 00007ffa`a26d00f9 4889442440 mov qword ptr [rsp+40h],rax 00007ffa`a26d00fe 4889442438 mov qword ptr [rsp+38h],rax 00007ffa`a26d0103 89442430 mov dword ptr [rsp+30h],eax 00007ffa`a26d0107 4889442428 mov qword ptr [rsp+28h],rax 00007ffa`a26d010c 48c744242000000000 mov qword ptr [rsp+20h],0 00007ffa`a26d0115 4889442448 mov qword ptr [rsp+48h],rax 00007ffa`a26d011a 48b838345ba2fa7f0000 mov rax,7FFAA25B3438h 00007ffa`a26d0124 8b00 mov eax,dword ptr [rax] 00007ffa`a26d0126 85c0 test eax,eax 00007ffa`a26d0128 7405 je 00007ffa`a26d012f 00007ffa`a26d012a e8c138b25f call clr!JIT_DbgIsJustMyCode (00007ffb`021f39f0) * <span style='color: blue; font-weight: bold'>00007ffa`a26d012f 90 nop</span> 00007ffa`a26d0130 488d0df184525e lea rcx,[mscorlib_ni+0x6a8628 (00007ffb`00bf8628)] 00007ffa`a26d0137 e8c421665f call clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`01d32300) 00007ffa`a26d013c 4889442450 mov qword ptr [rsp+50h],rax 00007ffa`a26d0141 488b442450 mov rax,qword ptr [rsp+50h] 00007ffa`a26d0146 4889442458 mov qword ptr [rsp+58h],rax 00007ffa`a26d014b 41b001 mov r8b,1 00007ffa`a26d014e 33d2 xor edx,edx 00007ffa`a26d0150 488b4c2458 mov rcx,qword ptr [rsp+58h] 00007ffa`a26d0155 e8d638c35e <span style='color: blue; font-weight: bold'>call mscorlib_ni+0xdb3a30 (00007ffb`01303a30)</span> <span style='color: blue; font-weight: bold'>00007ffa`a26d015a 4c8b5c2458 mov r11,qword ptr [rsp+58h] </span></pre> <br /> 이때의 GetFunctionPointer 반환값은 00007ffa`a26d00f0이고, GetNativeOffset은 6a였습니다. 이를 더하면 00007ffa`a26d015a 값이 되고 이는 new StackFrame의 호출 (call mscorlib_ni+0xdb3a30 (00007ffb`01303a30)) 바로 다음 주소를 가리키고 있습니다. 이 정도면... 뭐 그런대로 원하는 목적을 달성했습니다. ^^<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=887&boardid=331301885'>첨부 파일은 이 글에 실린 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1614
(왼쪽의 숫자를 입력해야 합니다.)