Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 11. (Managed) Main Method에 Break Point 걸기 [링크 복사], [링크+제목 복사]
조회: 24606
글쓴 사람
정성태 (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
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13605정성태4/23/2024237닷넷: 2246. C# - Python.NET을 이용한 파이썬 소스코드 연동파일 다운로드1
13604정성태4/22/2024247오류 유형: 901. Visual Studio - Unable to set the next statement. Set next statement cannot be used in '[Exception]' call stack frames.
13603정성태4/21/2024388닷넷: 2245. C# - IronPython을 이용한 파이썬 소스코드 연동파일 다운로드1
13602정성태4/20/2024728닷넷: 2244. C# - PCM 오디오 데이터를 연속(Streaming) 재생 (Windows Multimedia)파일 다운로드1
13601정성태4/19/2024769닷넷: 2243. C# - PCM 사운드 재생(NAudio)파일 다운로드1
13600정성태4/18/2024792닷넷: 2242. C# - 관리 스레드와 비관리 스레드
13599정성태4/17/2024827닷넷: 2241. C# - WAV 파일의 PCM 사운드 재생(Windows Multimedia)파일 다운로드1
13598정성태4/16/2024852닷넷: 2240. C# - WAV 파일 포맷 + LIST 헤더파일 다운로드2
13597정성태4/15/2024821닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력파일 다운로드1
13596정성태4/14/20241045닷넷: 2238. C# - WAV 기본 파일 포맷파일 다운로드1
13595정성태4/13/20241050닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)파일 다운로드1
13594정성태4/12/20241067닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)파일 다운로드1
13593정성태4/8/20241076닷넷: 2235. MSBuild - AccelerateBuildsInVisualStudio 옵션
13592정성태4/2/20241215C/C++: 165. CLion으로 만든 Rust Win32 DLL을 C#과 연동
13591정성태4/2/20241192닷넷: 2234. C# - WPF 응용 프로그램에 Blazor App 통합파일 다운로드1
13590정성태3/31/20241078Linux: 70. Python - uwsgi 응용 프로그램이 k8s 환경에서 OOM 발생하는 문제
13589정성태3/29/20241150닷넷: 2233. C# - 프로세스 CPU 사용량을 나타내는 성능 카운터와 Win32 API파일 다운로드1
13588정성태3/28/20241261닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신 [2]파일 다운로드1
13587정성태3/27/20241168오류 유형: 900. Windows Update 오류 - 8024402C, 80070643
13586정성태3/27/20241324Windows: 263. Windows - 복구 파티션(Recovery Partition) 용량을 늘리는 방법
13585정성태3/26/20241110Windows: 262. PerformanceCounter의 InstanceName에 pid를 추가한 "Process V2"
13584정성태3/26/20241060개발 환경 구성: 708. Unity3D - C# Windows Forms / WPF Application에 통합하는 방법파일 다운로드1
13583정성태3/25/20241175Windows: 261. CPU Utilization이 100% 넘는 경우를 성능 카운터로 확인하는 방법
13582정성태3/19/20241435Windows: 260. CPU 사용률을 나타내는 2가지 수치 - 사용량(Usage)과 활용률(Utilization)파일 다운로드1
13581정성태3/18/20241608개발 환경 구성: 707. 빌드한 Unity3D 프로그램을 C++ Windows Application에 통합하는 방법
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...