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