성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] How can I tell whether two programs...
[정성태] The case of the fail-fast crashes c...
[정성태] Creating Docker multi-arch images f...
[정성태] BinaryFormatter removed from .NET 9...
[정성태] Extending the Windows Shell Progres...
[우광현] 와..... 범위를 잡았으니 클라이언트가 해당 범위를 확인해본다...
[정성태] 딱히, 그것 이상으로 더 설명할 내용이 없습니다. 동적 포...
[정성태] If Windows 3.11 required a 32-bit p...
[정성태] What is a hard error, and what make...
[괴물신인] 질문작성자인데 이 글을 이제봤네요 ㄷㄷ 이 글처럼 타입별로 인...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <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;' > .NET Disassembly 창에서의 F11(Step-into) 키 동작 ; <a target='_tab' href='http://www.sysnet.pe.kr/2/0/1022'>http://www.sysnet.pe.kr/2/0/1022</a> </pre> <br /> 이전 글에서 살펴본 내용은, .NET 메서드가 JIT 컴파일 되기 전과 후의 변화를 확인하려는 의도에서 시도해 본 것인데 아쉽게도 .NET Disassembly 창에서는 이에 대해 직접적인 확인이 불가능했습니다.<br /> <br /> 이번에는 windbg로 시도해 볼텐데, 어떻게 확인이 되는지 기록을 남겨봅니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, 메서드에 대해 JIT 컴파일 전과 후를 windbg에서 쉽게 접근하도록 다음과 같은 간단한 예제 코드를 작성해 봅니다.<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;' > class Program { static void Main(string[] args) { Program pg = new Program(); <b style='COLOR: blue'>Console.ReadLine();</b> pg.Test(); <b style='COLOR: blue'>Console.ReadLine();</b> pg.Test(); } void Test() { Console.WriteLine("TEST"); } } </pre> <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 /> Visual Studio에서 "Ctrl + F5 (Start Without Debugging)" 단축키로 실행시킨 후, windbg를 이용해서 연결하고 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;' > 0:005> <b style='COLOR: blue'>.loadby sos clr</b> </pre> <br /> 이제, 닷넷 메서드를 호출하는 코드에 BP를 걸어야 하는데요. 이게 좀 애매합니다. ^^ 예를 들어, 다음과 같이 !bpmd를 사용한다면 어떻게 될까요?<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;' > 0:005> <b style='COLOR: blue'>!bpmd ConsoleApplication1 ConsoleApplication1.Program.Test</b> Found 1 methods in module 002a2e9c... MethodDesc = 002a33fc Adding pending breakpoints... </pre> <br /> !bpmd는 sos.dll의 확장명령어로 ".NET"에 대한 배려가 되어 있어서 실제로 이 상태에서 'g' 키를 눌러서 진행하면 아래와 같이 해당 메서드를 JIT 컴파일 시킨 후에야 메서드의 진입점에서 실행을 멈추게 해줍니다. (이전 글의 .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;' > 0:005> <b style='COLOR: blue'>g</b> (15fc.2540): CLR notification exception - code e0444143 (first chance) <b style='COLOR: blue'>JITTED</b> ConsoleApplication1!ConsoleApplication1.Program.Test() <b style='COLOR: blue'>Setting breakpoint: bp 009A7A20</b> [ConsoleApplication1.Program.Test()] Breakpoint 0 hit eax=002a33fc ebx=00000000 ecx=02e7bbac edx=00000000 esi=0049ca68 edi=001bf220 eip=009a7a20 esp=001bf1e4 ebp=001bf1f4 iopl=0 nv up ei pl nz ac po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212 009a7a20 55 push ebp </pre> <br /> 즉, 실제로 BP를 걸어서 확인해야 하는 지점은 Main 메서드의 기계어 코드에서 pg.Test(); 메서드를 호출하는 곳이어야 합니다.<br /> <br /> 이를 위해서 우선 Main 함수의 실제 주소를 알아내야 합니다.<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;' > 0:008> <b style='COLOR: blue'>!name2ee ConsoleApplication1.exe!ConsoleApplication1.Program.Main</b> Module: 00132e9c Assembly: ConsoleApplication1.exe Token: 06000001 MethodDesc: 001333f0 Name: ConsoleApplication1.Program.Main(System.String[]) <b style='COLOR: blue'>JITTED Code Address: 00762670</b> </pre> <a name='unassemble'></a> <br /> 그다음 !u(Unassemble) 명령어로 pg.Test 호출을 하는 주소를 알아냅니다.<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;' > 0:008> <b style='COLOR: blue'>!u 00762670</b> Normal JIT generated code ConsoleApplication1.Program.Main(System.String[]) Begin 00762670, size 64 *** WARNING: Unable to verify checksum for D:\...[생략]...\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe D:\...[생략]...\ConsoleApplication1\Program.cs @ 11: >>> 00762670 55 push ebp ...[생략]... D:\...[생략]...\ConsoleApplication1\Program.cs @ 15: 007626b0 8b4df8 mov ecx,dword ptr [ebp-8] 007626b3 3909 cmp dword ptr [ecx],ecx <b style='COLOR: blue'>007626b5 ff1504341300 call dword ptr ds:[133404h] (ConsoleApplication1.Program.Test(), mdToken: 06000002)</b> 007626bb 90 nop ...[생략]... 007626cf 90 nop 007626d0 8be5 mov esp,ebp 007626d2 5d pop ebp 007626d3 c3 ret </pre> <br /> ds:[133404h] 주소에 어떤 값이 들어 있는지, "Alt + 5(Memory)"를 눌러 확인해보니 0x13c015 값이 나오는 군요.<br /> <br /> <img alt='windbg_disassembly_step_into_1.png' src='/SysWebRes/bbs/windbg_disassembly_step_into_1.png' /><br /> <br /> 여기까지가, 지난번 글(<a target='_tab' href='http://www.sysnet.pe.kr/2/0/1022'>.NET Disassembly 창에서의 F11(Step-into) 키 동작</a>")의 .NET Disassembly 창을 통한 출력 부분까지 수작업으로 온 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이제, 지난번에 확인할 수 없었던 것들을 한번 탐구해볼까요? ^^<br /> <br /> 원했던 데로, call 명령어에 BP를 걸어 두고, 그곳까지 실행시킵니다. (Console.ReadLine 상태이므로 '엔터 키'를 쳐야 합니다.)<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;' > 0:008> <b style='COLOR: blue'>bp 007626b5</b> 0:008> <b style='COLOR: blue'>g</b> Breakpoint 0 hit eax=03261ab4 ebx=00000000 ecx=0326bbac edx=00000000 esi=0047ca60 edi=003cedb0 eip=007626b5 esp=003ced78 ebp=003ced84 iopl=0 nv up ei ng nz ac po cy cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000293 <b style='COLOR: blue'>007626b5 ff1504341300 call dword ptr ds:[133404h] ds:002b:00133404=0013c015</b> </pre> <br /> 'F11 (Step-into)' 키를 누르면 원하는 대로 EIP=0x13c015로 이동하는 것을 확인할 수 있습니다.<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;' > 0:000> <b style='COLOR: blue'>t <== step-into 명령어</b> eax=03261ab4 ebx=00000000 ecx=0326bbac edx=00000000 esi=0047ca60 edi=003cedb0 <b style='COLOR: blue'>eip=0013c015</b> esp=003ced74 ebp=003ced84 iopl=0 nv up ei ng nz ac po cy cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000293 <b style='COLOR: blue'>0013c015</b> b003 mov al,3 </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;' > 0:000> <b style='COLOR: blue'>u 0013c015</b> <b style='COLOR: blue'>0013c015 b003 mov al,3 0013c017 eb04 jmp 0013c01d </b>0013c019 b006 mov al,6 0013c01b eb00 jmp 0013c01d 0013c01d 0fb6c0 movzx eax,al 0013c020 c1e002 shl eax,2 0013c023 05f0331300 add eax,1333F0h 0013c028 e9e347a000 jmp 00b40810 0:000> <b style='COLOR: blue'>u 0013c01d</b> 0013c01d 0fb6c0 movzx eax,al 0013c020 c1e002 shl eax,2 0013c023 05f0331300 add eax,1333F0h <b style='COLOR: blue'>0013c028 e9e347a000 jmp 00b40810</b> 0013c02d 0000 add byte ptr [eax],al 0013c02f 00e8 add al,ch 0013c031 b362 mov bl,62h 0013c033 f36f rep outs dx,dword ptr [esi] 0:000> <b style='COLOR: blue'>uf 00b40810</b> 00b40810 50 push eax 00b40811 52 push edx 00b40812 68f0360770 push offset clr!PrestubMethodFrame::`vftable' (700736f0) 00b40817 55 push ebp 00b40818 53 push ebx 00b40819 56 push esi 00b4081a 57 push edi 00b4081b 8d742410 lea esi,[esp+10h] 00b4081f ff760c push dword ptr [esi+0Ch] 00b40822 55 push ebp 00b40823 89e5 mov ebp,esp 00b40825 51 push ecx 00b40826 52 push edx 00b40827 648b1d780e0000 mov ebx,dword ptr fs:[0E78h] 00b4082e 8b7b0c mov edi,dword ptr [ebx+0Ch] 00b40831 897e04 mov dword ptr [esi+4],edi 00b40834 89730c mov dword ptr [ebx+0Ch],esi 00b40837 6849783df4 push 0F43D7849h 00b4083c 56 push esi <b style='COLOR: blue'>00b4083d e8ff17556f call clr!PreStubWorker (70092041)</b> 00b40842 897b0c mov dword ptr [ebx+0Ch],edi 00b40845 8b4e08 mov ecx,dword ptr [esi+8] 00b40848 894608 mov dword ptr [esi+8],eax 00b4084b 8bc1 mov eax,ecx 00b4084d 83c404 add esp,4 00b40850 5a pop edx 00b40851 59 pop ecx 00b40852 89ec mov esp,ebp 00b40854 5d pop ebp 00b40855 83c404 add esp,4 00b40858 5f pop edi 00b40859 5e pop esi 00b4085a 5b pop ebx 00b4085b 5d pop ebp 00b4085c 83c408 add esp,8 00b4085f c3 ret </pre> <br /> 음... 더 이상 알게 뭡니까? 이 정도해서 clr!PreStubWorker 메서드를 호출한다는 것까지 봤으면 된 거죠? ^^;<br /> <br /> 그래도, 정말 clr!PreStubWorker 함수가 JIT 역할을 하는지 보기 위해서 그 지점까지 다시 bp를 걸고 F10(Step-over)를 해보겠습니다.<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;' > 0:000> <b style='COLOR: blue'>bp 00b4083d</b> 0:000> <b style='COLOR: blue'>g</b> Breakpoint 1 hit eax=001333fc ebx=0047ca60 ecx=0326bbac edx=00000000 esi=003ced68 edi=003cefbc eip=00b4083d esp=003ced40 ebp=003ced50 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 <b style='COLOR: blue'>00b4083d e8ff17556f call clr!PreStubWorker (70092041)</b> 0:000> <b style='COLOR: blue'>p <== step-over 명령어</b> <b style='COLOR: blue'>eax=00767a20</b> ebx=0047ca60 ecx=7009214a edx=00000002 esi=003ced68 edi=003cefbc eip=00b40842 esp=003ced44 ebp=003ced50 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 00b40842 897b0c mov dword ptr [ebx+0Ch],edi ds:002b:0047ca6c=003ced68 </pre> <br /> 재미있는 것은, "call clr!PreStubWorker" 코드의 반환값이 담긴 eax 레지스터에 "00767a20" 값이 담겨있고, 동시에 위에서 메모리 창을 통해 확인한 0x133404 에 위치한 값이 0x13c015에서 0x00767a20로 바뀌었음을 볼 수 있습니다.<br /> <br /> <img alt='windbg_disassembly_step_into_2.png' src='/SysWebRes/bbs/windbg_disassembly_step_into_2.png' /><br /> <br /> 그리고, 마지막 ret 문에서 한 단계 더 명령어를 실행하면 EIP가 0x767a20으로 변하는 것을 확인할 수 있습니다.<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;' > 0:000> <b style='COLOR: blue'>t</b> eax=001333fc ebx=00000000 ecx=0326bbac edx=00000000 esi=0047ca60 edi=003cedb0 eip=00b4085f esp=003ced70 ebp=003ced84 iopl=0 nv up ei pl nz ac po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212 <b style='COLOR: blue'>00b4085f c3 ret</b> 0:000> <b style='COLOR: blue'>t</b> eax=001333fc ebx=00000000 ecx=0326bbac edx=00000000 esi=0047ca60 edi=003cedb0 eip=00767a20 esp=003ced74 ebp=003ced84 iopl=0 nv up ei pl nz ac po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212 <b style='COLOR: blue'>00767a20 55 push ebp</b> </pre> <br /> 0x767a20 지점의 기계어 코드를 한번 확인해 볼까요? 예상하는 것처럼, IL 코드로 존재하던 닷넷 메서드가 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;' > 0:000> <b style='COLOR: blue'>uf 00767a20</b> 00767a20 55 push ebp 00767a21 8bec mov ebp,esp 00767a23 50 push eax 00767a24 894dfc mov dword ptr [ebp-4],ecx 00767a27 833d3c31130000 cmp dword ptr ds:[13313Ch],0 00767a2e 7405 je 00767a35 00767a30 e8e6e9bb6f call clr!JIT_DbgIsJustMyCode (7032641b) 00767a35 90 nop 00767a36 8b0d68372604 mov ecx,dword ptr ds:[4263768h] 00767a3c ff1538737200 call dword ptr ds:[727338h] <=== Console.WriteLine 호출 00767a42 90 nop 00767a43 90 nop 00767a44 8be5 mov esp,ebp 00767a46 5d pop ebp 00767a47 c3 ret </pre> <br /> 따라서, 이렇게 한번 JIT 컴파일 된 이후에 동일하게 "call dword ptr ds:[133404h]" 코드가 호출되면 더 이상의 PreStubWorker 실행 없이 곧바로 기계어로 변환된 함수를 실행시키게 되는 것입니다.<br /> <br /> 비록 현실적인 .NET 프로그래밍에 도움이 되진 않겠지만, 그래도 ^^ 가끔 이런 탐구를 하는 것도 나름 재미있기는 합니다.<br /> </div>
첨부파일
스팸 방지용 인증 번호
1974
(왼쪽의 숫자를 입력해야 합니다.)