Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 11. (Managed) Main Method에 Break Point 걸기 [링크 복사], [링크+제목 복사],
조회: 24812
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 8개 있습니다.)


(Managed) Main Method에 Break Point 걸기


[실습 준비]
1. "Debugging Tools for Windows" 설치.
2. 심벌 환경 설정: 커널 구조체 살펴보기 토픽을 참조. 
그냥 간단하게 다음과 같은 환경변수를 추가해 주셔도 됩니다.

환경변수 이름: _NT_SYMBOL_PATH
환경변수 값  : SRV*c:\Symbol\OSSymbols*http://msdl.microsoft.com/download/symbols;C:\Windows\system32;.




.NET의 JIT 컴파일러는 IL-code를 Natvie-code로 변경하는 작업을, 해당 메서드가 실행되기 바로 직전에 수행합니다. 즉, "클래스" 단위가 아닌 "메서드" 단위로 JIT 컴파일링이 된다는 것인데요. 이에 관해서는 다음의 토픽에서 잘 설명해 주고 있습니다.

Method Calls: Part 1 (Normal Call) 
; http://codebetter.com/blogs/gregyoung/archive/2006/07/20/147512.aspx

위의 토픽에서는 VS.NET 2005 환경에서 SOS.dll만을 가지고 메서드가 JIT 컴파일 되기 전/후의 상황을 Method Description 테이블을 통해서 직접 확인하는 방법을 알려주고 있습니다. 물론, 이것이 현실적으로 직접적인 도움이 되는 지식은 아니지만, 이렇게 눈으로 확인해 봄으로써 .NET에 대한 이해의 깊이를 더해줄 수 있기 때문에, ^^ 시간되시는 분은 따라해 보시기 바랍니다.

결국, 기계가 실행할 수 있는 것은 "Managed"가 아닌 "Unmanaged" 코드임을 알 수 있는데요. 사실, ".NET 어셈블리" 자체도 "Unmanaged 스타트업" 코드가 초기에 실행되어, mscoree.dll에서 제공해 주는 "CLR 호스팅 API"를 사용해서 내부에서 JIT 컴파일러를 통해 적절하게 "Unmanaged" 코드로 변경시켜 주면서 실행해 주는 구조일뿐입니다.

개인적으로, 바로 이 부분에서 의문이 생기더군요. (사실 이것도 그다지 밥벌이하고는 상관없겠지만!) 그렇다면 "Main" 함수에는 어떻게 접근할 수 있을까? 라는 것이었습니다. 이미 .NET 환경이 초기화된 이후에는 sos.dll을 사용해서 적절하게 처리를 할 수 있는 반면 Main 함수 자체는 최초의 실행 코드이기 때문에 그 부분에 BP를 거는 것이 그다지 쉽지는 않을 거란 생각이 들었습니다.

그래도 ^^ 한번 해봐야 겠지요. 그래서 자료를 먼저 찾아보았습니다. 대략 다음과 같은 주옥같은 자료들이 눈에 띄였습니다.

NTSD and SOS basics
; https://docs.microsoft.com/en-us/archive/blogs/thottams/ntsd-and-sos-basics

COM Interop 소개
; https://docs.microsoft.com/en-us/archive/msdn-magazine/2007/january/clr-inside-out-introduction-to-com-interop

Bp at the Main(entry point) function of managed application 
; http://byung.egloos.com/2434057

그럼, 위의 글을 모두 종합해서 ... 저랑 같이 실습을 해볼까요? ^^




0. 테스트 코드

namespace ConsoleApplication1
{
	using System;
	public class Program
	{
		string str;
		public void MyMethod(string arg)
		{
			str = "Member Variable";
		}
		static void Main()
		{
			Program s = new Program();
			s.MyMethod("Hello");
		}
	}
}

1. 디버거 실행
	C:\temp>windbg.exe ConsoleApplication1.exe
	또는
	C:\temp>ntsd.exe ConsoleApplication1.exe	

비스타를 사용하시는 분이라면, "NTSD.exe"의 경우에는 "Run as administrator"를 이용하여 명령어 창을 띄운 후, 그 안에서 실행해 주시면 됩니다. (참고로 여기서는 WinDBG.exe를 기준으로 설명하겠지만, 사실 NTSD.exe와 거의 차이가 없습니다.)

위와 같이 실행했으면 화면에는 다음과 같은 텍스트가 보이게 됩니다.

Microsoft (R) Windows Debugger  Version 6.6.0007.5
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: D:\temp\DumpTest\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe
Symbol search path is: SRV*\\localhost\d$\Symbol\OSSymbols*http://msdl.microsoft.com/download/symbols;SRV*\\localhost\d$\Symbol\ProductSymbols;C:\Windows\system32;.
Executable search path is: 
ModLoad: 00920000 00928000   ConsoleApplication1.exe
ModLoad: 77c40000 77d5e000   ntdll.dll
eax=009226fe ebx=7ffd5000 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=77ca0f18 esp=001efdb8 ebp=00000000 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000200
ntdll!RtlUserThreadStart:
77ca0f18 89442404        mov     dword ptr [esp+4],eax ss:0023:001efdbc=00000000
 

로드되자마자 BP가 걸려서 응용 프로그램 실행이 멈춰 있습니다. VS.NET 2005와 비교해 본다면 "F11" 키를 눌러서 디버그 실행을 한 것과 같다고 볼 수 있겠습니다.

2. Symbol 폴더 설정
환경변수에 "_NT_SYMBOL_PATH" 값을 설정하신 분은 건너뛰셔도 됩니다. 만약 하지 않은 경우라면 별도의 ".symfix", ".sympath" 등의 명령어를 사용해서 임의로 구성이 가능합니다.

3. sos.dll 로드 확인
".chain" 명령어를 사용해서 sos 확장 DLL이 로드되었는 지 확인합니다. 아마 "WinDbg 6.6.0007.5" 버전을 사용하고 계신 분이라면, 기본적으로 sos.dll이 로드되었을 것이므로 별도로 명령을 주지 않아도 됩니다. 아래는 실제 실행 예입니다.

0:000> .chain
Extension DLL search Path:
    C:\Program Files\De....[중간 생략]...les\Debugging Tools for Windows\
Extension DLL chain:
    C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos: image 2.0.50727.312, API 1.0.0, built Thu Oct 19 12:37:43 2006
        [path: C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll]
    dbghelp: image 6.6.0007.5, API 6.0.6, built Sun Jul 09 05:11:32 2006
        [path: C:\Program Files\Debugging Tools for Windows\dbghelp.dll]
    ext: image 6.6.0007.5, API 1.0.0, built Sun Jul 09 05:10:52 2006
        [path: C:\Program Files\Debugging Tools for Windows\winext\ext.dll]
    exts: image 6.6.0007.5, API 1.0.0, built Sun Jul 09 05:10:48 2006
        [path: C:\Program Files\Debugging Tools for Windows\WINXP\exts.dll]
    uext: image 6.6.0007.5, API 1.0.0, built Sun Jul 09 05:11:02 2006
        [path: C:\Program Files\Debugging Tools for Windows\winext\uext.dll]
    ntsdexts: image 6.0.5457.0, API 1.0.0, built Sun Jul 09 05:29:38 2006
        [path: C:\Program Files\Debugging Tools for Windows\WINXP\ntsdexts.dll]

참고로, windbg.exe와는 달리 ntsd.exe에서는 기본적으로 sos.dll이 로드되어 있지 않았습니다.

4. sos 확장 API 사용을 위한 프로그램 부분 실행
sos.dll에서 제공되는 확장 명령어를 사용하려면 mscorwks.dll이 로드되어야 합니다. 실제로, mscorwks.dll이 로드되기 전에 sos 명령어를 사용하게 되면 다음과 같은 오류를 보게 됩니다.

0:000> !clrstack
Failed to find runtime DLL (mscorwks.dll), 0x80004005
Extension commands need mscorwks.dll in order to have something to do.

mscorwks.dll을 로드하는 시점까지 프로그램을 실행시키는 방법은 "sxe ld mscorwks" 명령어를 사용하는 방법이 있습니다.
출력 결과는 아래와 같습니다.

0:000> sxe ld mscorwks
0:000> g
ModLoad: 79000000 79045000   C:\Windows\system32\mscoree.dll
ModLoad: 76720000 767f8000   C:\Windows\system32\KERNEL32.dll
(154c.b70): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0026f53c edx=77ca0f34 esi=fffffffe edi=77d05d14
eip=77c82ea8 esp=0026f554 ebp=0026f584 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!DbgBreakPoint:
77c82ea8 cc              int     3
0:000> g
ModLoad: 77b80000 77c3f000   C:\Windows\system32\ADVAPI32.dll
ModLoad: 77db0000 77e73000   C:\Windows\system32\RPCRT4.dll
... [중간 생략]...
ModLoad: 76690000 7670d000   C:\Windows\system32\USP10.dll
ModLoad: 752a0000 75434000   C:\Windows\WinSxS\x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.6000.16386_none_5d07289e07e1d100\comctl32.dll
ModLoad: 79e70000 7a3d6000   C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
eax=0026eb90 ebx=00000000 ecx=0026eb88 edx=00343bc2 esi=7ffdf000 edi=20000000
eip=77ca0f34 esp=0026ebcc ebp=0026ec10 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCallRet:
77ca0f34 c3              ret

하지만, 아직 CLR에 "ConsoleApplication1"의 IL들이 로드조차 되지 않아서 그런지 sos.dll에서 제공되는 "!bpmd" 메서드를 사용해도 정상적으로 동작되지 않았습니다. 그래서 저 같은 경우에는 일단 "unmanaged" 방식으로 돌아와서 디버깅 방법을 찾기 시작했습니다. 우선, 테스트 소스에서 Main 함수에 예외를 발생하도록 일부러 다음과 같이 수정했습니다.

static void Main()
{
  throw new ApplicationException();
  Program s = new Program();
  s.MyMethod("Hello");
}

빌드하고, windbg.exe로 불러들여서 예외발생까지 코드를 실행시킨 후 "k" 명령어를 통해서 다음과 같이 호출 스택을 얻었습니다.

0:000> g
Tue Feb 27 23:25:40.120 2007 (GMT+9): ModLoad: 77b80000 77c3f000   C:\Windows\system32\ADVAPI32.dll
...[중간 생략]...
Tue Feb 27 23:25:40.300 2007 (GMT+9): ModLoad: 79e70000 7a3d6000   C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
ChildEBP RetAddr  Args to Child              
0026ebc8 77c9fb70 77c6e334 00000054 ffffffff ntdll!KiFastSystemCallRet
...[중간 생략]...
0026fa28 00000000 0034271e 7ffd3000 00000000 ntdll!_RtlUserThreadStart+0x23
Tue Feb 27 23:25:40.498 2007 (GMT+9): ModLoad: 78130000 781cb000   C:\Windows\WinSxS\x86_microsoft.vc80
....[중간 생략]...
Tue Feb 27 23:25:40.618 2007 (GMT+9): ModLoad: 79060000 790b3000   C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorjit.dll
Tue Feb 27 23:25:40.668 2007 (GMT+9): (12a8.f40): CLR exception - code e0434f4d (first chance)
Tue Feb 27 23:25:40.683 2007 (GMT+9): (12a8.f40): CLR exception - code e0434f4d (!!! second chance !!!)
eax=0026ed20 ebx=004b5108 ecx=00000001 edx=00000000 esi=0026eda8 edi=e0434f4d
eip=7673b09e esp=0026ed20 ebp=0026ed70 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
KERNEL32!RaiseException+0x58:
7673b09e c9              leave

0:000> k
ChildEBP RetAddr  
0026ed70 79f9691a KERNEL32!RaiseException+0x58
0026edd0 7a098e88 mscorwks!RaiseTheExceptionInternalOnly+0x226
0026ee94 003e00a7 mscorwks!JIT_Throw+0xfc
WARNING: Frame IP not in any known module. Following frames may be wrong.
0026eeb0 79e826bd 0x3e00a7
0026ef30 79e8451b mscorwks!CallDescrWorkerWithHandler+0xa3
0026f06c 79e84403 mscorwks!MethodDesc::CallDescr+0x19c
0026f084 79e843e0 mscorwks!MethodDesc::CallTargetWorker+0x20
0026f098 79ef3e20 mscorwks!MethodDescCallSite::CallWithValueTypes+0x18
0026f1fc 79ef3c19 mscorwks!ClassLoader::RunMain+0x220
0026f464 79ef3aee mscorwks!Assembly::ExecuteMainMethod+0xa6
0026f934 79ef3737 mscorwks!SystemDomain::ExecuteMainMethod+0x398
0026f984 79ef18d1 mscorwks!ExecuteEXE+0x59
0026f9cc 79003aa0 mscorwks!_CorExeMain+0x11b
0026f9dc 76763833 mscoree!_CorExeMain+0x2c
0026f9e8 77c7a9bd KERNEL32!BaseThreadInitThunk+0xe
0026fa28 00000000 ntdll!_RtlUserThreadStart+0x23

대강 봐도, "mscorwks!ClassLoader::RunMain"이 Main 메서드를 실행해 주는 함수라는 것을 쉽게 짐작할 수 있습니다.

이제, mscorwks.dll 로드를 굳이 명시적으로 확인할 필요없이 다음과 같이 직접 unmanaged API 함수에 BP를 걸어 놓는 것으로 문제를 해결할 수 있습니다.

0:000> bp mscorwks!ClassLoader::RunMain
Bp expression 'mscorwks!ClassLoader::RunMain' could not be resolved, adding deferred bp

현재는 mscorwks모듈이 로드되어 있지 않기 때문에 RunMain기호를 찾을 수 없지만, 이후에 혹시나 발견이 되면 설정해 주겠다고 친절하게(?) 메시지를 보여주고 있습니다. 이제 거의 다 되었습니다. "g" 명령어로 실행시키면 다음과 같이 RunMain 함수에서 실행이 멈추는 것을 확인할 수 있습니다.

0:000> g
Tue Feb 27 23:36:53.110 2007 (GMT+9): ModLoad: 77b80000 77c3f000   C:\Windows\system32\ADVAPI32.dll
....[중간 생략]...
Tue Feb 27 23:36:55.763 2007 (GMT+9): ModLoad: 79e70000 7a3d6000   C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
ChildEBP RetAddr  Args to Child              
0030ef14 77c9fb70 77c6e334 00000054 ffffffff ntdll!KiFastSystemCallRet
....[중간 생략]...
mscorlib\7fe79782947b85d961fd55cb5e02a129\mscorlib.ni.dll
Tue Feb 27 23:37:01.632 2007 (GMT+9): Breakpoint 0 hit
eax=0030f584 ebx=00000000 ecx=1e3ea55d edx=80000001 esi=007c3000 edi=00000000
eip=79ef3c78 esp=0030f54c ebp=0030f59c iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
mscorwks!ClassLoader::RunMain:
79ef3c78 55              push    ebp

5. Main 함수에 BP 설정
자, 다 되었습니다. 이제 sos.dll에서 제공되는 확장 명령어를 이용해서 Managed 함수에 쉽게 BP를 설정할 수 있습니다. 여기서는 Main 함수가 목표였기 때문에 다음과 같이 설정하시면 됩니다.

0:000> !bpmd ConsoleApplication1 ConsoleApplication1.Program.Main
Found 1 methods...
MethodDesc = 007c3000
Adding pending breakpoints...

0:000> g
Tue Feb 27 23:42:31.292 2007 (GMT+9): ModLoad: 79060000 790b3000   C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorjit.dll
Tue Feb 27 23:42:31.313 2007 (GMT+9): (f68.16c0): CLR notification exception - code e0444143 (first chance)
JITTED ConsoleApplication1!ConsoleApplication1.Program.Main()
Setting breakpoint: bp 00D30070 [ConsoleApplication1.Program.Main()]
Tue Feb 27 23:42:31.413 2007 (GMT+9): Breakpoint 1 hit
eax=007c3000 ebx=0030f21c ecx=ffffffff edx=ffffffff esi=00135108 edi=00000000
eip=00d30070 esp=0030f1f4 ebp=0030f200 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
00d30070 83ec08          sub     esp,8

어떠세요? 재미있지요? ^^

[첨부된 파일은 실제로 위의 실습을 한 WinDBG.exe 실행 화면의 텍스트를 캡쳐한 것입니다.]



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

[연관 글]






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

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

비밀번호

댓글 작성자
 



2008-06-09 08시21분
kevin25
2011-04-18 11시17분
.NET 4.0 응용 프로그램의 Main 함수에 BreakPoint 걸기
; http://www.sysnet.pe.kr/2/0/1021
정성태
2019-04-06 12시10분
0:000> sxe ld mscorwks.dll

0:000> g

0:000> bp mscorwks!ClassLoader::RunMain

0:000> g

0:000> !bpmd ConsoleApp1 ConsoleApp1.Program.Main

0:000> g

0:000> !clrstack
정성태

... 31  32  33  34  35  [36]  37  38  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12756정성태8/6/20218526.NET Framework: 1084. C# - .NET Core Web API 단위 테스트 방법 [1]파일 다운로드1
12755정성태8/5/20217778개발 환경 구성: 593. MSTest - 단위 테스트에 static/instance 유형의 private 멤버 접근 방법파일 다운로드1
12754정성태8/5/20218590오류 유형: 750. manage.py - Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
12753정성태8/5/20218911오류 유형: 749. PyCharm - Error: Django is not importable in this environment
12752정성태8/4/20216946개발 환경 구성: 592. JetBrains의 IDE(예를 들어, PyCharm)에서 Visual Studio 키보드 매핑 적용
12751정성태8/4/202110082개발 환경 구성: 591. Windows 10 WSL2 환경에서 docker-compose 빌드하는 방법
12750정성태8/3/20216839디버깅 기술: 181. windbg - 콜 스택의 "Call Site" 오프셋 값이 가리키는 위치
12749정성태8/2/20216241개발 환경 구성: 590. Visual Studio 2017부터 단위 테스트에 DataRow 특성 지원
12748정성태8/2/20216877개발 환경 구성: 589. Azure Active Directory - tenant의 관리자(admin) 계정 로그인 방법
12747정성태8/1/20217419오류 유형: 748. 오류 기록 - MICROSOFT GRAPH – HOW TO IMPLEMENT IAUTHENTICATIONPROVIDER파일 다운로드1
12746정성태7/31/20219528개발 환경 구성: 588. 네트워크 장비 환경을 시뮬레이션하는 Packet Tracer 프로그램 소개
12745정성태7/31/20217315개발 환경 구성: 587. Azure Active Directory - tenant의 관리자 계정 로그인 방법
12744정성태7/30/20217965개발 환경 구성: 586. Azure Active Directory에 연결된 App 목록을 확인하는 방법?
12743정성태7/30/20218659.NET Framework: 1083. Azure Active Directory - 외부 Token Cache 저장소를 사용하는 방법파일 다운로드1
12742정성태7/30/20217827개발 환경 구성: 585. Azure AD 인증을 위한 사용자 인증 유형
12741정성태7/29/20219060.NET Framework: 1082. Azure Active Directory - Microsoft Graph API 호출 방법파일 다운로드1
12740정성태7/29/20217702오류 유형: 747. SharePoint - InvalidOperationException 0x80131509
12739정성태7/28/20217636오류 유형: 746. Azure Active Directory - IDW10106: The 'ClientId' option must be provided.
12738정성태7/28/20218319오류 유형: 745. Azure Active Directory - Client credential flows must have a scope value with /.default suffixed to the resource identifier (application ID URI).
12737정성태7/28/20217207오류 유형: 744. Azure Active Directory - The resource principal named api://...[client_id]... was not found in the tenant
12736정성태7/28/20217792오류 유형: 743. Active Azure Directory에서 "API permissions"의 권한 설정이 "Not granted for ..."로 나오는 문제
12735정성태7/27/20218375.NET Framework: 1081. C# - Azure AD 인증을 지원하는 데스크톱 애플리케이션 예제(Windows Forms) [2]파일 다운로드1
12734정성태7/26/202124460스크립트: 20. 특정 단어로 시작하거나/끝나는 문자열을 포함/제외하는 정규 표현식 - Look-around
12733정성태7/23/202111581.NET Framework: 1081. Self-Contained/SingleFile 유형의 .NET Core/5+ 실행 파일을 임베딩한다면? [1]파일 다운로드2
12732정성태7/23/20216804오류 유형: 742. SharePoint - The super user account utilized by the cache is not configured.
12731정성태7/23/20218341개발 환경 구성: 584. Add Internal URLs 화면에서 "Save" 버튼이 비활성화 된 경우
... 31  32  33  34  35  [36]  37  38  39  40  41  42  43  44  45  ...