성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>windbg - x64 역어셈블 코드에서 닷넷 메서드 호출의 인자를 확인하는 방법</h1> <p> 실습을 위해 다음과 같은 예제를 만듭니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Text; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Console.WriteLine(Encoding.UTF8.GetString(TestIt())); } private static byte[] TestIt() { try { string str = typeof(Program).ToString(); byte[] buffer = Encoding.UTF8.GetBytes(string.Format("ClassName: {1}(len={0})", str.Length, str)); <span style='color: blue; font-weight: bold'>Console.ReadLine();</span> return buffer; } catch { } return null; } } } </pre> <br /> 실행하면, ReadLine에서 멈추기 때문에 쉽게 windbg를 연결할 수 있습니다. 그다음, SOS 확장 로드 후 !clrstack으로 다음과 같이 호출 스택을 확인합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!clrstack</span> OS Thread Id: 0x84b4 (0) Child SP IP Call Site 000000e2e58fea98 00007ffbe0255464 [InlinedCallFrame: 000000e2e58fea98] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 000000e2e58fea98 00007ffba0f47786 [InlinedCallFrame: 000000e2e58fea98] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 000000e2e58fea60 00007ffba0f47786 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 000000e2e58feb40 00007ffba177554a System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205] 000000e2e58febd0 00007ffba1775456 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134] 000000e2e58fec30 00007ffba0ed242c System.IO.StreamReader.ReadBuffer() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595] 000000e2e58fec80 00007ffba0ed27f3 System.IO.StreamReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748] 000000e2e58fecf0 00007ffba193bd7e System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363] 000000e2e58fed50 00007ffba1709d97 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984] <span style='color: blue; font-weight: bold'>000000e2e58fed80 00007ffb7073062c ConsoleApp1.Program.TestIt() [E:\ConsoleApp1\Program.cs @ 26]</span> 000000e2e58fee40 00007ffb707304c0 ConsoleApp1.Program.Main(System.String[]) [E:\ConsoleApp1\ConsoleApp1\Program.cs @ 13] 000000e2e58ff0d0 00007ffbcfd26793 [GCFrame: 000000e2e58ff0d0] </pre> <br /> 사용자 코드는 ConsoleApp1.Program.TestIt 메서드까지이고 이후는 Console.ReadeLine 호출로 인한 BCL 메서드입니다.<br /> <br /> 자, 그럼 (TestIt 메서드를 실행 중인) 이 상태에서 내부 코드 중에 string.Format으로 전달한 인자들이 무엇인지 확인을 해보겠습니다. 이를 위해 우선 역어셈블을 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!U /d 00007ffb7073062c</span> Normal JIT generated code ConsoleApp1.Program.TestIt() Begin 00007ffb70730520, size 15b E:\ConsoleApp1\ConsoleApp1\Program.cs @ 19: 00007ffb`70730520 55 push rbp 00007ffb`70730521 57 push rdi 00007ffb`70730522 4881eca8000000 sub rsp,0A8h 00007ffb`70730529 488dac24b0000000 lea rbp,[rsp+0B0h] 00007ffb`70730531 488dbd78ffffff lea rdi,[rbp-88h] 00007ffb`70730538 b920000000 mov ecx,20h 00007ffb`7073053d 33c0 xor eax,eax 00007ffb`7073053f f3ab rep stos dword ptr [rdi] 00007ffb`70730541 4889a570ffffff mov qword ptr [rbp-90h],rsp 00007ffb`70730548 833d3950efff00 cmp dword ptr [00007ffb`70625588],0 00007ffb`7073054f 7405 je 00007ffb`70730556 00007ffb`70730551 e80abfa85f call clr!JIT_DbgIsJustMyCode (00007ffb`d01bc460) 00007ffb`70730556 90 nop ...[생략]... 00007ffb`707305b5 e856e6675f call clr!COMString::Length (00007ffb`cfdaec10) 00007ffb`707305ba 8945ec mov dword ptr [rbp-14h],eax 00007ffb`707305bd 48b988920ca1fb7f0000 mov rcx,offset mscorlib_ni+0x709288 (00007ffb`a10c9288) (MT: System.Int32) 00007ffb`707305c7 e8441f5f5f call clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`cfd22510) 00007ffb`707305cc 488945a8 mov qword ptr [rbp-58h],rax 00007ffb`707305d0 488b4db0 mov rcx,qword ptr [rbp-50h] 00007ffb`707305d4 48894d80 mov qword ptr [rbp-80h],rcx 00007ffb`707305d8 488b4da8 mov rcx,qword ptr [rbp-58h] 00007ffb`707305dc 8b55ec mov edx,dword ptr [rbp-14h] 00007ffb`707305df 895108 mov dword ptr [rcx+8],edx 00007ffb`707305e2 488b4da8 mov rcx,qword ptr [rbp-58h] 00007ffb`707305e6 48898d78ffffff mov qword ptr [rbp-88h],rcx 00007ffb`707305ed 488b4d80 mov rcx,qword ptr [rbp-80h] 00007ffb`707305f1 488b9578ffffff mov rdx,qword ptr [rbp-88h] 00007ffb`707305f8 4c8b45e0 mov r8,qword ptr [rbp-20h] <span style='color: blue; font-weight: bold'>00007ffb`707305fc e8af71ed30 call mscorlib_ni+0xc477b0 (00007ffb`a16077b0) (System.String.Format(System.String, System.Object, System.Object), mdToken: 000000000600052a)</span> 00007ffb`70730601 488945a0 mov qword ptr [rbp-60h],rax 00007ffb`70730605 488b4db8 mov rcx,qword ptr [rbp-48h] 00007ffb`70730609 488b55a0 mov rdx,qword ptr [rbp-60h] 00007ffb`7073060d 488b45b8 mov rax,qword ptr [rbp-48h] 00007ffb`70730611 488b00 mov rax,qword ptr [rax] 00007ffb`70730614 488b4058 mov rax,qword ptr [rax+58h] 00007ffb`70730618 ff5008 call qword ptr [rax+8] 00007ffb`7073061b 48894598 mov qword ptr [rbp-68h],rax 00007ffb`7073061f 488b4598 mov rax,qword ptr [rbp-68h] 00007ffb`70730623 488945d8 mov qword ptr [rbp-28h],rax E:\ConsoleApp1\ConsoleApp1\Program.cs @ 26: 00007ffb`70730627 e85497fd30 call mscorlib_ni+0xd49d80 (00007ffb`a1709d80) (<span style='color: blue; font-weight: bold'>System.Console.ReadLine()</span>, mdToken: 0000000006000b40) <span style='color: blue; font-weight: bold'>>>> 00007ffb`7073062c 48894590 mov qword ptr [rbp-70h],rax</span> 00007ffb`70730630 90 nop ...[생략]... </pre> <br /> 위의 출력 결과를 보면 마지막 즈음에 >>> 표시를 통해 바로 위의 Console.ReadLine 코드가 호출 중임을 알 수 있습니다. 또한 그보다 위에 보면 System.String.Format 메서드의 호출이 보입니다. 메서드 호출이 있다면 당연히 인자에 대한 전달 코드가 그전에 나옵니다. x64 호출 규약에서는 처음 4개의 인자를 rcx, rdx, r8, r9에 전달한다는 것을 염두에 두면 System.String.Format을 호출하기 전 인자 전달 코드를 다음과 같이 추려낼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 00007ffb`707305cc 488945a8 mov qword ptr [rbp-58h],rax 00007ffb`707305d0 488b4db0 mov rcx,qword ptr [rbp-50h] 00007ffb`707305d4 48894d80 mov qword ptr [rbp-80h],rcx 00007ffb`707305d8 488b4da8 mov rcx,qword ptr [rbp-58h] 00007ffb`707305dc 8b55ec mov edx,dword ptr [rbp-14h] 00007ffb`707305df 895108 mov dword ptr [rcx+8],edx 00007ffb`707305e2 488b4da8 mov rcx,qword ptr [rbp-58h] 00007ffb`707305e6 48898d78ffffff mov qword ptr [rbp-88h],rcx 00007ffb`707305ed 488b4d80 mov <span style='color: blue; font-weight: bold'>rcx,qword ptr [rbp-80h]</span> 00007ffb`707305f1 488b9578ffffff mov <span style='color: blue; font-weight: bold'>rdx,qword ptr [rbp-88h]</span> 00007ffb`707305f8 4c8b45e0 mov <span style='color: blue; font-weight: bold'>r8,qword ptr [rbp-20h]</span> </pre> <br /> 꽤나 복잡한데, 결국 String.Format에 전달한 3개의 인자는 다음과 같은 영역에 보관되어 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 첫 번째 인자: rcx == [rbp - 80h] 두 번째 인자: rdx == [rbp - 88h] 세 번째 인자: r8 == [rbp - 20h] </pre> <br /> 그러니까, 여기서 관건은 rbp 레지스터의 값을 알아내야 하는 것입니다. 이 값을 알아내려면 우선 TestIt 메서드에 대한 Child SP 값을 !clrstack 결과로부터 얻어야 하는데 이 글의 처음 부분에서 이미 실행했으므로 그 내용을 다시 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[생략]... 000000e2e58fed50 00007ffba1709d97 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984] <span style='color: blue; font-weight: bold'>000000e2e58fed80</span> 00007ffb7073062c ConsoleApp1.Program.TestIt() [E:\ConsoleApp1\ConsoleApp1\Program.cs @ 26] 000000e2e58fee40 00007ffb707304c0 ConsoleApp1.Program.Main(System.String[]) [E:\ConsoleApp1\ConsoleApp1\Program.cs @ 13] 000000e2e58ff0d0 00007ffbcfd26793 [GCFrame: 000000e2e58ff0d0] </pre> <br /> 아래와 같이 구할 수 있습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ConsoleApp1.Program.TestIt Child SP = 000000e2e58fed80 IP = 00007ffb7073062c </pre> <br /> 그다음 TestIt 메서드의 prologue 코드를 알아야 합니다. 이것 역시 위에서 이미 !U 명령어로 실행했었는데 그로부터 prologue만 보면 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!U /d 00007ffb7073062c</span> Normal JIT generated code ConsoleApp1.Program.TestIt() Begin 00007ffb70730520, size 15b E:\ConsoleApp1\ConsoleApp1\Program.cs @ 19: 00007ffb`70730520 55 push rbp 00007ffb`70730521 57 push rdi 00007ffb`70730522 4881eca8000000 sub rsp,0A8h <span style='color: blue; font-weight: bold'>00007ffb`70730529 488dac24b0000000 lea rbp,[rsp+0B0h]</span> </pre> <br /> !clrstack이 출력하는 Child SP 값은 위의 prologue에서 sub rsp, 0a8h까지 수행된 상태의 RSP 값입니다. 따라서, RBP에 들어간 값은 Child SP에 0xb0을 더하면 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>? 000000e2e58fed80 + 0b0h</span> Evaluate expression: 974514023984 = <span style='color: blue; font-weight: bold'>000000e2`e58fee30</span> </pre> <br /> 이렇게 RBP를 구했으면 이제 게임 끝입니다. ^^ String.Format에 전달한 인자의 값이 다음과 같으므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > RBP = 000000e2`e58fee30 rcx == [rbp - 80h] rdx == [rbp - 88h] r8 == [rbp - 20h] </pre> <br /> 하나씩 주솟값을 확인해 덤프하면 됩니다. 우선 세 번째 인자를 담고 있는 [rbp - 20h]부터 갑니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>? 000000e2`e58fee30 - 20h</span> Evaluate expression: 974514023952 = <span style='color: blue; font-weight: bold'>000000e2`e58fee10</span> 0:000> <span style='color: blue; font-weight: bold'>dq 000000e2`e58fee10 L1</span> 000000e2`e58fee10 <span style='color: blue; font-weight: bold'>00000238`a3734010</span> 0:000> <span style='color: blue; font-weight: bold'>!do 00000238`a3734010</span> Name: System.String MethodTable: 00007ffba10c6948 EEClass: 00007ffba09c50e0 Size: 64(0x40) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll <span style='color: blue; font-weight: bold'>String: ConsoleApp1.Program</span> Fields: MT Field Offset Type VT Attr Value Name 00007ffba10c9288 400026f 8 System.Int32 1 instance 19 m_stringLength 00007ffba10c7b00 4000270 c System.Char 1 instance 43 m_firstChar 00007ffba10c6948 4000274 90 System.String 0 shared static Empty >> Domain:Value 00000238a1b738a0:NotInit << </pre> <br /> 보는 바와 같이 String.Format의 3번째 인자에는 "ConsoleApp1.Program" 문자열이 들어갑니다. (실제로 C# 코드에서도 그렇게 전달하고 있습니다.)<br /> <br /> 두 번째와 첫 번째 인자 역시 다음과 같이 마저 확인합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> ? <span style='color: blue; font-weight: bold'>000000e2`e58fee30 - 88h</span> Evaluate expression: 974514023848 = <span style='color: blue; font-weight: bold'>000000e2`e58feda8</span> 0:000> <span style='color: blue; font-weight: bold'>dq 000000e2`e58feda8 L1</span> 000000e2`e58feda8 <span style='color: blue; font-weight: bold'>00000238`a3734050</span> 0:000> <span style='color: blue; font-weight: bold'>!do 00000238`a3734050</span> Name: System.Int32 MethodTable: 00007ffba10c9288 EEClass: 00007ffba0a8b358 Size: 24(0x18) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields: MT Field Offset Type VT Attr Value Name 00007ffba10c9288 400058b 8 <span style='color: blue; font-weight: bold'>System.Int32</span> 1 instance <span style='color: blue; font-weight: bold'>19</span> m_value </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>? 000000e2`e58fee30 - 80h</span> Evaluate expression: 974514023856 = <span style='color: blue; font-weight: bold'>000000e2`e58fedb0</span> 0:000> <span style='color: blue; font-weight: bold'>dq 000000e2`e58fedb0 L1</span> 000000e2`e58fedb0 <span style='color: blue; font-weight: bold'>00000238`a3733ea8</span> 0:000> <span style='color: blue; font-weight: bold'>!do 00000238`a3733ea8</span> Name: System.String MethodTable: 00007ffba10c6948 EEClass: 00007ffba09c50e0 Size: 80(0x50) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll <span style='color: blue; font-weight: bold'>String: ClassName: {1}(len={0})</span> Fields: MT Field Offset Type VT Attr Value Name 00007ffba10c9288 400026f 8 System.Int32 1 instance 23 m_stringLength 00007ffba10c7b00 4000270 c System.Char 1 instance 43 m_firstChar 00007ffba10c6948 4000274 90 System.String 0 shared static Empty >> Domain:Value 00000238a1b738a0:NotInit << </pre> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, 값이 스택에만 잘 쌓여 있다면 간단하게 !dso 명령어와 함께 약간의 감을 이용한다면 일부 값은 쉽게 알아낼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!dso</span> OS Thread Id: 0x84b4 (0) RSP/REG Object Name ...[생략]... 000000E2E58FEC98 00000238a3737300 System.IO.StreamReader 000000E2E58FECA0 00000238a3736c30 System.Text.DBCSCodePageEncoding 000000E2E58FECC0 00000238a3737740 System.IO.TextReader+SyncTextReader 000000E2E58FED20 00000238a3737740 System.IO.TextReader+SyncTextReader ...[생략]... 000000E2E58FEDE8 00000238a3733e18 System.Text.UTF8Encoding <span style='color: blue; font-weight: bold'>000000E2E58FEDF0 00000238a3734010 System.String ConsoleApp1.Program</span> 000000E2E58FEDF8 00000238a3733ef8 System.RuntimeType ...[생략]... </pre> <br /> 상단의 System.IO.StreamReader 등은 Console.ReadLine으로 쌓인 객체이므로 조금 아래로 내려가다 보면 System.String 객체가 보일 거라는 짐작과 함께 저렇게 값을 추정해 볼 수 있습니다. 하지만 확실히 하고 싶다면 역어셈블 결과를 추적하는 것이 좋습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
9747
(왼쪽의 숫자를 입력해야 합니다.)