Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 11. (Managed) Main Method에 Break Point 걸기 [링크 복사], [링크+제목 복사],
조회: 24657
글쓴 사람
정성태 (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)
13289정성태3/18/20233683Windows: 231. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 자식 윈도우를 생성하는 방법파일 다운로드1
13288정성태3/17/20233804Windows: 230. Win32 - 대화창의 DLU 단위를 pixel로 변경하는 방법파일 다운로드1
13287정성태3/16/20233982Windows: 229. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 윈도우를 직접 띄우는 방법파일 다운로드1
13286정성태3/15/20234413Windows: 228. Win32 - 리소스에 포함된 대화창 Template의 2진 코드 해석 방법
13285정성태3/14/20233964Windows: 227. Win32 C/C++ - Dialog Procedure를 재정의하는 방법파일 다운로드1
13284정성태3/13/20234230Windows: 226. Win32 C/C++ - Dialog에서 값을 반환하는 방법파일 다운로드1
13283정성태3/12/20233712오류 유형: 852. 파이썬 - TypeError: coercing to Unicode: need string or buffer, NoneType found
13282정성태3/12/20234041Linux: 58. WSL - nohup 옵션이 필요한 경우
13281정성태3/12/20234024Windows: 225. 윈도우 바탕화면의 아이콘들이 넓게 퍼지는 경우 [2]
13280정성태3/9/20234774개발 환경 구성: 670. WSL 2에서 호스팅 중인 TCP 서버를 외부에서 접근하는 방법
13279정성태3/9/20234272오류 유형: 851. 파이썬 ModuleNotFoundError: No module named '_cffi_backend'
13278정성태3/8/20234280개발 환경 구성: 669. WSL 2의 (init이 아닌) systemd 지원 [1]
13277정성태3/6/20234933개발 환경 구성: 668. 코드 사인용 인증서 신청 및 적용 방법(예: Digicert)
13276정성태3/5/20234609.NET Framework: 2102. C# 11 - ref struct/ref field를 위해 새롭게 도입된 scoped 예약어
13275정성태3/3/20234891.NET Framework: 2101. C# 11의 ref 필드 설명
13274정성태3/2/20234503.NET Framework: 2100. C# - ref 필드로 ref struct 타입을 허용하지 않는 이유
13273정성태2/28/20234234.NET Framework: 2099. C# - 관리 포인터로서의 ref 예약어 의미
13272정성태2/27/20234473오류 유형: 850. SSMS - mdf 파일을 Attach 시킬 때 Operating system error 5: "5(Access is denied.)" 에러
13271정성태2/25/20234426오류 유형: 849. Sql Server Configuration Manager가 시작 메뉴에 없는 경우
13270정성태2/24/20233963.NET Framework: 2098. dotnet build에 /p 옵션을 적용 시 유의점
13269정성태2/23/20234596스크립트: 46. 파이썬 - uvicorn의 콘솔 출력을 UDP로 전송
13268정성태2/22/20235097개발 환경 구성: 667. WSL 2 내부에서 열고 있는 UDP 서버를 호스트 측에서 접속하는 방법
13267정성태2/21/20234979.NET Framework: 2097. C# - 비동기 소켓 사용 시 메모리 해제가 finalizer 단계에서 발생하는 사례파일 다운로드1
13266정성태2/20/20234624오류 유형: 848. .NET Core/5+ - Process terminated. Couldn't find a valid ICU package installed on the system
13265정성태2/18/20234564.NET Framework: 2096. .NET Core/5+ - PublishSingleFile 유형에 대한 runtimeconfig.json 설정
13264정성태2/17/20236093스크립트: 45. 파이썬 - uvicorn 사용자 정의 Logger 작성
1  2  3  4  5  6  7  8  9  10  11  12  13  [14]  15  ...