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

비밀번호

댓글 작성자
 




1  2  [3]  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13568정성태2/27/20241889닷넷: 2220. C# - .NET Framework 프로세스의 LoaderOptimization 설정을 확인하는 방법파일 다운로드1
13567정성태2/27/20241836오류 유형: 898. .NET Framework 3.5 이하에서 mscoree.tlb 참조 시 System.BadImageFormatException파일 다운로드1
13566정성태2/27/20241892오류 유형: 897. Windows 7 SDK 설치 시 ".NET Development" 옵션이 비활성으로 선택이 안 되는 경우
13565정성태2/23/20241734닷넷: 2219. .NET CLR2 보안 모델에서의 개별 System.Security.Permissions 제어
13564정성태2/22/20241980Windows: 259. Hyper-V Generation 1 유형의 VM을 Generation 2 유형으로 바꾸는 방법
13563정성태2/21/20241961디버깅 기술: 196. windbg - async/await 비동기인 경우 메모리 덤프 분석의 어려움
13562정성태2/21/20242008오류 유형: 896. ASP.NET - .NET Framework 기본 예제에서 System.Web에 대한 System.IO.FileNotFoundException 예외 발생
13561정성태2/20/20242083닷넷: 2218. C# - (예를 들어, Socket) 비동기 I/O에 대한 await 호출 시 CancellationToken을 이용한 취소파일 다운로드1
13560정성태2/19/20242130디버깅 기술: 195. windbg 분석 사례 - Semaphore 잠금으로 인한 Hang 현상 (닷넷)
13559정성태2/19/20242954오류 유형: 895. ASP.NET - System.Security.SecurityException: 'Requested registry access is not allowed.'
13558정성태2/18/20242207닷넷: 2217. C# - 최댓값이 1인 SemaphoreSlim 보다 Mutex 또는 lock(obj)를 선택하는 것이 나은 이유
13557정성태2/18/20241941Windows: 258. Task Scheduler의 Author 속성 값을 변경하는 방법
13556정성태2/17/20242002Windows: 257. Windows - Symbolic (hard/soft) Link 및 Junction 차이점
13555정성태2/15/20242152닷넷: 2216. C# - SemaphoreSlim 사용 시 주의점
13554정성태2/15/20241875VS.NET IDE: 189. Visual Studio - 닷넷 소스코드 디컴파일 찾기가 안 될 때
13553정성태2/14/20241967닷넷: 2215. windbg - thin/fat lock 없이 동작하는 Monitor.Wait + Pulse
13552정성태2/13/20241916닷넷: 2214. windbg - Monitor.Enter의 thin lock과 fat lock
13551정성태2/12/20242107닷넷: 2213. ASP.NET/Core 웹 응용 프로그램 - 2차 스레드의 예외로 인한 비정상 종료
13550정성태2/11/20242271Windows: 256. C# - Server socket이 닫히면 Accept 시켰던 자식 소켓이 닫힐까요?
13549정성태2/3/20242732개발 환경 구성: 706. C# - 컨테이너에서 실행하기 위한 (소켓) 콘솔 프로젝트 구성
13548정성태2/1/20242532개발 환경 구성: 705. "Docker Desktop for Windows" - ASP.NET Core 응용 프로그램의 소켓 주소 바인딩(IPv4/IPv6 loopback, Any)
13547정성태1/31/20242317개발 환경 구성: 704. Visual Studio - .NET 8 프로젝트부터 dockerfile에 추가된 "USER app" 설정
13546정성태1/30/20242157Windows: 255. (디버거의 영향 등으로) 대상 프로세스가 멈추면 Socket KeepAlive로 연결이 끊길까요?
13545정성태1/30/20242007닷넷: 2212. ASP.NET Core - 우선순위에 따른 HTTP/HTTPS 호스트:포트 바인딩 방법
13544정성태1/30/20242033오류 유형: 894. Microsoft.Data.SqlClient - Could not load file or assembly 'System.Security.Permissions, ...'
13543정성태1/30/20242065Windows: 254. Windows - 기본 사용 중인 5357 포트 비활성화는 방법
1  2  [3]  4  5  6  7  8  9  10  11  12  13  14  15  ...