실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 네 번째 이야기(Monitor.Enter 후킹)
.NET Profiler 기술을 활용하면,
CLR Profiler - 별도 정의한 .NET 코드를 호출하도록 IL 코드 변경
; https://www.sysnet.pe.kr/2/0/10959
런타임에 .NET 응용 프로그램의 IL 코드를 변경하는 것이 가능합니다. 그런데, 만능은 아닙니다. ^^ 가령 Monitor.Enter와 같은 메서드는 IL 코드를 변경할 수 없는데, 왜냐하면 애당초 IL 코드가 없기 때문입니다.
[SecuritySafeCritical]
[__DynamicallyInvokable]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void Enter(object obj);
보는 바와 같이 IL 코드 없이 곧바로 "InternalCall"로 내부의 Native 함수에 연결되어 있습니다. 따라서, 이런 메서드를 가로채기 하려면 trampoline과 같은 별도의 후킹 방식을 사용해야 합니다. (혹은, Monitor.Enter에 대한 모든 IL 호출을 다른 메서드로 변경하거나.)
우선, 해당 메서드의 정보를 DetourFunc 라이브러리의 MethodDesc을 이용해 살펴볼까요? ^^
// Install-Package DetourFunc -Version 1.1.0
{
MethodDesc md = MethodDesc.ReadFromAddress(action.Method.MethodHandle.Value);
md.Dump(Console.Out);
}
/* 출력 결과
[MethodDesc 0x7ffc31f3c300 - FCall]
wTokenRemainder = bb40 (Token = 6003b40)
chunkIndex = 0
bFlags2 = 000000B1 (Flags2 == 177)
wSlotNumber = 4
wFlags = 20a9 (IsFullSlotNumber == False)
MethodTablePtr = 7ffc31f55b10
FunctionPtr = 7ffc34c626b0
*/
위의 결과와 함께 windbg로 분석을 해보면,
0:000> !dumpmd 7ffc31f3c300
Method Name: System.Threading.Monitor.Enter(System.Object)
Class: 00007ffc320c5528
MethodTable: 00007ffc31f55b10
mdToken: 0000000006003b40
Module: 00007ffc31f31000
IsJitted: yes
CodeAddr: 00007ffc34c626b0
Transparency: Safe critical
00007ffc34c626b0 주소의 코드들이 마치 패치를 위한 배려라도 한 것처럼,
clr!JIT_MonEnter:
00007ffc`34c626b0 33d2 xor edx,edx
00007ffc`34c626b2 6666666666660f1f840000000000 nop word ptr [rax+rax]
clr!JIT_MonEnterWorker_InlineGetThread:
00007ffc`34c626c0 56 push rsi
00007ffc`34c626c1 488bf2 mov rsi,rdx
00007ffc`34c626c4 4885c9 test rcx,rcx
00007ffc`34c626c7 0f84a0010000 je clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x1a0 (00007ffc`34c6286d)
clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel:
...[생략]...
웬만한 길이의 trampoline 코드를 넘어서는
nop 코드를 마이크로소프트가 끼워두었습니다. (왜 그랬는지 궁금하군요. ^^;)
덕분에 아무런 걱정 없이 trampoline 패치를 할 수 있지만,
using DetourFunc;
using DetourFunc.Clr;
using System;
using System.Threading;
namespace ConsoleApp1
{
public delegate void MonitorEnterMethodDelegate(object obj);
public delegate void MonitorEnterMethodRefDelegate(IntPtr objAddress);
class Program
{
static MonitorEnterMethodDelegate s_originalMethod;
static MonitorEnterMethodRefDelegate s_originalMethodRef;
static void Main(string[] _)
{
Person obj = new Person { Name = "TEST" };
IntPtr ptrBodyTestMethod;
{
MonitorEnterMethodDelegate enterMethod = Monitor.Enter;
MethodDesc md = MethodDesc.ReadFromMethodInfo(enterMethod.Method);
ptrBodyTestMethod = md.GetNativeFunctionPointer();
}
IntPtr ptrBodyReplaceMethod;
{
MonitorEnterMethodDelegate action = Replaced_TestMethod;
MethodDesc md = MethodDesc.ReadFromMethodInfo(action.Method);
ptrBodyReplaceMethod = md.GetNativeFunctionPointer();
}
using (var item = new TrampolinePatch<MonitorEnterMethodDelegate>())
{
if (item.JumpPatch(ptrBodyTestMethod, ptrBodyReplaceMethod) == true)
{
s_originalMethod = item.GetOriginalFunc();
}
Console.WriteLine("[After trampoline]");
Monitor.Enter(obj);
Console.WriteLine("[before Exit]");
Monitor.Exit(obj);
}
Console.ReadLine();
}
public static void Replaced_TestMethod(object obj)
{
Console.WriteLine("Replaced_TestMethod called!: " + obj?.ToString());
Console.WriteLine("[before orgmethod]");
s_originalMethod?.Invoke(obj);
}
}
public class Person
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
}
매끄러웠던 진행과 달리, 실행해 보면 예상치 않은 결과에 당황하게 됩니다. ^^;
[After trampoline]
Replaced_TestMethod called!: TEST
[before orgmethod]
[before Exit]
Unhandled Exception: System.Threading.SynchronizationLockException: Object synchronization method was called from an unsynchronized block of code.
at ConsoleApp1.Program.Main(String[] _) in C:\ConsoleApp1\ConsoleApp1\Program.cs:line 84
그러니까, 분명히 가로채기는 성공했고 s_originalMethod 메서드 호출까지도 예외 없이 성공했는데, Monitor.Exit 호출 시 해당
object 인자가 동기화에 사용된 적이 없다고 하는 것입니다.
정리해 보면, 결국 다음의 3가지 메서드에 전달된 object 인자 중 어떤 하나(또는 셋 모두)는 다른 주소를 가리킨다는 것을 의미합니다.
Monitor.Enter(object obj)
Replaced_TestMethod(object obj)
s_originalMethod?.Invoke(obj)
원인을 밝히기 위해 windbg로 해당 메서드들의 object 인자를 추적하다가 s_originalMethod?.Invoke 호출 내에서 다음과 같이 마샬러가 수행되는 것을 발견했습니다.
[Marshal.GetDelegateForFunctionPointer가 반환한 s_originalMethod 델리게이트]
...[생략]...
00007ffb`d577a5f0 488d55a8 lea rdx,[rbp-58h]
00007ffb`d577a5f4 488b4d18 mov rcx,qword ptr [rbp+18h]
00007ffb`d577a5f8 e8c3755d5f call clr!StubHelpers::ObjectMarshaler__ConvertToNative (00007ffc`34d51bc0)
00007ffb`d577a5fd c745c401000000 mov dword ptr [rbp-3Ch],1 ss:00000000`00dce824=00000000
...[생략]...
즉, ObjectMarshaler__ConvertToNative 함수에 의해 마샬링된 값을 Marshal.GetDelegateForFunctionPointer가 입력으로 받았던 함수에 전달하는 것입니다. 실제로 그 원본 함수에 전달하는 시점에 다시 rcx 값을 덤프해 보면,
0:000> dd @rcx L6
00000000`004fe738 0000000d 00000000 007b0018 00000000 00000000 00000000
어디서 많이 봤던 패턴이 나옵니다.
C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법
; https://www.sysnet.pe.kr/2/0/12157#object_marshal
그러니까, s_originalMethod?.Invoke(obj) 호출은 내부적으로 다음과 같은 식의 마샬링 작업을 수행했던 것입니다.
// 가상 코드
void s_originalMethod(object obj)
{
IntPtr pAlloc = Marshal.AllocHGlobal(24); // x86 == 16, x64 == 24
Marshal.GetNativeVariantForObject(obj, pAlloc);
VARIANT vt = new VARIANT(pAlloc);
call [_trampline_jmp_for_monitor_enter](vt);
}
당연히, 원본 Monitor.Enter는 (object가 아닌) VARIANT 타입의 값을 받게 되고 엉뚱한 객체의 sync block에 대해 잠금 표시를 수행한 것입니다. 그리고, 순서가 바뀌긴 했지만 바로 이 문제를 파악하다가 알게 된 내용들을 정리한 것이 아래의 글들이었습니다.
C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법
; https://www.sysnet.pe.kr/2/0/12157
C# - Marshal.GetIUnknownForObject/GetIDispatchForObject 사용 시 메모리 누수(Memory Leak) 발생
; https://www.sysnet.pe.kr/2/0/12158
C#에서 만든 COM 객체를 C/C++로 P/Invoke Interop 시 메모리 누수(Memory Leak) 발생
; https://www.sysnet.pe.kr/2/0/12162
결국, Marshal.GetDelegateForFunctionPointer로 동적 생성한 delegate 함수는 내부적으로 Object 등의 객체를 마샬링하는 과정에 Marshal.GetNativeVariantForObject를 수행하고, 여기에서도 VariantClear를 수행하지 않으므로 메모리 누수 현상이 동일하게 발생합니다. 확인을 위해 원문의 코드를 다음과 같이 변경시키면,
while (true)
{
Person obj = new Person { Name = "TEST" };
Monitor.Enter(obj); // 가로채기되었으므로 s_originalMethod 호출 내부의 메모리 누수 발생
}
메모리가 지속적으로 증가하는 것을 볼 수 있습니다.
자, 그럼 어떻게 해야 Monitor.Enter를 (메모리 누수도 없이) 정상적으로 가로채기 할 수 있을까요? 이 문제를 해결하려면 최종적으로 s_originalMethod?.Invoke의 호출에 원본 객체를 자동 마샬링 과정 없이 전달해야 합니다.
그러고 보니 닷넷 객체를 가리키는 변수가 담은 값의 실체가 실은 GC 힙의 주소라는 것을 알고 있으니, 따라서 object가 아닌 IntPtr로 s_originalMethod?.Invoke에 전달한다면 내부적으로 마샬링이 발생하지 않을 수 있습니다. 마침
객체에 대한 GC Heap 상의 주소를 얻는 메서드를 MethodDesc 타입에 추가해 두었으니, 이를 이용해 다음과 같은 식으로 처리할 수 있습니다.
public delegate void MonitorEnterMethodRefDelegate(IntPtr objAddress);
class Program
{
static MonitorEnterMethodRefDelegate s_originalMethodRef;
static void Main(string[] _)
{
...[생략]...
using (var item = new TrampolinePatch<MonitorEnterMethodRefDelegate>())
{
if (item.JumpPatch(ptrBodyTestMethod, ptrBodyReplaceMethod) == true)
{
s_originalMethodRef = item.GetOriginalFunc();
}
Monitor.Enter(obj);
Monitor.Exit(obj);
}
Console.ReadLine();
}
public static void Replaced_TestMethod(object obj)
{
IntPtr objAddr = MethodDesc.GetObjectAddress(obj);
s_originalMethodRef?.Invoke(objAddr);
}
}
바뀐 코드로 실행해 보면, 정상적으로 Monitor.Exit가 실행이 되며 동기화가 풀리는 것을 확인할 수 있습니다. 정리해 보면, C#에서 Trampoline 기법을 이용해 우회한 메서드가 표면상 IL 코드이긴 해도 결국 내부적으로는 [Managed-To-Native] 층을 거쳐 인자 값들이 마샬링된다는 사실을 잊어서는 안 됩니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
예전의 출력 결과 유형이 IL인 반면, 이번 Monitor.Enter의 경우 "FCall"이라고 나옵니다.
/* 출력 결과
[MethodDesc 0x7ffc31f3c300 - FCall]
wTokenRemainder = bb40 (Token = 6003b40)
chunkIndex = 0
bFlags2 = 000000B1 (Flags2 == 177)
wSlotNumber = 4
wFlags = 20a9 (IsFullSlotNumber == False)
MethodTablePtr = 7ffc31f55b10
FunctionPtr = 7ffc34c626b0
*/
이참에 ^^ 기존에 만들어 둔 MethodDesc에,
C# 코드로 접근하는 MethodDesc, MethodTable
; https://www.sysnet.pe.kr/2/0/12142
FCall 유형의 메서드에 대한 필드를,
class FCallMethodDesc : public MethodDesc
{
DWORD m_dwECallID;
#ifdef HOST_64BIT
DWORD m_padding;
#endif
};
추가해 다음과 같은 식의 구조체를 마련해 둘 수 있습니다.
[StructLayout(LayoutKind.Sequential)]
internal struct FCallMethodDescInternalx86
{
readonly MethodDescInternal _methodDesc;
readonly uint _dwECallID;
public FCallMethodDescInternalx86(MethodDescInternal methodDesc, uint dwECallID)
{
_methodDesc = methodDesc;
_dwECallID = dwECallID;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct FCallMethodDescInternalx64
{
readonly MethodDescInternal _methodDesc;
readonly uint _dwECallID;
readonly uint _padding;
public FCallMethodDescInternalx64(MethodDescInternal methodDesc, uint dwECallID, uint padding)
{
_methodDesc = methodDesc;
_dwECallID = dwECallID;
_padding = padding;
}
}
실제로 x64 환경에서 Monitor.Enter의 MethodDesc에 대해 덤프를 해 보면,
0:000> dd 7ffc31f3c300 L6
00007ffc`31f3c300 b100bb40 20a95404 004f0001 00000000
00007ffc`31f3c310 34c626b0 00007ffc
dwECallID와 padding을 확인할 수 있습니다.
m_dwECallID == 004f0001
m_padding == 00000000
또한, IL 메서드와 마찬가지로 FCall 메서드의 경우에도 FCallMethodDesc 구조체의 바로 다음 (x64의 경우) 8바이트가 실제 메서드의 주소입니다.
0:000> dq 00007ffc`31f3c310 L1
00007ffc`31f3c310 00007ffc`34c626b0 // 00007ffc`34c626b0 == clr!JIT_MonEnter
마지막으로, trampoline 과정을 실어 보겠습니다. 우선 Monitor.Enter의 trampoline 가로채기를 위해 아래의 원본 함수에서,
clr!JIT_MonEnter:
00007ffc`34c626b0 33d2 xor edx,edx
00007ffc`34c626b2 6666666666660f1f840000000000 nop word ptr [rax+rax]
clr!JIT_MonEnterWorker_InlineGetThread:
00007ffc`34c626c0 56 push rsi
00007ffc`34c626c1 488bf2 mov rsi,rdx
00007ffc`34c626c4 4885c9 test rcx,rcx
00007ffc`34c626c7 0f84a0010000 je clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x1a0 (00007ffc`34c6286d)
clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel:
00007ffc`34c626cd 654c8b1c2598140000 mov r11,qword ptr gs:[1498h]
00007ffc`34c626d6 448b152b09a100 mov r10d,dword ptr [clr!g_SpinConstants (00007ffc`35673008)]
00007ffc`34c626dd 418b4308 mov eax,dword ptr [r11+8]
00007ffc`34c626e1 83e05f and eax,5Fh
00007ffc`34c626e4 0f8583010000 jne clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x1a0 (00007ffc`34c6286d)
00007ffc`34c626ea 4c8d41fc lea r8,[rcx-4]
00007ffc`34c626ee 418b00 mov eax,dword ptr [r8]
00007ffc`34c626f1 a9ffff0018 test eax,1800FFFFh
00007ffc`34c626f6 7528 jne clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x53 (00007ffc`34c62720)
00007ffc`34c626f8 418b532c mov edx,dword ptr [r11+2Ch]
00007ffc`34c626fc 81faff030000 cmp edx,3FFh
00007ffc`34c62702 0f8765010000 ja clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x1a0 (00007ffc`34c6286d)
00007ffc`34c62708 0bd0 or edx,eax
00007ffc`34c6270a f0410fb110 lock cmpxchg dword ptr [r8],edx
00007ffc`34c6270f 754f jne clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x93 (00007ffc`34c62760)
00007ffc`34c62711 4183432801 add dword ptr [r11+28h],1
00007ffc`34c62716 4885f6 test rsi,rsi
00007ffc`34c62719 7403 je clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x51 (00007ffc`34c6271e)
00007ffc`34c6271b c60601 mov byte ptr [rsi],1
00007ffc`34c6271e 5e pop rsi
00007ffc`34c6271f c3 ret
(다행히 우리가 교체할 메서드 - 이 글에서는 Replaced_TestMethod - 의 위치가 clr!JIT_MonEnter 위치에서 +/- 2GB 이내의 위치여서) 처음 5바이트를 Replaced_TestMethod의 주소로 패치하고,
clr!JIT_MonEnter:
00007ffc`34c626b0 e92b5cb2a0 jmp 00007ffb`d57882e0
00007ffc`34c626b5 6666660f1f840000000000 nop word ptr [rax+rax]
clr!JIT_MonEnterWorker_InlineGetThread:
00007ffc`34c626c0 56 push rsi
00007ffc`34c626c1 488bf2 mov rsi,rdx
00007ffc`34c626c4 4885c9 test rcx,rcx
...[생략]...
패치 위치의 원본 코드를 VirualAlloc으로 할당받은 주소에 (패치 이후의 원본 함수로 JMP하는 코드까지 포함해) 보관해 두는 것으로 trampoline 처리는 완료됩니다.
00000000`00660000 33d2 xor edx,edx
00000000`00660002 6666666666660f1f840000000000 nop word ptr [rax+rax]
00000000`00660010 48b8c026c634fc7f0000 mov rax,offset clr!JIT_MonEnterWorker_InlineGetThread (00007ffc`34c626c0)
00000000`0066001a ffe0 jmp rax
00000000`0066001c 0000 add byte ptr [rax],al
가로채기가 완료된 코드를 실행하면 이후 원본 함수를 실행하기 전 Marshal.GetDelegateForFunctionPointer로 동적 생성한 s_originalMethod 메서드의 body를 지나게 되는데,
00007ffb`d577a572 57 push rdi
00007ffb`d577a573 4156 push r14
00007ffb`d577a575 4155 push r13
00007ffb`d577a577 4154 push r12
00007ffb`d577a579 57 push rdi
00007ffb`d577a57a 56 push rsi
00007ffb`d577a57b 53 push rbx
00007ffb`d577a57c 4881ecd8000000 sub rsp,0D8h
00007ffb`d577a583 c5f877 vzeroupper
00007ffb`d577a586 488dac2410010000 lea rbp,[rsp+110h]
00007ffb`d577a58e 4c899560ffffff mov qword ptr [rbp-0A0h],r10
00007ffb`d577a595 4889a510ffffff mov qword ptr [rbp-0F0h],rsp
00007ffb`d577a59c 48894d10 mov qword ptr [rbp+10h],rcx
00007ffb`d577a5a0 48895518 mov qword ptr [rbp+18h],rdx ss:00000000`00d5e898=0000000000000000
00007ffb`d577a5a4 488d8d28ffffff lea rcx,[rbp-0D8h]
00007ffb`d577a5ab 498bd2 mov rdx,r10
00007ffb`d577a5ae e88da84e5f call clr!JIT_InitPInvokeFrame (00007ffc`34c64e40)
00007ffb`d577a5b3 48894580 mov qword ptr [rbp-80h],rax
00007ffb`d577a5b7 488bcc mov rcx,rsp
00007ffb`d577a5ba 48898d48ffffff mov qword ptr [rbp-0B8h],rcx
00007ffb`d577a5c1 488bcd mov rcx,rbp
00007ffb`d577a5c4 48898d58ffffff mov qword ptr [rbp-0A8h],rcx
00007ffb`d577a5cb 488b4d80 mov rcx,qword ptr [rbp-80h]
00007ffb`d577a5cf 488d8528ffffff lea rax,[rbp-0D8h]
00007ffb`d577a5d6 48894110 mov qword ptr [rcx+10h],rax
00007ffb`d577a5da 33c9 xor ecx,ecx
00007ffb`d577a5dc 4863c9 movsxd rcx,ecx
00007ffb`d577a5df e8ac6a625f call clr!StubHelpers::DemandPermission (00007ffc`34da1090)
00007ffb`d577a5e4 33d2 xor edx,edx
00007ffb`d577a5e6 8955c4 mov dword ptr [rbp-3Ch],edx
00007ffb`d577a5e9 90 nop
00007ffb`d577a5ea 66c745a80000 mov word ptr [rbp-58h],0
00007ffb`d577a5f0 488d55a8 lea rdx,[rbp-58h]
00007ffb`d577a5f4 488b4d18 mov rcx,qword ptr [rbp+18h]
00007ffb`d577a5f8 e8c3755d5f call clr!StubHelpers::ObjectMarshaler__ConvertToNative (00007ffc`34d51bc0)
00007ffb`d577a5fd c745c401000000 mov dword ptr [rbp-3Ch],1
00007ffb`d577a604 90 nop
00007ffb`d577a605 c4e17a6f4da8 vmovdqu xmm1,xmmword ptr [rbp-58h]
00007ffb`d577a60b c4e17a7f4d90 vmovdqu xmmword ptr [rbp-70h],xmm1
00007ffb`d577a611 488b55b8 mov rdx,qword ptr [rbp-48h]
00007ffb`d577a615 488955a0 mov qword ptr [rbp-60h],rdx
00007ffb`d577a619 488d9560ffffff lea rdx,[rbp-0A0h]
00007ffb`d577a620 488b4d10 mov rcx,qword ptr [rbp+10h]
00007ffb`d577a624 e847a46d5f call clr!StubHelpers::GetDelegateTarget (00007ffc`34e54a70)
00007ffb`d577a629 48894588 mov qword ptr [rbp-78h],rax
00007ffb`d577a62d c4e17a6f4d90 vmovdqu xmm1,xmmword ptr [rbp-70h]
00007ffb`d577a633 c4e17a7f8d68ffffff vmovdqu xmmword ptr [rbp-98h],xmm1
00007ffb`d577a63c 488b4da0 mov rcx,qword ptr [rbp-60h]
00007ffb`d577a640 48898d78ffffff mov qword ptr [rbp-88h],rcx
00007ffb`d577a647 488d8d68ffffff lea rcx,[rbp-98h]
00007ffb`d577a64e 4533db xor r11d,r11d
00007ffb`d577a651 488b8560ffffff mov rax,qword ptr [rbp-0A0h]
00007ffb`d577a658 48898538ffffff mov qword ptr [rbp-0C8h],rax
00007ffb`d577a65f 488d0515000000 lea rax,[00007ffb`d577a67b]
00007ffb`d577a666 48898550ffffff mov qword ptr [rbp-0B0h],rax
00007ffb`d577a66d 488b4580 mov rax,qword ptr [rbp-80h]
00007ffb`d577a671 c6400c00 mov byte ptr [rax+0Ch],0
00007ffb`d577a675 488b4588 mov rax,qword ptr [rbp-78h]
00007ffb`d577a679 ffd0 call rax
00007ffb`d577a67b 488b5580 mov rdx,qword ptr [rbp-80h]
00007ffb`d577a67f c6420c01 mov byte ptr [rdx+0Ch],1
00007ffb`d577a683 833dbe89ef5f00 cmp dword ptr [clr!g_TrapReturningThreads (00007ffc`35673048)],0
00007ffb`d577a68a 7406 je 00007ffb`d577a692
00007ffb`d577a68c ff157695ef5f call qword ptr [clr!hlpDynamicFuncTable+0x68 (00007ffc`35673c08)]
00007ffb`d577a692 e809c64e5f call clr!StubHelpers::SetLastError (00007ffc`34c66ca0)
00007ffb`d577a697 90 nop
00007ffb`d577a698 90 nop
..[생략]...
00007ffb`d577a679 주소의 call rax는 00000000`00660000로 실행이 전달돼 원본 Monitor.Enter의 처리를 진행하게 됩니다. 또한 이전에 언급한 ObjectMarshaler__ConvertToNative로 인해 s_originalMethod.Invoke에 전달된 닷넷 객체가 마샬링 되는데,
clr!StubHelpers::ObjectMarshaler__ConvertToNative:
00007ffc`34d51bc0 488bc4 mov rax,rsp
00007ffc`34d51bc3 48895010 mov qword ptr [rax+10h],rdx
00007ffc`34d51bc7 4156 push r14
00007ffc`34d51bc9 4881ec60010000 sub rsp,160h
00007ffc`34d51bd0 48c7442458feffffff mov qword ptr [rsp+58h],0FFFFFFFFFFFFFFFEh
00007ffc`34d51bd9 48895808 mov qword ptr [rax+8],rbx
00007ffc`34d51bdd 48897018 mov qword ptr [rax+18h],rsi
00007ffc`34d51be1 48897820 mov qword ptr [rax+20h],rdi
00007ffc`34d51be5 488bda mov rbx,rdx
00007ffc`34d51be8 488d3dd1ffffff lea rdi,[clr!StubHelpers::ObjectMarshaler__ConvertToNative (00007ffc`34d51bc0)]
00007ffc`34d51bef 48897c2438 mov qword ptr [rsp+38h],rdi
00007ffc`34d51bf4 48894c2428 mov qword ptr [rsp+28h],rcx
00007ffc`34d51bf9 83a4249000000000 and dword ptr [rsp+90h],0
00007ffc`34d51c01 4889bc24a0000000 mov qword ptr [rsp+0A0h],rdi
00007ffc`34d51c09 488d05e8036e00 lea rax,[clr!HelperMethodFrame_1OBJ::`vftable' (00007ffc`35431ff8)]
00007ffc`34d51c10 4889442478 mov qword ptr [rsp+78h],rax
00007ffc`34d51c15 488d442428 lea rax,[rsp+28h]
00007ffc`34d51c1a 4889842450010000 mov qword ptr [rsp+150h],rax
00007ffc`34d51c22 488d8c24a8000000 lea rcx,[rsp+0A8h]
00007ffc`34d51c2a e8c132f1ff call clr!LazyMachStateCaptureState (00007ffc`34c64ef0)
00007ffc`34d51c2f 488d4c2478 lea rcx,[rsp+78h]
00007ffc`34d51c34 e8f732f1ff call clr!HelperMethodFrame::Push (00007ffc`34c64f30)
00007ffc`34d51c39 488b8c2498000000 mov rcx,qword ptr [rsp+98h]
00007ffc`34d51c41 33f6 xor esi,esi
00007ffc`34d51c43 4532f6 xor r14b,r14b
00007ffc`34d51c46 8a05ec139200 mov al,byte ptr [clr!g_StackProbingEnabled (00007ffc`35673038)]
00007ffc`34d51c4c 84c0 test al,al
00007ffc`34d51c4e 741a je clr!StubHelpers::ObjectMarshaler__ConvertToNative+0xaa (00007ffc`34d51c6a)
00007ffc`34d51c50 e877052400 call clr!DefaultRetailStackProbeWorker (00007ffc`34f921cc)
00007ffc`34d51c55 8a05dd139200 mov al,byte ptr [clr!g_StackProbingEnabled (00007ffc`35673038)]
00007ffc`34d51c5b 84c0 test al,al
00007ffc`34d51c5d 740b je clr!StubHelpers::ObjectMarshaler__ConvertToNative+0xaa (00007ffc`34d51c6a)
00007ffc`34d51c5f 488d4c2448 lea rcx,[rsp+48h]
00007ffc`34d51c64 e82b162400 call clr!SOIntolerantTransitionHandler::CtorImpl (00007ffc`34f93294)
00007ffc`34d51c69 90 nop
00007ffc`34d51c6a b800400000 mov eax,4000h
00007ffc`34d51c6f 488bd3 mov rdx,rbx
00007ffc`34d51c72 668503 test word ptr [rbx],ax
00007ffc`34d51c75 740c je clr!StubHelpers::ObjectMarshaler__ConvertToNative+0xc3 (00007ffc`34d51c83)
00007ffc`34d51c77 488d4c2428 lea rcx,[rsp+28h]
00007ffc`34d51c7c e83ffbf9ff call clr!OleVariant::MarshalOleRefVariantForObject (00007ffc`34cf17c0)
00007ffc`34d51c81 eb14 jmp clr!StubHelpers::ObjectMarshaler__ConvertToNative+0xd7 (00007ffc`34d51c97)
00007ffc`34d51c83 488d442428 lea rax,[rsp+28h]
00007ffc`34d51c88 4889442430 mov qword ptr [rsp+30h],rax
00007ffc`34d51c8d 488d4c2430 lea rcx,[rsp+30h]
00007ffc`34d51c92 e879000000 call clr!OleVariant::MarshalOleVariantForObject (00007ffc`34d51d10)
00007ffc`34d51c97 c644244800 mov byte ptr [rsp+48h],0 ss:00000000`0078e548=00
00007ffc`34d51c9c 803d9513920000 cmp byte ptr [clr!g_StackProbingEnabled (00007ffc`35673038)],0
00007ffc`34d51ca3 740b je clr!StubHelpers::ObjectMarshaler__ConvertToNative+0xf0 (00007ffc`34d51cb0)
00007ffc`34d51ca5 488d4c2448 lea rcx,[rsp+48h]
00007ffc`34d51caa e81d162400 call clr!SOIntolerantTransitionHandler::DtorImpl (00007ffc`34f932cc)
00007ffc`34d51caf 90 nop
00007ffc`34d51cb0 eb17 jmp clr!StubHelpers::ObjectMarshaler__ConvertToNative+0x109 (00007ffc`34d51cc9)
00007ffc`34d51cb2 488b9c2478010000 mov rbx,qword ptr [rsp+178h]
00007ffc`34d51cba 488b7c2438 mov rdi,qword ptr [rsp+38h]
00007ffc`34d51cbf 488b742440 mov rsi,qword ptr [rsp+40h]
00007ffc`34d51cc4 448a742420 mov r14b,byte ptr [rsp+20h]
00007ffc`34d51cc9 4584f6 test r14b,r14b
00007ffc`34d51ccc 7409 je clr!StubHelpers::ObjectMarshaler__ConvertToNative+0x117 (00007ffc`34d51cd7)
00007ffc`34d51cce 488bd6 mov rdx,rsi
00007ffc`34d51cd1 e862c10f00 call clr!UnwindAndContinueRethrowHelperAfterCatch (00007ffc`34e4de38)
00007ffc`34d51cd6 cc int 3
00007ffc`34d51cd7 488d4c2478 lea rcx,[rsp+78h]
00007ffc`34d51cdc e88f32f1ff call clr!HelperMethodFrame::Pop (00007ffc`34c64f70)
00007ffc`34d51ce1 488d8c24a8000000 lea rcx,[rsp+0A8h]
00007ffc`34d51ce9 e8e226f1ff call clr!HelperMethodFrameRestoreState (00007ffc`34c643d0)
00007ffc`34d51cee 85c0 test eax,eax
00007ffc`34d51cf0 0f8503ffffff jne clr!StubHelpers::ObjectMarshaler__ConvertToNative+0x39 (00007ffc`34d51bf9)
00007ffc`34d51cf6 4c8d9c2460010000 lea r11,[rsp+160h]
00007ffc`34d51cfe 498b5b10 mov rbx,qword ptr [r11+10h]
00007ffc`34d51d02 498b7320 mov rsi,qword ptr [r11+20h]
00007ffc`34d51d06 498b7b28 mov rdi,qword ptr [r11+28h]
00007ffc`34d51d0a 498be3 mov rsp,r11
00007ffc`34d51d0d 415e pop r14
00007ffc`34d51d0f c3 ret
아마도 MarshalOleRefVariantForObject 호출이 내부적으로는 Marshal.GetNativeVariantForObject 코드와 유사한 역할을 수행하는 듯합니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]