Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 4개 있습니다.)
(시리즈 글이 8개 있습니다.)
개발 환경 구성: 105. 풀 덤프 파일을 남기는 방법
; https://www.sysnet.pe.kr/2/0/991

.NET Framework: 205. 코드(C#)를 통한 풀 덤프 만드는 방법
; https://www.sysnet.pe.kr/2/0/995

디버깅 기술: 51. 닷넷 응용 프로그램에서 특정 예외가 발생했을 때 풀 덤프 받는 방법
; https://www.sysnet.pe.kr/2/0/1376

.NET Framework: 380. 프로세스 스스로 풀 덤프 남기는 방법
; https://www.sysnet.pe.kr/2/0/1485

디버깅 기술: 81. try/catch로 조용히 사라진 예외를 파악하고 싶다면?
; https://www.sysnet.pe.kr/2/0/10965

.NET Framework: 868. (닷넷 프로세스를 대상으로) 디버거 방식이 아닌 CLR Profiler를 이용해 procdump.exe 기능 구현
; https://www.sysnet.pe.kr/2/0/12049

개발 환경 구성: 462. 시작하자마자 비정상 종료하는 프로세스의 메모리 덤프 - procdump
; https://www.sysnet.pe.kr/2/0/12051

개발 환경 구성: 752. ProcDump - C/C++ 예외 코드 필터를 지정한 덤프 생성
; https://www.sysnet.pe.kr/2/0/13960




try/catch로 조용히 사라진 예외를 파악하고 싶다면?

try/catch를 남용하는 경우, 문제가 발생해도 도대체 그것이 어디서 발생했는지 파악하기가 어려울 수 있습니다. 더욱 문제는, 개발 시 try/catch가 필요한 상황이 나올 수밖에 없기 때문에 관련 예외를 thrown 상태로 설정할 수 없다는 점입니다. 물론, 설정해도 되지만 디버깅 시 자꾸 걸리기 때문에 여간 귀찮은 문제가 아닐 수 없습니다.

사실 이 귀찮음 때문에, 비주얼 스튜디오도 대부분의 예외에 대해 "first-chance exception"인 경우 굳이 멈추지 않고 진행할 수 있도록 Thrown 기본값이 꺼진 상태입니다.

그래서 저는, 때때로 그렇게 사라지는 예외를 최소한으로 줄이기 위한 나름의 방법을 사용하는데요. 바로 procdump.exe를 이용하는 것입니다. 예를 들어, 응용 프로그램의 Process id가 11096인 경우 다음과 같은 식으로 실행해 주면 됩니다.

procdump -e 1 -f "" 11096

"" 문자열은 무엇을 넣어도 상관없습니다. 어차피 그 예외를 기대하지 않는데다, 그냥 상황을 지켜보기 위함이기 때문입니다. 이렇게 실행하면 화면에 try/catch로 사라졌던 모든 예외들이 출력되는 것을 볼 수 있습니다.

[...] Exception: E0434F4D.System.Web.HttpException ("The component '{48D11CC5-C50A-403D-A4AC-0825A56E5C13}' cannot be created.  Apartment threaded components can only be created on pages with an <%@ Page aspcompat=true %> page directive.")

[...] Exception: E0434F4D.System.ObjectDisposedException ("Cannot access a closed file.")

[...] Exception: E0434F4D.System.Net.WebException ("The remote server returned an error: (503) Server Unavailable.")

[...] Exception: E0434F4D.System.Net.Sockets.SocketException ("An operation was attempted on something that is not a socket")

[...] Exception: E0434F4D.System.IO.IOException ("Unable to write data to the transport connection: An operation was attempted on something that is not a socket.")

이를 통해, 자신이 무심코 넘겼던 예외를 다시 한번 돌아보게 됩니다. ^^ 만약, 위의 예외 중에 심각한 것이 있다면 그에 대해 덤프를 남겨서 좀 더 상세하게 분석할 수 있습니다. 예를 들어, 위에서 "System.ObjectDisposedException"을 알고 싶다면 procdump를 다음의 옵션으로 다시 실행하면 됩니다.

procdump -ma  -e 1 -f System.ObjectDisposedException 11096

또는, 비주얼 스튜디오가 설치된 경우라면 Thrown 설정을 해서 쉽게 원인 파악을 할 수 있습니다.

이를 응용할 수 있는 사례를 하나 떠 올려보면. 배포한 응용 프로그램에서 사용자가 어떤 동작을 시켰는데 아무런 실행 결과도 없을 때가 있습니다. 대개의 경우, 개발자가 과다한 try/catch를 사용해 어디선가 예외가 먹혀서 아무런 동작도 발생하지 않은 것처럼 보이는 건데요. 물론 상세한 오류 로그를 남기는 것으로 이 상황을 타개할 수는 있지만 그런 오류 남기는 작업이 안되어 있다면 관련 코드를 넣은 후 다시 사용자 컴퓨터에 배포한 후 실행해 봐야 하는 수고로움이 있습니다.

그럴 때, procdump.exe를 실행해 놓고 사용자가 그 기능을 다시 실행하면 됩니다. 만약 '예외가 먹힌 상황'이었다면 화면에 여지없이 그에 대한 내역이 출력될 것입니다. 아울러 덤프도 남겨서 사후 분석도 가능하고. (덤프를 남겼다면, 잊지 말고 사용자 컴퓨터에 있는 해당 닷넷 버전의 sos.dll과 mscordacwks.dll 파일을 챙기세요. ^^)




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/10/2021]

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

비밀번호

댓글 작성자
 



2025-07-14 10시51분
Decoding the parameters of a thrown C++ exception (0xE06D7363)
; https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273

0xE06D7363 에러 코드는 Visual C++ 컴파일러가 다루는 코드라서 Win32 관련 헤더 파일에는 정의돼 있지 않다고 합니다. 예를 들어, 아래와 같은 코드는 C++에서의 오류일 뿐 Win32와는 직접적으로 상관이 없는 경우입니다.

---------------------
int test()
{
    throw 1;
}
---------------------

따라서, 이런 에러가 발생했을 때는 Win32와 무관하게 Visual C++이 생성한 규칙에 따라 정보를 파악해야 하는데, 위의 글에서는 그 방법을 다룹니다. (단지, Visual C++의 내부 구현 사항이라 향후 달라질 가능성도 있다고 합니다.)

즉, 해당 에러가 발생했을 때의 인자가 4개까지 관여할 수 있는데,

parameter 0: [생략]
parameter 1: "throw ..."로 던져진 예외 개체에 대한 포인터
parameter 2: "throw ..."로 던져진 개체를 설명하는 정보를 가리키는 포인터
parameter 3: 예외가 발생한 모듈의 HINSTANCE (64비트에서만 유효)

예를 들어 예외 개체에 대한 class name을 다음의 단계로 구할 수 있습니다.

EXCEPTION_RECORD
+----------+
| E06D7363 |
+----------+
| ~~~ |
+----------+
|* ~~~ |
+----------+
|* ~~~ |
+----------+
| 3 or 4 | // 3 == 32비트, 4 == 64비트
+----------+
|* ~~~ |
+----------+
|*Object |
+----------+ +---+
|* ------> |~~~|
+----------+ +---+
|*HINSTANCE| |~~~|
+----------+ +---+
                 |~~~|
                 +---+ +---+
                 | -----> |~~~|
                 +---+ +---+ +---+
                          | -----> |~~~|
                          +---+ +---+ +----------+
                                   | -----> |* ~~~ |
                                   +---+ +----------+
                                            |* ~~~ |
                                            +----------+
                                            |Class name|
                                            +----------+

만약 windbg에서 64비트 덤프를 분석하는 경우라면,

0:008> .exr 00000000`015dede0
ExceptionAddress: 000007fefd23bb5d (KERNEL32!RaiseException+0x39)
   ExceptionCode: e06d7363 (C++ EH exception)
  ExceptionFlags: 00000001
NumberParameters: 4 // this is running on 64-bit Windows
   Parameter[0]: 0000000019930520
   Parameter[1]: 00000000015def30 // object being thrown
   Parameter[2]: 00000000100cefa8 // magic Parameter 2
   Parameter[3]: 0000000010000000 // HINSTANCE

이후, parameter 2의 4번째 값을 구하고,

0:008> dd 100cefc8 l2
00000000`100cefc8 00000005 000ceff8
                            ^^^^^^^^

(64비트 환경이므로) 저 값에 HINSTNACE 값을 더한 위치의 2번째 값을 구하고,

0:008> dd 100ceff8 l2
00000000`100ceff8 00000001 000d6670
                            ^^^^^^^^

마지막으로 다시 HINSTNACE + 000d6670의 위치에서 64비트인 경우 16바이트, 32비트인 경우 8바이트 이후의 위치를 덤프하면 클래스 이름이 나옵니다.

0:008> da 100d6670+10
00000000`100d6680 ".PEAVCResourceException@@"

mangling 텍스트를 제외하면 실제 예외 이름은 "CResource­Exception"이 됩니다. 참고로, 만약 저 과정을 32비트에서 했다면 다음과 같이 풀이할 수 있습니다.

0:000> .exr 0008f2e4
ExceptionAddress: 7671b046 (kernel32!RaiseException)
   ExceptionCode: e06d7363 (C++ EH exception)
  ExceptionFlags: 00000001
NumberParameters: 3 // 32-bit platform
   Parameter[0]: 19930520
   Parameter[1]: 0008f384 // object being thrown
   Parameter[2]: 10cfed60 // magic Parameter 2

0:000> dd 10cfed60 l4
10cfed60 00000000 00000000 00000000 10db297c

0:000> dd 10db297c l2
10db297c 00000004 10db2990

0:000> dd 10db2990 l2
10db2990 00000001 10dbccac

0:000> da 10dbccac+8
10dbccb4 ".PAVCFileException@@"

-------------------------------------------------------

이후의 글에서,

Using the wrong HINSTANCE in RegisterClass is like identity theft
; https://devblogs.microsoft.com/oldnewthing/20110715-00/?p=10133

저 사례가 발생했던 경우를 이야기로 풀어내고 있습니다. 간단하게 정리하면, DLL에서 RegisterClass를 사용했는데,

RegisterClassA 함수(winuser.h)
; https://learn.microsoft.com/ko-kr/windows/win32/api/winuser/nf-winuser-registerclassa

WNDCLASS.hInstance 필드에 DLL의 Module Instance가 아닌 EXE의 HINSTANCE를 전달했기 때문입니다.
정성태
2025-07-14 11시11분
If you want to terminate on an unexpected exception, then don’t sniff at every exception; just let the process terminate
; https://devblogs.microsoft.com/oldnewthing/20191024-00/?p=103022

Visual C++에서 noexcept와, 그것을 사용자가 직접 코드로 catch (...)로 잡아 std::terminate를 호출했을 때의 차이점을 설명하고 있습니다.

예를 들어, 아래의 예제 코드는,

#include <cstdlib>
#include <new>
#include <exception>
#include <errno.h>

struct MyCustomExceptionClass
{
    int code;
};

int oopsie()
{
    int value = std::rand();
    if (value >= 0) throw 1; // totally disallowed exception
    return value;
}

int victim() try
{
    return oopsie();
}
catch (MyCustomExceptionClass const& ex)
{
    return ex.code;
}
catch (std::bad_alloc const& ex)
{
    return ENOMEM;
}
catch (...)
{
   std::terminate();
}

int main()
{
    return victim();
}

crash 덤프에서 실제 terminate를 호출한 oopsie 함수가 안 보이는데,

------------------------------
_exit+0x11
abort+0xe8
terminate+0x3b
victim+0x5b ⇐ no sign of oopsie
main+0xd
------------------------------

대신 다음과 같이 victim 함수에서 예외를 처리하도록 바꾸면,

void victim() noexcept try
{
    oopsie();
}
catch (MyCustomExceptionClass const& ex)
{
    return ex.code;
}
catch (std::bad_alloc const& ex)
{
    return ENOMEM;
}
// catch (...)
// {
// std::terminate();
// }

이제 정상적인 호출 스택을 얻을 수 있습니다.

_exit+0x11
abort+0xe8
terminate+0x3b
FindHandler+0x377
__InternalCxxFrameHandler+0xf7
__CxxFrameHandler2+0x26
ExecuteHandler2+0x26
ExecuteHandler+0x24
KiUserExceptionDispatcher+0x26
RaiseException+0x62
_CxxThrowException+0x68
oopsie+0x2c ⇐ here's the bad boy
victim+0x3a
main+0x33

참고로, 위와 같은 스택 풀이(unwinding) 여부는 C++ 표준에서 구현체에 맡기는 걸로 돼 있다고 합니다. 단지 Visual C++의 경우에는 스택 해제를 하지 않는 방식을 채택한 것이고, 따라서 위와 같은 상황에서 oopsie 스택 프레임으로 전환하면 로컬 변수의 값의 값까지 확인할 수 있게 됩니다.

0:000> .frame 5
05 02a9fba0 00751e0a scratch!oopsie+0x2c
0:000> dv
          value = 0n41
정성태

... 136  137  [138]  139  140  141  142  143  144  145  146  147  148  149  150  ...
NoWriterDateCnt.TitleFile(s)
1647정성태3/3/201424070오류 유형: 224. 스카이드라이브 비정상 종료 - Error 0x80040A41: No error description available
1646정성태3/3/201433366오류 유형: 223. Microsoft-Windows-DistributedCOM 10016 이벤트 로그 에러 [1]
1645정성태3/1/201422984기타: 43. 마이크로소프트 MVP들이 모여 전국 세미나를 엽니다.
1644정성태2/26/201430026.NET Framework: 426. m3u8 스트리밍 파일을 윈도우 8.1 Store App에서 재생하는 방법파일 다운로드1
1643정성태2/25/201425768오류 유형: 222. 윈도우 8 Store App - APPX1204 SignTool Error: An unexpected internal error has occurred [1]
1642정성태2/25/201430502Windows: 91. 한글이 포함된 사용자 프로파일 경로 변경 [2]
1641정성태2/24/201427060기타: 42. 클래스 설명 [5]
1640정성태2/24/201448084.NET Framework: 425. C# - VLC(ActiveX) 컨트롤을 레지스트리 등록 없이 사용하는 방법 [15]
1639정성태2/23/201423853기타: 41. BBS 스토어 앱 개인정보 보호 정책 안내
1638정성태2/18/201446728Windows: 90. 실행 파일로부터 관리자 요구 권한을 제거하는 방법(부제: 크랙 버전을 보다 안전하게 실행하는 방법) [8]
1637정성태2/14/201427568Windows: 89. 컴퓨터를 껐는데도 어느 순간 자동으로 켜진다면? - 두 번째 이야기
1636정성태2/14/201423632Windows: 88. Hyper-V가 설치된 컴퓨터의 윈도우 백업 설정
1635정성태2/14/201424402오류 유형: 221. SharePoint - System.InvalidOperationException: The farm is unavailable.
1634정성태2/14/201424727.NET Framework: 424. C# - CSharpCodeProvider로 컴파일한 메서드의 실행이 일반 메서드보다 더 빠르다? [1]파일 다운로드1
1633정성태2/13/201427614오류 유형: 220. 2014년 2월 13일 이후로 Visual Studio 2010 Macro가 동작하지 않는다면? [3]
1632정성태2/12/201445557.NET Framework: 423. C#에서 DirectShow를 이용한 미디어 재생 [2]파일 다운로드1
1631정성태2/11/201424213개발 환경 구성: 217. Realtek 사운드 장치에서 재생되는 오디오를 GraphEditor로 녹음하는 방법
1630정성태2/5/201424878개발 환경 구성: 216. Hyper-V에 올려진 윈도우 XP VM에서 24bit 컬러 및 ClearType 활성화하는 방법
1629정성태2/5/201434466개발 환경 구성: 215. DOS batch - 하나의 .bat 파일에서 다중 .bat 파일을 (비동기로) 실행하는 방법 [1]
1628정성태2/4/201435990Windows: 87. 윈도우 8.1에서 .NET 3.5 설치가 안된다면? [2]
1627정성태2/4/201430996개발 환경 구성: 214. SQL Server Reporting Services를 이용해 간단한 리포트 제작하는 방법
1626정성태2/4/201423118Windows: 86. 윈도우 8.1의 Skydrive 내용이 동기화가 안된다면?
1625정성태2/2/201429985.NET Framework: 422. C++과 C#의 Event 공유파일 다운로드1
1624정성태2/2/201425709.NET Framework: 421. ASP.NET에서 Server.CreateObject와 COM Interop 클래스 생성의 차이점
1623정성태2/1/201430552개발 환경 구성: 213. x86/x64별로 나뉘어진 어셈블리를 한 프로젝트에서 참조하는 방법 [1]파일 다운로드1
1622정성태1/31/201430775VC++: 74. 어떤 것을 쓰면 좋을까요? wvnsprintf, _vsnwprintf_s, StringCbVPrintfW [4]
... 136  137  [138]  139  140  141  142  143  144  145  146  147  148  149  150  ...