WinDbg + SOS로 크래시 덤프 읽기 ── 수집 이후의 실무 분석 입문

· 업데이트: · · WinDbg, SOS, 크래시 덤프, .NET, CSharp, 디버깅, PDB, 장애 조사, 기술 상담

수정 이력(5건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
기사 서두에 「이 기사의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1283건) 대응으로 본문을 업데이트했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
주요 명령에 대해 출력의 어느 행을 읽는지 주석을 달았습니다(출력은 공식 문서 게재분의 인용입니다). 더불어 심볼 유무에 따라 무엇이 보이지 않는지의 비교표, 연습용 덤프를 직접 만드는 절차, 전제와 약어 설명을 추가했습니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.21635372)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「WinDbg + SOS로 크래시 덤프 읽기 ── 수집 이후의 실무 분석 입문」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/windbg-sos-crash-dump-analysis/

DOI(등록된 아카이브)
10.5281/zenodo.21635372
DOI(마지막 등록 버전)
10.5281/zenodo.21635373

이전 기사 「Windows 크래시 덤프 수집 입문」에서는 WER LocalDumps·ProcDump·MiniDumpWriteDump를 사용해 덤프를 「수집하는」 지점까지 정리했습니다. 다만 덤프는 수집한 것만으로는 아무것도 알려 주지 않습니다. 실제로 「어느 스레드가」「왜」 종료됐는지, 혹은 「무엇이」 메모리를 계속 잡고 있는지 읽어 내야 비로소 조사 재료가 됩니다.

이 기사에서는 수집편에 이어, 수집한 덤프를 WinDbg와 SOS 확장으로 실제로 읽는 절차에 한정해 씁니다. 설치와 심볼 설정, .NET 앱에서 필수가 되는 SOS 확장 로드, !clrstack이나 !dumpheap -stat 같은 대표적인 명령으로 「무엇을 보고, 어떻게 판단할지」, 네이티브 크래시의 !analyze -v, 그리고 WinDbg를 쓰지 않는 dotnet-dump analyze와의 역할 분담까지 다룹니다.

이 기사의 전제

항목 내용
대상 독자 이미 크래시 덤프(.dmp)가 있고, 이제부터 내용을 읽을 Windows / .NET 앱 개발·유지보수 담당
선수 지식 C# 스택 트레이스를 읽을 수 있을 것. WinDbg 사용 경험은 전제로 두지 않습니다
선행 기사 덤프를 수집하는 방법은 수집편에서 다룹니다. 이 기사는 「이미 덤프가 있는」 지점부터 시작합니다. 아직 읽지 않았더라도 제 2.4절 절차로 연습용 덤프를 직접 만들면 이어서 읽을 수 있습니다
사용하는 도구 WinDbg(현행 버전), SOS 확장, 필요에 따라 dotnet-dump

이후 별도 설명 없이 쓰는 약어는 다음 네 가지입니다.

약어 풀네임 이 기사에서의 의미
WER Windows Error Reporting Windows 표준 오류 보고 메커니즘. LocalDumps 설정으로 크래시 시 덤프를 자동 저장할 수 있습니다(설정 절차는 수집편)
PDB Program Database 빌드 시 생성되는 심볼 파일. 소스 파일 이름·행 번호의 대응표를 가집니다(제 8장)
CLR Common Language Runtime .NET 실행 엔진 본체. clr.dll / coreclr.dll로 로드됩니다1
SOS Son of Strike 그 CLR 내부를 읽기 위한 디버거 확장. 제 3장에서 로드합니다2

1. 먼저 결론

  • 덤프 분석의 주역은 WinDbg(현행 버전. 구칭 WinDbg Preview)입니다. winget install Microsoft.WinDbg 또는 Microsoft Store에서 구할 수 있으며, Windows 10 Anniversary Update (1607) 이후 / Windows 11의 x64·ARM64에서 동작합니다. 3
  • 심볼(PDB)을 로드하지 못해도 !clrstack·!dumpheap -stat·!gcroot 등은 CLR의 메타데이터·힙 데이터에서 그대로 동작합니다. 잃는 것은 매니지드 소스 파일 이름·행 번호와 네이티브 프레임의 심볼 이름입니다. 그래도 소스 행까지 따라가려면 이야기가 달라지므로, _NT_SYMBOL_PATH에 Microsoft 퍼블릭 심볼 서버와 자사 PDB 위치를 모두 넣는 것이 정석입니다. 4 심볼 설정 절차는 제 2장, 「PDB가 있을 때/없을 때 무엇이 보이지 않는지」와 운영은 제 8장에 정리했습니다.
  • .NET(Framework / Core / 5+) 앱의 덤프에서는 SOS 확장을 로드해야 비로소 매니지드 정보가 보입니다. 네이티브 k(스택 표시)만으로는 C# 코드를 따라갈 수 없습니다. 2
  • 대표적인 조사 유형은 세 가지입니다. 예외로 종료됐다면 !clrstack → !pe, 메모리가 계속 늘어난다면 !dumpheap -stat → !gcroot, 네이티브 크래시라면 !analyze -v부터 들어갑니다.
  • WinDbg를 쓰지 않는 선택지로 dotnet-dump analyze가 있습니다. SOS 명령의 상당수를 그대로 쓰지만, 네이티브 스택 프레임은 다루지 못합니다. 네이티브 DLL이나 COM이 얽히지 않는 매니지드 전용 조사이면 이쪽이 도입이 가볍습니다. 5
  • 여기서 쓰는 절차는 「읽는 방법」이지 「수집하는 방법」이 아닙니다. 덤프 취득 방법(WER / ProcDump / MiniDumpWriteDump)은 수집편을, 크래시 시 로그와 맞춰 보는 설계는 「크래시 시 로그와 덤프를 남기는 설계」를 참조하세요.

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 19건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. WinDbg를 설치하고 심볼을 연결한다

2.1 설치

현행 WinDbg는 다음 중 하나로 설치합니다. 3

winget install Microsoft.WinDbg

Microsoft Store를 통해서도 같은 엔진이 들어가며, 명령·확장·워크플로는 공통입니다. 설치 후에는 자동 업데이트되므로(Store·직접 설치의 경우 백그라운드에서, winget의 경우 winget upgrade Microsoft.WinDbg로) 버전 차이에 따른 동작 차이에는 크게 시달리지 않습니다. 3

2.2 덤프 열기

windbg -z C:\CrashDumps\MyApp\MyApp_20260702_101500.dmp

-z는 덤프 파일을 지정해 시작하는 옵션입니다. GUI에서 여는 경우에는 File 메뉴의 덤프 열기 항목(WinDbg Classic에서의 표기는 Open crash dump, 바로 가기는 Ctrl+D)을 씁니다. 6

참고로 이 기사에는 화면 캡처를 싣지 않습니다. 대신 명령을 입력하는 위치와 메뉴 이름을 글로 씁니다. 덤프를 열면 Command 도구 창이 열리고, 7 그 맨 아래의 0:000> 같은 프롬프트가 입력란입니다. 이후에 나오는 !가 붙은 명령은 모두 이 Command 창에 입력합니다(Microsoft의 분석 예에서도 0:000> !analyze -v 형태로 적혀 있습니다). 8 프롬프트의 0:000은 「프로세스 0의 스레드 0을 선택 중」이라는 뜻이며, 제 4장의 ~5s로 스레드를 바꾸면 0:005>로 바뀝니다.

2.3 심볼 경로 설정

Windows 디버거가 심볼 파일(PDB)을 찾는 위치는 _NT_SYMBOL_PATH 환경 변수, 또는 세션 안의 .sympath 명령으로 지정합니다. 4 실무에서는 Microsoft 퍼블릭 심볼 서버와 자사 PDB 위치를 모두 넣는 것이 기본형입니다.

.symfix C:\Symbols\Microsoft
.sympath+ C:\Symbols\MyApp
.reload
  • .symfix는 Microsoft 퍼블릭 심볼 서버(https://msdl.microsoft.com/download/symbols) 경로를 지정한 로컬 캐시와 함께 설정하는 바로 가기입니다. OS 표준 DLL 심볼은 여기서 자동 다운로드됩니다. 9
  • .sympath+는 기존 경로에 자사 PDB 위치를 추가합니다. 자사 코드의 PDB는 직접 준비해야 하며, Microsoft 심볼 서버에는 올라가지 않습니다.
  • .reload로 다시 로드해 모듈 목록의 심볼 상태를 확인합니다.

환경 변수로 영구 설정하는 경우에는 다음 형태입니다. CI나 빌드 서버의 자동 분석에는 이쪽이 맞습니다.

set _NT_SYMBOL_PATH=srv*C:\Symbols\Microsoft*https://msdl.microsoft.com/download/symbols;C:\Symbols\MyApp

심볼이 올바르게 읽히는지 여부는 lm(loaded modules) 명령으로 모듈 목록을 내고, 대상 모듈이 pdb symbols인지로 확인할 수 있습니다. deferred로 남아 있으면 아직 심볼이 해석되지 않은 상태입니다.

2.4 연습용 덤프를 직접 만들기

갑자기 운영 장애 덤프로 연습하기는 힘드므로, 일부러 죽는 미니 앱을 마련해 이 기사의 절차를 한 바퀴 돌려 볼 수 있게 해 두면 이해가 빨라집니다. 읽을 덤프가 없으면 먼저 여기서 시작하세요.

콘솔 앱을 하나 만듭니다(.NET 8 / C# 12. dotnet new console -n CrashLab으로 만든 프로젝트의 Program.cs를 다음으로 통째로 바꿉니다).

// Program.cs
using System;
using System.Collections.Generic;

// (A) 제5장의 !dumpheap -stat / !gcroot용: 해제되지 않은 채 늘어나는 배열
var cache = new List<byte[]>();
for (int i = 0; i < 2000; i++)
{
    cache.Add(new byte[100_000]);
}
Console.WriteLine($"cached: {cache.Count} blocks");

// (B) 제4장의 !clrstack / !pe용: 처리되지 않은 예외로 프로세스를 종료
string? name = null;
Console.WriteLine(name!.Length);   // 여기서 NullReferenceException

이어서 Sysinternals의 ProcDump로 「처리되지 않은 예외가 일어나면 풀 덤프를 쓴다」로 설정하고 이 앱을 시작합니다. -ma는 풀 덤프, -e는 「프로세스가 처리되지 않은 예외를 만났을 때 덤프를 쓴다」옵션, -x <출력 폴더> <실행 파일>은 지정한 실행 파일을 ProcDump 자신이 시작해 감시하는 옵션입니다. 10

procdump.exe -accepteula -ma -e -x C:\CrashDumps CrashLab.exe

이미 동작 중인 프로세스를 대상으로 하려면 procdump.exe -accepteula -ma -e CrashLab.exe처럼 프로세스 이름(또는 PID)을 넘깁니다. 출력되는 덤프의 기본 파일 이름은 PROCESSNAME_YYMMDD_HHMMSS.dmp입니다. 10

만들어진 .dmp를 제 2.2절 절차로 WinDbg에 읽히면, 제 4장과 제 5장을 이 한 파일로 연습할 수 있습니다. 매니지드 전용 조사로 충분하면 dotnet-dump collect --name CrashLab으로도 동등한 덤프를 수집할 수 있습니다(제 7장). 다만 dotnet-dump collect는 「임의의 시점에 수집하는」 도구이므로, 죽은 순간을 잡고 싶다면 ProcDump 또는 WER LocalDumps를 사용하세요.

3. SOS 확장 로드

.NET 앱의 덤프는 네이티브 WinDbg 명령만으로는 「매니지드 힙의 내용」「C# 스택 프레임」「예외 객체의 내용」이 보이지 않습니다. 이 공백을 메우는 것이 SOS(Son of Strike) 확장입니다. 힙 조사, 힙 손상 검출, 런타임 내부 데이터 형식 표시, 실행 중인 매니지드 코드 상태 파악까지 SOS 명령을 통해 수행합니다. 2

3.1 런타임에 따른 차이

대상 앱이 .NET Framework인지 .NET (Core) / .NET 5+인지에 따라, 로드해야 할 런타임과 SOS의 출처가 달라집니다.

대상 런타임 본체 로드 명령
.NET Framework clr.dll .loadby sos clr
.NET Core / .NET 5+ coreclr.dll .loadby sos coreclr

.loadby는 지정한 모듈(clr이나 coreclr)이 있는 디렉터리에서, 같은 위치의 확장 DLL(sos.dll)을 찾아 로드하는 명령입니다. 전체 경로를 치지 않고, 덤프를 수집한 환경에 맞는 버전의 SOS를 확실히 가져올 수 있는 점이 이점입니다. 1

버전 10.0.18317.1001 이후의 WinDbg·cdb에서는 대상 프로세스가 coreclr.dll(또는 Linux/macOS의 libcoreclr.so)을 로드한 것을 감지하면 .NET용 확장을 Microsoft Extension Gallery에서 자동으로 로드합니다. 1 자동 로드가 잘 되지 않는 경우나 이전 버전 디버거를 쓰는 경우에, 위의 .loadby를 직접 입력합니다.

3.2 SOS를 찾지 못하는 경우

자동 로드가 동작하지 않는 환경에서는 dotnet-sos 도구로 로컬에 설치할 수 있습니다.

dotnet tool install --global dotnet-sos
dotnet-sos install

설치 후에는 WinDbg에서 다음과 같이 수동 로드도 할 수 있습니다(이전 디버거에서는 이쪽이 필요할 수 있습니다). 11

.load %USERPROFILE%\.dotnet\sos\sos.dll

3.3 로드되었는지 확인

!sos.help

또는 대상이 Core 계열이면 !Threads를, Framework 계열이면 !sosstatus를 시험해, 오류 없이 어떤 정보가 돌아오면 로드는 성공입니다. 여기서 명령이 Unable to find module 같은 오류로 실패하면, 거의 심볼 경로 또는 런타임 불일치(덤프 수집 환경과 로컬 런타임의 비트 수·버전 차이 등)가 원인입니다. 다음 장의 명령 이전에 여기서 막히는 경우는 실무에서도 드물지 않습니다.

4. 예외와 스택 읽기 ── !clrstack과 !pe

처리되지 않은 예외로 크래시한 덤프의 첫 수입니다.

!threads

먼저 !Threads(lldb 환경에서는 clrthreads 별칭)로 매니지드 스레드 목록과 각 스레드의 Exception 열을 확인합니다. 12 예외를 가진 스레드가 있으면 그 스레드로 전환합니다.

출력은 한 행이 한 스레드인 표이며, 디버거상의 일련번호·CLR 스레드 ID·OS 스레드 ID에 더해, 실행 중인 애플리케이션 도메인을 나타내는 Domain 열, COM 아파트먼트 모드를 나타내는 APT 열, 그 스레드에서 마지막으로 throw된 예외를 나타내는 Exception 열이 나열됩니다. 12 먼저 볼 것은 Exception 열뿐입니다. 여기에 System.NullReferenceException 같은 형식 이름이 들어 있는 행이 조사 대상이며, 그 행 왼쪽 끝의 일련번호를 적어 둡니다.

~5s
!clrstack

~<번호>s는 적어 둔 일련번호의 스레드로 전환하는 명령입니다. 이어지는 !CLRStack은 매니지드 코드만의 스택 트레이스를 표시합니다. 13 인수나 변수까지 보고 싶으면 -a(-l과 -p를 합친 바로 가기)를 붙입니다.

!clrstack -a

출력은 다음 형태가 됩니다(Microsoft Learn에 실린 clrstack 예의 발췌입니다. dotnet-dump 세션 예이지만, WinDbg의 !clrstack도 같은 표시입니다). 5

OS Thread Id: 0x573d (0)
    Child SP               IP Call Site
00007FFD28B42C58 00007fb22c1a8ed9 [HelperMethodFrame_PROTECTOBJ: 00007ffd28b42c58] System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[], System.Signature, Boolean, Boolean)
00007FFD28B42E20 00007FB1B18D33ED SymbolTestApp.Program.Foo4(System.String) [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 54]
00007FFD28B42ED0 00007FB1B18D2FC4 SymbolTestApp.Program.Foo2(Int32, System.String) [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 29]
00007FFD28B42F00 00007FB1B18D2F5A SymbolTestApp.Program.Foo1(Int32, System.String) [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 24]
00007FFD28B42F30 00007FB1B18D168E SymbolTestApp.Program.Main(System.String[]) [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 19]
00007FFD28B43210 00007fb22aa9cedf [GCFrame: 00007ffd28b43210]

어느 행을 읽을지는 다음과 같습니다.

  1. Call Site 열만 아래에서 위로 읽습니다. 아래가 호출한 쪽, 위가 호출된 쪽입니다. 이 예라면 Main → Foo1 → Foo2 → Foo4로, 어떤 경로로 죽는 지점까지 왔는지 읽을 수 있습니다.
  2. 각괄호의 [... .cs @ 54]가 소스 파일 이름과 행 번호입니다. 여기가 나오면 심볼은 해석된 것입니다. 나오지 않을 때의 대응은 제 8장입니다.
  3. [HelperMethodFrame_...] [GCFrame: ...] 행은 건너뜁니다. 런타임 내부 프레임이며, 자사 코드 버그와 직접 대응하지 않습니다.
  4. Child SP와 IP 두 열은 일반적인 조사에서는 읽지 않아도 됩니다. 각각 스택 포인터와 명령 포인터이며, !dumpstackobjects 등에 주소를 넘길 때만 씁니다.
  • CLRStack은 CLR 메타데이터에서 매니지드 프레임을 직접 열거하므로, 심볼 유무는 프레임이 표시되는지에 영향을 주지 않습니다. PDB가 부족할 때 사라지는 것은 위 2.의 소스 파일 이름·행 번호뿐이며, 프레임 자체가 생략되지는 않습니다. 13 심볼 이야기는 제 8장에 모았으므로, 행 번호가 나오지 않으면 그쪽을 참조하세요.
  • 자사 코드 프레임이 하나도 보이지 않는 경우에는 심볼 부족이 아니라 다음을 의심하세요. 선택한 스레드가 예외를 갖지 않은 다른 스레드인 경우(스레드 선택 실수), 네이티브 쪽에서만 죽어 매니지드 프레임이 애초에 없는 경우, 또는 덤프 종류(Mini 등)가 그 시점의 스택 정보를 충분히 담지 않은 경우입니다.

이어서 예외 객체 자체를 봅니다.

!pe

!PrintException(약칭 !pe)은 지정하지 않으면 현재 스레드에서 마지막으로 throw된 예외를 표시합니다. 형식 이름, 메시지, 내부 예외(-nested로 표시), 스택 트레이스 문자열까지 얻을 수 있습니다. 14 출력은 다음 형태입니다(이쪽도 Microsoft Learn 게재 예의 발췌이며, -lines를 붙여 소스 정보도 낸 것입니다). 5

Exception object: 00007fb18c038590
Exception type:   System.Reflection.TargetInvocationException
Message:          Exception has been thrown by the target of an invocation.
InnerException:   System.Exception, Use !PrintException 00007FB18C038368 to see more.
StackTrace (generated):
SP               IP               Function
00007FFD28B42E20 00007FB1B18D33ED SymbolTestApp.dll!SymbolTestApp.Program.Foo4(System.String)+0x15d [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 54]
00007FFD28B42F30 00007FB1B18D168E SymbolTestApp.dll!SymbolTestApp.Program.Main(System.String[])+0x6e [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 19]

StackTraceString: <none>
HResult: 80131604

읽는 것은 위에서 4행입니다. Exception type이 어떤 예외인지, Message가 그 설명, 그리고 InnerException이 「진짜 원인」입니다. 이 예처럼 TargetInvocationException이 나온 때는 겉에 보이는 형식이 리플렉션 호출의 포장일 뿐이므로, InnerException 행에 적힌 주소를 !pe 00007FB18C038368처럼 넘겨 안쪽 예외를 엽니다(!pe -nested로도 같은 정보를 얻습니다). 14 HResult는 예외에 대응하는 HRESULT 값, StackTrace (generated) 이후는 예외가 가진 스택 트레이스이며, !clrstack이 「지금 스택」인 데 비해 이쪽은 「예외가 throw된 시점의 스택」이라는 차이가 있습니다. 둘이 어긋난 경우에는 예외가 catch된 뒤 다른 곳에서 다시 throw되었을 가능성을 의심하세요.

System.NullReferenceException처럼 형식 이름만으로는 아무것도 말하지 않는 예외일수록, !clrstack -a로 보이는 로컬 변수 값과 맞춰 보는 작업이 필요합니다.

5. 힙과 누수 추적 ── !dumpheap -stat과 !gcroot

「메모리가 조금씩 늘어나 수 시간〜수일 뒤에 죽는」 계열 조사에서 중심이 되는 명령입니다. GC 대기인지 진짜 누수인지를 가르는 앞단계는 「.NET에서 GC 대기와 메모리 누수를 구분하기」에서 자세히 썼습니다. 이 기사는 그 후속으로, 덤프 한 장을 읽고 「무엇이 잡고 있는지」까지 들어가는 부분에 해당합니다.

!dumpheap -stat

-stat 옵션은 매니지드 힙의 통계 요약만 표시합니다. 형식별 건수와 합계 크기가 많은 쪽에 가까운 형태로 늘어나므로, 먼저 「양으로 치고 있는 형식」을 특정합니다. 15 출력은 다음 형태입니다(Microsoft Learn의 메모리 누수 조사 튜토리얼에 실린 실제 출력입니다). 15

Statistics:
              MT    Count    TotalSize Class Name
00007f6c1eeefba8      576        59904 System.Reflection.RuntimeMethodInfo
00007f6c1dc021c8     1749        95696 System.SByte[]
00000000008c9db0     3847       116080      Free
00007f6c1e784a18      175       128640 System.Char[]
00007f6c1dbf5510      217       133504 System.Object[]
00007f6c1dc014c0      467       416464 System.Byte[]
00007f6c21625038        6      4063376 testwebapi.Controllers.Customer[]
00007f6c20a67498   200000      4800000 testwebapi.Controllers.Customer
00007f6c1dc00f90   206770     19494060 System.String
Total 428516 objects

어느 행을 읽을지는 다음과 같습니다.

  1. 맨 아래부터 읽습니다. TotalSize(바이트)가 작은 순으로 늘어나므로, 맨 아래 행이 힙을 가장 많이 쓰는 형식입니다. 이 예에서는 System.String이 약 19MB, Customer가 약 4.8MB를 차지합니다.
  2. Count 열과 TotalSize 열을 나눠 봅니다. 거대한 배열이 몇 개뿐인지(크기만 큼)와, 작은 객체가 수십만 개인지(건수만 큼)에 따라 의심할 곳이 달라집니다.
  3. Free 행은 누수가 아닙니다. 회수됐지만 아직 재사용되지 않은 틈이며, 단편화의 가늠으로 보는 행입니다.
  4. MT 열(MethodTable 주소)은 적어 둡니다. 다음 단계의 !dumpheap -mt <MT>에 넘기면 그 형식의 개별 인스턴스 주소 목록이 나옵니다.

실무에서 자주 보는 형식은 다음 두 계통입니다.

  • 업무 클래스 자체가 늘어난다(예: MyApp.Models.Customer가 수십만 건)── 어딘가에 계속 유지되는 강한 참조가 있다
  • System.String이나 배열만 극단적으로 많다── 상위에 보이는 업무 클래스의 내부 데이터가 원인인 경우가 많고, 개별 인스턴스까지 보기 전에 먼저 업무 클래스 쪽을 의심하는 편이 빠른 경우가 많습니다

대상을 좁혔으면 개별 인스턴스 주소를 가져옵니다.

!dumpheap -type MyApp.Models.Customer

-type은 형식 이름의 부분 일치, -mt는 앞에서 적어 둔 MethodTable 주소 지정입니다. 둘 다 출력은 「한 행이 한 인스턴스」인 목록이 됩니다. 15

         Address               MT     Size
00007f6ad09421f8 00007f6c20a67498       24
00007f6ad0942210 00007f6c20a67498       24

여기서 쓰는 것은 왼쪽 끝 Address 열뿐입니다. 이 값을 다음 명령에 넘깁니다.

그리고 왜 그 객체가 GC에서 회수되지 않고 남아 있는지를 조사합니다.

!gcroot 000001a2b3c4d5e0

!GCRoot는 매니지드 힙과 핸들 테이블 전체를 검색해, 지정한 객체에 도달하는 루트(스택상의 변수, 정적 필드, GC 핸들 등)를 찾아냅니다. 16 출력은 다음 형태입니다(이쪽도 Microsoft Learn 게재의 실제 출력입니다). 15

Thread 3f68:
    00007F6795BB58A0 00007F6C1D7D0745 testwebapi.Controllers.CustomerCache.GetAll()
        rbx:  (interior)
            ->  00007F6BDFFFF038 System.Object[]
            ->  00007F69D0033570 testwebapi.Controllers.Processor
            ->  00007F69D0033588 testwebapi.Controllers.CustomerCache
            ->  00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer]]
            ->  00007F6C000148A0 testwebapi.Controllers.Customer[]
            ->  00007F6AD0942258 testwebapi.Controllers.Customer

Found 1 root.

어느 행을 읽을지는 다음과 같습니다.

  1. 첫 머리글 행이 루트 종류입니다. Thread 3f68:이면 스레드 스택상의 변수, HandleTable:이면 GC 핸들((pinned handle) 같은 종별이 함께 적힙니다)이 루트입니다. 루트가 정적 필드이면 그 필드를 가진 형식 이름이 여기에 나타납니다.
  2. -> 연쇄를 위에서 아래로 읽습니다. 「누가 누구를 잡고 있는지」의 사슬이며, 끝이 지정한 객체입니다.
  3. 범인은 끝이 아니라 사슬 중간에 나오는 컬렉션이나 장수명 객체입니다. 이 예라면 CustomerCache가 List<Customer>를 놓지 않고 잡고 있음을 알 수 있습니다. 여기를 고치지 않는 한, 끝의 Customer만 아무리 봐도 해결되지 않습니다.
  4. 마지막 행의 Found N root.로 개수를 확인합니다. 여러 루트가 나온 경우 모두 끊지 않으면 회수되지 않습니다. 하나 고치고 끝내지 마세요.

출력에 캐시용 정적 필드나 이벤트 핸들러 구독이 보이면, 그곳이 해제 누락의 범인 후보입니다. 다음과 같이 「캐시에 넣어 두고 해제하지 않는」 코드는 전형입니다.

public static class CustomerCache
{
    // 해제하는 경로가 없고, 계속 늘어나기만 하는 정적 사전
    private static readonly Dictionary<int, Customer> _cache = new();

    public static void Add(Customer c) => _cache[c.Id] = c;
}

!gcroot 출력에 CustomerCache 같은 정적 컨테이너가 나타나면, 코드 쪽에서는 유효 기간이나 상한 건수, 또는 WeakReference로의 전환을 검토할 재료가 됩니다. 15

6. 네이티브 크래시의 자동 분석 ── !analyze -v

C++ DLL, COM, vendor SDK가 얽히는 네이티브 크래시(액세스 위반 등)에서는 먼저 이 명령부터 들어갑니다.

!analyze -v

!analyze는 크래시·예외의 자동 분석을 수행하는 확장 명령이며, -v를 붙이면 상세 표시가 됩니다. 8 출력은 수십 행에 이르지만, Microsoft Learn의 사용자 모드 분석 예에서 주요 필드만 뽑으면 다음과 같습니다. 8

FAULTING_IP:
ntdll!PropertyLengthAsVariant+73
77f97704 cc               int     3

EXCEPTION_RECORD:  ffffffff -- (.exr ffffffffffffffff)
ExceptionAddress: 77f97704 (ntdll!PropertyLengthAsVariant+0x00000073)
   ExceptionCode: 80000003 (Break instruction exception)
  ExceptionFlags: 00000000

BUGCHECK_STR:  80000003

PROCESS_NAME:  MyApp.exe

STACK_TEXT:
0006b9dc 01050963 00000000 0006ba04 000603fd ntdll!PropertyLengthAsVariant+0x73
0006b9f0 010509af 00000002 0006ba04 77e1a449 MyApp!FatalErrorBox+0x55 [D:\source_files\MyApp\util.c @ 541]
0006da04 01029f4e 01069850 0000034f 01069828 MyApp!ShowAssert+0x47 [D:\source_files\MyApp\util.c @ 579]
0006ff70 01062cbf 00000001 00683ed8 00682b88 MyApp!main+0x1e6 [D:\source_files\MyApp\MyApp.c @ 263]

FOLLOWUP_IP:
MyApp!FatalErrorBox+55
01050963 5e               pop     esi

SYMBOL_NAME:  MyApp!FatalErrorBox+55

MODULE_NAME:  MyApp

IMAGE_NAME:  MyApp.exe

출력에서 특히 볼 항목은 다음 세 가지입니다.

  • EXCEPTION_CODE / BUGCHECK_STR: 어떤 종류의 이상인지(액세스 위반, 스택 오버플로 등). 위 예에서는 80000003(브레이크 명령 예외)이며, EXCEPTION_RECORD의 ExceptionCode 행에 사람용 설명이 함께 적힙니다. 사용자 모드 덤프에서는 BUGCHECK_STR에도 이 예외 코드가 들어갑니다(이름은 커널 유래라 헷갈리지만, 버그 체크=블루스크린이 아닙니다). 8
  • FAULTING_IP / FOLLOWUP_IP: 실제로 죽은 명령 주소와, 대응하는 모듈·함수 이름. FAULTING_IP는 「죽은 위치」, FOLLOWUP_IP는 !analyze가 「원인은 여기일 것이다」고 추정한 위치이며, 둘은 일치하지 않는 경우가 더 많습니다. 위 예는 ntdll에서 죽었는데, 추정 원인은 자사의 MyApp!FatalErrorBox입니다.
  • MODULE_NAME / IMAGE_NAME: 죽은 위치가 자사 모듈인지, 서드파티 DLL인지. SYMBOL_NAME과 함께 FOLLOWUP_IP의 소속 모듈이 적힙니다.

STACK_TEXT는 첫 행이 가장 안쪽(죽은 지점), 아래로 갈수록 호출한 쪽입니다. 자사 모듈 이름(위 예라면 MyApp!)이 나타나는 첫 행을 위에서 찾아, 그 행의 [파일 이름 @ 행 번호]를 보는 것이 가장 짧은 읽기입니다.

자사 모듈이 아니라 벤더 DLL 안에서 죽은 경우, 그 앞을 따라가려면 벤더 PDB가 필요합니다(대개 구할 수 없습니다). 실무에서는 「호출한 쪽(자사 코드가 마지막으로 넘긴 인수)까지 거슬러, 넘긴 값이 이상하지 않았는지」를 의심하는 것이 현실적인 결론입니다. !analyze -v의 STACK_TEXT 필드에서, 자사 코드에서 무엇을 호출한 직후에 죽었는지를 확인하세요.

!analyze는 예외가 일어난 덤프 외에도 쓸 수 있습니다. hang을 의심하는 경우에는 대상 스레드를 선택한 뒤 다음을 실행하면, 스레드 간 블록 관계를 분석해 줍니다.

!analyze -hang

7. WinDbg를 쓰지 않는 선택지 ── dotnet-dump analyze

.NET Core / .NET 5+의 매니지드 코드만 조사하려면(네이티브 DLL이나 COM이 얽히지 않으면) WinDbg보다 가벼운 dotnet-dump도 선택지입니다.

dotnet tool install --global dotnet-dump
dotnet-dump analyze C:\CrashDumps\MyApp\MyApp_20260702_101500.dmp

analyze 하위 명령은 SOS가 preinstall된 대화형 세션을 열고, clrstack, dumpheap, gcroot 등 여기까지 소개한 명령의 상당수를 ! 접두사 없이 그대로 쓸 수 있습니다. 5

역할 분담의 기준은 다음과 같습니다.

관점 WinDbg + SOS dotnet-dump analyze
네이티브 스택 프레임 보인다 보이지 않는다(매니지드만)5
!analyze -v에 의한 네이티브 자동 분석 쓸 수 있다 쓸 수 없다
Linux 덤프 Windows상의 WinDbg로 분석 가능(x64 덤프에는 x64판, Arm64 덤프에는 x64판, x86 덤프에는 x86판을 사용)17 대응(동일 플랫폼 비트 수의 도구를 사용)17
macOS 덤프 비대응(WinDbg의 Linux 덤프 지원은 macOS를 포함하지 않음) 대응(.NET 5 이후)5
도입의 가벼움 설치 프로그램 또는 winget dotnet global tool 한 명령
크로스 플랫폼 CI에 넣기 손이 간다 하기 쉽다

「COM이나 P/Invoke, native DLL이 얽힐 가능성이 있으면」 WinDbg, 「순수 매니지드 코드의 메모리 누수 조사로, CI나 여러 플랫폼에서도 돌리고 싶으면」 dotnet-dump analyze가 실무상의 구분입니다. 둘은 SOS의 명령 체계를 공유하므로, 한쪽에서 익힌 명령은 다른 쪽에서도 거의 그대로 쓸 수 있습니다. macOS에서 수집한 덤프를 다룰 때는 WinDbg는 선택지에 들어가지 않으므로 dotnet-dump(또는 LLDB)만 남는다는 점에 주의하세요.

8. 심볼을 읽지 못하면 아무것도 시작되지 않는다

심볼(PDB) 이야기는 이 장에 모읍니다. 여기까지의 각 장에서는 「PDB가 있으면 행 번호가 나온다」고만 썼지만, 그 내용은 여기만 읽으면 충분합니다.

먼저, 여기까지의 절차 일부는 PDB가 올바르게 로드되지 않아도 어느 정도 동작합니다. !clrstack·!dumpheap -stat·!gcroot는 CLR의 메타데이터나 힙 데이터를 직접 읽기 때문에, PDB가 없어도 프레임이나 형식 정보 자체는 표시됩니다. PDB가 빠져 잃는 것은 매니지드 코드의 소스 파일 이름·행 번호와 네이티브 프레임·네이티브 모듈의 심볼 이름(함수 이름 대신 주소만 표시되는 것)입니다.

보고 싶은 것 PDB 없음 PDB 있음
매니지드 스택 프레임(메서드 이름·인수의 형식) 나온다 나온다
매니지드 소스 파일 이름·행 번호(!clrstack) 나오지 않는다 나온다13
예외의 형식·메시지·내부 예외(!pe) 나온다 나온다
힙상의 형식 이름·건수·크기(!dumpheap -stat) 나온다 나온다
GC 루트로의 참조 경로(!gcroot) 나온다 나온다
네이티브 프레임의 함수 이름(k / !analyze -v의 STACK_TEXT) 나오지 않는다(주소만) 나온다

즉 덤프와 실행 파일만 있는, 「우선 무엇이 일어났는지만이라도 잡고 싶다」는 장면에서는 PDB 찾기에 막히기 전에 !threads → !clrstack부터 착수해도 문제 없습니다. 그래도 소스 행까지 따라가 원인 위치를 특정하려면 이야기가 달라집니다. 제 2장에서 .sympath+에 자사 PDB 경로를 더했지만, 실무에서는 「배포한 EXE/DLL에 대응하는 PDB가 어디에 있는지 모른다」는 것만으로 소스 행 특정에 닿지 못해 조사가 멈추는 장면이, 수집편에서 쓴 이야기보다 더 자주 일어납니다.

PDB 자체가 무엇을 갖고 무엇을 갖지 않는지, Portable PDB, 그리고 Source Link(어셈블리 안에 소스 관리 메타데이터를 심어, 디버거가 대응하는 커밋 시점의 소스를 직접 가져올 수 있게 하는 구조)에 대해서는 「PDB란 무엇인가」에 한 편으로 정리했습니다. 18 덤프 분석을 지속 운영에 넣으려면 빌드마다 PDB를 보관하고 Source Link를 켜 두는 일이, 수집 설정 그 자체만큼 중요한 준비입니다. 여기를 건너뛰면 !clrstack 출력에서 소스 행이 전혀 나오지 않아, 주소와 형식 이름만으로 더듬게 됩니다.

9. 실례 ── 핸들 누수 조사에서의 덤프 분석

이전에 쓴 「산업용 카메라 장기 가동 크래시 조사 - 핸들 누수편」은, 장시간 운전 후 갑자기 죽는 산업용 카메라 제어 앱 조사에서 주범이 메모리 누수가 아니라 핸들 누수였던 사례입니다. 이런 조사에서 덤프가 먹히는 것은 다음 같은 조합일 때입니다.

  1. !dumpheap -stat으로 매니지드 힙 쪽은 정상(형식별 건수·크기가 계속 늘지 않음)임을 확인한다
  2. 그래도 프로세스의 핸들 수만 늘고 있다면, 누수하는 것은 매니지드 객체가 아니라 OS 핸들(파일, 이벤트, 카메라 SDK가 내부에서 확보하는 핸들 등)이라고 가를 수 있다
  3. SafeHandle을 가진 매니지드 래퍼 객체가 남아 있다면, !gcroot로 그 래퍼의 GC 루트를 따라 「해제되어야 하는데 참조가 남은 위치」를 특정한다

즉 !dumpheap -stat은 「매니지드 힙 증가인지 여부」를 판정하는 분기점으로 동작하고, 그렇지 않다고 알게 된 시점에서 Application Verifier 같은 네이티브 경계의 이상 검출 도구로 조사의 주축이 옮겨갑니다. 이 이상계 테스트 기반을 만드는 방법은 「Application Verifier로 만드는 Windows 이상계 테스트 기반」에서 다룹니다. 덤프 분석은 「지금 일어나고 있는 상태」를, Application Verifier는 「이상을 앞당겨 재현시킨다」는 역할 분담이며, 둘을 병용하는 것이 장시간 운전 계열 장애 조사의 정석입니다.

10. 정리

크래시 덤프는 「수집하는」 것보다 「읽는」 쪽이 익숙해지는 데 시간이 걸리지만, 유형은 그리 많지 않습니다.

  1. WinDbg를 넣고, 심볼 경로에 Microsoft 퍼블릭 심볼 서버와 자사 PDB를 모두 넣는다(제 2장)
  2. .NET 앱이면 SOS 확장을 로드한다(.loadby sos clr / .loadby sos coreclr, 또는 자동 로드. 제 3장)
  3. 예외 유래이면 !clrstack → !pe, 메모리 증가이면 !dumpheap -stat → !gcroot, 네이티브 크래시이면 !analyze -v부터 들어간다(제 4〜6장)
  4. 용도가 순수 매니지드 조사이면 가벼운 dotnet-dump analyze도 검토한다(제 7장)

그리고 이 모든 것의 토대가 되는 것이 PDB와 심볼 관리입니다. 덤프 수집 설정을 넣는 타이밍에 빌드별 PDB 보관과 Source Link 활성화도 함께 정해 두면, 실제로 장애가 났을 때의 조사 시간이 크게 달라집니다. 자사에서 분석이 어렵거나 시간을 내기 어려운 경우에는, 덤프와 로그를 일식으로 보내 주시면 이쪽에서 분석하는 것도 가능합니다.

관련 기사

관련 상담 영역

合同会社小村ソフト에서는 크래시 덤프와 로그를 조합한 장애 원인 조사, 장기 가동 후에만 발생하는 장애의 원인 분리, 덤프·PDB 보관이나 분석 체제 자체의 설계 상담을 다룹니다.

참고 링크

  1. Microsoft Learn, Debugging Managed Code Using the Windows Debugger. .NET Framework 런타임이 clr.dll, .NET Core/.NET 5+ 런타임이 coreclr.dll이라는 점, .loadby로 모듈 근처에서 확장을 로드하는 점, WinDbg 10.0.18317.1001 이후의 자동 로드에 대해. ↩ ↩2 ↩3

  2. Microsoft Learn, SOS debugging extension. SOS 확장이 managed heap 정보 수집, 힙 손상 검출, 런타임 내부 데이터 형식 표시에 쓸 수 있다는 점, WinDbg에서의 구문이 ![command]라는 점에 대해. ↩ ↩2 ↩3

  3. Microsoft Learn, Install the Windows debugger. WinDbg의 winget / Microsoft Store를 통한 설치 방법, 지원 OS(Windows 10 1607 이후·Windows 11)와 아키텍처(x64·ARM64), 자동 업데이트 동작에 대해. ↩ ↩2 ↩3

  4. Microsoft Learn, Symbol path for Windows debuggers. _NT_SYMBOL_PATH 환경 변수로 심볼 경로를 설정하는 방법과, .symfix 명령으로 퍼블릭 심볼 서버의 기본 경로를 설정할 수 있다는 점에 대해. ↩ ↩2

  5. Microsoft Learn, Dump collection and analysis utility (dotnet-dump). dotnet-dump analyze가 SOS 명령을 그대로 쓰는 대화형 세션을 제공하는 한편, 네이티브 디버거가 아니므로 네이티브 스택 프레임을 표시하지 못한다는 점, macOS 대응은 .NET 5 이후라는 점에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  6. Microsoft Learn, Open a Dump File with WinDbg. File 메뉴의 Open crash dump(Ctrl+D)로 덤프를 여는 절차와, 명령줄의 -y(심볼 경로)·-i(이미지 경로)·-z(덤프 파일) 옵션에 대해. ↩

  7. Microsoft Learn, Windows Debugger WinDbg Overview. WinDbg의 Command·Source code·Disassembly·Breakpoints 등 도구 창 구성과, File 메뉴에서의 연결 설정·설정 변경에 대해. ↩

  8. Microsoft Learn, Using the !analyze Extension 및 !analyze (WinDbg). !analyze -v에 의한 크래시·예외 자동 분석과, 출력에 포함되는 FAULTING_IP·MODULE_NAME 등 필드의 의미, hang 조사용 !analyze -hang에 대해. ↩ ↩2 ↩3 ↩4

  9. Microsoft Learn, Microsoft public symbol server. srv*DownstreamStore*https://msdl.microsoft.com/download/symbols 형식의 심볼 경로 구문과, .symfix에 의한 로컬 캐시 포함 설정에 대해. ↩

  10. Microsoft Learn, ProcDump - Sysinternals. -ma(풀 덤프), -e(처리되지 않은 예외 발생 시 덤프를 씀), -x <Dump_Folder> <Image_File>(대상을 시작해 감시) 각 옵션과, 기본 덤프 파일 이름 PROCESSNAME_YYMMDD_HHMMSS.dmp에 대해. ↩ ↩2

  11. Microsoft Learn, SOS installer (dotnet-sos). dotnet-sos install로 로컬에 SOS 확장을 설치하는 것과, 이전 버전 디버거에서의 수동 로드 명령에 대해. ↩

  12. Microsoft Learn, SOS debugging extension - Commands. Threads(lldb 환경에서의 별칭 clrthreads) 명령이 각 스레드의 ID·도메인·마지막으로 throw된 예외 등을 목록 표시한다는 점에 대해. ↩ ↩2

  13. Microsoft Learn, SOS debugging extension - Commands. CLRStack 명령이 매니지드 코드만의 스택 트레이스를 표시하고, -a 옵션으로 로컬 변수와 인수 둘 다를 표시한다는 점, 심볼(SYMOPT_LOAD_LINES)은 소스 파일 이름·행 번호 표시 여부에만 영향을 주고 프레임 자체 표시에는 관계없다는 점에 대해. ↩ ↩2 ↩3

  14. Microsoft Learn, SOS debugging extension - Commands. PrintException(pe) 명령이 주소 생략 시 현재 스레드에서 마지막으로 throw된 예외를 표시하고, -nested로 중첩 예외도 표시할 수 있다는 점에 대해. ↩ ↩2

  15. Microsoft Learn, Debug a memory leak in .NET 및 Dump collection and analysis utility (dotnet-dump) - Analyze memory leaks and allocations. dumpheap -stat으로 형식별 건수·합계 크기 통계를 표시하고, 그것을 출발점으로 한 조사 진행에 대해. ↩ ↩2 ↩3 ↩4 ↩5

  16. Microsoft Learn, SOS debugging extension - Commands. GCRoot 명령이 매니지드 힙과 핸들 테이블 전체를 검색해, 지정 객체로의 참조(루트)를 찾아낸다는 점에 대해. ↩

  17. Microsoft Learn, Debug Linux dumps. Linux 덤프를 Windows상에서 WinDbg 또는 dotnet-dump로 분석할 수 있다는 점, 수집 환경(x64/Arm64/x86)의 비트 수에 맞춘 도구 버전을 써야 한다는 점에 대해. ↩ ↩2

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

.NET 앱의 크래시 덤프는 어느 명령부터 읽기 시작하면 되나요?
조사 유형은 세 가지입니다. 처리되지 않은 예외로 종료된 경우에는 !threads로 예외를 가진 스레드를 특정하고, !clrstack으로 매니지드 스택을 표시한 뒤 !pe로 예외 객체의 형식·메시지·내부 예외를 확인합니다. 메모리가 계속 늘어나는 조사이면 !dumpheap -stat으로 양이 많은 형식을 특정하고, !gcroot로 그 객체를 잡고 있는 루트(정적 필드나 이벤트 핸들러 구독)를 찾아냅니다. 네이티브 크래시면 !analyze -v의 자동 분석부터 들어갑니다.
WinDbg에서 SOS 확장은 어떻게 로드하나요?
.NET Framework이면 .loadby sos clr, .NET Core/.NET 5+이면 .loadby sos coreclr입니다. 버전 10.0.18317.1001 이후의 WinDbg는 대상 프로세스가 coreclr.dll을 로드한 것을 감지하면 SOS를 자동으로 로드합니다. 자동 로드가 동작하지 않는 환경에서는 dotnet-sos 도구로 로컬에 설치한 뒤 수동으로 .load할 수도 있습니다. 로드되었는지는 !sos.help나 !Threads가 오류 없이 반환되는지로 확인합니다.
PDB(심볼)가 없어도 덤프 분석은 가능한가요?
어느 정도는 가능합니다. !clrstack·!dumpheap -stat·!gcroot는 CLR의 메타데이터나 힙 데이터를 직접 읽기 때문에, PDB가 없어도 프레임이나 형식 정보는 표시됩니다. 잃는 것은 매니지드 코드의 소스 파일 이름·행 번호와 네이티브 프레임의 심볼 이름입니다. 소스 행까지 따라가 원인 위치를 특정하려면 Microsoft의 퍼블릭 심볼 서버와 자사 PDB 위치를 모두 심볼 경로에 넣어야 합니다. 빌드별 PDB 보관과 Source Link 활성화가 운영상 중요합니다.
dotnet-dump analyze와 WinDbg는 어떻게 나눠 쓰나요?
COM이나 P/Invoke, 네이티브 DLL이 얽힐 가능성이 있으면 WinDbg, 순수 매니지드 코드 조사이면 dotnet-dump analyze가 기준입니다. dotnet-dump는 dotnet global tool 한 명령으로 도입할 수 있고, clrstack이나 dumpheap 등 SOS 명령의 상당수를 그대로 쓰지만, 네이티브 스택 프레임은 다루지 못하며 !analyze -v도 쓸 수 없습니다. macOS에서 수집한 덤프는 WinDbg로 분석할 수 없으므로, dotnet-dump 또는 LLDB만 선택지가 됩니다. 둘은 SOS의 명령 체계를 공유하므로, 익힌 명령은 거의 서로 그대로 쓸 수 있습니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기