성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>(닷넷 프로세스를 대상으로) 디버거 방식이 아닌 CLR Profiler를 이용해 procdump.exe 기능 구현</h1> <p> 예전 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 풀 덤프 파일을 남기는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/991'>http://www.sysnet.pe.kr/2/0/991</a> </pre> <br /> <a target='tab' href='https://learn.microsoft.com/en-us/sysinternals/downloads/procdump'>procdump.exe</a>에 대해 소개한 적이 있는데요. 대상을 닷넷 프로그램으로 제한한다면 이와 유사한 프로그램을 CLR Profiler 기법을 이용해서도,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 기본적인 CLR Profiler 소스 코드 설명 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/10950'>http://www.sysnet.pe.kr/2/0/10950</a> </pre> <br /> 만들 수 있습니다. 예를 들어, 특정 메서드가 처음 실행되는 순간을 <a target='tab' href='https://learn.microsoft.com/ko-kr/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-jitcompilationstarted-method'>JITCompilationStarted</a> 이벤트로 잡아,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HRESULT CDAEProfiler::<span style='color: blue; font-weight: bold'>JITCompilationStarted</span>(FunctionID functionId, BOOL fIsSafeToBlock) { ClassID classId; ModuleID moduleId = 0; mdToken methodToken = 0; HRESULT hr = m_pICorProfilerInfo->GetFunctionInfo(functionId, &classId, &moduleId, &methodToken); IMetaDataImport* pMetaDataImport = nullptr; IMetaDataAssemblyImport* pMetaDataAssemblyImport = nullptr; IMetaDataEmit* pMetaDataEmit = nullptr; IMethodMalloc* pMalloc = nullptr; do { hr = m_pICorProfilerInfo->GetModuleMetaData(moduleId, (ofRead | ofWrite), IID_IMetaDataAssemblyImport, (LPUNKNOWN*)&pMetaDataAssemblyImport); pMetaDataAssemblyImport->QueryInterface(IID_IMetaDataImport, (LPVOID*)&pMetaDataImport); ULONG cchFunction; mdToken typeToken; wchar_t szFunction[2048]; wchar_t szClass[2048]; hr = pMetaDataImport->GetMethodProps(methodToken, &typeToken, szFunction, 2048, &cchFunction, NULL, NULL, NULL, NULL, NULL); if (S_OK != hr) { break; } ULONG cchClass; hr = pMetaDataImport->GetTypeDefProps(typeToken, szClass, 2048, &cchClass, 0, 0); if (S_OK != hr) { break; } HRESULT hr = pMetaDataAssemblyImport->QueryInterface(IID_IMetaDataEmit, (LPVOID*)&pMetaDataEmit); if (S_OK != hr) { break; } hr = m_pICorProfilerInfo->GetILFunctionBodyAllocator(moduleId, &pMalloc); if (S_OK != hr) { break; } const int bufLen = 2048; wchar_t buf[bufLen]; _snwprintf_s(buf, bufLen, _TRUNCATE, L"%s.%s", szClass, szFunction); <span style='color: blue; font-weight: bold'>if (wcscmp(buf, L"...[원하는 메서드]...") == 0) { // 가장 처음 실행되었을 때... // dump를 남기는 식의 동작 가능 break; }</span> } while (false); if (pMalloc != nullptr) { pMalloc->Release(); } if (pMetaDataEmit != nullptr) { pMetaDataEmit->Release(); } if (pMetaDataAssemblyImport != nullptr) { pMetaDataAssemblyImport->Release(); } if (pMetaDataImport != nullptr) { pMetaDataImport->Release(); } return S_OK; } </pre> <br /> 다음의 코드와 결합시키면 덤프를 남기는 것도 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 코드(C#)를 통한 풀 덤프 만드는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/995'>http://www.sysnet.pe.kr/2/0/995</a> </pre> <br /> 또는 지난번에 설명한 예제를 곁들이면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CLR Profiler - 별도 정의한 .NET 코드를 호출하도록 IL 코드 변경 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/10959'>http://www.sysnet.pe.kr/2/0/10959</a> </pre> <br /> 특정 메서드가 몇 번째 수행되었을 때...라는 식의 조건을 추가할 수 있습니다. 하지만, 대개의 경우 진단 목적이라면 예외 발생 시점에 대한 문제 분석이 중요할 텐데요, 이런 경우라면 다음과 같이 예외가 발생하는 순간을 잡아,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HRESULT CDAEProfiler::<span style='color: blue; font-weight: bold'><a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-exceptionthrown-method'>ExceptionThrown</a></span>(ObjectID thrownObjectId) { ClassID classId; ModuleID moduleId; mdTypeDef typeDefToken; m_pICorProfilerInfo->GetClassFromObject(thrownObjectId, &classId); m_pICorProfilerInfo->GetClassIDInfo(classId, &moduleId, &typeDefToken); IMetaDataImport* pMetaDataImport = nullptr; do { m_pICorProfilerInfo->GetModuleMetaData(moduleId, ofRead, IID_IMetaDataImport, (LPUNKNOWN*)&pMetaDataImport); mdToken tkExtends; DWORD dwTypeDefFlags; ULONG chTypeDef; const ULONG cchTypeDef = 1024; wchar_t szTypeDef[cchTypeDef]; pMetaDataImport->GetTypeDefProps(typeDefToken, szTypeDef, cchTypeDef, &chTypeDef, &dwTypeDefFlags, &tkExtends); printf("ExceptionThrown == %S (ObjectID == %x)\n", szTypeDef, thrownObjectId); } while (false); if (pMetaDataImport != nullptr) { pMetaDataImport->Release(); } <span style='color: blue; font-weight: bold'>{ // 예외가 발생했을 때 프로그램을 멈추거나, printf("Waiting for being attached from debugger, or press any key to continue...\n"); getchar(); // 덤프를 남기는 것도 가능 }</span> return S_OK; } </pre> <br /> 마찬가지로 덤프를 남기거나, 또는 디버거를 attach할 수 있도록 실행을 멈출 수 있습니다. 물론, 코드만 약간 수정하면 n번째 발생한 예외에 대해서만 덤프를 남기는 식으로도 구현할 수 있습니다.<br /> <br /> 응용하기에 따라, (유독 고객사에서만 발생하는 ^^) 데스크톱 용 프로그램의 예외 발생 상황을 분석하는데 활용할 수도 있을 것입니다. 만약 w3wp.exe에 적용하고 싶다면 아래의 글에 설명한 레지스트리를 통해 Profiler 관련 환경 변수를 설정하거나,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > w3wp.exe에 환경 변수 전달하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/840'>http://www.sysnet.pe.kr/2/0/840</a> </pre> <br /> 아니면 (약간 더 귀찮아지겠지만) 전역 시스템 환경 변수에 설정하면 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 글의 예제는 다음의 github에 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > stjeong/DebugAtException ; <a target='tab' href='https://github.com/stjeong/DebugAtException'>https://github.com/stjeong/DebugAtException</a> </pre> <br /> 빌드하면 "daeProxy" 프로젝트의 출력 폴더에 모든 DLL/EXE들이 모이고 다음과 같이 (일부러 예외가 발생하도록 만든) SampleApp.exe를 인자로 실행시키면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > D:\temp\daeProxy\bin\Debug> <span style='color: blue; font-weight: bold'>daeProxy.exe SampleApp.exe</span> ExceptionThrown == System.IO.FileNotFoundException (ObjectID == 1475df86da0) Waiting for being attached from debugger, or press any key to continue... </pre> <br /> FileNotFoundException이 발생하는 시점에 실행이 멈추게 됩니다. 원한다면 이때 디버거를 attach하거나 덤프를 뜨는 식의 부가 동작을 하면 됩니다. 혹은 요청을 하시면 ^^ 그에 대한 옵션을 만들어 나가는 식으로 살을 붙이겠습니다. (또는, 아예 Pull Request를 날리시는 것도! ^^)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1179
(왼쪽의 숫자를 입력해야 합니다.)