성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
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'>x64 콜 스택 인자 추적과 windbg의 Child-SP, RetAddr, Args to Child 값 확인</h1> <p> .NET Thread.Sleep 함수를 호출한 경우로 예를 들어볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Thread.Sleep(60 * 1000); </pre> <br /> .NET Reflector로 확인해 보면 내부적으로 SleepInternal로 내려가고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ================ Thread.cs <span style='color: blue; font-weight: bold'>[MethodImplAttribute(MethodImplOptions.InternalCall)] private static extern void SleepInternal(int millisecondsTimeout);</span> public static void Sleep(int millisecondsTimeout) { <span style='color: blue; font-weight: bold'>SleepInternal</span>(millisecondsTimeout); } </pre> <br /> sscli를 참고 삼아 추적해 가면, .\sscli\clr\src\vm\ecall.cpp 파일에서 ThreadNative::Sleep으로 연결되는 것을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > FCFuncStart(gThreadFuncs) ...[생략]... FCFuncElement(<span style='color: blue; font-weight: bold'>"SleepInternal", ThreadNative::Sleep</span>) ...[생략]... FCFuncEnd() </pre> <br /> ThreadNative::Sleep 정의는 .\sscli\clr\src\vm\comsynchronizable.cpp에 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > FCIMPL1(void, <span style='color: blue; font-weight: bold'>ThreadNative::Sleep</span>, INT32 iTime) { ...[생략]... GetThread()->UserSleep(iTime); ..[생략]... } FCIMPLEND </pre> <br /> GetThread()->UserSleep 메서드는 .\sscli\clr\src\vm\threads.cpp 파일에 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Implementation of Thread.Sleep(). void <span style='color: blue; font-weight: bold'>Thread::UserSleep</span>(INT32 time) { ...[생략]... res = <span style='color: blue; font-weight: bold'>ClrSleepEx</span> (time, TRUE); ...[생략]... } </pre> <br /> 겨우 Sleep 하나에 길게도 내려가는군요. ^^; 계속해서 ClrSleepEx의 정의를 .\sscli\clr\src\vm\hosting.h에서 찾을 수 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #define ClrSleepEx <span style='color: blue; font-weight: bold'>EESleepEx</span> </pre> <br /> EESleepEx 함수의 실제 정의는 .\sscli\clr\src\utilcode\clrhost.cpp에서 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DWORD ClrSleepEx(DWORD dwMilliseconds, BOOL bAlertable) { WRAPPER_CONTRACT; return GetExecutionEngine()->ClrSleepEx(dwMilliseconds, bAlertable); } </pre> <br /> 자, 마지막입니다. ^^ GetExecutionEngine()->ClrSleepEx의 정의는 .\sscli\clr\src\utilcode\hostimpl.cpp 파일에 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DWORD STDMETHODCALLTYPE UtilExecutionEngine::ClrSleepEx(DWORD dwMilliseconds, BOOL bAlertable) { return <span style='color: blue; font-weight: bold'>SleepEx</span> (dwMilliseconds, bAlertable); } </pre> <br /> 이제서야 Win32 API의 SleepEx가 불리는 것을 볼 수 있습니다. ^^<br /> <br /> 이후부터는 windbg를 통해 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:035> <span style='color: blue; font-weight: bold'>kb</span> RetAddr : Args to Child : Call Site 00007ffb`ba62121a : 000000ca`9bbad108 000000ca`9bbad110 000000c6`161889c0 00007ffb`4686d129 : <span style='color: blue; font-weight: bold'>ntdll!NtDelayExecution+0xa</span> 00007ffb`a5152e73 : 00000000`00000000 00000000`00000001 00000000`00000000 00000000`00000000 : <span style='color: blue; font-weight: bold'>KERNELBASE!SleepEx+0xa2</span> 00007ffb`a515da6c : 000000ca`9ad04f70 00000000`0000ea60 000000c6`161889c0 00007ffb`45fe5db2 : clr!EESleepEx+0x24 00007ffb`a515db71 : 06000000`00000001 00000000`008363dd 04000000`00000001 00007ffb`4686d705 : clr!Thread::UserSleep+0xa5 00007ffb`4615e43f : 000000ca`0000ea60 000000ca`9b8307a0 000000c6`96a05d28 000000ca`9bbad4b0 : clr!ThreadNative::Sleep+0xad 00007ffb`47072761 : 000000c6`0000ea60 000000c8`16187508 00007ffb`00000000 000000ca`99ad1ca3 : 0x00007ffb`4615e43f 00007ffb`467b9762 : 000000c6`961fa428 000000c6`961fa428 000000c6`961ae3d0 000000ca`9b8307a0 : 0x00007ffb`47072761 00007ffb`467b9258 : 000000c6`961fe978 000000c6`961fa428 000000c6`961ae3d0 00000000`00000000 : 0x00007ffb`467b9762 </pre> <br /> EESleepEx에서 Win32 API인 SleepEx가 불렸고 거기서 다시 NtDelayExecution이 불립니다. windbg로 NtDelayExecution을 살펴보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ntdll!NtDelayExecution: 00007ffb`bd1b1500 4c8bd1 mov r10,rcx 00007ffb`bd1b1503 b833000000 mov eax,33h 00007ffb`bd1b1508 0f05 <span style='color: blue; font-weight: bold'>syscall</span> 00007ffb`bd1b150a c3 ret </pre> <br /> 커널 호출로 넘어가는 syscall로 마무리되고 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> x64에서 콜스택을 역으로 추적하려면 다음의 2가지 특성을 알고 있어야 합니다.<br /> <br /> <ul> <li>x86에서는 EBP 레지스터를 통해 스택 프레임을 형성했던 것과는 달리 x64에서는 EBP(RBP) 레지스터가 범용으로 바뀌었음</li> <li>대신 ESP(RSP) 레지스터는 함수의 진입점에서 한번만 바뀌고, 함수의 반환점에서 호출 이전의 값으로 복원됨.</li> </ul> <br /> 자, 그럼 이제 windbg에서 NtDelayExecution으로부터 역으로 콜스택을 추적해 보겠습니다. 우선, 위에서 보여준 NtDelayExecution를 코드를 다시 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ntdll!NtDelayExecution: 00007ffb`bd1b1500 4c8bd1 mov r10,rcx 00007ffb`bd1b1503 b833000000 mov eax,33h 00007ffb`bd1b1508 0f05 <span style='color: blue; font-weight: bold'>syscall</span> 00007ffb`bd1b150a c3 ret </pre> <br /> 스택 관련 명령어가 없으므로 NtDelayExecution의 RSP는 변경되지 않은 상태이므로 현재의 RSP에는 NtDelayExecution의 ret 명령어로 돌아갈 상위 SleepEx로의 반환 주소를 가리키고 있습니다. 확인을 위해 windbg에서 rsp 레지스터의 값을 확인해 보았고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > rsp == 000000ca`9bbad048 </pre> <br /> 000000ca`9bbad048 주소의 값을 덤프해 보니 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [000000ca`9bbad048 주소의 메모리 값] <span style='color: blue; font-weight: bold'>ba62121a 00007ffb</span> 9bbad108 000000ca 9bbad110 000000ca 161889c0 000000c6 </pre> <br /> 즉, "ba62121a 00007ffb" 8바이트 값이 NtDelayExecution의 ret 코드로 인해 꺼내질 SleepEx로의 반환 주소입니다. 또한 이 값은 kb 명령어로도 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:035> <span style='color: blue; font-weight: bold'>kb</span> <span style='color: blue; font-weight: bold'>RetAddr</span> : Args to Child : Call Site <span style='color: blue; font-weight: bold'>00007ffb`ba62121a</span> : 000000ca`9bbad108 000000ca`9bbad110 000000c6`161889c0 00007ffb`4686d129 : ntdll!NtDelayExecution+0xa 00007ffb`a5152e73 : 00000000`00000000 00000000`00000001 00000000`00000000 00000000`00000000 : KERNELBASE!SleepEx+0xa2 00007ffb`a515da6c : 000000ca`9ad04f70 00000000`0000ea60 000000c6`161889c0 00007ffb`45fe5db2 : clr!EESleepEx+0x24 00007ffb`a515db71 : 06000000`00000001 00000000`008363dd 04000000`00000001 00007ffb`4686d705 : clr!Thread::UserSleep+0xa5 00007ffb`4615e43f : 000000ca`0000ea60 000000ca`9b8307a0 000000c6`96a05d28 000000ca`9bbad4b0 : clr!ThreadNative::Sleep+0xad 00007ffb`47072761 : 000000c6`0000ea60 000000c8`16187508 00007ffb`00000000 000000ca`99ad1ca3 : 0x00007ffb`4615e43f 00007ffb`467b9762 : 000000c6`961fa428 000000c6`961fa428 000000c6`961ae3d0 000000ca`9b8307a0 : 0x00007ffb`47072761 00007ffb`467b9258 : 000000c6`961fe978 000000c6`961fa428 000000c6`961ae3d0 00000000`00000000 : 0x00007ffb`467b9762 </pre> <br /> 이어서 SleepEx 코드를 보면, 예상했던 대로 00007ffb`ba62121a 주소가 NtDelayExecution 호출 바로 다음에 있는 것을 확인할 수 있습니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > KERNELBASE!SleepEx: 00007ffb`ba621170 4c8bdc mov r11,rsp 00007ffb`ba621173 49895b08 mov qword ptr [r11+8],rbx 00007ffb`ba621177 89542410 mov dword ptr [rsp+10h],edx 00007ffb`ba62117b 56 push rsi 00007ffb`ba62117c 57 push rdi 00007ffb`ba62117d 4156 push r14 00007ffb`ba62117f 4881ec80000000 sub rsp,80h 00007ffb`ba621186 8bda mov ebx,edx 00007ffb`ba621188 8bf1 mov esi,ecx 00007ffb`ba62118a 49c7439848000000 mov qword ptr [r11-68h],48h 00007ffb`ba621192 c744243801000000 mov dword ptr [rsp+38h],1 00007ffb`ba62119a 33c0 xor eax,eax 00007ffb`ba62119c 498943a8 mov qword ptr [r11-58h],rax 00007ffb`ba6211a0 498943b0 mov qword ptr [r11-50h],rax 00007ffb`ba6211a4 498943b8 mov qword ptr [r11-48h],rax 00007ffb`ba6211a8 498943c0 mov qword ptr [r11-40h],rax ...[생략]... 00007ffb`ba621208 0f853a190900 jne KERNELBASE!SleepEx+0xc6 (00007ffb`ba6b2b48) 00007ffb`ba62120e 498bd6 mov rdx,r14 // r14 == ca9bbad070 00007ffb`ba621211 0fb6cb movzx ecx,bl 00007ffb`ba621214 ff15de8b1000 call qword ptr [KERNELBASE!_imp_NtDelayExecution (00007ffb`ba729df8)] <span style='color: blue; font-weight: bold'>00007ffb`ba62121a 8bf0 mov esi,eax // _imp_NtDelayExecution 실행 후 돌아오는 주소</span> 00007ffb`ba62121c 898424b0000000 mov dword ptr [rsp+0B0h],eax 00007ffb`ba621223 85db test ebx,ebx 00007ffb`ba621225 0f85a2bb0000 jne KERNELBASE!SleepEx+0xaf (00007ffb`ba62cdcd) 00007ffb`ba62122b 488b8424b8000000 mov rax,qword ptr [rsp+0B8h] 00007ffb`ba621233 4885c0 test rax,rax 00007ffb`ba621236 0f8523190900 jne KERNELBASE!SleepEx+0x12d (00007ffb`ba6b2b5f) 00007ffb`ba62123c 85db test ebx,ebx 00007ffb`ba62123e 0f859abb0000 jne KERNELBASE!SleepEx+0x120 (00007ffb`ba62cdde) 00007ffb`ba621244 b8c0000000 mov eax,0C0h 00007ffb`ba621249 3bf0 cmp esi,eax 00007ffb`ba62124b 7402 je KERNELBASE!SleepEx+0xf2 (00007ffb`ba62124f) 00007ffb`ba62124d 8bc7 mov eax,edi 00007ffb`ba62124f 488b9c24a0000000 mov rbx,qword ptr [rsp+0A0h] 00007ffb`ba621257 4881c480000000 add rsp,80h 00007ffb`ba62125e 415e pop r14 00007ffb`ba621260 5f pop rdi 00007ffb`ba621261 5e pop rsi 00007ffb`ba621262 c3 ret </pre> <br /> 그 외에, SleepEx 코드를 보면 이전에 말했던 대로 진입점과 반환점 이외에는 push/pop 스택 명령어 및 rsp 레지스터에 대한 sub/add 명령어가 없는 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > KERNELBASE!SleepEx: 00007ffb`ba621170 4c8bdc mov r11,rsp 00007ffb`ba621173 49895b08 mov qword ptr [r11+8],rbx 00007ffb`ba621177 89542410 mov dword ptr [rsp+10h],edx 00007ffb`ba62117b 56 <span style='color: blue; font-weight: bold'>push rsi</span> 00007ffb`ba62117c 57 <span style='color: blue; font-weight: bold'>push rdi</span> 00007ffb`ba62117d 4156 <span style='color: blue; font-weight: bold'>push r14</span> 00007ffb`ba62117f 4881ec80000000 <span style='color: blue; font-weight: bold'>sub rsp,80h</span> 00007ffb`ba621186 8bda mov ebx,edx ...[생략]... 00007ffb`ba621214 ff15de8b1000 call qword ptr [KERNELBASE!_imp_NtDelayExecution (00007ffb`ba729df8)] 00007ffb`ba62121a 8bf0 mov esi,eax 00007ffb`ba62121c 898424b0000000 mov dword ptr [rsp+0B0h],eax ...[생략]... 00007ffb`ba62124f 488b9c24a0000000 mov rbx,qword ptr [rsp+0A0h] 00007ffb`ba621257 4881c480000000 <span style='color: blue; font-weight: bold'>add rsp,80h</span> 00007ffb`ba62125e 415e <span style='color: blue; font-weight: bold'>pop r14</span> 00007ffb`ba621260 5f <span style='color: blue; font-weight: bold'>pop rdi</span> 00007ffb`ba621261 5e <span style='color: blue; font-weight: bold'>pop rsi</span> 00007ffb`ba621262 c3 ret </pre> <br /> 이런 성질 덕분에 콜스택을 추적하는 것이 가능합니다. 위의 코드에서 SleepEx 진입점을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > push rsi push rdi push r14 sub rsp,80h </pre> <br /> 총 0x80 + (8 * 3)개의 스택이 점유된 것을 알 수 있습니다. 이전의 RSP 레지스터에 대한 메모리 덤프를 좀 더 많이 해보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 000000ca`9bbad048 <span style='color: blue; font-weight: bold'>ba62121a 00007ffb</span> 9bbad108 000000ca 9bbad110 000000ca 161889c0 000000c6 000000ca`9bbad068 4686d129 00007ffb dc3cba00 ffffffff 9bbad070 000000ca 00000048 00000000 000000ca`9bbad088 00000001 000000ca 00000000 00000000 00000000 00000000 00000030 00000000 000000ca`9bbad0a8 ffffffff ffffffff ffffffff ffffffff ba62cdbf 00007ffb 00000000 00000000 000000ca`9bbad0c8 00000015 000000c6 a515dac4 00007ffb 0000ea60 00000000 008363dd 00000000 000000ca`9bbad0e8 a5152e73 00007ffb 00000000 00000000 00000001 00000000 00000000 00000000 000000ca`9bbad108 00000000 00000000 9ad04f70 000000ca a515da6c 00007ffb 9ad04f70 000000ca 000000ca`9bbad128 0000ea60 00000000 161889c0 000000c6 45fe5db2 00007ffb fffffffe ffffffff 000000ca`9bbad148 9ad04f70 000000ca 00000001 000000ca 96a31838 000000c6 00000000 00000000 </pre> <br /> 현재 NtDelayExecution 함수가 실행되고 있는 상태이므로 ba62121a 00007ffb 값은 이전에 살펴본 것처럼 SleepEx로의 반환 주소입니다. 따라서 ret 코드 이후 RSP는 (000000ca`9bbad048 + 8) 주소로 이동합니다. 이를 다시 말하면 NtDelayExecution 함수를 호출(call)하기 전에는 RSP의 값이 (000000ca`9bbad048 + 8) 상태였다는 것을 의미합니다.<br /> <br /> 따라서, (000000ca`9bbad048 + 8) 위치로부터 0x80 (8바이트 16개 메모리 분량)까지는 SleepEx의 초기 "sub rsp, 80h"로 인해 점유된 공간이고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 000000ca`9bbad048 ba62121a 00007ffb <span style='color: blue; font-weight: bold'>9bbad108 000000ca 9bbad110 000000ca 161889c0 000000c6</span> 000000ca`9bbad068 <span style='color: blue; font-weight: bold'>4686d129 00007ffb dc3cba00 ffffffff 9bbad070 000000ca 00000048 00000000</span> 000000ca`9bbad088 <span style='color: blue; font-weight: bold'>00000001 000000ca 00000000 00000000 00000000 00000000 00000030 00000000</span> 000000ca`9bbad0a8 <span style='color: blue; font-weight: bold'>ffffffff ffffffff ffffffff ffffffff ba62cdbf 00007ffb 00000000 00000000</span> 000000ca`9bbad0c8 <span style='color: blue; font-weight: bold'>00000015 000000c6</span> a515dac4 00007ffb 0000ea60 00000000 008363dd 00000000 000000ca`9bbad0e8 a5152e73 00007ffb 00000000 00000000 00000001 00000000 00000000 00000000 000000ca`9bbad108 00000000 00000000 9ad04f70 000000ca a515da6c 00007ffb 9ad04f70 000000ca 000000ca`9bbad128 0000ea60 00000000 161889c0 000000c6 45fe5db2 00007ffb fffffffe ffffffff 000000ca`9bbad148 9ad04f70 000000ca 00000001 000000ca 96a31838 000000c6 00000000 00000000 </pre> <br /> 다시 이후의 8바이트 * 3개의 영역은 각각 push rsi, push rdi, push r14으로 점유된 공간입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 000000ca`9bbad048 ba62121a 00007ffb 9bbad108 000000ca 9bbad110 000000ca 161889c0 000000c6 000000ca`9bbad068 4686d129 00007ffb dc3cba00 ffffffff 9bbad070 000000ca 00000048 00000000 000000ca`9bbad088 00000001 000000ca 00000000 00000000 00000000 00000000 00000030 00000000 000000ca`9bbad0a8 ffffffff ffffffff ffffffff ffffffff ba62cdbf 00007ffb 00000000 00000000 000000ca`9bbad0c8 00000015 000000c6 <span style='color: blue; font-weight: bold'>a515dac4 00007ffb 0000ea60 00000000 008363dd 00000000</span> 000000ca`9bbad0e8 a5152e73 00007ffb 00000000 00000000 00000001 00000000 00000000 00000000 000000ca`9bbad108 00000000 00000000 9ad04f70 000000ca a515da6c 00007ffb 9ad04f70 000000ca 000000ca`9bbad128 0000ea60 00000000 161889c0 000000c6 45fe5db2 00007ffb fffffffe ffffffff 000000ca`9bbad148 9ad04f70 000000ca 00000001 000000ca 96a31838 000000c6 00000000 00000000 </pre> <br /> 결국 이 스택 영역(0x80 + 8 * 3)은 SleepEx의 반환점에서 다음의 명령어로 정리가 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > add rsp,80h pop r14 pop rdi pop rsi </pre> <br /> 그렇다면 이제 자연스럽게 SleepEx의 ret으로 인해 반환될 주소가 "a5152e73 00007ffb"임을 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 000000ca`9bbad048 ba62121a 00007ffb 9bbad108 000000ca 9bbad110 000000ca 161889c0 000000c6 000000ca`9bbad068 4686d129 00007ffb dc3cba00 ffffffff 9bbad070 000000ca 00000048 00000000 000000ca`9bbad088 00000001 000000ca 00000000 00000000 00000000 00000000 00000030 00000000 000000ca`9bbad0a8 ffffffff ffffffff ffffffff ffffffff ba62cdbf 00007ffb 00000000 00000000 000000ca`9bbad0c8 00000015 000000c6 a515dac4 00007ffb 0000ea60 00000000 008363dd 00000000 000000ca`9bbad0e8 <span style='color: blue; font-weight: bold'>a5152e73 00007ffb</span> 00000000 00000000 00000001 00000000 00000000 00000000 000000ca`9bbad108 00000000 00000000 9ad04f70 000000ca a515da6c 00007ffb 9ad04f70 000000ca 000000ca`9bbad128 0000ea60 00000000 161889c0 000000c6 45fe5db2 00007ffb fffffffe ffffffff 000000ca`9bbad148 9ad04f70 000000ca 00000001 000000ca 96a31838 000000c6 00000000 00000000 </pre> <br /> 이렇게 반복하면 x64 프로그램에서 현재의 RSP 레지스터 값만으로 호출 스택을 차례로 알아낼 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 실습을 위해 ^^ SleepEx도 한번 해볼까요?<br /> <br /> SleepEx의 ret 으로 인해 돌아갈 주소는 "00007ffb`a5152e73"이고 당연히 EESleepEx 함수 내의 _imp_SleepEx 호출 이후의 주소에 해당합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > clr!EESleepEx: 00007ffb`a5152e50 53 push rbx 00007ffb`a5152e51 4883ec20 sub rsp,20h 00007ffb`a5152e55 448bc9 mov r9d,ecx 00007ffb`a5152e58 33db xor ebx,ebx 00007ffb`a5152e5a 488b0d8f588e00 mov rcx,qword ptr [clr!CorHost2::m_HostTaskManager (00007ffb`a5a386f0)] 00007ffb`a5152e61 4885c9 test rcx,rcx 00007ffb`a5152e64 0f85aec13700 jne clr!EESleepEx+0x37c1c8 (00007ffb`a54cf018) 00007ffb`a5152e6a 418bc9 mov ecx,r9d 00007ffb`a5152e6d ff1535f86b00 call qword ptr [clr!_imp_SleepEx (00007ffb`a58126a8)] <span style='color: blue; font-weight: bold'>00007ffb`a5152e73 8bd8 mov ebx,eax // 00007ffb`a5152e73 여기를 가리킴.</span> 00007ffb`a5152e75 8bc3 mov eax,ebx 00007ffb`a5152e77 4883c420 add rsp,20h 00007ffb`a5152e7b 5b pop rbx 00007ffb`a5152e7c c3 ret 00007ffb`a5152e7d 90 nop 00007ffb`a5152e7e 90 nop </pre> <br /> 다시 말씀드리지만 함수의 진입점과 반환점 이외의 곳에서는 스택 조작 명령어가 x64에서는 없습니다. 이로 인해 처음과 마지막의 스택 조작 명령어는 쌍을 이루게 되고 그 중 하나만 확인해도 그 함수가 예약한 스택의 총 크기를 알 수 있습니다. 위의 EESleepEx의 경우에도 다음과 같이 스택 조작 명령어를 처음/끝에서 확인할 수 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > push rbx sub rsp,20h ...[생략]... add rsp,20h pop rbx </pre> <br /> 따라서 EESleepEx는 0x20(8바이트로 4개) + 8의 스택 공간을 사용하고 있는 것입니다. 이전의 RSP 덤프 메모리에 이어서 값을 확인해 보면, "sub rsp, 20h"로 인해 점유된 공간이 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 000000ca`9bbad0e8 a5152e73 00007ffb <span style='color: blue; font-weight: bold'>00000000 00000000 00000001 00000000 00000000 00000000</span> 000000ca`9bbad108 <span style='color: blue; font-weight: bold'>00000000 00000000</span> 9ad04f70 000000ca a515da6c 00007ffb 9ad04f70 000000ca 000000ca`9bbad128 0000ea60 00000000 161889c0 000000c6 45fe5db2 00007ffb fffffffe ffffffff 000000ca`9bbad148 9ad04f70 000000ca 00000001 000000ca 96a31838 000000c6 00000000 00000000 000000ca`9bbad168 0000ea60 00000000 9bbad310 000000ca a515db71 00007ffb 00000001 06000000 </pre> <br /> "push rbx" 공간까지 제외하고 나면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 000000ca`9bbad0e8 a5152e73 00007ffb 00000000 00000000 00000001 00000000 00000000 00000000 000000ca`9bbad108 00000000 00000000 <span style='color: blue; font-weight: bold'>9ad04f70 000000ca</span> a515da6c 00007ffb 9ad04f70 000000ca 000000ca`9bbad128 0000ea60 00000000 161889c0 000000c6 45fe5db2 00007ffb fffffffe ffffffff 000000ca`9bbad148 9ad04f70 000000ca 00000001 000000ca 96a31838 000000c6 00000000 00000000 000000ca`9bbad168 0000ea60 00000000 9bbad310 000000ca a515db71 00007ffb 00000001 06000000 </pre> <br /> EESleepEx 실행 후 돌아갈 주소가 구해집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 000000ca`9bbad0e8 a5152e73 00007ffb 00000000 00000000 00000001 00000000 00000000 00000000 000000ca`9bbad108 00000000 00000000 9ad04f70 000000ca <span style='color: blue; font-weight: bold'>a515da6c 00007ffb</span> 9ad04f70 000000ca 000000ca`9bbad128 0000ea60 00000000 161889c0 000000c6 45fe5db2 00007ffb fffffffe ffffffff 000000ca`9bbad148 9ad04f70 000000ca 00000001 000000ca 96a31838 000000c6 00000000 00000000 000000ca`9bbad168 0000ea60 00000000 9bbad310 000000ca a515db71 00007ffb 00000001 06000000 </pre> <br /> 이 정도면, RSP 값을 이용해 콜 스택을 추적하는 방법을 확실히 아셨겠지요! ^^<br /> <br /> <hr style='width: 50%' /><br /> <a name='x64_arg_abi'></a> <br /> 콜스택 추적과 함께 중요한 점이 있다면 바로 인자(argument) 값에 대한 추적입니다. 아시는 바와 같이 x64 ABI(응용 프로그램 이진 인터페이스)에서 처음 4개의 인수는 레지스터 RCX, RDX, R8 및 R9에 전달됩니다. 하지만, rcx, rdx, r8, r9 레지스터는 해당 함수 내에서 재사용할 수 있으므로 이런 경우 백업을 위해 무조건 rsp + (8 * 4 == 0x20)개 영역의 스택을 확보하게 됩니다. 따라서, 거의 대부분의 x64 플랫폼의 함수 호출에서는 진입점에서 "sub rsp, ??h" 코드가 0x20 이상의 값을 가지게 됩니다.<br /> <br /> 확인 차원에서 ThreadNative::Sleep의 코드를 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > clr!ThreadNative::Sleep: 00007ffb`a515dac4 488bc4 mov rax,rsp 00007ffb`a515dac7 894808 <span style='color: blue; font-weight: bold'>mov dword ptr [rax+8],ecx</span> 00007ffb`a515daca 4154 push r12 00007ffb`a515dacc 4156 push r14 00007ffb`a515dace 4157 push r15 00007ffb`a515dad0 4881ec40010000 <span style='color: blue; font-weight: bold'>sub rsp,140h</span> 00007ffb`a515dad7 48c7442448feffffff mov qword ptr [rsp+48h],0FFFFFFFFFFFFFFFEh 00007ffb`a515dae0 48895810 mov qword ptr [rax+10h],rbx </pre> <br /> Sleep 함수는 1개의 인자를 받으므로 rcx에 전달되었고, 이 값을 rax == rsp, [rax + 8] 위치에 보관하고 있습니다. 위의 코드에서 또 한가지 재미있는 점이 있다면, rcx에 전달된 인자를 스택에 백업하는 "move dword ptr [rax + 8], ecx" 코드에서 ecx가 쓰였다는 것입니다. 물론, Sleep의 인자 형이 int 4byte이기 때문에 ecx가 사용되어도 무방하지만 이로 인해 rcx 8바이트 중 상위 4바이트는 삭제되지 않고 보관되어 있기 때문에 [rax + 8] 위치의 값 전체를 인자 값으로 여겨서는 안된다는 점입니다.<br /> <br /> 실제로 windbg에서 kb 명령으로 확인하는 경우,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:035> kb RetAddr : <span style='color: blue; font-weight: bold'>Args to Child</span> : Call Site 00007ffb`ba62121a : 000000ca`9bbad108 000000ca`9bbad110 000000c6`161889c0 00007ffb`4686d129 : ntdll!NtDelayExecution+0xa 00007ffb`a5152e73 : 00000000`00000000 00000000`00000001 00000000`00000000 00000000`00000000 : KERNELBASE!SleepEx+0xa2 00007ffb`a515da6c : 000000ca`9ad04f70 00000000`0000ea60 000000c6`161889c0 00007ffb`45fe5db2 : clr!EESleepEx+0x24 00007ffb`a515db71 : 06000000`00000001 00000000`008363dd 04000000`00000001 00007ffb`4686d705 : clr!Thread::UserSleep+0xa5 00007ffb`4615e43f : <span style='color: blue; font-weight: bold'>000000ca`0000ea60</span> 000000ca`9b8307a0 000000c6`96a05d28 000000ca`9bbad4b0 : clr!<span style='color: blue; font-weight: bold'>ThreadNative::Sleep</span>+0xad 00007ffb`47072761 : 000000c6`0000ea60 000000c8`16187508 00007ffb`00000000 000000ca`99ad1ca3 : 0x00007ffb`4615e43f 00007ffb`467b9762 : 000000c6`961fa428 000000c6`961fa428 000000c6`961ae3d0 000000ca`9b8307a0 : 0x00007ffb`47072761 00007ffb`467b9258 : 000000c6`961fe978 000000c6`961fa428 000000c6`961ae3d0 00000000`00000000 : 0x00007ffb`467b9762 ...[생략]... </pre> <br /> "Args to Child" 컬럼에 대해 ThreadNative::Sleep의 첫 번째 인자로 보여지고 있는 "000000ca 0000ea60" 값을 그 함수의 실제 첫 번째 값이라고 여기면 프로그램의 진행을 잘못 해석하게 됩니다. 일례로 예제 코드에서는 Thread.Sleep에 60초에 해당하는 60000 값을 전달했기 때문에 "ea60" 값이어야 하는데 "ca0000ea60"으로 해석해 버리면 "867583453792"라는 어마어마한 값이 되어버립니다.<br /> <br /> x64에서의 인자 해석이 어려운 첫 번째 사항이 바로 이것입니다. windbg의 출력값만 믿어서는 안되고 실제 해당 기계어의 진입점 코드를 봐야만 올바른 값을 얻을 수 있다는 것입니다.<br /> <br /> 아쉽게도 "두 번째 어려움"까지 있는데, 심지어 windbg의 kb 명령어로 출력된 "Args to Child" 값 전체를 믿을 수 없다는 것입니다. 왜냐하면 4개의 인자 rcx, rdx, r8, r9에 대한 백업용 스택 공간 확보는 의무적이긴하지만 거기에 인자 값을 백업하는 것은 의무가 아니기 때문입니다. 따라서, rcx, rdx, r8, r9 레지스터 값이 다른 목적으로 사용되지 않는 한 백업 코드는 늘 있다고 볼 수 없습니다. 실제로 kb로 출력된 값을 보면 최초 Thread.Sleep에 전달했던 60000(0xea60)값이 NtDelayExecution까지의 "Args to Child" 목록에 항상 나타나고 있지 않음을 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:035> kb RetAddr : Args to Child : Call Site 00007ffb`ba62121a : 000000ca`9bbad108 000000ca`9bbad110 000000c6`161889c0 00007ffb`4686d129 : ntdll!NtDelayExecution+0xa 00007ffb`a5152e73 : 00000000`00000000 00000000`00000001 00000000`00000000 00000000`00000000 : KERNELBASE!SleepEx+0xa2 00007ffb`a515da6c : 000000ca`9ad04f70 00000000`<span style='color: blue; font-weight: bold'>0000ea60</span> 000000c6`161889c0 00007ffb`45fe5db2 : clr!EESleepEx+0x24 00007ffb`a515db71 : 06000000`00000001 00000000`008363dd 04000000`00000001 00007ffb`4686d705 : clr!Thread::UserSleep+0xa5 00007ffb`4615e43f : 000000ca`<span style='color: blue; font-weight: bold'>0000ea60</span> 000000ca`9b8307a0 000000c6`96a05d28 000000ca`9bbad4b0 : clr!ThreadNative::Sleep+0xad </pre> <br /> 왜냐하면, 해당 함수에서 rcx, rdx, r8, r9의 내용이 변하지 않는다면 굳이 성능 저하를 발생시키는 백업 작업을 하진 않기 때문입니다. 정리해 보면, windbg의 "Args to Child"는 백업용 스택 공간의 값을 '무조건' 보여주는 것일 뿐 실제 인자가 아닐 수 있습니다. 결국 운이 좋다면 "Args to Child"에 백업된 인자 값을 확인할 수 있겠지만 그렇지 않다면 어셈블리 코드를 따라가며 값을 확인해야 합니다.<br /> <br /> 물론, 가장 상위의 함수 호출에서 BP(Breakpoint)가 잡힌 상태라면 현재 레지스터의 rcx, rdx, r8, r9를 통해 그나마 쉽게 인자 값을 확인할 수 있지만 그 부모의 함수에 전달된 인자 값을 확인하려면 어셈블리 코드를 확인해야 하는 경우가 발생할 수밖에 없습니다.<br /> <br /> <hr style='width: 50%' /><br /> <a name='homing_space'></a> <br /> rcx, rdx, r8, r9에 대한 백업 용도로 확보되는 공간을 "Parameter Homing Space"라고 합니다. 백업이 된다고 가정하고, 스택에 보관되는 정형화된 패턴은 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // http://www.osronline.com/showthread.cfm?link=230224 module!callee: mov qword ptr [rsp+08h],rcx mov qword ptr [rsp+10h],rdx mov qword ptr [rsp+18h],r8 mov qword ptr [rsp+20h],r9 add rsp, ??h </pre> <br /> 참고로, [rsp + 0h] 영역에는 해당 함수를 호출한 부모의 반환 주소값이 들어가 있기 때문에 함수의 진입점에서는 [rsp + 08h] 이후부터 인자 값이 위치하게 됩니다.<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;' > [rsp+08h] : 호출 함수에 전달한 파라미터 1을 저장 [rsp+10h] : 호출 함수에 전달한 파라미터 2를 저장 [rsp+18h] : 호출 함수에 전달한 파라미터 3을 저장 [rsp+20h] : 호출 함수에 전달한 파라미터 4를 저장 </pre> <br /> 하지만 여기서 문제가 있습니다. 위에서 보는 것처럼, 일련의 "mov ..."가 있은 후 "add rsp, ??h"로 인해 RSP 주소가 변경되었으므로 일반적인 디버깅 상황에서는 [rsp + 08h]와 같은 수식을 쓸 수 없습니다. 당연히 부모 함수의 경우에도 rsp가 한참 변경된 상태이므로 쓸 수 없습니다.<br /> <br /> 따라서, 위의 수식을 정상적으로 사용하려면 콜스택을 확인하는 방법을 동원해 해당 함수가 호출된 시점의 RSP 값을 알아내야 합니다. 물론, 이 작업은 매우 번거로운데요. 다행히 windbg에서 kv 명령어를 통해 호출 당시의 RSP 값을 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:035> <span style='color: blue; font-weight: bold'>kv</span> <span style='color: blue; font-weight: bold'>Child-SP</span> RetAddr : Args to Child : Call Site <span style='color: blue; font-weight: bold'>000000ca`9bbad048</span> 00007ffb`ba62121a : 000000ca`9bbad108 000000ca`9bbad110 000000c6`161889c0 00007ffb`4686d129 : ntdll!NtDelayExecution+0xa <span style='color: blue; font-weight: bold'>000000ca`9bbad050</span> 00007ffb`a5152e73 : 00000000`00000000 00000000`00000001 00000000`00000000 00000000`00000000 : KERNELBASE!SleepEx+0xa2 <span style='color: blue; font-weight: bold'>000000ca`9bbad0f0</span> 00007ffb`a515da6c : 000000ca`9ad04f70 00000000`0000ea60 000000c6`161889c0 00007ffb`45fe5db2 : clr!EESleepEx+0x24 <span style='color: blue; font-weight: bold'>000000ca`9bbad120</span> 00007ffb`a515db71 : 06000000`00000001 00000000`008363dd 04000000`00000001 00007ffb`4686d705 : clr!Thread::UserSleep+0xa5 <span style='color: blue; font-weight: bold'>000000ca`9bbad180</span> 00007ffb`4615e43f : 000000ca`0000ea60 000000ca`9b8307a0 000000c6`96a05d28 000000ca`9bbad4b0 : clr!ThreadNative::Sleep+0xad <span style='color: blue; font-weight: bold'>000000ca`9bbad2e0</span> 00007ffb`47072761 : 000000c6`0000ea60 000000c8`16187508 00007ffb`00000000 000000ca`99ad1ca3 : 0x00007ffb`4615e43f <span style='color: blue; font-weight: bold'>000000ca`9bbad320</span> 00007ffb`467b9762 : 000000c6`961fa428 000000c6`961fa428 000000c6`961ae3d0 000000ca`9b8307a0 : 0x00007ffb`47072761 <span style='color: blue; font-weight: bold'>000000ca`9bbad4d0</span> 00007ffb`467b9258 : 000000c6`961fe978 000000c6`961fa428 000000c6`961ae3d0 00000000`00000000 : 0x00007ffb`467b9762 ...[생략]... </pre> <br /> "Child-SP"라는 컬럼명에서 알 수 있듯이 해당 값은 부모 함수에서 현재의 함수를 호출할 당시의 RSP 값입니다. 예를 들어 NtDelayExecution 항목을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Child-SP RetAddr : Args to Child <span style='color: blue; font-weight: bold'>000000ca`9bbad048</span> 00007ffb`ba62121a : 000000ca`9bbad108 000000ca`9bbad110 000000c6`161889c0 00007ffb`4686d129 : ntdll!NtDelayExecution+0xa </pre> <br /> "000000ca`9bbad048" 값이 NtDelayExecution 실행 중의 RSP 값이 아니고, 그 부모 함수인 KERNELBASE!SleepEx가 ntdll!NtDelayExecution 함수를 call 했을 때의 RSP 값입니다. 여기서 중요한 것은 call을 이미 한 상태의 RSP 값이기 때문에 '반환주소' 8바이트가 이미 고려되었으므로 인자 값을 알아내는 패턴이 다음과 같이 바뀝니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [rsp+00h] : 호출 함수에 전달할 파라미터 1을 저장 [rsp+08h] : 호출 함수에 전달할 파라미터 2를 저장 [rsp+10h] : 호출 함수에 전달할 파라미터 3을 저장 [rsp+18h] : 호출 함수에 전달할 파라미터 4를 저장 </pre> <br /> 개인적으로는 위의 표현은 좀 혼란스럽다고 봅니다. 왜냐하면 실제 어셈블리 코드에서는 [rsp + 08h]이 첫 번째 인자를 가리키기 때문인데요. 그래서 이를 명확하게 하기 위해 rsp 대신 "childsp"를 쓰는 것이 나을 것 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [ChildSP+00h] : 호출 함수에 전달할 파라미터 1을 저장 [ChildSP+08h] : 호출 함수에 전달할 파라미터 2를 저장 [ChildSP+10h] : 호출 함수에 전달할 파라미터 3을 저장 [ChildSP+18h] : 호출 함수에 전달할 파라미터 4를 저장 </pre> <br /> 실제로 KERNELBASE!SleepEx의 코드를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > KERNELBASE!SleepEx: 00007ffb`ba621170 4c8bdc mov r11,rsp 00007ffb`ba621173 49895b08 mov qword ptr [r11+8],rbx ; <== 첫 번째 파라미터 저장 == 00000000`00000000 00007ffb`ba621177 89542410 mov dword ptr [rsp+10h],edx ; <== 두 번째 파라미터 저장 == 00000000`00000001 ...[생략]... </pre> <br /> [r11 + 8], [rsp + 10h] 표기로 각각 첫 번째/두 번째 인자를 전달하고 있기 때문에 [rsp + 00h]가 첫 번째 파라미터라고 인지하고 있으면 해석에 혼란을 줄 수 있습니다.<br /> <br /> 최종적으로, NtDelayExecution의 경우 인자에 대한 백업이 되었다면 kv 명령어로 얻은 Child-SP 값에 따라 다음의 패턴으로 인자 값을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [000000ca`9bbad048 + 08h] : 호출 함수에 전달할 파라미터 1를 저장 [000000ca`9bbad048 + 10h] : 호출 함수에 전달할 파라미터 2을 저장 [000000ca`9bbad048 + 18h] : 호출 함수에 전달할 파라미터 3를 저장 [000000ca`9bbad048 + 20h] : 호출 함수에 전달할 파라미터 4을 저장 </pre> <br /> 물론, 위의 인자 4개는 kb 명령을 통해 windbg에서 쉽게 확인할 수 있어서 실제로 의미있는 경우는 5개 이상의 파라미터 값을 확인할 때와 로컬 변수의 시작점 정도를 파악할 때나 유용합니다.<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;' > x64 디버깅 강좌 (1) - x64 Stack 개요 ; <a target='tab' href='http://kuaaan.tistory.com/449'>http://kuaaan.tistory.com/449</a> 수동으로 64비트 콜스택 추적하기 ; <a target='tab' href='http://greemate.tistory.com/entry/수동으로-64비트-콜스택-추적하기'>http://greemate.tistory.com/entry/수동으로-64비트-콜스택-추적하기</a> 64비트 콜스택에서 함수 파라미터 찾기 ; <a target='tab' href='http://greemate.tistory.com/entry/64-bit에서-NtQueryAttributesFile-파라미터-찾기'>http://greemate.tistory.com/entry/64-bit에서-NtQueryAttributesFile-파라미터-찾기</a> 수동으로 64비트 콜스택 추적하기 (2) ; <a target='tab' href='http://greemate.tistory.com/entry/수동으로-64비트-콜스택-추적하기-2'>http://greemate.tistory.com/entry/수동으로-64비트-콜스택-추적하기-2</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1030
(왼쪽의 숫자를 입력해야 합니다.)