Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 4개 있습니다.)
(시리즈 글이 11개 있습니다.)
.NET Framework: 202. CLR JIT 컴파일러가 생성한 기계어 코드 확인하는 방법
; https://www.sysnet.pe.kr/2/0/975

.NET Framework: 210. Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후
; https://www.sysnet.pe.kr/2/0/1023

.NET Framework: 395. C# - 프로퍼티로 정의하면 필드보다 느릴까요?
; https://www.sysnet.pe.kr/2/0/1545

.NET Framework: 396. C# - 프로퍼티로 정의하면 필드보다 느릴까요? - windbg / ollydbg
; https://www.sysnet.pe.kr/2/0/1546

.NET Framework: 542. 닷넷 - 특정 클래스가 로드되었는지 여부를 알 수 있을까?
; https://www.sysnet.pe.kr/2/0/10888

.NET Framework: 545. 닷넷 - 특정 클래스가 로드되었는지 여부를 알 수 있을까? - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/10893

.NET Framework: 763. .NET Core 2.1 - Tiered Compilation 도입
; https://www.sysnet.pe.kr/2/0/11539

디버깅 기술: 161. Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12133

.NET Framework: 2015. C# - 인라인 메서드(inline methods)
; https://www.sysnet.pe.kr/2/0/13063

.NET Framework: 2016. C# - JIT 컴파일러의 인라인 메서드 처리 유무
; https://www.sysnet.pe.kr/2/0/13064

닷넷: 2132. C# - sealed 클래스의 메서드를 callback 호출했을 때 인라인 처리가 될까요?
; https://www.sysnet.pe.kr/2/0/13391





이번 글은 지난번 이야기에서 이어서 진행됩니다.

.NET Disassembly 창에서의 F11(Step-into) 키 동작
; https://www.sysnet.pe.kr/2/0/1022

이전 글에서 살펴본 내용은, .NET 메서드가 JIT 컴파일 되기 전과 후의 변화를 확인하려는 의도에서 시도해 본 것인데 아쉽게도 .NET Disassembly 창에서는 이에 대해 직접적인 확인이 불가능했습니다.

이번에는 windbg로 시도해 볼텐데, 어떻게 확인이 되는지 기록을 남겨봅니다.




우선, 메서드에 대해 JIT 컴파일 전과 후를 windbg에서 쉽게 접근하도록 다음과 같은 간단한 예제 코드를 작성해 봅니다.

class Program
{
    static void Main(string[] args)
    {
        Program pg = new Program();

        Console.ReadLine();
        pg.Test();
        Console.ReadLine();
        pg.Test();
    }

    void Test()
    {
        Console.WriteLine("TEST");
    }
}

프로젝트 환경은 다음과 같이 구성되어 있고,

  • .NET 4.0 Console Application
  • Debug Build
  • 체크 해제 - Enable the Visual Studio hosting process
  • 체크 - Enable unmanaged code debugging
  • 심벌 서버 지정 - 마이크로소프트 공용 PDB 심벌 파일 배포 서버

Visual Studio에서 "Ctrl + F5 (Start Without Debugging)" 단축키로 실행시킨 후, windbg를 이용해서 연결하고 sos.dll을 로드합니다.

0:005> .loadby sos clr

이제, 닷넷 메서드를 호출하는 코드에 BP를 걸어야 하는데요. 이게 좀 애매합니다. ^^ 예를 들어, 다음과 같이 !bpmd를 사용한다면 어떻게 될까요?

0:005> !bpmd ConsoleApplication1 ConsoleApplication1.Program.Test
Found 1 methods in module 002a2e9c...
MethodDesc = 002a33fc
Adding pending breakpoints...

!bpmd는 sos.dll의 확장명령어로 ".NET"에 대한 배려가 되어 있어서 실제로 이 상태에서 'g' 키를 눌러서 진행하면 아래와 같이 해당 메서드를 JIT 컴파일 시킨 후에야 메서드의 진입점에서 실행을 멈추게 해줍니다. (이전 글의 .NET Disassembly 창에서의 배려와 유사하죠.)

0:005> g
(15fc.2540): CLR notification exception - code e0444143 (first chance)
JITTED ConsoleApplication1!ConsoleApplication1.Program.Test()
Setting breakpoint: bp 009A7A20 [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

즉, 실제로 BP를 걸어서 확인해야 하는 지점은 Main 메서드의 기계어 코드에서 pg.Test(); 메서드를 호출하는 곳이어야 합니다.

이를 위해서 우선 Main 함수의 실제 주소를 알아내야 합니다.

0:008> !name2ee ConsoleApplication1.exe!ConsoleApplication1.Program.Main
Module:      00132e9c
Assembly:    ConsoleApplication1.exe
Token:       06000001
MethodDesc:  001333f0
Name:        ConsoleApplication1.Program.Main(System.String[])
JITTED Code Address: 00762670

그다음 !u(Unassemble) 명령어로 pg.Test 호출을 하는 주소를 알아냅니다.

0:008> !u 00762670
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
007626b5 ff1504341300    call    dword ptr ds:[133404h] (ConsoleApplication1.Program.Test(), mdToken: 06000002)
007626bb 90              nop

...[생략]...
007626cf 90              nop
007626d0 8be5            mov     esp,ebp
007626d2 5d              pop     ebp
007626d3 c3              ret

ds:[133404h] 주소에 어떤 값이 들어 있는지, "Alt + 5(Memory)"를 눌러 확인해보니 0x13c015 값이 나오는군요.

windbg_disassembly_step_into_1.png

여기까지가, 지난번 글(.NET Disassembly 창에서의 F11(Step-into) 키 동작")의 .NET Disassembly 창을 통한 출력 부분까지 수작업으로 온 것입니다.




이제, 지난번에 확인할 수 없었던 것들을 한번 탐구해 볼까요? ^^

원했던 대로, call 명령어에 BP를 걸어 두고, 그곳까지 실행시킵니다. (Console.ReadLine 상태이므로 '엔터 키'를 쳐야 합니다.)

0:008> bp 007626b5

0:008> g
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
007626b5 ff1504341300    call    dword ptr ds:[133404h] ds:002b:00133404=0013c015

'F11 (Step-into)' 키를 누르면 원하는 대로 EIP=0x13c015로 이동하는 것을 확인할 수 있습니다.

0:000> t <== step-into 명령어
eax=03261ab4 ebx=00000000 ecx=0326bbac edx=00000000 esi=0047ca60 edi=003cedb0
eip=0013c015 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
0013c015 b003            mov     al,3

이후 해당 코드에 대해서 차례로 역어셈블을 하면서 코드를 확인하면 다음과 같습니다.

0:000> u 0013c015
0013c015 b003            mov     al,3
0013c017 eb04            jmp     0013c01d
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> u 0013c01d
0013c01d 0fb6c0          movzx   eax,al
0013c020 c1e002          shl     eax,2
0013c023 05f0331300      add     eax,1333F0h
0013c028 e9e347a000      jmp     00b40810
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> uf 00b40810
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
00b4083d e8ff17556f      call    clr!PreStubWorker (70092041)
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

음... 더 이상 알게 뭡니까? 이 정도해서 clr!PreStubWorker 메서드를 호출한다는 것까지 봤으면 된 거죠? ^^;

그래도, 정말 clr!PreStubWorker 함수가 JIT 역할을 하는지 보기 위해서 그 지점까지 다시 bp를 걸고 F10(Step-over)를 해보겠습니다.

0:000> bp 00b4083d

0:000> g
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
00b4083d e8ff17556f      call    clr!PreStubWorker (70092041)

0:000> p <== step-over 명령어
eax=00767a20 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

재미있는 것은, "call clr!PreStubWorker" 코드의 반환값이 담긴 eax 레지스터에 "00767a20" 값이 담겨있고, 동시에 위에서 메모리 창을 통해 확인한 0x133404 에 위치한 값이 0x13c015에서 0x00767a20로 바뀌었음을 볼 수 있습니다.

windbg_disassembly_step_into_2.png

그리고, 마지막 ret 문에서 한 단계 더 명령어를 실행하면 EIP가 0x767a20으로 변하는 것을 확인할 수 있습니다.

0:000> t
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
00b4085f c3              ret

0:000> t
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
00767a20 55              push    ebp

0x767a20 지점의 기계어 코드를 한번 확인해 볼까요? 예상하는 것처럼, IL 코드로 존재하던 닷넷 메서드가 JIT 컴파일 된 기계어가 위치하고 있습니다.

0:000> uf 00767a20
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

따라서, 이렇게 한번 JIT 컴파일 된 이후에 동일하게 "call dword ptr ds:[133404h]" 코드가 호출되면 더 이상의 PreStubWorker 실행 없이 곧바로 기계어로 변환된 함수를 실행시키게 되는 것입니다.

비록 현실적인 .NET 프로그래밍에 도움이 되진 않겠지만, 그래도 ^^ 가끔 이런 탐구를 하는 것도 나름 재미있기는 합니다.

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/8/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2015-01-05 02시35분
.NET Internals and Native Compiling
; http://www.ntcore.com/files/netint_native.htm

.NET Internals and Code Injection
; http://www.ntcore.com/files/netint_injection.htm
정성태

... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1780정성태10/15/201424175오류 유형: 249. The application-specific permission settings do not grant Local Activation permission for the COM Server application with CLSID
1779정성태10/15/201419705오류 유형: 248. Active Directory에서 OU가 지워지지 않는 경우
1778정성태10/10/201418156오류 유형: 247. The Netlogon service could not create server share C:\Windows\SYSVOL\sysvol\[도메인명]\SCRIPTS.
1777정성태10/10/201421241오류 유형: 246. The processing of Group Policy failed. Windows attempted to read the file \\[도메인]\sysvol\[도메인]\Policies\{...GUID...}\gpt.ini
1776정성태10/10/201418288오류 유형: 245. 이벤트 로그 - Name resolution for the name _ldap._tcp.dc._msdcs.[도메인명]. timed out after none of the configured DNS servers responded.
1775정성태10/9/201419423오류 유형: 244. Visual Studio 디버깅 (2) - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
1774정성태10/9/201426616개발 환경 구성: 246. IIS 작업자 프로세스의 20분 자동 재생(Recycle)을 끄는 방법
1773정성태10/8/201429776.NET Framework: 471. 웹 브라우저로 다운로드가 되는 파일을 왜 C# 코드로 하면 안되는 걸까요? [1]
1772정성태10/3/201418568.NET Framework: 470. C# 3.0의 기본 인자(default parameter)가 .NET 1.1/2.0에서도 실행될까? [3]
1771정성태10/2/201428072개발 환경 구성: 245. 실행된 프로세스(EXE)의 명령행 인자를 확인하고 싶다면 - Sysmon [4]
1770정성태10/2/201421685개발 환경 구성: 244. 매크로 정의를 이용해 파일 하나로 C++과 C#에서 공유하는 방법 [1]파일 다운로드1
1769정성태10/1/201424105개발 환경 구성: 243. Scala 개발 환경 구성(JVM, 닷넷) [1]
1768정성태10/1/201419529개발 환경 구성: 242. 배치 파일에서 Thread.Sleep 효과를 주는 방법 [5]
1767정성태10/1/201424629VS.NET IDE: 94. Visual Studio 2012/2013에서의 매크로 구현 - Visual Commander [2]
1766정성태10/1/201422480개발 환경 구성: 241. 책 "프로그래밍 클로저: Lisp"을 읽고 나서. [1]
1765정성태9/30/201426048.NET Framework: 469. Unity3d에서 transform을 변수에 할당해 사용하는 특별한 이유가 있을까요?
1764정성태9/30/201422284오류 유형: 243. 파일 삭제가 안 되는 경우 - The action can't be comleted because the file is open in System
1763정성태9/30/201423851.NET Framework: 468. PDB 파일을 연동해 소스 코드 라인 정보를 알아내는 방법파일 다운로드1
1762정성태9/30/201424549.NET Framework: 467. 닷넷에서 EIP/RIP 레지스터 값을 구하는 방법 [1]파일 다운로드1
1761정성태9/29/201421570.NET Framework: 466. 윈도우 운영체제의 보안 그룹 이름 및 설명 문자열을 바꾸는 방법파일 다운로드1
1760정성태9/28/201419830.NET Framework: 465. ICorProfilerInfo::GetILToNativeMapping 메서드가 0x80131358을 반환하는 경우
1759정성태9/27/201430976개발 환경 구성: 240. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법파일 다운로드1
1758정성태9/23/201437880개발 환경 구성: 239. 원격 데스크톱 접속(RDP)을 기존의 콘솔 모드처럼 사용하는 방법 [1]
1757정성태9/23/201418406오류 유형: 242. Lync로 모임 참여 시 소리만 들리지 않는 경우 - 두 번째 이야기
1756정성태9/23/201427407기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201434200오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...