|
5 | 5 | #include <Windows.h> |
6 | 6 | #include <windowsx.h> |
7 | 7 | #include <timeapi.h> |
| 8 | +#include <DbgHelp.h> |
8 | 9 | #include <XGameRuntime.h> |
9 | 10 |
|
10 | 11 | #pragma comment(lib, "winmm.lib") |
|
27 | 28 | #include <iostream> |
28 | 29 | #include <sstream> |
29 | 30 | #include <string> |
| 31 | +#include <cstring> |
30 | 32 |
|
31 | 33 | namespace fs = std::filesystem; |
32 | 34 | using namespace next2d; |
@@ -238,8 +240,96 @@ HWND CreateHostWindow(HINSTANCE instance, int width, int height) |
238 | 240 |
|
239 | 241 | } // namespace |
240 | 242 |
|
| 243 | +// 未処理例外(segfault/AV 等)発生時にコールスタックを next2d-error.log へ書き出す。 |
| 244 | +// GUI 実行では stderr が見えないため、C++ クラッシュ地点を掴む唯一の手段。 |
| 245 | +// DbgHelp の StackWalk64 で例外コンテキストからフレームを辿り、可能なら |
| 246 | +// シンボル名 (関数+行) とモジュールを添える (PDB があれば関数/行まで解決)。 |
| 247 | +static LONG WINAPI CrashHandler(EXCEPTION_POINTERS* ep) |
| 248 | +{ |
| 249 | + std::ofstream ofs("next2d-error.log", std::ios::app); |
| 250 | + if (!ofs || !ep || !ep->ExceptionRecord || !ep->ContextRecord) { |
| 251 | + return EXCEPTION_EXECUTE_HANDLER; |
| 252 | + } |
| 253 | + |
| 254 | + ofs << "\n[CRASH] code=0x" << std::hex |
| 255 | + << ep->ExceptionRecord->ExceptionCode |
| 256 | + << " addr=" << ep->ExceptionRecord->ExceptionAddress |
| 257 | + << std::dec << std::endl; |
| 258 | + |
| 259 | + HANDLE process = GetCurrentProcess(); |
| 260 | + HANDLE thread = GetCurrentThread(); |
| 261 | + SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); |
| 262 | + SymInitialize(process, nullptr, TRUE); |
| 263 | + |
| 264 | + CONTEXT ctx = *ep->ContextRecord; |
| 265 | + STACKFRAME64 frame = {}; |
| 266 | + DWORD machine = 0; |
| 267 | +#if defined(_M_X64) |
| 268 | + machine = IMAGE_FILE_MACHINE_AMD64; |
| 269 | + frame.AddrPC.Offset = ctx.Rip; |
| 270 | + frame.AddrFrame.Offset = ctx.Rbp; |
| 271 | + frame.AddrStack.Offset = ctx.Rsp; |
| 272 | +#elif defined(_M_ARM64) |
| 273 | + machine = IMAGE_FILE_MACHINE_ARM64; |
| 274 | + frame.AddrPC.Offset = ctx.Pc; |
| 275 | + frame.AddrFrame.Offset = ctx.Fp; |
| 276 | + frame.AddrStack.Offset = ctx.Sp; |
| 277 | +#endif |
| 278 | + frame.AddrPC.Mode = AddrModeFlat; |
| 279 | + frame.AddrFrame.Mode = AddrModeFlat; |
| 280 | + frame.AddrStack.Mode = AddrModeFlat; |
| 281 | + |
| 282 | + char symbuf[sizeof(SYMBOL_INFO) + 256] = {}; |
| 283 | + SYMBOL_INFO* sym = reinterpret_cast<SYMBOL_INFO*>(symbuf); |
| 284 | + sym->SizeOfStruct = sizeof(SYMBOL_INFO); |
| 285 | + sym->MaxNameLen = 255; |
| 286 | + |
| 287 | + for (int i = 0; i < 48; ++i) { |
| 288 | + if (!StackWalk64(machine, process, thread, &frame, &ctx, |
| 289 | + nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) { |
| 290 | + break; |
| 291 | + } |
| 292 | + const DWORD64 pc = frame.AddrPC.Offset; |
| 293 | + if (!pc) { |
| 294 | + break; |
| 295 | + } |
| 296 | + |
| 297 | + ofs << " #" << i << " 0x" << std::hex << pc << std::dec; |
| 298 | + |
| 299 | + DWORD64 disp = 0; |
| 300 | + if (SymFromAddr(process, pc, &disp, sym)) { |
| 301 | + ofs << " " << sym->Name << "+0x" << std::hex << disp << std::dec; |
| 302 | + } |
| 303 | + |
| 304 | + const DWORD64 modbase = SymGetModuleBase64(process, pc); |
| 305 | + if (modbase) { |
| 306 | + char modname[MAX_PATH] = {}; |
| 307 | + if (GetModuleFileNameA(reinterpret_cast<HMODULE>(modbase), modname, MAX_PATH)) { |
| 308 | + const char* base = std::strrchr(modname, '\\'); |
| 309 | + ofs << " [" << (base ? base + 1 : modname) << "]"; |
| 310 | + } |
| 311 | + } |
| 312 | + |
| 313 | + IMAGEHLP_LINE64 line = {}; |
| 314 | + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
| 315 | + DWORD line_disp = 0; |
| 316 | + if (SymGetLineFromAddr64(process, pc, &line_disp, &line) && line.FileName) { |
| 317 | + const char* fbase = std::strrchr(line.FileName, '\\'); |
| 318 | + ofs << " (" << (fbase ? fbase + 1 : line.FileName) << ":" << line.LineNumber << ")"; |
| 319 | + } |
| 320 | + ofs << std::endl; |
| 321 | + } |
| 322 | + |
| 323 | + ofs.flush(); |
| 324 | + SymCleanup(process); |
| 325 | + return EXCEPTION_EXECUTE_HANDLER; // プロセスを終了させる (無限再入回避) |
| 326 | +} |
| 327 | + |
241 | 328 | int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int) |
242 | 329 | { |
| 330 | + // 最初にクラッシュハンドラを設置し、以降の segfault 等でスタックを記録する。 |
| 331 | + SetUnhandledExceptionFilter(CrashHandler); |
| 332 | + |
243 | 333 | // Sleep() の分解能を 1ms に上げる (フレームペーシング用。既定 15.6ms では |
244 | 334 | // 60Hz を刻めない) |
245 | 335 | timeBeginPeriod(1); |
|
0 commit comments