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

비밀번호

댓글 작성자
 




... [16]  17  18  19  20  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13239정성태2/1/20233814디버깅 기술: 186. C# - CacheDependency의 숨겨진 예외 - System.Web.HttpException
13238정성태1/31/20235924.NET Framework: 2092. IIS 웹 사이트를 TLS 1.2 또는 TLS 1.3 프로토콜로만 운영하는 방법
13237정성태1/30/20235600.NET Framework: 2091. C# - 웹 사이트가 어떤 버전의 TLS/SSL을 지원하는지 확인하는 방법
13236정성태1/29/20235138개발 환경 구성: 663. openssl을 이용해 인트라넷 IIS 사이트의 SSL 인증서 생성
13235정성태1/29/20234711개발 환경 구성: 662. openssl - 윈도우 환경의 명령행에서 SAN 적용하는 방법
13234정성태1/28/20235805개발 환경 구성: 661. dnSpy를 이용해 소스 코드가 없는 .NET 어셈블리의 코드를 변경하는 방법 [1]
13233정성태1/28/20237191오류 유형: 840. C# - WebClient로 https 호출 시 "The request was aborted: Could not create SSL/TLS secure channel" 예외 발생
13232정성태1/27/20234930스크립트: 43. uwsgi의 --processes와 --threads 옵션
13231정성태1/27/20233911오류 유형: 839. python - TypeError: '...' object is not callable
13230정성태1/26/20234268개발 환경 구성: 660. WSL 2 내부로부터 호스트 측의 네트워크로 UDP 데이터가 1개의 패킷으로만 제한되는 문제
13229정성태1/25/20235284.NET Framework: 2090. C# - UDP Datagram의 최대 크기
13228정성태1/24/20235386.NET Framework: 2089. C# - WMI 논리 디스크가 속한 물리 디스크의 정보를 얻는 방법 [2]파일 다운로드1
13227정성태1/23/20235062개발 환경 구성: 659. Windows - IP MTU 값을 바꿀 수 있을까요? [1]
13226정성태1/23/20234748.NET Framework: 2088. .NET 5부터 지원하는 GetRawSocketOption 사용 시 주의할 점
13225정성태1/21/20233946개발 환경 구성: 658. Windows에서 실행 중인 소켓 서버를 다른 PC 또는 WSL에서 접속할 수 없는 경우
13224정성태1/21/20234358Windows: 221. Windows - Private/Public/Domain이 아닌 네트워크 어댑터 단위로 방화벽을 on/off하는 방법
13223정성태1/20/20234538오류 유형: 838. RDP 연결 오류 - The two computers couldn't connect in the amount of time allotted
13222정성태1/20/20234226개발 환경 구성: 657. WSL - DockerDesktop.vhdx 파일 위치를 옮기는 방법
13221정성태1/19/20234436Linux: 57. C# - 리눅스 프로세스 메모리 정보파일 다운로드1
13220정성태1/19/20234527오류 유형: 837. NETSDK1045 The current .NET SDK does not support targeting .NET ...
13219정성태1/18/20234123Windows: 220. 네트워크의 인터넷 접속 가능 여부에 대한 판단 기준
13218정성태1/17/20234032VS.NET IDE: 178. Visual Studio 17.5 (Preview 2) - 포트 터널링을 이용한 웹 응용 프로그램의 외부 접근 허용
13217정성태1/13/20234655디버깅 기술: 185. windbg - 64비트 운영체제에서 작업 관리자로 뜬 32비트 프로세스의 덤프를 sos로 디버깅하는 방법
13216정성태1/12/20234885디버깅 기술: 184. windbg - 32비트 프로세스의 메모리 덤프인 경우 !peb 명령어로 나타나지 않는 환경 변수
13215정성태1/11/20236541Linux: 56. 리눅스 - /proc/pid/stat 정보를 이용해 프로세스의 CPU 사용량 구하는 방법 [1]
13214정성태1/10/20236006.NET Framework: 2087. .NET 6부터 SourceGenerator와 통합된 System.Text.Json [1]파일 다운로드1
... [16]  17  18  19  20  21  22  23  24  25  26  27  28  29  30  ...