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

비밀번호

댓글 작성자
 




... [196]  197 
NoWriterDateCnt.TitleFile(s)
40정성태7/23/200321858COM 개체 관련: 10. IE BHO 개체를 개발할 때, 인터넷 익스플로러가 아닌 탐색기에서 활성화 되는 문제 해결 [1]
41김성현7/24/200320685    답변글 COM 개체 관련: 10.1. [답변]: IE BHO 개체를 개발할 때, 인터넷 익스플로러가 아닌 탐색기에서 활성화 되는 문제 해결
42정성태7/29/200318636        답변글 COM 개체 관련: 10.2. feedback 을 받기 위해서 답변 기능을 가능하게 해두었습니다.
39정성태7/17/200324431VS.NET IDE: 5. 원격 제어 3가지 방법
38정성태7/17/200320983.NET Framework: 8. IIS 서버 재설치와 ASP.NET 서비스의 문제점
36정성태7/17/200321632.NET Framework: 7. 시행착오 - WebService 참조 추가 오류
35정성태7/17/200322175.NET Framework: 6. Win2000에서의 .NET COM+ 자동 등록 오류 발생 해결
34정성태7/17/200320823VS.NET IDE: 4. VC++ 원격 디버깅파일 다운로드1
33정성태7/17/200320992VS.NET IDE: 3. Win2000 NAT 서비스
32정성태7/17/200322174COM 개체 관련: 9. _bstr_t, CComBSTR, string 클래스 사용 [1]
31정성태7/17/200319256COM 개체 관련: 8. IDL 구문에서 구조체를 pack 하는 방법
30정성태7/17/200336510VC++: 7. [STL] vector 사용법 및 reference 사용예 [1]파일 다운로드1
28정성태7/17/200320921스크립트: 3. Programming Microsoft Internet Explorer 5 - CHM 파일
29정성태7/17/200320399    답변글 스크립트: 3.1. Programming Microsoft Internet Explorer 5 - 소스코드
27정성태7/17/200319354COM 개체 관련: 7. HTML Control에서 DELETE, 화살표 키 등이 안 먹는 문제
26정성태7/17/200320524COM 개체 관련: 6. WebBrowser 콘트롤에서 프레임을 구하는 소스
25정성태7/17/200318128COM 개체 관련: 5. C++ Attributes - Make COM Programming a Breeze with New Feature in Visual Studio .NET [2]파일 다운로드1
24정성태7/17/200321749.NET Framework: 5. (MHT 변환해서 가져온 글) .NET 의 COM+ 서비스 사용파일 다운로드1
23정성태7/17/200325436.NET Framework: 4. webservice.htc - HTML Script에서도 웹서비스 엑세스 [2]파일 다운로드1
22정성태7/17/200319990.NET Framework: 3. .NET Framework SDK 퀵 스타트 자습서
21정성태7/17/200319039.NET Framework: 2. 김현승님의 "ASP.NET & .NET EnterpriseServices & Remoting 코드 템플릿"
20정성태2/15/200526097VS.NET IDE: 2. Platform SDK 설치
19정성태7/17/200322029.NET Framework: 1. JScript.NET 강좌 사이트[영문]
18정성태7/17/200319439COM 개체 관련: 4. Exchanging Data Over the Internet Using XML [1]파일 다운로드1
17정성태7/17/200327384VC++: 6. Win32 API Hook - 소스는 "공개소스"에있습니다. [2]
16정성태7/17/200319699COM 개체 관련: 3. IE 툴밴드의 위치문제파일 다운로드1
... [196]  197