Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 7개 있습니다.)
.NET Framework: 167. 다른 스레드의 호출 스택 덤프 구하는 방법
; https://www.sysnet.pe.kr/2/0/802

.NET Framework: 260. .NET 스레드 콜 스택 덤프 (2) - Managed Stack Explorer 소스 코드를 이용한 스택 덤프 구하는 방법
; https://www.sysnet.pe.kr/2/0/1162

.NET Framework: 261. .NET 스레드 콜 스택 덤프 (3) - MSE 소스 코드 개선
; https://www.sysnet.pe.kr/2/0/1163

.NET Framework: 262. .NET 스레드 콜 스택 덤프 (4) - .NET 4.0을 지원하지 않는 MSE 응용 프로그램 원인 분석
; https://www.sysnet.pe.kr/2/0/1164

.NET Framework: 311. .NET 스레드 콜 스택 덤프 (5) - ICorDebug 인터페이스 사용법
; https://www.sysnet.pe.kr/2/0/1249

.NET Framework: 392. .NET 스레드 콜 스택 덤프 (6) - MDbg를 이용한 방법
; https://www.sysnet.pe.kr/2/0/1534

.NET Framework: 606. .NET 스레드 콜 스택 덤프 (7) - ClrMD(Microsoft.Diagnostics.Runtime)를 이용한 방법
; https://www.sysnet.pe.kr/2/0/11043




.NET 스레드 콜 스택 덤프 (2) - Managed Stack Explorer 소스 코드를 이용한 스택 덤프 구하는 방법


예전에, 스레드 콜 스택을 뜨는 기능을 소개해 드린 적이 있지요.

.NET에서의 스레드 콜 스택 덤프
; https://www.sysnet.pe.kr/2/0/802

윗 글에서는 "현재 스레드가 아닌, 다른 스레드의 Call Stack"을 덤프하는 코드에 대한 이야기를 했었는데요.

하지만, 아쉽게도 이 기능이 그리 안정적이지 못합니다. 왜냐하면, 다음과 같은 식으로 현재 스레드가 동작중이냐 아니냐에 따라서 다른 처리를 해야 하기 때문입니다.

bool suspend = false;
if ((newThread.ThreadState & System.Threading.ThreadState.Suspended) != System.Threading.ThreadState.Suspended)
{
    newThread.Suspend();
    suspend = true;
}

DoCallStack();

if (suspend == true)
{
    newThread.Resume();
}

헛점이 눈에 보이시죠? 만약, Suspended 상태 체크를 하는 시점에 해당 스레드가 Suspended였다가 식의 평가 이후 곧바로 Resume 모드로 들어가면 콜스택을 남기는 함수는 실패하고 맙니다. 게다가 그 반대의 경우도 발생할 수 있습니다. 조건의 평가 시점에 "Running"이었다가 곧바로 Suspend로 넘어간 스레드의 경우에는 오히려 나중에 콜 스택을 남긴 후 곧바로 Resume 상태로 "강제로" 전환되어 버립니다.

어찌 보면, 가능성이 희박하기 때문에 그나마 mission-critical하지 않은 프로젝트에서는 허용될 수 있는 수준이라고 할 수 있겠지만, 문제는 거기서 끝이 아닙니다. 아래의 글에서도 나오지만, Suspend로 인한 lock이 유발될 수 있다는 경고가 각종 웹 자료에 널려 있기 때문입니다.

Thread.Suspend 메서드
; https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.thread.suspend

스레드의 동작을 동기화하기 위해 Suspend 및 Resume 메서드를 사용하지 마십시오.스레드를 일시 중단하면 스레드에서 실행하고 있는 코드를 알 수 있는 방법이 없습니다.스레드에서 보안 권한 확인 도중 잠금을 보유하고 있을 때 스레드를 일시 중단하면 AppDomain의 다른 스레드가 차단될 수 있습니다.스레드에서 클래스 생성자를 실행하고 있을 때 스레드를 일시 중단하면 AppDomain에서 해당 클래스를 사용하려고 시도하는 다른 스레드가 차단됩니다.따라서 교착 상태가 매우 쉽게 발생할 수 있습니다.


아쉽게도, 현재로써는 응용 프로그램 내에서 다른 스레드의 call stack을 얻을 수 있는 안전한 방법은 없습니다.




방법을 찾아보면, 일단 API 수준에서는 없지만 다행히 Debugger 수준의 힘을 빌려서 콜 스택을 남기는 것이 가능합니다.

How do I make a thread dump in .NET ? (a la JVM thread dumps)
; http://stackoverflow.com/questions/190236/how-do-i-make-a-thread-dump-in-net-a-la-jvm-thread-dumps

위의 덧글에 소개된 "MSE(Managed Stack Explorer)" 또한 ICorDebug 관련 기능을 이용하여 콜 스택을 남기는 유틸리티인데요. 다행히, MSE의 소스 코드가 공개되어 있기 때문에 이것을 잘 사용하면 어렵지 않게 콜스택을 남기는 것이 가능합니다.

그럼... ^^ 시도해 볼까요?

우선 다음의 codeplex 웹 사이트에서 MSE 소스 코드를 다운로드 받습니다.

Managed Stack Explorer 
; http://mse.codeplex.com/

일단, 기본적인 프로젝트 구성을 보면 아래와 같은데,

  • CorApi: IL 코드로 표현한 코드들 - 이 때문에 ilasm.exe를 이용하여 빌드됨.
  • CorApi2: CorApi 기반에서 C#으로 작성된 래퍼 코드들
  • MSE: Console Application 형식의 Windows Form 응용 프로그램, Post build event에서 CorApi + CorApi2 + MSE 어셈블리들을 ILMerge 에 의해 합쳐서 하나의 MSE.exe 생성
  • mseTest: NUnit을 이용한 MSE 프로젝트의 단위 테스트 프로젝트
  • UnitTestHelper: mseTest에 대한 Helper 프로젝트

만약 여러분들이 MSE 프로젝트 자체를 발전시키는 것이라면 ILMerge와 NUnit을 설치해서 위의 프로젝트 구조를 그대로 유지하면서 변경시키는 것이 좋습니다.

하지만, 여기서는 콜 스택을 남기는 기능만 구현할 것이기 때문에 mse.sln에서 변형을 가할 것입니다.

  1. UnitTestHelper와 mseTest 프로젝트를 제거.
  2. MSE 프로젝트의 "Post build event" 내용을 삭제 (ILMerge.exe 실행하는 명령행을 제거)

그다음 MSE.csproj 파일을 열어서 다음의 Import 노드를 주석처리합니다. (MSE.settings.targets 파일은 필요없으니 지우셔도 됩니다.)

<!-- Import MSE.settings.targets so the build can find the 3rd party tools -->
<--Import Project="..\MSE.Settings.targets" /-->

자, 그럼 이제부터 우리만의 예제 프로젝트를 만들어 봍 텐데요. 우선 ConsoleApplication1 프로젝트를 생성한 후 CorApi, CorApi2 프로젝트를 참조시킵니다. 이후, MSE 프로젝트에 있는 핵심 파일들을 추가하면 되는데, 다음과 같이 "mseLibrary"라는 폴더를 하나 만들고 MSE 프로젝트에 있는 mseLibrary 하위에 있는 파일들을 'Add as Link'로 추가합니다. (물론, MSE 프로젝트의 mseLibrary 폴더를 복사해서 ConsoleApplication1에 추가하고 MSE 프로젝트를 제거해도 됩니다.)

mse_clr_stack_1.png

이걸로 준비는 끝입니다. 그다음은 ICorDebug를 이용한 코드를 다음과 같은 식으로 작성하면 됩니다.

class Program
{
    static void Main(string[] args)
    {
        Process [] processes = Process.GetProcessesByName("Sample");
        if (processes.Length == 0)
        {
            return;
        }

        int targetProcessId = processes[0].Id;

        string txt = CorDebugger.GetDefaultDebuggerVersion();
        CorDebugger debugger = new CorDebugger(txt);
        using (ProcessInfo procInfo = new ProcessInfo(targetProcessId, debugger))
        {
            procInfo.UpdateAllStackTraces(0);

            foreach (var info in procInfo.ThreadInfos)
            {
                Console.WriteLine("================");
                foreach (var frame in info.Value.FrameStack)
                {
                    Console.WriteLine(frame.FunctionShortName);
                }
            }
        }

    }
}

뭐... 그다지 어렵지 않지요. ^^ 이처럼 ICorDebug 사용법은 간단한 반면, 제약 조건이 약간 까다로운 문제가 있습니다. 예를 들어, 이와 같은 콜 스택 덤프를 뜨기 위한 주의 사항이 2가지 정도 있습니다.

  • 스스로의 프로세스(EXE)에 대한 덤프를 뜰 수 없다.
  • 덤프를 뜨려는 프로세스와 대상이 되는 프로세스의 플랫폼(x86/x64)이 같아야 한다.

이 때문에, 무조건 새로운 프로세스를 만들어서(spawn) 특정 EXE에 대한 콜스택을 얻어낸다고 생각하면 편합니다.

즉, 정상적인 콜 스택 덤프를 뜨려면 위의 코드 예제를 x86과 x64 바이너리로 각각 빌드 한 다음(예를 들어, dump32.exe, dump64.exe), 대상 프로세스가 x86인지, x64인지 알아낸 후에 그에 맞는 덤프 프로세스를 실행시켜 주어야 합니다. 게다가, 그 프로세스가 자기 자신이라면 어차피 스스로의 덤프를 뜰 수 없으므로 새로운 프로세스를 만들어 내야 합니다.

그렇긴 해도 ^^, 어쨌든 MSE 프로젝트가 ICorDebug에 대한 래퍼 클래스를 잘 만들어 두어서 일이 쉬워진 것만은 분명합니다.

첨부된 파일은 위의 코드를 포함한 예제 프로젝트입니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/21/2023]

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

비밀번호

댓글 작성자
 




... 151  152  153  154  155  156  [157]  158  159  160  161  162  163  164  165  ...
NoWriterDateCnt.TitleFile(s)
1121정성태9/12/201127569Java: 5. WTP 내에서 서블릿을 실행하는 환경
1120정성태9/11/201127484.NET Framework: 239. IHttpHandler.IsReusable 속성 이야기파일 다운로드1
1119정성태9/11/201126612Java: 4. 이클립스에 WTP SDK가 설치되지 않는다면? [2]
1118정성태9/11/201138252Java: 3. 이클립스에서 서블릿 디버깅하는 방법 [4]
1117정성태9/9/201125547제니퍼 .NET: 17. 제니퍼 닷넷 적용 사례 (2) - 웹 애플리케이션 hang의 원인을 알려주다.
1116정성태9/8/201156626Java: 2. 자바에서 "Microsoft SQL Server JDBC Driver" 사용하는 방법
1115정성태9/4/201130103Java: 1. 닷넷 개발자가 처음 실습해 본 서블릿
1114정성태9/4/201134613Math: 2. "Zhang Suen 알고리즘(세선화, Thinning/Skeletonization)"의 C# 버전 [4]파일 다운로드1
1113정성태9/2/201134201개발 환경 구성: 129. Hyper-V에 CentOS 설치하기
1112정성태9/2/201150931Linux: 1. 리눅스 <-> 윈도우 원격 접속 프로그램 사용 [3]
1111정성태8/29/201125395제니퍼 .NET: 16. 적용 사례 (1) - DB Connection Pooling을 사용하지 않았을 때의 성능 저하를 알려주다. [1]
1110정성태8/26/201126726오류 유형: 136. RDP 접속이 불연속적으로 끊기는 문제
1109정성태8/26/201129581오류 유형: 135. 어느 순간 Active Directory 접속이 안되는 문제
1108정성태8/22/201131168오류 유형: 134. OLE/COM Object Viewer - DllRegisterServer in IVIEWERS.DLL failed. [1]
1107정성태8/21/201128877디버깅 기술: 43. Windows Form의 Load 이벤트에서 발생하는 예외가 Visual Studio에서 잡히지 않는 문제
1106정성태8/20/201127207웹: 26. FailedRequestTracing 설정으로 인한 iisexpress.exe 비정상 종료 문제
1105정성태8/19/201127138.NET Framework: 238. Web Site Model 프로젝트에서 Trace.WriteLine 출력이 dbgview.exe에서 확인이 안 되는 문제파일 다운로드1
1104정성태8/19/201127291웹: 25. WebDev보다 IIS Express가 더 나은 점 - 다중 가상 디렉터리 매핑 [1]
1103정성태8/19/201133185오류 유형: 133. WCF 포트 바인딩 실패 오류 - TCP error(10013) [1]
1102정성태8/19/201130956Math: 1. 방탈출3 - Room 10의 '중복가능한 조합' 문제를 위한 C# 프로그래밍 [2]파일 다운로드1
1101정성태8/19/201129629.NET Framework: 237. WCF AJAX 서비스와 JavaScript 간의 DateTime 연동 [1]파일 다운로드1
1100정성태8/17/201128733.NET Framework: 236. SqlDbType - DateTime, DateTime2, DateTimeOffset의 차이점파일 다운로드1
1099정성태8/15/201128156오류 유형: 132. 어느 순간 갑자기 접속이 안 되는 TFS 서버
1098정성태8/15/201150169웹: 24. 네이버는 어떻게 로그인 처리를 할까요? [2]
1097정성태8/15/201121472.NET Framework: 235. 메서드의 메타 데이터 토큰 값으로 클래스를 찾아내는 방법
1096정성태8/15/201125631디버깅 기술: 42. Watson Bucket 정보를 이용한 CLR 응용 프로그램 예외 분석 - (2)
... 151  152  153  154  155  156  [157]  158  159  160  161  162  163  164  165  ...