fix : 덤프 남길때 현재 콜스택 저장 기능 추가

This commit is contained in:
qornwh1
2026-03-10 09:03:46 +09:00
parent 275d09001b
commit a3bcbd073e

View File

@@ -6,19 +6,17 @@ using Serilog;
namespace ServerLib.Utils;
/// <summary>
/// 릴리즈 빌드 크래시 덤프 핸들러
///
/// Register() 를 Program.cs 최상단에서 한 번 호출.
///
/// 생성 파일 (crashes/ 폴더):
/// Debug : crash_YYYY-MM-DD_HH-mm-ss.log
/// Release : crash_YYYY-MM-DD_HH-mm-ss.log
/// crash_YYYY-MM-DD_HH-mm-ss.dmp ← 메모리 덤프 추가
/// 릴리즈 빌드 크래시 덤프 핸들러
/// Register() 를 Program.cs 최상단에서 한 번 호출.
/// 생성 파일 (crashes/ 폴더):
/// Debug : crash_YYYY-MM-DD_HH-mm-ss.log
/// Release : crash_YYYY-MM-DD_HH-mm-ss.log
/// crash_YYYY-MM-DD_HH-mm-ss.dmp ← 메모리 덤프 추가
/// </summary>
public static class CrashDumpHandler
{
private const string CRASH_DIR = "crashes";
private static int registered = 0;
private static int registered;
public static void Register()
{
@@ -29,7 +27,7 @@ public static class CrashDumpHandler
}
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
Log.Information("[CrashDump] 핸들러 등록 완료 (CrashDir={Dir})", Path.GetFullPath(CRASH_DIR));
}
@@ -38,31 +36,34 @@ public static class CrashDumpHandler
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
// 예외 컨텍스트가 살아있는 지금 즉시 캡처
IntPtr exceptionPointers = Marshal.GetExceptionPointers();
Exception? ex = e.ExceptionObject as Exception;
bool isTerminating = e.IsTerminating;
string tag = $"[CrashDump] UnhandledException (IsTerminating={isTerminating})";
WriteCrash(tag, ex);
string tag = $"[CrashDump] UnhandledException (IsTerminating={e.IsTerminating})";
WriteCrash(tag, ex, exceptionPointers);
}
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
// Task 예외는 파이널라이저 스레드에서 통보되므로 포인터가 없을 수 있음
IntPtr exceptionPointers = Marshal.GetExceptionPointers();
e.SetObserved(); // 프로세스 종료 방지
string tag = "[CrashDump] UnobservedTaskException";
WriteCrash(tag, e.Exception);
WriteCrash(tag, e.Exception, exceptionPointers);
}
// ─── 핵심 처리 ───────────────────────────────────────────────────────
private static void WriteCrash(string tag, Exception? ex)
private static void WriteCrash(string tag, Exception? ex, IntPtr exceptionPointers)
{
try
{
Directory.CreateDirectory(CRASH_DIR);
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
string basePath = Path.Combine(CRASH_DIR, $"crash_{timestamp}");
string basePath = Path.Combine(CRASH_DIR, $"crash_{timestamp}");
// 1. 크래시 로그 작성
string logPath = $"{basePath}.log";
@@ -73,7 +74,7 @@ public static class CrashDumpHandler
#if !DEBUG
// 2. 메모리 덤프 작성 (Release only)
string dmpPath = $"{basePath}.dmp";
WriteDumpFile(dmpPath);
WriteDumpFile(dmpPath, exceptionPointers);
Log.Fatal("[CrashDump] 덤프 파일 저장 완료 → {Dmp}", dmpPath);
#endif
}
@@ -118,7 +119,7 @@ public static class CrashDumpHandler
}
else
{
AppendException(sb, ex, depth: 0);
AppendException(sb, ex, 0);
}
File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
@@ -126,7 +127,7 @@ public static class CrashDumpHandler
private static void AppendException(StringBuilder sb, Exception ex, int depth)
{
string indent = new string(' ', depth * 2);
string indent = new(' ', depth * 2);
sb.AppendLine($"{indent} Type : {ex.GetType().FullName}");
sb.AppendLine($"{indent} Message : {ex.Message}");
sb.AppendLine($"{indent} Source : {ex.Source}");
@@ -159,16 +160,27 @@ public static class CrashDumpHandler
}
#if !DEBUG
// Windows MiniDumpWriteDump P/Invoke
// ─── Windows P/Invoke ────────────────────────────────────────────────
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct MinidumpExceptionInformation
{
public uint ThreadId;
public IntPtr ExceptionPointers;
public int ClientPointers; // 0 = 서버 자신의 주소 공간
}
[DllImport("kernel32.dll")]
private static extern uint GetCurrentThreadId();
[DllImport("dbghelp.dll", SetLastError = true)]
private static extern bool MiniDumpWriteDump(
IntPtr hProcess, uint processId, IntPtr hFile,
uint dumpType, IntPtr exceptionParam,
IntPtr userStreamParam, IntPtr callbackParam);
private const uint MiniDumpWithFullMemory = 0x00000002;
private const uint MINI_DUMP_WITH_FULL_MEMORY = 0x00000002;
private static void WriteDumpFile(string path)
private static void WriteDumpFile(string path, IntPtr exceptionPointers)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
@@ -177,19 +189,43 @@ public static class CrashDumpHandler
}
using Process process = Process.GetCurrentProcess();
using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
using FileStream fs = new(path, FileMode.Create, FileAccess.Write, FileShare.None);
bool success = MiniDumpWriteDump(
process.Handle,
(uint)process.Id,
fs.SafeFileHandle.DangerousGetHandle(),
MiniDumpWithFullMemory,
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (!success)
IntPtr exInfoPtr = IntPtr.Zero;
try
{
int err = Marshal.GetLastWin32Error();
Log.Error("[CrashDump] MiniDumpWriteDump 실패 (Win32 Error={Err})", err);
if (exceptionPointers != IntPtr.Zero)
{
MinidumpExceptionInformation exInfo = new()
{
ThreadId = GetCurrentThreadId(),
ExceptionPointers = exceptionPointers,
ClientPointers = 0
};
exInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(exInfo));
Marshal.StructureToPtr(exInfo, exInfoPtr, false);
}
bool success = MiniDumpWriteDump(
process.Handle,
(uint)process.Id,
fs.SafeFileHandle.DangerousGetHandle(),
MINI_DUMP_WITH_FULL_MEMORY,
exInfoPtr, // 크래시 원인 스레드 & 예외 정보
IntPtr.Zero, IntPtr.Zero);
if (!success)
{
int err = Marshal.GetLastWin32Error();
Log.Error("[CrashDump] MiniDumpWriteDump 실패 (Win32 Error={Err})", err);
}
}
finally
{
if (exInfoPtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(exInfoPtr);
}
}
}
#endif