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)
13518정성태1/9/20242397닷넷: 2197. C# - ClientWebSocket의 Ping, Pong 처리
13517정성태1/8/20242226스크립트: 63. Python - 공개 패키지를 이용한 위성 이미지 생성 (pystac_client, odc.stac)
13516정성태1/7/20242347닷넷: 2196. IIS - AppPool의 "Disable Overlapped Recycle" 옵션의 부작용
13515정성태1/6/20242630닷넷: 2195. async 메서드 내에서 C# 7의 discard 구문 활용 사례 [1]
13514정성태1/5/20242326개발 환경 구성: 702. IIS - AppPool의 "Disable Overlapped Recycle" 옵션
13513정성태1/5/20242228닷넷: 2194. C# - WebActivatorEx / System.Web의 PreApplicationStartMethod 특성
13512정성태1/4/20242185개발 환경 구성: 701. IIS - w3wp.exe 프로세스의 ASP.NET 런타임을 항상 Warmup 모드로 유지하는 preload Enabled 설정
13511정성태1/4/20242202닷넷: 2193. C# - ASP.NET Web Application + OpenAPI(Swashbuckle) 스펙 제공
13510정성태1/3/20242122닷넷: 2192. C# - 특정 실행 파일이 있는지 확인하는 방법 (Linux)
13509정성태1/3/20242172오류 유형: 887. .NET Core 2 이하의 프로젝트에서 System.Runtime.CompilerServices.Unsafe doesn't support netcoreapp2.0.
13508정성태1/3/20242245오류 유형: 886. ORA-28000: the account is locked
13507정성태1/2/20242904닷넷: 2191. C# - IPGlobalProperties를 이용해 netstat처럼 사용 중인 Socket 목록 구하는 방법파일 다운로드1
13506정성태12/29/20232485닷넷: 2190. C# - 닷넷 코어/5+에서 달라지는 System.Text.Encoding 지원
13505정성태12/27/20233014닷넷: 2189. C# - WebSocket 클라이언트를 닷넷으로 구현하는 예제 (System.Net.WebSockets)파일 다운로드1
13504정성태12/27/20232595닷넷: 2188. C# - ASP.NET Core SignalR로 구현하는 채팅 서비스 예제파일 다운로드1
13503정성태12/27/20232468Linux: 67. WSL 환경 + mlocate(locate) 도구의 /mnt 디렉터리 검색 문제
13502정성태12/26/20232581닷넷: 2187. C# - 다른 프로세스의 환경변수 읽는 예제파일 다운로드1
13501정성태12/25/20232331개발 환경 구성: 700. WSL + uwsgi - IPv6로 바인딩하는 방법
13500정성태12/24/20232428디버깅 기술: 194. Windbg - x64 가상 주소를 물리 주소로 변환
13498정성태12/23/20233117닷넷: 2186. 한국투자증권 KIS Developers OpenAPI의 C# 래퍼 버전 - eFriendOpenAPI NuGet 패키지
13497정성태12/22/20232512오류 유형: 885. Visual Studiio - error : Could not connect to the remote system. Please verify your connection settings, and that your machine is on the network and reachable.
13496정성태12/21/20232522Linux: 66. 리눅스 - 실행 중인 프로세스 내부의 환경변수 설정을 구하는 방법 (gdb)
13495정성태12/20/20232442Linux: 65. clang++로 공유 라이브러리의 -static 옵션 빌드가 가능할까요?
13494정성태12/20/20232620Linux: 64. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기
13493정성태12/19/20232796닷넷: 2185. C# - object를 QueryString으로 직렬화하는 방법
13492정성태12/19/20232441개발 환경 구성: 699. WSL에 nopCommerce 예제 구성
1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...