Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 6개 있습니다.)
(시리즈 글이 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




Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기

이전 글에서,

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

x86 환경에서의 JIT 컴파일 전/후에 대한 코드를 살펴봤는데요. 간단하게 다시 요약해 보면, .NET 메서드를 호출하는 call 호출인 경우,

01020877 ff15584de400    call    dword ptr ds:[0E44D58h] ds:002b:00e44d58=01020440

최초 시점에 [0E44D58h] 주소에는 JIT 컴파일을 수행하는 코드의 주소가 담겨 있습니다. 하지만, 일단 한 번이라도 수행이 되면 [0E44D58h] 주소에는 IL 코드가 기계어로 번역된 주소로 치환되므로,

0:000> dd 0e44d58 L1
00e44d58  010208d0

0:000> !ip2md 010208d0
MethodDesc:   00e44d50
Method Name:  ConsoleApp1.Program.Start()
Class:        00e412a0
MethodTable:  00e44d64
mdToken:      06000002
Module:       00e44044
IsJitted:     yes
CodeAddr:     010208d0
Transparency: Critical
Source file:  C:\..\ConsoleApp1\Program.cs @ 34

이후 다시 "call [0E44d58h]" 코드가 수행되면 "call 010208d0"와 같이 동작하므로 곧바로 기계어로 번역된 함수의 본체를 실행하게 됩니다.




반면, 최신 버전의 .NET 4.8/x64에서는 이런 과정이 완전히 달라졌는데요, 아래는 이 과정을 잘 표현하고 있습니다. (세부적인 사항은 환경에 따라 달라질 수 있다는 점을 염두에 두어야 합니다.)

[그림 출처: https://www.cnblogs.com/zkweb/p/7746222.html]
jit_before_after.jpg

그럼, .NET 4.8/x64 환경으로 지난 글의 코드를 테스트해볼까요? ^^ 우선 !clrstack으로 Program.Main의 IP를 확인하고,

0:000> !clrstack
OS Thread Id: 0x6240 (0)
        Child SP               IP Call Site
000000e6b43fe698 00007ffd8b55c184 [InlinedCallFrame: 000000e6b43fe698] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
000000e6b43fe698 00007ffd6ffeca28 [InlinedCallFrame: 000000e6b43fe698] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
000000e6b43fe660 00007ffd6ffeca28 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
000000e6b43fe740 00007ffd707f89fc System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef)
000000e6b43fe7d0 00007ffd707f8905 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)
000000e6b43fe830 00007ffd6ff27b19 System.IO.StreamReader.ReadBuffer()
000000e6b43fe880 00007ffd6ff27753 System.IO.StreamReader.ReadLine()
000000e6b43fe8e0 00007ffd709aa44d System.IO.TextReader+SyncTextReader.ReadLine()
000000e6b43fe940 00007ffd70774688 System.Console.ReadLine()
000000e6b43fe970 00007ffd148b08f3 ConsoleApp1.Program.Main(System.String[]) [C:\...\ConsoleApp1\Program.cs @ 19]
000000e6b43feb98 00007ffd73df6bd3 [GCFrame: 000000e6b43feb98] 

덤프해 보면 대충 Program.Start 메서드의 호출 코드를 다음과 같이 특정할 수 있습니다.

0:000> !U /d 00007ffd148b08f3
Normal JIT generated code
ConsoleApp1.Program.Main(System.String[])
Begin 00007ffd148b0890, size 90

...[생략]...
C:\..\ConsoleApp1\Program.cs @ 19:
00007ffd`148b08ee e87d3dec5b      call    mscorlib_ni+0xdc4670 (00007ffd`70774670) (System.Console.ReadLine(), mdToken: 0000000006000b6b)
>>> 00007ffd`148b08f3 488bce          mov     rcx,rsi
00007ffd`148b08f6 e88dfbffff      call    00007ffd`148b0488 (ConsoleApp1.Program.Start(), mdToken: 0000000006000002)
...[생략]...

오호~~~, call [...] 형식이 아니라 곧바로 offset 범위의 호출을 하고 있습니다. 해당 위치(00007ffd`148b0488)의 코드를 덤프하면 다음과 같은 형식을 띠고 있습니다.

// 다이어그램 상으로 보면 이 단계가 "Fixup Precode"입니다.

00007ffd`148b0488 e8a340545f      call    clr!PrecodeFixupThunk (00007ffd`73df4530)
00007ffd`148b048d 5e              pop     rsi
00007ffd`148b048e 0201            add     al,byte ptr [rcx]
00007ffd`148b0490 e89b40545f      call    clr!PrecodeFixupThunk (00007ffd`73df4530)
00007ffd`148b0495 5e              pop     rsi
00007ffd`148b0496 0400            add     al,0 
00007ffd`148b0498 e8597a14fd      call    00007ffd`119f7ef6
00007ffd`148b049d 7f00            jg      00007ffd`148b049f 
00007ffd`148b049f 00e8            add     al,ch
00007ffd`148b04a1 8b4054          mov     eax,dword ptr [rax+54h]

처음부터 clr!PrecodeFixupThunk 함수를 call하고 이후로는 일반적인 함수의 prologue 형식에 맞지 않는 코드가 나오고 있습니다. 사실 "5e 0201e89b40545f은 코드가 아니라 PrecodeFixupThunk 함수 내에서 다음과 같이 데이터로써 다뤄지게 됩니다.

// 다이어그램 상으로 보면 이 단계가 "Fixup Precode Chunk"입니다.

clr!PrecodeFixupThunk:
00007ffd`73df4530 58              pop     rax // call clr!PrecodeFixupThunk의 바로 다음 주소: 00007ffd`148b048d
                                              // 여기서 pop을 한번 했으므로,
                                              // 다음번 ret에서 돌아갈 주소는 Main에서 Program.Method를 호출한 다음 지점
00007ffd`73df4531 4c0fb65002      movzx   r10,byte ptr [rax+2] // 00007ffd`148b048d+2의 위치 == 1
00007ffd`73df4536 4c0fb65801      movzx   r11,byte ptr [rax+1] // 00007ffd`148b048d+1의 위치 == 2
00007ffd`73df453b 4a8b44d003      mov     rax,qword ptr [rax+r10*8+3] // [rax + 1*8 + 3] == [00007ffd`148b0498] == 00007ffd`147a59e8
00007ffd`73df4540 4e8d14d8        lea     r10,[rax+r11*8]  // 00007ffd`147a59e8 + r11 * 8 == 00007ffd`147a59f8
00007ffd`73df4544 e9b7020000      jmp     clr!ThePreStub (00007ffd`73df4800)
00007ffd`73df4549 0f1f8000000000  nop     dword ptr [rax]

/* 위의 코드에서 rax, r10과 r11을 사용하는데 이는 volatile 레지스터이기 때문입니다. */

결국 그 데이터로 구해지는 것은, 즉 r10 레지스터에 들어 있는 값은 해당 메서드의 MethodDesc값(위의 예에서는 00007ffd`147a59f8)입니다. 따라서 이 메서드에 대해 clr!ThePreStub으로 jump해 기계어 코드로 번역을 하는 것입니다. 이로 인해 당연히 00007ffd`147a59f8 값으로 덤프해 보는 것도 가능합니다.

0:000> !DumpMD /d 00007ffd147a59f8
Method Name:  ConsoleApp1.Program.Start()
Class:        00007ffd147a2510
MethodTable:  00007ffd147a5a10
mdToken:      0000000006000002
Module:       00007ffd147a4148
IsJitted:     no
CodeAddr:     ffffffffffffffff
Transparency: Critical

이후의 코드는 "jmp clr!ThePreStub"으로 넘어가 "compileMethod"까지 불려 비로소 닷넷 메서드의 "Native Code"가 실행됩니다.




그런데 재미있는 것은, 일단 저렇게 ConsoleApp1.Program.Start가 실행되었는데도 불구하고 다시 해당 메서드를 호출하는 코드에 진입해 보면 여전히 Fixup Precode -> PrecodeFixupThunk -> ThePreStub -> PreStubWorker 단계로 실행이 연결된다는 점입니다.

이상하군요... 닷넷 메서드의 호출에 저렇게 많은 코드가 실행된다는 것은 결코 효율적인 실행 방식이라고 볼 수 없습니다. 한마디로... 혼란스러웠는데요, 그러다 생각난 것이 바로 ^^ 아래의 글이었습니다.

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

즉, .NET 4.8/x64 환경에서는 처음 몇 번 실행되는 닷넷 메서드의 경우 시간이 많이 걸리는 최적화 코드를 생성하기보다는 대충 돌아가는 코드를 만들어 놓고, 이후 최적화가 필요할 정도로 실행되었다고 판단될 때 "Fixup Precode"가,

00007ffd`148b0488 e8a340545f      call    clr!PrecodeFixupThunk (00007ffd`73df4530)
00007ffd`148b048d 5e              pop     rsi
00007ffd`148b048e 0201            add     al,byte ptr [rcx]
00007ffd`148b0490 e89b40545f      call    clr!PrecodeFixupThunk (00007ffd`73df4530)
00007ffd`148b0495 5e              pop     rsi
00007ffd`148b0496 0400            add     al,0 
00007ffd`148b0498 e8597a14fd      call    00007ffd`119f7ef6
00007ffd`148b049d 7f00            jg      00007ffd`148b049f 

이렇게 단순 점프 문으로 바뀝니다.

00007ffd`148b0488 e9e3040000      jmp     00007ffd`148b0970
00007ffd`148b048d 5f              pop     rdi
00007ffd`148b048e 0203            add     al,byte ptr [rbx]
00007ffd`148b0490 e90b050000      jmp     00007ffd`148b09a0

그리고 점프 대상이 되는 코드는 새롭게 JIT 컴파일된 주소를 가리킵니다.

0:000> !ip2md 00007ffd`148b0970
MethodDesc:   00007ffd147a59f8
Method Name:  ConsoleApp1.Program.Start1()
Class:        00007ffd147a24e8
MethodTable:  00007ffd147a5a30
mdToken:      0000000006000002
Module:       00007ffd147a4148
IsJitted:     yes
CodeAddr:     00007ffd148b0970 // 00007ffd`148b0970
Transparency: Critical
Source file:  C:\..\ConsoleApp1\Program.cs @ 49

이로써 다이어그램에서 본 두 번째 "After JIT"와 같이 "Fixup Precode" -> "Native Code"대로 실행하는 것을 확인할 수 있습니다.




위에서 언급한 Fixup Precode의 데이터에 대한 해석은,

00007ffd`148b0488 e8a340545f      call    clr!PrecodeFixupThunk (00007ffd`73df4530)
00007ffd`148b048d 5e              pop     rsi
00007ffd`148b048e 0201            add     al,byte ptr [rcx]
00007ffd`148b0490 e89b40545f      call    clr!PrecodeFixupThunk (00007ffd`73df4530)
00007ffd`148b0495 5e              pop     rsi
00007ffd`148b0496 0400            add     al,0 
00007ffd`148b0498 e8597a14fd      call    00007ffd`119f7ef6
00007ffd`148b049d 7f00            jg      00007ffd`148b049f 
00007ffd`148b049f 00e8            add     al,ch
00007ffd`148b04a1 8b4054          mov     eax,dword ptr [rax+54h]

아래의 문서에서 자세하게 설명하고 있습니다.

Method Descriptor: Precode
; https://github.com/dotnet/runtime/blob/master/docs/design/coreclr/botr/method-descriptor.md#precode

또한 다이어그램에서 본 "ThePreStub" 다음 단계로 나오는 "clr!PreStubWorker"의 코드는 다음과 같고,

clr!PreStubWorker:
00007ffd`73e00dc0 4053            push    rbx
00007ffd`73e00dc2 56              push    rsi
00007ffd`73e00dc3 57              push    rdi
00007ffd`73e00dc4 4154            push    r12
00007ffd`73e00dc6 4155            push    r13
00007ffd`73e00dc8 4156            push    r14
00007ffd`73e00dca 4157            push    r15
00007ffd`73e00dcc 4881ec00030000  sub     rsp,300h
00007ffd`73e00dd3 48c7842450020000feffffff mov qword ptr [rsp+250h],0FFFFFFFFFFFFFFFEh
00007ffd`73e00ddf 488bf2          mov     rsi,rdx
00007ffd`73e00de2 488bf9          mov     rdi,rcx
00007ffd`73e00de5 4533ff          xor     r15d,r15d
00007ffd`73e00de8 4c897c2458      mov     qword ptr [rsp+58h],r15
00007ffd`73e00ded ff1515367b00    call    qword ptr [clr!_imp_GetLastError (00007ffd`745b4408)]
00007ffd`73e00df3 448be8          mov     r13d,eax
00007ffd`73e00df6 e86532ffff      call    clr!GetThread (00007ffd`73df4060)
00007ffd`73e00dfb 488bd8          mov     rbx,rax
00007ffd`73e00dfe 4c8b058bec7b00  mov     r8,qword ptr [clr!s_gsCookie (00007ffd`745bfa90)]
00007ffd`73e00e05 4c89842428010000 mov     qword ptr [rsp+128h],r8
00007ffd`73e00e0d 4889bc2440010000 mov     qword ptr [rsp+140h],rdi
00007ffd`73e00e15 4889b42448010000 mov     qword ptr [rsp+148h],rsi
00007ffd`73e00e1d 488d057c527c00  lea     rax,[clr!PrestubMethodFrame::`vftable' (00007ffd`745c60a0)]
00007ffd`73e00e24 4889842430010000 mov     qword ptr [rsp+130h],rax
00007ffd`73e00e2c 4c8da42430010000 lea     r12,[rsp+130h]
00007ffd`73e00e34 488b4b10        mov     rcx,qword ptr [rbx+10h]
00007ffd`73e00e38 48898c2438010000 mov     qword ptr [rsp+138h],rcx
00007ffd`73e00e40 488d842430010000 lea     rax,[rsp+130h]
00007ffd`73e00e48 48894310        mov     qword ptr [rbx+10h],rax
00007ffd`73e00e4c 488d842430010000 lea     rax,[rsp+130h]
00007ffd`73e00e54 4889842410010000 mov     qword ptr [rsp+110h],rax
00007ffd`73e00e5c 44383ddd71a000  cmp     byte ptr [clr!g_StackProbingEnabled (00007ffd`74808040)],r15b
00007ffd`73e00e63 0f85bf673a00    jne     clr!PreStubWorker+0x3a6868 (00007ffd`741a7628) [br=0]
00007ffd`73e00e69 488b051880a000  mov     rax,qword ptr [clr!Microsoft_Windows_DotNETRuntimePrivateHandle (00007ffd`74808e88)] ds:00007ffd`74808e88=002301b3ef242c20
00007ffd`73e00e70 48898424c0020000 mov     qword ptr [rsp+2C0h],rax
00007ffd`73e00e78 4889842480010000 mov     qword ptr [rsp+180h],rax
00007ffd`73e00e80 4c8d15c9c07d00  lea     r10,[clr!PrestubWorker_V1 (00007ffd`745dcf50)]
00007ffd`73e00e87 4c89942488010000 mov     qword ptr [rsp+188h],r10
00007ffd`73e00e8f 488d0dcac07d00  lea     rcx,[clr!PrestubWorkerEnd_V1 (00007ffd`745dcf60)]
00007ffd`73e00e96 48898c2498010000 mov     qword ptr [rsp+198h],rcx
00007ffd`73e00e9e 488d0d9bc07d00  lea     rcx,[clr!StartupId (00007ffd`745dcf40)]
00007ffd`73e00ea5 48898c2490010000 mov     qword ptr [rsp+190h],rcx
00007ffd`73e00ead 48898c24a0010000 mov     qword ptr [rsp+1A0h],rcx
00007ffd`73e00eb5 48898c24c8020000 mov     qword ptr [rsp+2C8h],rcx
00007ffd`73e00ebd 4c899424d0020000 mov     qword ptr [rsp+2D0h],r10
00007ffd`73e00ec5 48898424d8020000 mov     qword ptr [rsp+2D8h],rax
00007ffd`73e00ecd 0f10057cc07d00  movups  xmm0,xmmword ptr [clr!PrestubWorker_V1 (00007ffd`745dcf50)]
00007ffd`73e00ed4 0f11842450010000 movups  xmmword ptr [rsp+150h],xmm0
00007ffd`73e00edc 833d417fa00000  cmp     dword ptr [clr!MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context+0x24 (00007ffd`74808e24)],0
00007ffd`73e00ee3 0f854e673a00    jne     clr!PreStubWorker+0x3a6877 (00007ffd`741a7637)
00007ffd`73e00ee9 833d5072a00000  cmp     dword ptr [clr!g_IBCLogger (00007ffd`74808140)],0
00007ffd`73e00ef0 0f8555673a00    jne     clr!PreStubWorker+0x3a688b (00007ffd`741a764b)
00007ffd`73e00ef6 0fb64602        movzx   eax,byte ptr [rsi+2]
00007ffd`73e00efa c1e003          shl     eax,3
00007ffd`73e00efd 4863c8          movsxd  rcx,eax
00007ffd`73e00f00 488bc6          mov     rax,rsi
00007ffd`73e00f03 482bc1          sub     rax,rcx
00007ffd`73e00f06 4883e818        sub     rax,18h
00007ffd`73e00f0a 4889442460      mov     qword ptr [rsp+60h],rax
00007ffd`73e00f0f 48898424a8010000 mov     qword ptr [rsp+1A8h],rax
00007ffd`73e00f17 48898424b0010000 mov     qword ptr [rsp+1B0h],rax
00007ffd`73e00f1f 488b00          mov     rax,qword ptr [rax] ds:00007ffd`147959d0=0000000000000060
00007ffd`73e00f22 4889842488000000 mov     qword ptr [rsp+88h],rax
00007ffd`73e00f2a 488b4c2460      mov     rcx,qword ptr [rsp+60h]
00007ffd`73e00f2f 488bd0          mov     rdx,rax
00007ffd`73e00f32 4803d1          add     rdx,rcx
00007ffd`73e00f35 48899424b8010000 mov     qword ptr [rsp+1B8h],rdx
00007ffd`73e00f3d f6c201          test    dl,1
00007ffd`73e00f40 0f8514673a00    jne     clr!PreStubWorker+0x3a689a (00007ffd`741a765a)
00007ffd`73e00f46 44897c2424      mov     dword ptr [rsp+24h],r15d
00007ffd`73e00f4b 48898c24c0010000 mov     qword ptr [rsp+1C0h],rcx
00007ffd`73e00f53 488b01          mov     rax,qword ptr [rcx]
00007ffd`73e00f56 4889842490000000 mov     qword ptr [rsp+90h],rax
00007ffd`73e00f5e 488bf8          mov     rdi,rax
00007ffd`73e00f61 48037c2460      add     rdi,qword ptr [rsp+60h]
00007ffd`73e00f66 4889bc2498000000 mov     qword ptr [rsp+98h],rdi
00007ffd`73e00f6e 40f6c701        test    dil,1
00007ffd`73e00f72 0f85cb121d00    jne     clr!PreStubWorker+0x1d1483 (00007ffd`73fd2243)
00007ffd`73e00f78 833dc171a00000  cmp     dword ptr [clr!g_IBCLogger (00007ffd`74808140)],0
00007ffd`73e00f7f 0f85e8663a00    jne     clr!PreStubWorker+0x3a68ad (00007ffd`741a766d)
00007ffd`73e00f85 f6470840        test    byte ptr [rdi+8],40h
00007ffd`73e00f89 7522            jne     clr!PreStubWorker+0x1ed (00007ffd`73e00fad)
00007ffd`73e00f8b 833dae71a00000  cmp     dword ptr [clr!g_IBCLogger (00007ffd`74808140)],0
00007ffd`73e00f92 0f85e4663a00    jne     clr!PreStubWorker+0x3a68bc (00007ffd`741a767c)
00007ffd`73e00f98 488b4720        mov     rax,qword ptr [rdi+20h]
00007ffd`73e00f9c 48898424c8010000 mov     qword ptr [rsp+1C8h],rax
00007ffd`73e00fa4 f60004          test    byte ptr [rax],4
00007ffd`73e00fa7 0f856d790f00    jne     clr!PreStubWorker+0x531 (00007ffd`73ef891a)
00007ffd`73e00fad c744242801000000 mov     dword ptr [rsp+28h],1
00007ffd`73e00fb5 0fb64606        movzx   eax,byte ptr [rsi+6]
00007ffd`73e00fb9 2407            and     al,7
00007ffd`73e00fbb 3c05            cmp     al,5
00007ffd`73e00fbd 0f84fa0d1600    je      clr!PreStubWorker+0x50a (00007ffd`73f61dbd) [br=0]
00007ffd`73e00fc3 833d7671a00000  cmp     dword ptr [clr!g_IBCLogger (00007ffd`74808140)],0
00007ffd`73e00fca 0f85bb663a00    jne     clr!PreStubWorker+0x3a68cb (00007ffd`741a768b)
00007ffd`73e00fd0 0fb64606        movzx   eax,byte ptr [rsi+6]
00007ffd`73e00fd4 2407            and     al,7
00007ffd`73e00fd6 3c07            cmp     al,7
00007ffd`73e00fd8 0f84bc663a00    je      clr!PreStubWorker+0x3a68da (00007ffd`741a769a)
00007ffd`73e00fde c744242001000000 mov     dword ptr [rsp+20h],1
00007ffd`73e00fe6 833d5371a00000  cmp     dword ptr [clr!g_IBCLogger (00007ffd`74808140)],0
00007ffd`73e00fed 0f85ba663a00    jne     clr!PreStubWorker+0x3a68ed (00007ffd`741a76ad)
00007ffd`73e00ff3 0fb64602        movzx   eax,byte ptr [rsi+2]
00007ffd`73e00ff7 c1e003          shl     eax,3
00007ffd`73e00ffa 4863c8          movsxd  rcx,eax
00007ffd`73e00ffd 488bc6          mov     rax,rsi
00007ffd`73e01000 482bc1          sub     rax,rcx
00007ffd`73e01003 4883e818        sub     rax,18h
00007ffd`73e01007 48898424a0000000 mov     qword ptr [rsp+0A0h],rax ss:00000052`625fe980=0000000000000001
00007ffd`73e0100f 48898424f0010000 mov     qword ptr [rsp+1F0h],rax
00007ffd`73e01017 488b00          mov     rax,qword ptr [rax]
00007ffd`73e0101a 48898424a8000000 mov     qword ptr [rsp+0A8h],rax
00007ffd`73e01022 488bf8          mov     rdi,rax
00007ffd`73e01025 4803bc24a0000000 add     rdi,qword ptr [rsp+0A0h]
00007ffd`73e0102d 4889bc24b0000000 mov     qword ptr [rsp+0B0h],rdi
00007ffd`73e01035 40f6c701        test    dil,1
00007ffd`73e01039 0f8519121d00    jne     clr!PreStubWorker+0x1d1498 (00007ffd`73fd2258)
00007ffd`73e0103f f6470840        test    byte ptr [rdi+8],40h
00007ffd`73e01043 7522            jne     clr!PreStubWorker+0x2a7 (00007ffd`73e01067)
00007ffd`73e01045 833df470a00000  cmp     dword ptr [clr!g_IBCLogger (00007ffd`74808140)],0
00007ffd`73e0104c 0f856a663a00    jne     clr!PreStubWorker+0x3a68fc (00007ffd`741a76bc)
00007ffd`73e01052 488b4720        mov     rax,qword ptr [rdi+20h]
00007ffd`73e01056 48898424f8010000 mov     qword ptr [rsp+1F8h],rax
00007ffd`73e0105e f60040          test    byte ptr [rax],40h
00007ffd`73e01061 0f85700d1600    jne     clr!PreStubWorker+0x53b (00007ffd`73f61dd7)
00007ffd`73e01067 4c897c2450      mov     qword ptr [rsp+50h],r15
00007ffd`73e0106c 833dcd70a00000  cmp     dword ptr [clr!g_IBCLogger (00007ffd`74808140)],0
00007ffd`73e01073 0f85c0663a00    jne     clr!PreStubWorker+0x3a6979 (00007ffd`741a7739)
00007ffd`73e01079 0fb64602        movzx   eax,byte ptr [rsi+2]
00007ffd`73e0107d c1e003          shl     eax,3
00007ffd`73e01080 4863c8          movsxd  rcx,eax
00007ffd`73e01083 488bc6          mov     rax,rsi
00007ffd`73e01086 482bc1          sub     rax,rcx
00007ffd`73e01089 4883e818        sub     rax,18h
00007ffd`73e0108d 48898424f0000000 mov     qword ptr [rsp+0F0h],rax
00007ffd`73e01095 4889842478020000 mov     qword ptr [rsp+278h],rax
00007ffd`73e0109d 488b00          mov     rax,qword ptr [rax]
00007ffd`73e010a0 48898424f8000000 mov     qword ptr [rsp+0F8h],rax
00007ffd`73e010a8 4c8bf0          mov     r14,rax
00007ffd`73e010ab 4c03b424f0000000 add     r14,qword ptr [rsp+0F0h]
00007ffd`73e010b3 4c89b42400010000 mov     qword ptr [rsp+100h],r14
00007ffd`73e010bb 41f6c601        test    r14b,1
00007ffd`73e010bf 0f85a8111d00    jne     clr!PreStubWorker+0x1d14ad (00007ffd`73fd226d)
00007ffd`73e010c5 4c89b42480020000 mov     qword ptr [rsp+280h],r14
00007ffd`73e010cd 8b0d6d70a000    mov     ecx,dword ptr [clr!g_IBCLogger (00007ffd`74808140)]
00007ffd`73e010d3 85c9            test    ecx,ecx
00007ffd`73e010d5 0f856d663a00    jne     clr!PreStubWorker+0x3a6988 (00007ffd`741a7748)
00007ffd`73e010db 0fb64606        movzx   eax,byte ptr [rsi+6]
00007ffd`73e010df 2407            and     al,7
00007ffd`73e010e1 3c05            cmp     al,5
00007ffd`73e010e3 0f843c0d1600    je      clr!PreStubWorker+0x51e (00007ffd`73f61e25)
00007ffd`73e010e9 85c9            test    ecx,ecx
00007ffd`73e010eb 0f8566663a00    jne     clr!PreStubWorker+0x3a6997 (00007ffd`741a7757)
00007ffd`73e010f1 418b06          mov     eax,dword ptr [r14]
00007ffd`73e010f4 2500000c00      and     eax,0C0000h
00007ffd`73e010f9 3d00000400      cmp     eax,40000h
00007ffd`73e010fe 0f8452661500    je      clr!PreStubWorker+0x4e4 (00007ffd`73f57756)
00007ffd`73e01104 b800800000      mov     eax,8000h
00007ffd`73e01109 0fb77e04        movzx   edi,word ptr [rsi+4]
00007ffd`73e0110d 66854606        test    word ptr [rsi+6],ax
00007ffd`73e01111 7508            jne     clr!PreStubWorker+0x35b (00007ffd`73e0111b)
00007ffd`73e01113 b8ff030000      mov     eax,3FFh
00007ffd`73e01118 6623f8          and     di,ax
00007ffd`73e0111b 6689bc2458030000 mov     word ptr [rsp+358h],di
00007ffd`73e01123 85c9            test    ecx,ecx
00007ffd`73e01125 0f853b663a00    jne     clr!PreStubWorker+0x3a69a6 (00007ffd`741a7766)
00007ffd`73e0112b 410fb7460c      movzx   eax,word ptr [r14+0Ch]
00007ffd`73e01130 6689442438      mov     word ptr [rsp+38h],ax
00007ffd`73e01135 663bf8          cmp     di,ax
00007ffd`73e01138 0f82ad070000    jb      clr!PreStubWorker+0x477 (00007ffd`73e018eb)
00007ffd`73e0113e 48899c2418010000 mov     qword ptr [rsp+118h],rbx
00007ffd`73e01146 8b430c          mov     eax,dword ptr [rbx+0Ch]
00007ffd`73e01149 89842420010000  mov     dword ptr [rsp+120h],eax
00007ffd`73e01150 85c0            test    eax,eax
00007ffd`73e01152 742b            je      clr!PreStubWorker+0x3bf (00007ffd`73e0117f)
00007ffd`73e01154 48899c2498020000 mov     qword ptr [rsp+298h],rbx
00007ffd`73e0115c 44897c2478      mov     dword ptr [rsp+78h],r15d
00007ffd`73e01161 44897b0c        mov     dword ptr [rbx+0Ch],r15d
00007ffd`73e01165 488d4308        lea     rax,[rbx+8]
00007ffd`73e01169 48898424f8020000 mov     qword ptr [rsp+2F8h],rax
00007ffd`73e01171 8b00            mov     eax,dword ptr [rax]
00007ffd`73e01173 89442444        mov     dword ptr [rsp+44h],eax
00007ffd`73e01177 a85f            test    al,5Fh
00007ffd`73e01179 0f85f6653a00    jne     clr!PreStubWorker+0x3a69b5 (00007ffd`741a7775)
00007ffd`73e01179 0f85f6653a00    jne     clr!PreStubWorker+0x3a69b5 (00007ffd`741a7775) [br=0]
00007ffd`73e0117f 488b542450      mov     rdx,qword ptr [rsp+50h]
00007ffd`73e01184 488bce          mov     rcx,rsi
00007ffd`73e01187 e8b4000000      call    clr!MethodDesc::DoPrestub (00007ffd`73e01240)
00007ffd`73e0118c 4889442458      mov     qword ptr [rsp+58h],rax
00007ffd`73e01191 83bc242001000000 cmp     dword ptr [rsp+120h],0
00007ffd`73e01199 8b430c          mov     eax,dword ptr [rbx+0Ch]
00007ffd`73e0119c 0f84e2653a00    je      clr!PreStubWorker+0x3a69c4 (00007ffd`741a7784)
00007ffd`73e011a2 85c0            test    eax,eax
00007ffd`73e011a4 7525            jne     clr!PreStubWorker+0x40b (00007ffd`73e011cb)
00007ffd`73e011a6 48899c24a8020000 mov     qword ptr [rsp+2A8h],rbx
00007ffd`73e011ae c744247c01000000 mov     dword ptr [rsp+7Ch],1
00007ffd`73e011b6 c7430c01000000  mov     dword ptr [rbx+0Ch],1
00007ffd`73e011bd 8b058d6ea000    mov     eax,dword ptr [clr!g_TrapReturningThreads (00007ffd`74808050)]
00007ffd`73e011c3 85c0            test    eax,eax
00007ffd`73e011c5 0f85c0c00f00    jne     clr!PreStubWorker+0x4fd (00007ffd`73efd28b)
00007ffd`73e011cb 488b942498010000 mov     rdx,qword ptr [rsp+198h]
00007ffd`73e011d3 0f1002          movups  xmm0,xmmword ptr [rdx]
00007ffd`73e011d6 0f11842460010000 movups  xmmword ptr [rsp+160h],xmm0
00007ffd`73e011de 833d3f7ca00000  cmp     dword ptr [clr!MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context+0x24 (00007ffd`74808e24)],0
00007ffd`73e011e5 0f85a7653a00    jne     clr!PreStubWorker+0x3a69d2 (00007ffd`741a7792)
00007ffd`73e011eb c684247001000000 mov     byte ptr [rsp+170h],0
00007ffd`73e011f3 803d466ea00000  cmp     byte ptr [clr!g_StackProbingEnabled (00007ffd`74808040)],0
00007ffd`73e011fa 0f85a6653a00    jne     clr!PreStubWorker+0x3a69e6 (00007ffd`741a77a6)
00007ffd`73e01200 32c0            xor     al,al
00007ffd`73e01202 0f8525f91c00    jne     clr!PreStubWorker+0x1cfd6d (00007ffd`73fd0b2d)
00007ffd`73e01208 e89336ffff      call    clr!ThePreStubPatch (00007ffd`73df48a0)
00007ffd`73e0120d 498b442408      mov     rax,qword ptr [r12+8]
00007ffd`73e01212 48894310        mov     qword ptr [rbx+10h],rax
00007ffd`73e01216 418bcd          mov     ecx,r13d
00007ffd`73e01219 ff1539327b00    call    qword ptr [clr!_imp_SetLastError (00007ffd`745b4458)]
00007ffd`73e0121f 488b442458      mov     rax,qword ptr [rsp+58h]
00007ffd`73e01224 4881c400030000  add     rsp,300h
00007ffd`73e0122b 415f            pop     r15
00007ffd`73e0122d 415e            pop     r14
00007ffd`73e0122f 415d            pop     r13
00007ffd`73e01231 415c            pop     r12
00007ffd`73e01233 5f              pop     rdi
00007ffd`73e01234 5e              pop     rsi
00007ffd`73e01235 5b              pop     rbx
00007ffd`73e01236 c3              ret

마지막 즈음의 00007ffd`73e0121f 주소에서 실행하는 "mov rax,qword ptr [rsp+58h]" 코드의 rax 반환값은 Native Code의 주소에 해당합니다. 따라서 ThePreStub의 코드에서는,

clr!ThePreStub:
00007ffd`73df4800 4157            push    r15
00007ffd`73df4802 4156            push    r14
00007ffd`73df4804 4155            push    r13
00007ffd`73df4806 4154            push    r12
00007ffd`73df4808 55              push    rbp
00007ffd`73df4809 53              push    rbx
00007ffd`73df480a 56              push    rsi
00007ffd`73df480b 57              push    rdi
00007ffd`73df480c 4883ec68        sub     rsp,68h
00007ffd`73df4810 48898c24b0000000 mov     qword ptr [rsp+0B0h],rcx
00007ffd`73df4818 48899424b8000000 mov     qword ptr [rsp+0B8h],rdx
00007ffd`73df4820 4c898424c0000000 mov     qword ptr [rsp+0C0h],r8
00007ffd`73df4828 4c898c24c8000000 mov     qword ptr [rsp+0C8h],r9
00007ffd`73df4830 660f7f442420    movdqa  xmmword ptr [rsp+20h],xmm0
00007ffd`73df4836 660f7f4c2430    movdqa  xmmword ptr [rsp+30h],xmm1
00007ffd`73df483c 660f7f542440    movdqa  xmmword ptr [rsp+40h],xmm2
00007ffd`73df4842 660f7f5c2450    movdqa  xmmword ptr [rsp+50h],xmm3
00007ffd`73df4848 488d4c2468      lea     rcx,[rsp+68h] // sub rsp, 68h 이전의 rsp 위치 = push rdi 시점의 rsp
00007ffd`73df484d 498bd2          mov     rdx,r10 // PrecodeFixupThunk에서 계산한 값
00007ffd`73df4850 e86bc50000      call    clr!PreStubWorker (00007ffd`73e00dc0) // 반환값으로 Method의 기계어 위치를 반환
00007ffd`73df4855 660f6f442420    movdqa  xmm0,xmmword ptr [rsp+20h]
00007ffd`73df485b 660f6f4c2430    movdqa  xmm1,xmmword ptr [rsp+30h]
00007ffd`73df4861 660f6f542440    movdqa  xmm2,xmmword ptr [rsp+40h]
00007ffd`73df4867 660f6f5c2450    movdqa  xmm3,xmmword ptr [rsp+50h]
00007ffd`73df486d 488b8c24b0000000 mov     rcx,qword ptr [rsp+0B0h]
00007ffd`73df4875 488b9424b8000000 mov     rdx,qword ptr [rsp+0B8h]
00007ffd`73df487d 4c8b8424c0000000 mov     r8,qword ptr [rsp+0C0h]
00007ffd`73df4885 4c8b8c24c8000000 mov     r9,qword ptr [rsp+0C8h]
00007ffd`73df488d 4883c468        add     rsp,68h
00007ffd`73df4891 5f              pop     rdi
00007ffd`73df4892 5e              pop     rsi
00007ffd`73df4893 5b              pop     rbx
00007ffd`73df4894 5d              pop     rbp
00007ffd`73df4895 415c            pop     r12
00007ffd`73df4897 415d            pop     r13
00007ffd`73df4899 415e            pop     r14
00007ffd`73df489b 415f            pop     r15
00007ffd`73df489d 48ffe0          jmp     rax

이렇게 PreStubWorker의 반환 주소로 jmp하는 것으로 마무리를 하게 됩니다.

참고로, 메서드가 처음 실행되는 경우는 JIT 컴파일을 하겠지만 이후에는 해당 Native Code의 주소를 가리키도록 내부적으로 MethodDesc 구조체의 "CodeAddr" 주솟값에 보관하게 됩니다. 이후, 다시 최적화된 Native Code가 생성되기까지는 00007ffd`73e01187 위치의 "call clr!MethodDesc::DoPrestub" 함수 내의 "MethodDesc::GetMethodEntryPoint" 함수를 통해 Native Code 주소를 바로 사용하는 식입니다.

실제로 windbg에서 ba 명령으로 MethodDesc의 CodeAddr 위치 값이 "read"되는 시점을 잡아 보면 다음과 같은 callstack을 확인할 수 있습니다.

0:000> ba r8 7ffd147a5a18 // 7ffd147a5a18 == MethodDesc의 CodeAddr 위치

0:000> g
Breakpoint 1 hit
clr!MethodDesc::GetMethodEntryPoint+0x119:
00007ffd`73df700b eb01            jmp     clr!MethodDesc::GetMethodEntryPoint+0xed (00007ffd`73df700e)

0:000> k
 # Child-SP          RetAddr           Call Site
00 00000000`0079e850 00007ffd`73ff0acb clr!MethodDesc::GetMethodEntryPoint+0x119
01 00000000`0079e880 00007ffd`73e0118c clr!MethodDesc::DoPrestub+0xfee
02 00000000`0079eaa0 00007ffd`73df4855 clr!PreStubWorker+0x3cc
03 00000000`0079ede0 00007ffd`148b0cfc clr!ThePreStub+0x55
04 00000000`0079ee90 00007ffd`73df6bd3 0x00007ffd`148b0cfc
05 00000000`0079f020 00007ffd`73df6aa0 clr!CallDescrWorkerInternal+0x83
06 00000000`0079f060 00007ffd`73df7130 clr!CallDescrWorkerWithHandler+0x4e
07 00000000`0079f0a0 00007ffd`73ecf622 clr!MethodDescCallSite::CallTargetWorker+0x102
08 00000000`0079f1a0 00007ffd`73ecffe7 clr!RunMain+0x25f
09 00000000`0079f380 00007ffd`73ecfe9a clr!Assembly::ExecuteMainMethod+0xb7
0a 00000000`0079f670 00007ffd`73ecf7e3 clr!SystemDomain::ExecuteMainMethod+0x643
0b 00000000`0079fc70 00007ffd`73ecf761 clr!ExecuteEXE+0x3f
0c 00000000`0079fce0 00007ffd`73ed0ca4 clr!_CorExeMainInternal+0xb2
0d 00000000`0079fd70 00007ffd`75918c01 clr!CorExeMain+0x14
0e 00000000`0079fdb0 00007ffd`75a3a56c mscoreei!CorExeMain+0x112
0f 00000000`0079fe10 00007ffd`8b277bd4 MSCOREE!CorExeMain_Exported+0x6c
10 00000000`0079fe40 00007ffd`8b52ced1 KERNEL32!BaseThreadInitThunk+0x14
11 00000000`0079fe70 00000000`00000000 ntdll!RtlUserThreadStart+0x21




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/12/2021]

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

비밀번호

댓글 작성자
 




[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13754정성태10/4/2024258닷넷: 2304. C# 13 - (8) 부분 메서드 정의를 속성 및 인덱서에도 확대파일 다운로드1
13753정성태10/4/2024261Linux: 81. Linux - PATH 환경변수의 적용 규칙
13752정성태10/2/2024338닷넷: 2303. C# 13 - (7) ref struct의 interface 상속 및 제네릭 제약으로 사용 가능파일 다운로드1
13751정성태10/2/2024498C/C++: 176. C/C++ - ARM64로 포팅할 때 유의할 점
13750정성태10/1/2024650C/C++: 175. C++ - WinMain/wWinMain 호출 전의 CRT 초기화 단계
13749정성태9/30/2024795닷넷: 2302. C# - ssh-keygen으로 생성한 Private Key와 Public Key 연동파일 다운로드1
13748정성태9/29/2024889닷넷: 2301. C# - BigInteger 타입이 byte 배열로 직렬화하는 방식
13747정성태9/28/2024831닷넷: 2300. C# - OpenSSH의 공개키 파일에 대한 "BEGIN OPENSSH PUBLIC KEY" / "END OPENSSH PUBLIC KEY" PEM 포맷파일 다운로드1
13746정성태9/28/2024824오류 유형: 924. Python - LocalProtocolError("Illegal header value ...")
13745정성태9/28/2024839Linux: 80. 리눅스 - 실행 중인 프로세스 내부의 환경변수 설정을 구하는 방법 (lldb)
13744정성태9/27/2024915닷넷: 2299. C# - Windows Hello 사용자 인증 다이얼로그 표시하기파일 다운로드1
13743정성태9/26/20241163닷넷: 2298. C# - Console 프로젝트에서의 await 대상으로 Main 스레드 활용하는 방법 [1]
13742정성태9/26/2024969닷넷: 2297. C# - ssh-keygen으로 생성한 ecdsa 유형의 Public Key 파일 해석 [1]파일 다운로드1
13741정성태9/25/2024816디버깅 기술: 202. windbg - ASP.NET MVC Web Application (.NET Framework) 응용 프로그램의 덤프 분석 시 요령
13740정성태9/24/2024898기타: 86. RSA 공개키 등의 modulus 값에 0x00 선행 바이트가 있는 이유(ASN.1 인코딩)
13739정성태9/24/2024999닷넷: 2297. C# - ssh-keygen으로 생성한 Public Key 파일 해석과 fingerprint 값(md5, sha256) 생성파일 다운로드1
13738정성태9/22/2024938C/C++: 174. C/C++ - 윈도우 운영체제에서의 file descriptor, FILE*파일 다운로드1
13737정성태9/21/20241075개발 환경 구성: 727. Visual C++ - 리눅스 프로젝트를 위한 빌드 서버의 msbuild 구성
13736정성태9/20/20241200오류 유형: 923. Visual Studio Code - Could not establish connection to "...": Port forwarding is disabled.
13735정성태9/20/20241267개발 환경 구성: 726. ARM 플랫폼용 Visual C++ 리눅스 프로젝트 빌드
13734정성태9/19/20241332개발 환경 구성: 725. ssh를 이용한 원격 docker 서비스 사용
13733정성태9/19/20241242VS.NET IDE: 194. Visual Studio - Cross Platform / "Authentication Type: Private Key"로 접속하는 방법
13732정성태9/17/20241217개발 환경 구성: 724. ARM + docker 환경에서 .NET 8 설치
13731정성태9/15/20241601개발 환경 구성: 723. C# / Visual C++ - Control Flow Guard (CFG) 활성화 [1]파일 다운로드2
13730정성태9/10/20241183오류 유형: 922. docker - RULE_APPEND failed (No such file or directory): rule in chain DOCKER
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...