fix : 덤프 남길때 현재 콜스택 저장 기능 추가
This commit is contained in:
@@ -7,9 +7,7 @@ namespace ServerLib.Utils;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 릴리즈 빌드 크래시 덤프 핸들러
|
/// 릴리즈 빌드 크래시 덤프 핸들러
|
||||||
///
|
|
||||||
/// Register() 를 Program.cs 최상단에서 한 번 호출.
|
/// Register() 를 Program.cs 최상단에서 한 번 호출.
|
||||||
///
|
|
||||||
/// 생성 파일 (crashes/ 폴더):
|
/// 생성 파일 (crashes/ 폴더):
|
||||||
/// Debug : crash_YYYY-MM-DD_HH-mm-ss.log
|
/// Debug : crash_YYYY-MM-DD_HH-mm-ss.log
|
||||||
/// Release : crash_YYYY-MM-DD_HH-mm-ss.log
|
/// Release : crash_YYYY-MM-DD_HH-mm-ss.log
|
||||||
@@ -18,7 +16,7 @@ namespace ServerLib.Utils;
|
|||||||
public static class CrashDumpHandler
|
public static class CrashDumpHandler
|
||||||
{
|
{
|
||||||
private const string CRASH_DIR = "crashes";
|
private const string CRASH_DIR = "crashes";
|
||||||
private static int registered = 0;
|
private static int registered;
|
||||||
|
|
||||||
public static void Register()
|
public static void Register()
|
||||||
{
|
{
|
||||||
@@ -38,24 +36,27 @@ public static class CrashDumpHandler
|
|||||||
|
|
||||||
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
|
// 예외 컨텍스트가 살아있는 지금 즉시 캡처
|
||||||
|
IntPtr exceptionPointers = Marshal.GetExceptionPointers();
|
||||||
Exception? ex = e.ExceptionObject as Exception;
|
Exception? ex = e.ExceptionObject as Exception;
|
||||||
bool isTerminating = e.IsTerminating;
|
|
||||||
|
|
||||||
string tag = $"[CrashDump] UnhandledException (IsTerminating={isTerminating})";
|
string tag = $"[CrashDump] UnhandledException (IsTerminating={e.IsTerminating})";
|
||||||
WriteCrash(tag, ex);
|
WriteCrash(tag, ex, exceptionPointers);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
|
// Task 예외는 파이널라이저 스레드에서 통보되므로 포인터가 없을 수 있음
|
||||||
|
IntPtr exceptionPointers = Marshal.GetExceptionPointers();
|
||||||
e.SetObserved(); // 프로세스 종료 방지
|
e.SetObserved(); // 프로세스 종료 방지
|
||||||
|
|
||||||
string tag = "[CrashDump] UnobservedTaskException";
|
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
|
try
|
||||||
{
|
{
|
||||||
@@ -73,7 +74,7 @@ public static class CrashDumpHandler
|
|||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
// 2. 메모리 덤프 작성 (Release only)
|
// 2. 메모리 덤프 작성 (Release only)
|
||||||
string dmpPath = $"{basePath}.dmp";
|
string dmpPath = $"{basePath}.dmp";
|
||||||
WriteDumpFile(dmpPath);
|
WriteDumpFile(dmpPath, exceptionPointers);
|
||||||
Log.Fatal("[CrashDump] 덤프 파일 저장 완료 → {Dmp}", dmpPath);
|
Log.Fatal("[CrashDump] 덤프 파일 저장 완료 → {Dmp}", dmpPath);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -118,7 +119,7 @@ public static class CrashDumpHandler
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AppendException(sb, ex, depth: 0);
|
AppendException(sb, ex, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
|
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)
|
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} Type : {ex.GetType().FullName}");
|
||||||
sb.AppendLine($"{indent} Message : {ex.Message}");
|
sb.AppendLine($"{indent} Message : {ex.Message}");
|
||||||
sb.AppendLine($"{indent} Source : {ex.Source}");
|
sb.AppendLine($"{indent} Source : {ex.Source}");
|
||||||
@@ -159,16 +160,27 @@ public static class CrashDumpHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if !DEBUG
|
#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)]
|
[DllImport("dbghelp.dll", SetLastError = true)]
|
||||||
private static extern bool MiniDumpWriteDump(
|
private static extern bool MiniDumpWriteDump(
|
||||||
IntPtr hProcess, uint processId, IntPtr hFile,
|
IntPtr hProcess, uint processId, IntPtr hFile,
|
||||||
uint dumpType, IntPtr exceptionParam,
|
uint dumpType, IntPtr exceptionParam,
|
||||||
IntPtr userStreamParam, IntPtr callbackParam);
|
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))
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
@@ -177,14 +189,30 @@ public static class CrashDumpHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
using Process process = Process.GetCurrentProcess();
|
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);
|
||||||
|
|
||||||
|
IntPtr exInfoPtr = IntPtr.Zero;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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(
|
bool success = MiniDumpWriteDump(
|
||||||
process.Handle,
|
process.Handle,
|
||||||
(uint)process.Id,
|
(uint)process.Id,
|
||||||
fs.SafeFileHandle.DangerousGetHandle(),
|
fs.SafeFileHandle.DangerousGetHandle(),
|
||||||
MiniDumpWithFullMemory,
|
MINI_DUMP_WITH_FULL_MEMORY,
|
||||||
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
exInfoPtr, // 크래시 원인 스레드 & 예외 정보
|
||||||
|
IntPtr.Zero, IntPtr.Zero);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
@@ -192,5 +220,13 @@ public static class CrashDumpHandler
|
|||||||
Log.Error("[CrashDump] MiniDumpWriteDump 실패 (Win32 Error={Err})", err);
|
Log.Error("[CrashDump] MiniDumpWriteDump 실패 (Win32 Error={Err})", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (exInfoPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(exInfoPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user