From a3bcbd073e120ec3d563fd83c7356282aadbad4c Mon Sep 17 00:00:00 2001 From: qornwh1 Date: Tue, 10 Mar 2026 09:03:46 +0900 Subject: [PATCH] =?UTF-8?q?fix=20:=20=EB=8D=A4=ED=94=84=20=EB=82=A8?= =?UTF-8?q?=EA=B8=B8=EB=95=8C=20=ED=98=84=EC=9E=AC=20=EC=BD=9C=EC=8A=A4?= =?UTF-8?q?=ED=83=9D=20=EC=A0=80=EC=9E=A5=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ServerLib/Utils/CrashDumpHandler.cs | 102 ++++++++++++------ 1 file changed, 69 insertions(+), 33 deletions(-) diff --git a/MMOTestServer/ServerLib/Utils/CrashDumpHandler.cs b/MMOTestServer/ServerLib/Utils/CrashDumpHandler.cs index 8001246..8ee6c3f 100644 --- a/MMOTestServer/ServerLib/Utils/CrashDumpHandler.cs +++ b/MMOTestServer/ServerLib/Utils/CrashDumpHandler.cs @@ -6,19 +6,17 @@ using Serilog; namespace ServerLib.Utils; /// -/// 릴리즈 빌드 크래시 덤프 핸들러 -/// -/// 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 ← 메모리 덤프 추가 /// 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