Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 77. windbg의 콜스택 함수 인자를 쉽게 확인하는 방법 [링크 복사], [링크+제목 복사],
조회: 25493
글쓴 사람
정성태 (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의 콜스택 함수 인자를 쉽게 확인하는 방법

재미있는 일이 발생했습니다. 독립 실행형 COM+ 응용 프로그램의 메서드를 호출했더니 호출 측의 스레드가 멈춰버린 것입니다. 도대체 스레드가 왜 멈춰버린 것일까요? 이를 확인하기 위해 windbg로 dllhost.exe에 연결해 콜 스택을 확인했습니다.

0:012> ~*kv

...[생략]...

   1  Id: 339c.2874 Suspend: 1 Teb: 7ffdc000 Unfrozen
ChildEBP RetAddr  Args to Child              
009f1dc4 7c827519 773d7a4b 50000018 00000004 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
009f1dc8 773d7a4b 50000018 00000004 00000003 ntdll!ZwRaiseHardError+0xc (FPO: [6,0,0])
009f1e24 773b8377 009f2758 1018e1d8 00012012 USER32!ServiceMessageBox+0x145 (FPO: [Non-Fpo])
009f1f80 7739eec9 009f1f8c 00000028 00000000 USER32!MessageBoxWorker+0x13e (FPO: [Non-Fpo])
009f1fd8 7739ee65 00000000 009f2758 1018e1d8 USER32!MessageBoxTimeoutW+0x7a (FPO: [Non-Fpo])
009f1ff8 7739ee41 00000000 009f2758 1018e1d8 USER32!MessageBoxExW+0x1b (FPO: [Non-Fpo])
009f2014 100fcf2c 00000000 009f2758 1018e1d8 USER32!MessageBoxW+0x45 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
...[생략]...
009fdf7c 791b77cd 00fd7390 009fe010 00000000 mscorsvr!MakeJitWorker+0x21e (FPO: [Non-Fpo])
...[생략]...
009fffec 00000000 77c7b023 000a1560 00000000 kernel32!BaseThreadStart+0x34 (FPO: [Non-Fpo])

...[생략]...

# 12  Id: 339c.25b4 Suspend: 1 Teb: 7ffdd000 Unfrozen
ChildEBP RetAddr  Args to Child              
006bffc8 7c83fb38 00000005 00000004 00000001 ntdll!DbgBreakPoint (FPO: [0,0,0])
006bfff4 00000000 00000000 00000000 00000000 ntdll!DbgUiRemoteBreakin+0x36 (FPO: [Non-Fpo])

세상에... COM+ 메서드에서 메시지 박스가 수행된 것입니다. COM+ 응용 프로그램을 호스팅하는 dllhost.exe는 서비스 사용자 권한(LOCAL SERVICE)으로 실행중이기 때문에 세션이 달라 사용자의 interactive 데스크톱이 아닌, 그저 보여지지만 않을 뿐 저 세션 너머에서 띄워져 스레드를 blocking하고 있는 것입니다.

궁금한 것은 도대체 저 MessageBox에 전달된 인자가 뭐냐는 것이죠?!!




실력있는 분들은 금방 아실 수 있지만... 이런 경우 windbg 초보인 분들도 쉽게 해결할 수 있는 팁을 전달해 드릴까합니다. 방법은 간단한데, 단순한 유형의 콘솔 프로그램을 만들고 동일한 상황을 재현하면 됩니다. 가령 MessageBox라면 다음과 같이 C/C++ 코드로 만들어 줄 수 있습니다.

#include "stdafx.h"
#include <Windows.h>

int main()
{
    ::MessageBox(NULL, L"TEST_Text", L"Test_Caption", MB_OK);
    return 0;
}

이 프로그램을 실행하고 windbg로 붙여 역시 같은 방법으로 콜 스택을 확인합니다.

0:004> ~*kv

   0  Id: 20b4.4da0 Suspend: 1 Teb: 003bc000 Unfrozen
 # ChildEBP RetAddr  Args to Child              
00 0019f628 746299d9 00000000 00000000 00000000 USER32!NtUserWaitMessage+0xc (FPO: [0,0,0])
01 0019f670 74629804 00000000 00000000 00000000 USER32!DialogBox2+0x13c (FPO: [Non-Fpo])
02 0019f6a0 74681666 00000000 7467f510 0019f8e8 USER32!InternalDialogBox+0x104 (FPO: [Non-Fpo])
03 0019f76c 7468053a 0019f8e8 008d6b50 00000000 USER32!SoftModalMessageBox+0xea6 (FPO: [1,41,4])
04 0019f8d0 7468025c 0019fa70 0019f9a4 003b9000 USER32!MessageBoxWorker+0x299 (FPO: [Non-Fpo])
05 0019f950 7467ffcb 00000000 008d6b50 008d6b30 USER32!MessageBoxTimeoutW+0x6c (FPO: [6,27,4])
06 0019f970 74680298 00000000 008d6b50 008d6b30 USER32!MessageBoxExW+0x1b (FPO: [Non-Fpo])
*** WARNING: Unable to verify checksum for C:\...\Debug\msgboxShow.exe
07 0019f98c 008d16d4 00000000 008d6b50 008d6b30 USER32!MessageBoxW+0x18 (FPO: [Non-Fpo])
08 0019fa70 008d1dde 00000001 00593980 00599640 msgboxShow!main+0x34 (FPO: [Non-Fpo]) (CONV: cdecl) [c:\...\msgboxshow.cpp @ 9]
09 0019fa84 008d1c2a def28cab 008d1046 008d1046 msgboxShow!invoke_main+0x1e (FPO: [Non-Fpo]) (CONV: cdecl) [f:\...\exe_common.inl @ 64]
0a 0019fadc 008d1abd 0019faec 008d1df8 0019fb00 msgboxShow!__scrt_common_main_seh+0x15a (FPO: [Non-Fpo]) (CONV: cdecl) [f:\...\src\startup\exe_common.inl @ 255]
0b 0019fae4 008d1df8 0019fb00 763f38f4 003b9000 msgboxShow!__scrt_common_main+0xd (FPO: [Non-Fpo]) (CONV: cdecl) [f:\...\src\startup\exe_common.inl @ 300]
0c 0019faec 763f38f4 003b9000 763f38d0 03cefbb7 msgboxShow!mainCRTStartup+0x8 (FPO: [Non-Fpo]) (CONV: cdecl) [f:\...\startup\exe_main.cpp @ 17]
0d 0019fb00 77835de3 003b9000 bda21586 00000000 KERNEL32!BaseThreadInitThunk+0x24 (FPO: [Non-Fpo])
0e 0019fb48 77835dae ffffffff 7785b7c6 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
0f 0019fb58 00000000 008d1046 003b9000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

...[생략]...

#  4  Id: 20b4.6e98 Suspend: 1 Teb: 003c8000 Unfrozen
 # ChildEBP RetAddr  Args to Child              
00 0235fa9c 77879d59 bf8e1402 77879d20 77879d20 ntdll!DbgBreakPoint (FPO: [0,0,0])
01 0235facc 763f38f4 00000000 763f38d0 01e2fa57 ntdll!DbgUiRemoteBreakin+0x39 (FPO: [Non-Fpo])
02 0235fae0 77835de3 00000000 bf8e15e6 00000000 KERNEL32!BaseThreadInitThunk+0x24 (FPO: [Non-Fpo])
03 0235fb28 77835dae ffffffff 7785b7c6 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
04 0235fb38 00000000 77879d20 00000000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

7번 프레임이 사용자 코드에서 호출한 MessageBox에 해당한다는 것을 알 수 있습니다. 이것의 출력 결과는 다음과 같이 해석할 수 있습니다.

07 0019f98c 008d16d4 00000000 008d6b50 008d6b30 USER32!MessageBoxW+0x18 (FPO: [Non-Fpo])

ChildEBP = 0x0019f98c
RetAddr = 0x08d16d4
Args To Child = 00000000 008d6b50 008d6b30

간단하게는 "Args To Child"에 보여지는 값을 조사해야 할 시작점으로 잡으면 됩니다. 메시지 박스의 경우 전달된 문자열을 알아낼 것이기 때문에 "du" 명령어를 이용해 다음과 같이 확인해 볼 수 있습니다.

0:004> du 008d6b50
008d6b50  "TEST_Text"

0:004> du 008d6b30
008d6b30  "Test_Caption"

참 쉽죠~~~ ^^

인자가 많아 3개 이상이 되어 "Args To Child" 목록에 나오지 않아도 상관없습니다. ChildEBP 지점의 스택 메모리를 조사해 "Args To Child"에 나오는 인자 목록을 찾으면 그 근방의 값들이 나머지 인자에 해당하기 때문에 원하는 인자의 값이 어느 위치에 있는지 찾아낼 수 있습니다. 만약 이런 식의 간단한 프로그램을 이용하고 싶지 않다면 콜 스택을 추적하는 기술을 익히시면 됩니다. 가령 x64의 경우라면 다음과 같은 식으로 추적할 수 있습니다.

x64 콜스택 인자 추적과 windbg의 Child-SP, RetAddr, Args to Child 값 확인
; https://www.sysnet.pe.kr/2/0/10832

저 과정이 싫은 분들은 그냥 이 글대로 하시면 됩니다. ^^




간단한 프로그램을 이용해 특정 인자가 어디 있는지 확인을 했으면, 이제 문제가 되는 프로그램으로 다시 돌아가 보겠습니다. COM+를 호스팅하고 있는 dllhost.exe를 windbg로 연결해 구한 호출 스택을 다시 보면 이제는 제법 친숙한 느낌이 들 것입니다. ^^

0:012> ~*kv

...[생략]...

   1  Id: 339c.2874 Suspend: 1 Teb: 7ffdc000 Unfrozen
ChildEBP RetAddr  Args to Child              
009f1dc4 7c827519 773d7a4b 50000018 00000004 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
009f1dc8 773d7a4b 50000018 00000004 00000003 ntdll!ZwRaiseHardError+0xc (FPO: [6,0,0])
009f1e24 773b8377 009f2758 1018e1d8 00012012 USER32!ServiceMessageBox+0x145 (FPO: [Non-Fpo])
009f1f80 7739eec9 009f1f8c 00000028 00000000 USER32!MessageBoxWorker+0x13e (FPO: [Non-Fpo])
009f1fd8 7739ee65 00000000 009f2758 1018e1d8 USER32!MessageBoxTimeoutW+0x7a (FPO: [Non-Fpo])
009f1ff8 7739ee41 00000000 009f2758 1018e1d8 USER32!MessageBoxExW+0x1b (FPO: [Non-Fpo])
009f2014 100fcf2c 00000000 009f2758 1018e1d8 USER32!MessageBoxW+0x45 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
...[생략]...
009fdf7c 791b77cd 00fd7390 009fe010 00000000 mscorsvr!MakeJitWorker+0x21e (FPO: [Non-Fpo])
...[생략]...
009fffec 00000000 77c7b023 000a1560 00000000 kernel32!BaseThreadStart+0x34 (FPO: [Non-Fpo])

...[생략]...

# 12  Id: 339c.25b4 Suspend: 1 Teb: 7ffdd000 Unfrozen
ChildEBP RetAddr  Args to Child              
006bffc8 7c83fb38 00000005 00000004 00000001 ntdll!DbgBreakPoint (FPO: [0,0,0])
006bfff4 00000000 00000000 00000000 00000000 ntdll!DbgUiRemoteBreakin+0x36 (FPO: [Non-Fpo])

이전에 해봤기 때문에 "Args To Child"의 두 번째 항목이 MessageBox의 Text 인자임을 알고 있으므로 다음과 같이 자신있게 ^^ 내용을 확인해 볼 수 있습니다.

0:012> du 009f2758 
009f2758  "Assertion failed!..Program: d:\."
009f2798  "................................"
009f27d8  "............[생략].............."
009f2818  "................................"
009f2858  ".test.cpp.Line: 660..Expression: ""
009f2898  "............[생략].............."
009f28d8  ".."..For information on how your"
009f2918  " program can cause an assertion."
009f2958  "failure, see the Visual C++ docu"
009f2998  "mentation on asserts..(Press Ret"
009f29d8  "ry to debug the application - JI"
009f2a18  "T must be enabled)"

그렇습니다. assert 코드로 인해 메시지 박스가 뜬 것이었고 친절하게 코드의 파일과 라인 번호까지 알려주고 있습니다. ^^

(사실, 예제 상황이 현실적이지가 않습니다. 그냥 간단한 재현과정없이 무조건 "Args To Child"의 인자 목록을 확인해도 될 사례이니까요~~~!)

참고로, 이 글의 문제 상황 같은 경우 현재 로그인 사용자 세션 너머의 데스크톱 화면을 보고 싶다면 다음과 같은 방법을 이용해도 됩니다.

윈도우 8 - UI가 있는 프로그램을 Local SYSTEM 권한의 세션 0 데스크톱에서 실행하는 방법
; https://www.sysnet.pe.kr/2/0/1584




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







[최초 등록일: ]
[최종 수정일: 6/26/2021]

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

비밀번호

댓글 작성자
 



2021-10-29 11시20분
[이승은] 좋은 내용 감사합니다!:)
[guest]

... 121  122  123  124  125  126  [127]  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
2880정성태3/7/201521683개발 환경 구성: 260. C# Code Coverage 도구 - Semantic Designs 소개
2879정성태3/3/201526698개발 환경 구성: 259. Visual Studio 없이 Visual C++ 컴파일하는 방법
2878정성태2/28/201527611.NET Framework: 503. == 연산자보다는 Equals 메서드의 호출이 더 권장됩니다. [3]파일 다운로드1
2877정성태2/28/201521759.NET Framework: 502. 연산자 재정의(operator overloading)와 메서드 재정의(method overriding)의 다른 점 - 가상 함수 호출 여부 [3]파일 다운로드1
2876정성태2/27/201524219VS.NET IDE: 98. IntegraStudio - Visual Studio에서 Java 프로그램 개발
2875정성태2/26/201522848디버깅 기술: 72. Visual Studio 2013에서의 sos.dll 사용 제한
2874정성태2/26/201519587디버깅 기술: 71. windbg + 닷넷 디버깅 (2) - null 체크 패턴
2873정성태2/25/201537126.NET Framework: 501. FtpWebRequest 타입을 이용해 FTP 파일 업로드 [4]파일 다운로드1
2872정성태2/25/201521260디버깅 기술: 70. windbg + 닷넷 디버깅 (1) - 배열 인덱스 사용 패턴
2871정성태2/24/201525225개발 환경 구성: 258. 윈도우 8.1에서 방화벽과 함께 FTP 서버 여는 (하지만, 권장하지 않는) 방법 [1]
2870정성태2/24/201526364개발 환경 구성: 257. 윈도우 8.1에서 방화벽과 함께 FTP 서버 여는 방법
2869정성태2/23/201520320.NET Framework: 500. struct로 정의한 값 형식(Value Type)의 경우 Equals 재정의를 권장합니다.파일 다운로드1
2868정성태2/23/201524834VS.NET IDE: 97. Visual C++ 프로젝트 디버깅 시에 Step-Into(F11) 동작이 원치 않는 함수로 진입하는 것을 막는 방법 [2]
2867정성태2/23/201518555오류 유형: 273. File History - Failed to initiate user data backup (error 80070005)
2866정성태2/23/201520379오류 유형: 272. WAT080 : Failed to locate the Windows Azure SDK. Please make sure the Windows Azure SDK v2.1 is installed.
1868정성태2/20/201517682오류 유형: 271. The type '...' cannot be used as type parameter 'TContext' in the generic type or method 'System.ServiceModel.DomainServices.EntityFramework.LinqToEntitiesDomainService&lt;T&gt;
1866정성태2/20/201518616오류 유형: 270. "aspnet_regiis -i" 실행 시 0x00000006 오류 해결 방법
1865정성태2/20/201519922.NET Framework: 499. 특정 닷넷 프레임워크 버전 이후부터 제공되는 타입을 사용해야 한다면?
1864정성태2/18/201524873.NET Framework: 498. C#으로 간단하게 만들어 본 ASCII Art 프로그램 [2]파일 다운로드1
1862정성태2/18/201528726.NET Framework: 497. .NET Garbage Collection에 대한 정리 [6]
1861정성태2/18/201524073.NET Framework: 496. 마우스 커서가 놓인 지점의 문자열 얻는 방법 [1]파일 다운로드1
1860정성태2/18/201523901.NET Framework: 495. CorElementType의 요소 값 설명파일 다운로드1
1859정성태2/17/201524309Windows: 106. 컴퓨터를 재부팅하면 절전(Power Saver) 전원 모드로 돌아가는 경우
1858정성태2/16/201534268Windows: 105. 자동으로 로그아웃/잠김 화면 상태로 전환된다면? [2]
1857정성태2/16/201522289.NET Framework: 494. 값(struct) 형식의 제네릭(Generic) 타입이 박싱되는 경우의 메타데이터 토큰 값파일 다운로드1
1856정성태2/15/201521267.NET Framework: 493. TypeRef 메타테이블에 등록되는 타입의 조건파일 다운로드1
... 121  122  123  124  125  126  [127]  128  129  130  131  132  133  134  135  ...