Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

(시리즈 글이 9개 있습니다.)
디버깅 기술: 74. x64 콜 스택 인자 추적과 windbg의 Child-SP, RetAddr, Args to Child 값 확인
; https://www.sysnet.pe.kr/2/0/10832

디버깅 기술: 77. windbg의 콜스택 함수 인자를 쉽게 확인하는 방법
; https://www.sysnet.pe.kr/2/0/10934

디버깅 기술: 106. windbg - x64 역어셈블 코드에서 닷넷 메서드 호출의 인자를 확인하는 방법
; https://www.sysnet.pe.kr/2/0/11348

디버깅 기술: 107. windbg - x64 SOS 확장의 !clrstack 명령어가 출력하는 Child SP 값의 의미
; https://www.sysnet.pe.kr/2/0/11349

디버깅 기술: 111. windbg - x86 메모리 덤프 분석 시 닷넷 메서드의 호출 인자 값 확인
; https://www.sysnet.pe.kr/2/0/11451

디버깅 기술: 128. windbg - x64 환경에서 닷넷 예외가 발생한 경우 인자를 확인할 수 없었던 사례
; https://www.sysnet.pe.kr/2/0/11991

디버깅 기술: 139. windbg - x64 덤프 분석 시 메서드의 인자 또는 로컬 변수의 값을 확인하는 방법
; https://www.sysnet.pe.kr/2/0/12069

디버깅 기술: 172. windbg - 파일 열기 시점에 bp를 걸어 파일명 알아내는 방법(Managed/Unmanaged)
; https://www.sysnet.pe.kr/2/0/12377

디버깅 기술: 181. windbg - 콜 스택의 "Call Site" 오프셋 값이 가리키는 위치
; https://www.sysnet.pe.kr/2/0/12750




windbg - x64 역어셈블 코드에서 닷넷 메서드 호출의 인자를 확인하는 방법

실습을 위해 다음과 같은 예제를 만듭니다.

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));

                Console.ReadLine();

                return buffer;
            }
            catch { }

            return null;
        }
    }
}

실행하면, ReadLine에서 멈추기 때문에 쉽게 windbg를 연결할 수 있습니다. 그다음, SOS 확장 로드 후 !clrstack으로 다음과 같이 호출 스택을 확인합니다.

0:000> !clrstack
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]
000000e2e58fed80 00007ffb7073062c ConsoleApp1.Program.TestIt() [E:\ConsoleApp1\Program.cs @ 26]
000000e2e58fee40 00007ffb707304c0 ConsoleApp1.Program.Main(System.String[]) [E:\ConsoleApp1\ConsoleApp1\Program.cs @ 13]
000000e2e58ff0d0 00007ffbcfd26793 [GCFrame: 000000e2e58ff0d0] 

사용자 코드는 ConsoleApp1.Program.TestIt 메서드까지이고 이후는 Console.ReadeLine 호출로 인한 BCL 메서드입니다.

자, 그럼 (TestIt 메서드를 실행 중인) 이 상태에서 내부 코드 중에 string.Format으로 전달한 인자들이 무엇인지 확인을 해보겠습니다. 이를 위해 우선 역어셈블을 합니다.

0:000> !U /d 00007ffb7073062c
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]
00007ffb`707305fc e8af71ed30      call    mscorlib_ni+0xc477b0 (00007ffb`a16077b0) (System.String.Format(System.String, System.Object, System.Object), mdToken: 000000000600052a)
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) (System.Console.ReadLine(), mdToken: 0000000006000b40)
>>> 00007ffb`7073062c 48894590        mov     qword ptr [rbp-70h],rax
00007ffb`70730630 90              nop
...[생략]...

위의 출력 결과를 보면 마지막 즈음에 >>> 표시를 통해 바로 위의 Console.ReadLine 코드가 호출 중임을 알 수 있습니다. 또한 그보다 위에 보면 System.String.Format 메서드의 호출이 보입니다. 메서드 호출이 있다면 당연히 인자에 대한 전달 코드가 그전에 나옵니다. x64 호출 규약에서는 처음 4개의 인자를 rcx, rdx, r8, r9에 전달한다는 것을 염두에 두면 System.String.Format을 호출하기 전 인자 전달 코드를 다음과 같이 추려낼 수 있습니다.

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]

꽤나 복잡한데, 결국 String.Format에 전달한 3개의 인자는 다음과 같은 영역에 보관되어 있습니다.

첫 번째 인자: rcx == [rbp - 80h]
두 번째 인자: rdx == [rbp - 88h]
세 번째 인자: r8  == [rbp - 20h]

그러니까, 여기서 관건은 rbp 레지스터의 값을 알아내야 하는 것입니다. 이 값을 알아내려면 우선 TestIt 메서드에 대한 Child SP 값을 !clrstack 결과로부터 얻어야 하는데 이 글의 처음 부분에서 이미 실행했으므로 그 내용을 다시 보면,

...[생략]...
000000e2e58fed50 00007ffba1709d97 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984]
000000e2e58fed80 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] 

아래와 같이 구할 수 있습니다. ^^

ConsoleApp1.Program.TestIt
    Child SP = 000000e2e58fed80
    IP       = 00007ffb7073062c

그다음 TestIt 메서드의 prologue 코드를 알아야 합니다. 이것 역시 위에서 이미 !U 명령어로 실행했었는데 그로부터 prologue만 보면 다음과 같습니다.

0:000> !U /d 00007ffb7073062c
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]

!clrstack이 출력하는 Child SP 값은 위의 prologue에서 sub rsp, 0a8h까지 수행된 상태의 RSP 값입니다. 따라서, RBP에 들어간 값은 Child SP에 0xb0을 더하면 구할 수 있습니다.

0:000> ? 000000e2e58fed80 + 0b0h
Evaluate expression: 974514023984 = 000000e2`e58fee30

이렇게 RBP를 구했으면 이제 게임 끝입니다. ^^ String.Format에 전달한 인자의 값이 다음과 같으므로,

RBP = 000000e2`e58fee30

rcx == [rbp - 80h]
rdx == [rbp - 88h]
r8  == [rbp - 20h]

하나씩 주솟값을 확인해 덤프하면 됩니다. 우선 세 번째 인자를 담고 있는 [rbp - 20h]부터 갑니다.

0:000> ? 000000e2`e58fee30 - 20h
Evaluate expression: 974514023952 = 000000e2`e58fee10

0:000>  dq 000000e2`e58fee10 L1
000000e2`e58fee10  00000238`a3734010

0:000> !do 00000238`a3734010 
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
String:      ConsoleApp1.Program
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  <<

보는 바와 같이 String.Format의 3번째 인자에는 "ConsoleApp1.Program" 문자열이 들어갑니다. (실제로 C# 코드에서도 그렇게 전달하고 있습니다.)

두 번째와 첫 번째 인자 역시 다음과 같이 마저 확인합니다.

0:000> ? 000000e2`e58fee30 - 88h
Evaluate expression: 974514023848 = 000000e2`e58feda8

0:000> dq 000000e2`e58feda8 L1
000000e2`e58feda8  00000238`a3734050

0:000> !do 00000238`a3734050
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         System.Int32  1 instance               19 m_value

0:000> ? 000000e2`e58fee30 - 80h
Evaluate expression: 974514023856 = 000000e2`e58fedb0

0:000> dq 000000e2`e58fedb0 L1
000000e2`e58fedb0  00000238`a3733ea8

0:000>  !do 00000238`a3733ea8
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
String:      ClassName: {1}(len={0})

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  <<




참고로, 값이 스택에만 잘 쌓여 있다면 간단하게 !dso 명령어와 함께 약간의 감을 이용한다면 일부 값은 쉽게 알아낼 수 있습니다.

0:000> !dso
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
000000E2E58FEDF0 00000238a3734010 System.String    ConsoleApp1.Program
000000E2E58FEDF8 00000238a3733ef8 System.RuntimeType
...[생략]...

상단의 System.IO.StreamReader 등은 Console.ReadLine으로 쌓인 객체이므로 조금 아래로 내려가다 보면 System.String 객체가 보일 거라는 짐작과 함께 저렇게 값을 추정해 볼 수 있습니다. 하지만 확실히 하고 싶다면 역어셈블 결과를 추적하는 것이 좋습니다.




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







[최초 등록일: ]
[최종 수정일: 10/31/2017]

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

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1755정성태9/22/201434269오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
1754정성태9/22/201424591VC++: 80. 내 컴퓨터에서 C++ AMP 코드가 실행이 될까요? [1]
1753정성태9/22/201420601오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
1752정성태9/21/201441062Windows: 100. 윈도우 8 - RDP 연결을 이용해 VNC처럼 사용자 로그온 화면을 공유하는 방법 [5]
1751정성태9/20/201438928.NET Framework: 464. 프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법 [1]파일 다운로드1
1750정성태9/20/201423827.NET Framework: 463. PInvoke 호출을 이용한 비동기 파일 작업파일 다운로드1
1749정성태9/20/201423730.NET Framework: 462. 커널 객체를 위한 null DACL 생성 방법파일 다운로드1
1748정성태9/19/201425379개발 환경 구성: 238. [Synergy] 여러 컴퓨터에서 키보드, 마우스 공유
1747정성태9/19/201428403오류 유형: 239. psexec 실행 오류 - The system cannot find the file specified.
1746정성태9/18/201426091.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기 [6]파일 다운로드1
1745정성태9/17/201423032개발 환경 구성: 237. 리눅스 Integration Services 버전 업그레이드 하는 방법 [1]
1744정성태9/17/201431043.NET Framework: 460. GetTickCount / GetTickCount64와 0x7FFE0000 주솟값 [4]파일 다운로드1
1743정성태9/16/201420983오류 유형: 238. 설치 오류 - Failed to get size of pseudo bundle
1742정성태8/27/201426955개발 환경 구성: 236. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 [2]
1741정성태8/26/201421330.NET Framework: 459. GetModuleHandleEx로 알아보는 .NET 메서드의 DLL 모듈 관계파일 다운로드1
1740정성태8/25/201432500.NET Framework: 458. 닷넷 GC가 순환 참조를 해제할 수 있을까요? [2]파일 다운로드1
1739정성태8/24/201426504.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201422042.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201419750VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201425566VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201418155.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201419810오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201426342.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201434462Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201427071개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
1730정성태8/11/201422154개발 환경 구성: 234. Royal TS의 터미널(Terminal) 연결에서 한글이 깨지는 현상 해결 방법
... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...