|
48 | 48 | #include <stdio.h> |
49 | 49 | #include <string> |
50 | 50 | #include <string_view> |
| 51 | +#include <TlHelp32.h> |
51 | 52 | #include <variant> |
52 | 53 | #include <vector> |
53 | 54 | #if defined(_MSC_VER) |
@@ -3027,7 +3028,21 @@ namespace |
3027 | 3028 |
|
3028 | 3029 | inline WatchdogState g_watchdogState{}; |
3029 | 3030 |
|
3030 | | - [[nodiscard]] static bool TriggerWatchdogException(HANDLE targetThread, DWORD targetThreadId) |
| 3031 | + static bool TryGenerateWatchdogDump(EXCEPTION_POINTERS* pExPtrs, CExceptionInformation_Impl* pExInfo) |
| 3032 | + { |
| 3033 | + __try |
| 3034 | + { |
| 3035 | + CCrashDumpWriter::DumpCoreLog(pExPtrs, pExInfo); |
| 3036 | + CCrashDumpWriter::DumpMiniDump(pExPtrs, pExInfo); |
| 3037 | + return true; |
| 3038 | + } |
| 3039 | + __except (EXCEPTION_EXECUTE_HANDLER) |
| 3040 | + { |
| 3041 | + return false; |
| 3042 | + } |
| 3043 | + } |
| 3044 | + |
| 3045 | + [[nodiscard]] static bool TriggerWatchdogException(HANDLE targetThread, DWORD targetThreadId, bool dumpOnly = false) |
3031 | 3046 | { |
3032 | 3047 | AddReportLog(9300, SString("Watchdog freeze detected after %u seconds (thread %u)", g_watchdogState.timeoutSeconds.load(std::memory_order_relaxed), |
3033 | 3048 | targetThreadId)); |
@@ -3079,6 +3094,7 @@ namespace |
3079 | 3094 | // Now we can safely resume the thread - we have everything we need |
3080 | 3095 | if (ResumeThread(targetThread) == static_cast<DWORD>(-1)) |
3081 | 3096 | { |
| 3097 | + OutputDebugStringA("WATCHDOG: Failed to resume thread after stack capture\n"); |
3082 | 3098 | AddReportLog(9304, SString("Watchdog failed to resume thread %u after stack capture", targetThreadId)); |
3083 | 3099 | // Continue anyway - we still want the crash dump even if resume failed |
3084 | 3100 | } |
@@ -3151,6 +3167,25 @@ namespace |
3151 | 3167 | } |
3152 | 3168 | } |
3153 | 3169 |
|
| 3170 | + if (dumpOnly) |
| 3171 | + { |
| 3172 | + // Write dump without dialog or termination so the |
| 3173 | + // contributor can continue debugging after a breakpoint pause. |
| 3174 | + auto* pExInfo = new (std::nothrow) CExceptionInformation_Impl; |
| 3175 | + if (pExInfo) |
| 3176 | + { |
| 3177 | + pExInfo->Set(exceptionRecord.ExceptionCode, &exceptionPointers); |
| 3178 | + |
| 3179 | + if (!TryGenerateWatchdogDump(&exceptionPointers, pExInfo)) |
| 3180 | + { |
| 3181 | + AddReportLog(9316, "Watchdog: SEH exception caught during dump generation"); |
| 3182 | + } |
| 3183 | + |
| 3184 | + delete pExInfo; |
| 3185 | + } |
| 3186 | + return true; |
| 3187 | + } |
| 3188 | + |
3154 | 3189 | // Trigger the global exception handler |
3155 | 3190 | CCrashDumpWriter::HandleExceptionGlobal(&exceptionPointers); |
3156 | 3191 |
|
@@ -3190,14 +3225,57 @@ namespace |
3190 | 3225 | if (elapsed.count() >= static_cast<std::chrono::seconds::rep>(timeoutSecs)) |
3191 | 3226 | { |
3192 | 3227 | #ifdef MTA_DEBUG |
3193 | | - if (IsDebuggerPresent() == TRUE) |
| 3228 | + BOOL isDebuggerAttached = FALSE; |
| 3229 | + if (IsDebuggerPresent() != FALSE) |
| 3230 | + isDebuggerAttached = TRUE; |
| 3231 | + |
| 3232 | + if (isDebuggerAttached == FALSE) |
| 3233 | + CheckRemoteDebuggerPresent(GetCurrentProcess(), &isDebuggerAttached); |
| 3234 | + |
| 3235 | + if (isDebuggerAttached == FALSE) |
| 3236 | + { |
| 3237 | + // Check if parent process (e.g. VS attached to the launcher) has a debugger |
| 3238 | + if (HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); hSnapshot != INVALID_HANDLE_VALUE) |
| 3239 | + { |
| 3240 | + PROCESSENTRY32 pe32{}; |
| 3241 | + pe32.dwSize = sizeof(pe32); |
| 3242 | + if (Process32First(hSnapshot, &pe32)) |
| 3243 | + { |
| 3244 | + const DWORD currentPid = GetCurrentProcessId(); |
| 3245 | + do |
| 3246 | + { |
| 3247 | + if (pe32.th32ProcessID == currentPid) |
| 3248 | + { |
| 3249 | + if (HANDLE hParent = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ParentProcessID)) |
| 3250 | + { |
| 3251 | + CheckRemoteDebuggerPresent(hParent, &isDebuggerAttached); |
| 3252 | + CloseHandle(hParent); |
| 3253 | + } |
| 3254 | + break; |
| 3255 | + } |
| 3256 | + } while (Process32Next(hSnapshot, &pe32)); |
| 3257 | + } |
| 3258 | + CloseHandle(hSnapshot); |
| 3259 | + } |
| 3260 | + } |
| 3261 | + |
| 3262 | + if (isDebuggerAttached != FALSE) |
3194 | 3263 | { |
| 3264 | + // In debug builds with a debugger attached, the stall may just be a breakpoint pause. |
| 3265 | + // Generate a dump so the frozen stack is collected, then |
| 3266 | + // reset the heartbeat and keep monitoring instead of terminating. |
| 3267 | + OutputDebugStringA("WATCHDOG: Freeze detected - generating dump (no termination)\n"); |
| 3268 | + |
| 3269 | + AddReportLog(9311, SString("Watchdog detected freeze after %lld seconds (threshold %u, debugger attached)", |
| 3270 | + static_cast<long long>(elapsed.count()), timeoutSecs)); |
| 3271 | + |
| 3272 | + TriggerWatchdogException(targetThread.get(), targetThreadId, true); |
| 3273 | + |
3195 | 3274 | g_watchdogState.lastHeartbeat.store(std::chrono::steady_clock::now(), std::memory_order_release); |
3196 | 3275 | std::this_thread::sleep_for(kCheckInterval); |
3197 | 3276 | continue; |
3198 | 3277 | } |
3199 | 3278 | #endif |
3200 | | - |
3201 | 3279 | AddReportLog(9311, SString("Watchdog detected freeze after %lld seconds (threshold %u)", static_cast<long long>(elapsed.count()), timeoutSecs)); |
3202 | 3280 |
|
3203 | 3281 | const bool triggered = TriggerWatchdogException(targetThread.get(), targetThreadId); |
|
0 commit comments