diff --git a/MMOTestServer/MMOserver/Dockerfile b/MMOTestServer/MMOserver/Dockerfile
index 15ae802..6717832 100644
--- a/MMOTestServer/MMOserver/Dockerfile
+++ b/MMOTestServer/MMOserver/Dockerfile
@@ -5,7 +5,9 @@ WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
+# ServerLib 의존성 포함해서 restore (캐시 레이어 최적화)
COPY ["MMOserver/MMOserver.csproj", "MMOserver/"]
+COPY ["ServerLib/ServerLib.csproj", "ServerLib/"]
RUN dotnet restore "MMOserver/MMOserver.csproj"
COPY . .
WORKDIR "/src/MMOserver"
diff --git a/MMOTestServer/MMOserver/MMOserver.csproj b/MMOTestServer/MMOserver/MMOserver.csproj
index 3f9edfb..bc0803a 100644
--- a/MMOTestServer/MMOserver/MMOserver.csproj
+++ b/MMOTestServer/MMOserver/MMOserver.csproj
@@ -9,6 +9,11 @@
13
+
+ portable
+ true
+
+
diff --git a/MMOTestServer/ServerLib/Utils/CrashDumpHandler.cs b/MMOTestServer/ServerLib/Utils/CrashDumpHandler.cs
index 8ee6c3f..b14f8e6 100644
--- a/MMOTestServer/ServerLib/Utils/CrashDumpHandler.cs
+++ b/MMOTestServer/ServerLib/Utils/CrashDumpHandler.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using Serilog;
@@ -6,12 +5,17 @@ using Serilog;
namespace ServerLib.Utils;
///
-/// 릴리즈 빌드 크래시 덤프 핸들러
+/// 크래시 핸들러 (Windows / Linux 공통)
/// Register() 를 Program.cs 최상단에서 한 번 호출.
+///
+/// 덤프 생성은 CLR 환경변수로 처리 (스택 언와인드 전에 찍힘):
+/// DOTNET_DbgEnableMiniDump=1
+/// DOTNET_DbgMiniDumpType=4
+/// DOTNET_DbgMiniDumpName=crashes/crash_%p_%t.dmp
+///
/// 생성 파일 (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 ← 메모리 덤프 추가
+/// crash_YYYY-MM-DD_HH-mm-ss.log ← 항상 생성
+/// crash_%p_%t.dmp ← CLR이 직접 생성 (정확한 크래시 위치)
///
public static class CrashDumpHandler
{
@@ -27,7 +31,7 @@ public static class CrashDumpHandler
}
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
- TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
+ TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
Log.Information("[CrashDump] 핸들러 등록 완료 (CrashDir={Dir})", Path.GetFullPath(CRASH_DIR));
}
@@ -36,52 +40,35 @@ public static class CrashDumpHandler
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
- // 예외 컨텍스트가 살아있는 지금 즉시 캡처
- IntPtr exceptionPointers = Marshal.GetExceptionPointers();
Exception? ex = e.ExceptionObject as Exception;
-
string tag = $"[CrashDump] UnhandledException (IsTerminating={e.IsTerminating})";
- WriteCrash(tag, ex, exceptionPointers);
+ WriteCrash(tag, ex);
}
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
- // Task 예외는 파이널라이저 스레드에서 통보되므로 포인터가 없을 수 있음
- IntPtr exceptionPointers = Marshal.GetExceptionPointers();
e.SetObserved(); // 프로세스 종료 방지
-
string tag = "[CrashDump] UnobservedTaskException";
- WriteCrash(tag, e.Exception, exceptionPointers);
+ WriteCrash(tag, e.Exception);
}
// ─── 핵심 처리 ───────────────────────────────────────────────────────
- private static void WriteCrash(string tag, Exception? ex, IntPtr exceptionPointers)
+ private static void WriteCrash(string tag, Exception? ex)
{
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 logPath = Path.Combine(CRASH_DIR, $"crash_{timestamp}.log");
- // 1. 크래시 로그 작성
- string logPath = $"{basePath}.log";
WriteCrashLog(logPath, ex);
-
Log.Fatal("{Tag} → {Log}", tag, logPath);
-
-#if !DEBUG
- // 2. 메모리 덤프 작성 (Release only)
- string dmpPath = $"{basePath}.dmp";
- WriteDumpFile(dmpPath, exceptionPointers);
- Log.Fatal("[CrashDump] 덤프 파일 저장 완료 → {Dmp}", dmpPath);
-#endif
}
catch (Exception writeEx)
{
- // 덤프 저장 실패는 무시 (이미 크래시 중이므로)
- Log.Error(writeEx, "[CrashDump] 덤프 저장 실패");
+ Log.Error(writeEx, "[CrashDump] 로그 저장 실패");
}
finally
{
@@ -97,21 +84,18 @@ public static class CrashDumpHandler
sb.AppendLine("═══════════════════════════════════════════════════");
sb.AppendLine();
- // 환경 정보
sb.AppendLine("[Environment]");
- sb.AppendLine($" OS : {RuntimeInformation.OSDescription}");
- sb.AppendLine($" Runtime : {RuntimeInformation.FrameworkDescription}");
- sb.AppendLine($" PID : {Environment.ProcessId}");
- sb.AppendLine($" WorkDir : {Environment.CurrentDirectory}");
+ sb.AppendLine($" OS : {RuntimeInformation.OSDescription}");
+ sb.AppendLine($" Runtime : {RuntimeInformation.FrameworkDescription}");
+ sb.AppendLine($" PID : {Environment.ProcessId}");
+ sb.AppendLine($" WorkDir : {Environment.CurrentDirectory}");
sb.AppendLine($" MachineName: {Environment.MachineName}");
sb.AppendLine();
- // 스레드 정보
sb.AppendLine("[Thread]");
sb.AppendLine($" ThreadId : {Environment.CurrentManagedThreadId}");
sb.AppendLine();
- // 예외 정보
sb.AppendLine("[Exception]");
if (ex is null)
{
@@ -158,75 +142,4 @@ public static class CrashDumpHandler
AppendException(sb, ex.InnerException, depth + 1);
}
}
-
-#if !DEBUG
- // ─── 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 MINI_DUMP_WITH_FULL_MEMORY = 0x00000002;
-
- private static void WriteDumpFile(string path, IntPtr exceptionPointers)
- {
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- Log.Warning("[CrashDump] 덤프 생성은 Windows만 지원");
- return;
- }
-
- using Process process = Process.GetCurrentProcess();
- 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(
- 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
}
diff --git a/MMOTestServer/compose.yaml b/MMOTestServer/compose.yaml
index 5a308a7..0491508 100644
--- a/MMOTestServer/compose.yaml
+++ b/MMOTestServer/compose.yaml
@@ -7,4 +7,10 @@
ports:
- "9050:9050/udp" # LiteNetLib UDP 포트
- "9500:9500/udp" # LiteNetLib UDP 포트
+ environment:
+ - DOTNET_DbgEnableMiniDump=1 # 크래시 시 덤프 자동 생성
+ - DOTNET_DbgMiniDumpType=4 # 4 = Full dump
+ - DOTNET_DbgMiniDumpName=/app/crashes/crash_%p_%t.dmp
+ volumes:
+ - ./crashes:/app/crashes # 덤프/로그 로컬 마운트
restart: unless-stopped