Skip to content

Commit 81c457c

Browse files
committed
Update crash handler capabilities and, making use of that, add temp alloc instrumentation to OOM crash hotspots (To help identify leaks/suboptimal procedures)
1 parent d3a311c commit 81c457c

File tree

11 files changed

+1025
-16
lines changed

11 files changed

+1025
-16
lines changed

Client/core/CCrashDumpWriter.cpp

Lines changed: 374 additions & 2 deletions
Large diffs are not rendered by default.

Client/core/CrashHandler.cpp

Lines changed: 132 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
#include <SharedUtil.Detours.h>
2121
#include <SharedUtil.Misc.h>
2222
#include <SharedUtil.Memory.h>
23+
#include "../Shared/sdk/CrashTelemetry.h"
2324

2425
#include <algorithm>
2526
#include <array>
2627
#include <atomic>
2728
#include <chrono>
2829
#include <cctype>
2930
#include <cstdint>
31+
#include <cstring>
3032
#include <exception>
3133
#include <errno.h>
3234
#include <filesystem>
@@ -44,6 +46,9 @@
4446
#include <string_view>
4547
#include <variant>
4648
#include <vector>
49+
#if defined(_MSC_VER)
50+
#include <new.h>
51+
#endif
4752

4853
#if defined(_MSC_VER)
4954
#include <corecrt.h>
@@ -94,6 +99,16 @@ inline std::atomic<DWORD> g_initializationPhase{INIT_PHASE_MINIMAL};
9499

95100
using CrashHandlerResult = std::variant<std::monostate, std::string, DWORD, std::exception_ptr>;
96101

102+
#if defined(__cplusplus)
103+
[[noreturn]] void __cdecl CppNewHandler() noexcept;
104+
#endif
105+
106+
#if defined(_MSC_VER)
107+
static int __cdecl CppNewHandlerBridge(size_t size) noexcept;
108+
#else
109+
static void CppNewHandlerBridge() noexcept;
110+
#endif
111+
97112
[[nodiscard]] BOOL BUGSUTIL_DLLINTERFACE __stdcall IsFatalException(DWORD exceptionCode) noexcept
98113
{
99114
switch (exceptionCode)
@@ -173,13 +188,21 @@ static void StoreBasicExceptionInfo(_EXCEPTION_POINTERS* pException) noexcept
173188
return;
174189
}
175190

176-
g_lastExceptionInfo = ENHANCED_EXCEPTION_INFO{
191+
ENHANCED_EXCEPTION_INFO info{
177192
.exceptionCode = pException->ExceptionRecord->ExceptionCode,
178193
.exceptionAddress = pException->ExceptionRecord->ExceptionAddress,
179194
.timestamp = std::chrono::system_clock::now(),
180195
.threadId = GetCurrentThreadId(),
181196
.processId = GetCurrentProcessId()
182197
};
198+
199+
const auto telemetryNote = CrashTelemetry::BuildAllocationTelemetryNote();
200+
if (!telemetryNote.empty())
201+
{
202+
info.additionalInfo = telemetryNote;
203+
}
204+
205+
g_lastExceptionInfo = info;
183206
}
184207
catch (...)
185208
{
@@ -495,6 +518,12 @@ static void LogEnhancedExceptionInfo(_EXCEPTION_POINTERS* pException) noexcept
495518
case EXCEPTION_GUARD_PAGE:
496519
info.exceptionType = "SEH:GuardPage";
497520
break;
521+
case CUSTOM_EXCEPTION_CODE_OOM:
522+
info.exceptionType = "User:OutOfMemory";
523+
break;
524+
case CUSTOM_EXCEPTION_CODE_WATCHDOG_TIMEOUT:
525+
info.exceptionType = "User:WatchdogTimeout";
526+
break;
498527
default:
499528
if (info.exceptionCode >= 0xC0000000 && info.exceptionCode <= 0xCFFFFFFF)
500529
{
@@ -513,6 +542,15 @@ static void LogEnhancedExceptionInfo(_EXCEPTION_POINTERS* pException) noexcept
513542

514543
info.exceptionDescription = GetExceptionCodeDescription(info.exceptionCode);
515544

545+
if (auto telemetryNote = CrashTelemetry::BuildAllocationTelemetryNote(); !telemetryNote.empty())
546+
{
547+
if (!info.additionalInfo.empty())
548+
{
549+
info.additionalInfo.push_back('\n');
550+
}
551+
info.additionalInfo += telemetryNote;
552+
}
553+
516554
g_lastExceptionInfo = info;
517555
}
518556
catch (...)
@@ -522,7 +560,16 @@ static void LogEnhancedExceptionInfo(_EXCEPTION_POINTERS* pException) noexcept
522560
}
523561
}
524562

525-
static std::variant<DWORD, std::string> HandleExceptionModern(_EXCEPTION_POINTERS* pException)
563+
static void CaptureAllocationTelemetry(_EXCEPTION_POINTERS* pException) noexcept
564+
{
565+
if (pException == nullptr || pException->ExceptionRecord == nullptr)
566+
return;
567+
568+
StoreBasicExceptionInfo(pException);
569+
LogEnhancedExceptionInfo(pException);
570+
}
571+
572+
static std::variant<DWORD, std::string> HandleExceptionModern(_EXCEPTION_POINTERS* pException) noexcept
526573
{
527574
if (pException == nullptr || pException->ExceptionRecord == nullptr)
528575
{
@@ -625,7 +672,12 @@ static std::mutex g_handlerStateMutex;
625672
static std::atomic<PFNCHFILTFN> g_pfnCrashCallback{nullptr};
626673
static std::atomic<LPTOP_LEVEL_EXCEPTION_FILTER> g_pfnOrigFilt{nullptr};
627674
static std::atomic<std::terminate_handler> g_pfnOrigTerminate{nullptr};
675+
#if defined(_MSC_VER)
676+
using CrtNewHandler = int(__cdecl*)(size_t);
677+
static std::atomic<CrtNewHandler> g_pfnOrigNewHandler{nullptr};
678+
#else
628679
static std::atomic<std::new_handler> g_pfnOrigNewHandler{nullptr};
680+
#endif
629681
static std::atomic<decltype(&SetUnhandledExceptionFilter)> g_pfnKernelSetUnhandledExceptionFilter{nullptr};
630682
static decltype(&SetUnhandledExceptionFilter) g_kernelSetUnhandledExceptionFilterTrampoline = nullptr;
631683

@@ -641,10 +693,45 @@ static std::atomic<bool> g_symbolsInitialized{false};
641693
static std::atomic<HANDLE> g_symbolProcess{nullptr};
642694
static std::mutex g_symbolMutex;
643695

696+
#if defined(_MSC_VER)
697+
static int __cdecl CppNewHandlerBridge(size_t size) noexcept
698+
{
699+
const auto telemetry = CrashTelemetry::CaptureContext();
700+
if ((!telemetry.hasData || telemetry.requestedSize == 0) && size > 0)
701+
{
702+
CrashTelemetry::SetAllocationContext(size, nullptr, "operator new", "std::new_handler");
703+
}
704+
705+
if (auto previous = g_pfnOrigNewHandler.load(std::memory_order_acquire))
706+
{
707+
return previous(size);
708+
}
709+
710+
CppNewHandler();
711+
return 0;
712+
}
713+
#else
714+
static void CppNewHandlerBridge() noexcept
715+
{
716+
const auto telemetry = CrashTelemetry::CaptureContext();
717+
if (!telemetry.hasData)
718+
{
719+
CrashTelemetry::SetAllocationContext(0, nullptr, "operator new", "std::new_handler");
720+
}
721+
722+
if (auto previous = g_pfnOrigNewHandler.load(std::memory_order_acquire))
723+
{
724+
previous();
725+
return;
726+
}
727+
728+
CppNewHandler();
729+
}
730+
#endif
731+
644732
LONG __stdcall CrashHandlerExceptionFilter(EXCEPTION_POINTERS* pExPtrs);
645733

646734
[[noreturn]] void __cdecl CppTerminateHandler() noexcept;
647-
[[noreturn]] void __cdecl CppNewHandler() noexcept;
648735
void __cdecl AbortSignalHandler(int signal) noexcept;
649736
[[noreturn]] void __cdecl PureCallHandler() noexcept;
650737

@@ -766,6 +853,8 @@ void __cdecl AbortSignalHandler([[maybe_unused]] int signal) noexcept
766853
exPtrs.ExceptionRecord = &exRecord;
767854
exPtrs.ContextRecord = &ctx;
768855

856+
CaptureAllocationTelemetry(&exPtrs);
857+
769858
PFNCHFILTFN callback = g_pfnCrashCallback.load(std::memory_order_acquire);
770859

771860
if (callback != nullptr)
@@ -804,13 +893,19 @@ void __cdecl AbortSignalHandler([[maybe_unused]] int signal) noexcept
804893
LogHandlerEvent(DEBUG_PREFIX_PURECALL.data(), "Pure virtual function call detected");
805894
SafeDebugOutput(DEBUG_SEPARATOR);
806895

807-
EXCEPTION_RECORD* pExRecord{nullptr};
808-
CONTEXT* pCtx{nullptr};
896+
EXCEPTION_RECORD* pExRecord{nullptr};
897+
CONTEXT* pCtx{nullptr};
809898
EXCEPTION_POINTERS exPtrs{};
810899

900+
const bool haveContext = BuildExceptionContext(exPtrs, pExRecord, pCtx, EXCEPTION_NONCONTINUABLE_EXCEPTION);
901+
if (haveContext)
902+
{
903+
CaptureAllocationTelemetry(&exPtrs);
904+
}
905+
811906
PFNCHFILTFN callback = g_pfnCrashCallback.load(std::memory_order_acquire);
812907

813-
if (callback != nullptr && BuildExceptionContext(exPtrs, pExRecord, pCtx, EXCEPTION_NONCONTINUABLE_EXCEPTION))
908+
if (callback != nullptr && haveContext)
814909
{
815910
LogHandlerEvent(DEBUG_PREFIX_PURECALL.data(), "Calling crash handler callback");
816911

@@ -867,7 +962,11 @@ class CleanUpCrashHandler
867962

868963
if (auto newHandler = g_pfnOrigNewHandler.exchange(nullptr, std::memory_order_acq_rel); newHandler != nullptr)
869964
{
965+
#if defined(_MSC_VER)
966+
_set_new_handler(newHandler);
967+
#else
870968
std::set_new_handler(newHandler);
969+
#endif
871970
}
872971

873972
if (auto abortHandler = g_pfnOrigAbortHandler.exchange(nullptr, std::memory_order_acq_rel); abortHandler != nullptr)
@@ -1332,7 +1431,11 @@ static void InstallCppHandlers() noexcept
13321431

13331432
if (g_pfnOrigNewHandler.load(std::memory_order_acquire) == nullptr)
13341433
{
1335-
std::new_handler previous = std::set_new_handler(CppNewHandler);
1434+
#if defined(_MSC_VER)
1435+
CrtNewHandler previous = _set_new_handler(CppNewHandlerBridge);
1436+
#else
1437+
std::new_handler previous = std::set_new_handler(CppNewHandlerBridge);
1438+
#endif
13361439
g_pfnOrigNewHandler.store(previous, std::memory_order_release);
13371440
SafeDebugOutput("CrashHandler: C++ new handler installed\n");
13381441
}
@@ -1903,7 +2006,11 @@ static void UninstallCrashHandlers() noexcept
19032006

19042007
if (auto newHandler = g_pfnOrigNewHandler.exchange(nullptr, std::memory_order_acq_rel); newHandler != nullptr)
19052008
{
2009+
#if defined(_MSC_VER)
2010+
_set_new_handler(newHandler);
2011+
#else
19062012
std::set_new_handler(newHandler);
2013+
#endif
19072014
}
19082015

19092016
if (auto abortHandler = g_pfnOrigAbortHandler.exchange(nullptr, std::memory_order_acq_rel); abortHandler != nullptr)
@@ -2004,13 +2111,24 @@ static bool BuildExceptionContext(EXCEPTION_POINTERS& outExPtrs, EXCEPTION_RECOR
20042111

20052112
SafeDebugOutput(DEBUG_SEPARATOR);
20062113

2114+
if (auto telemetryNote = CrashTelemetry::BuildAllocationTelemetryNote(); !telemetryNote.empty())
2115+
{
2116+
SafeDebugPrintPrefixed(DEBUG_PREFIX_CPP, "%s\n", telemetryNote.c_str());
2117+
}
2118+
20072119
EXCEPTION_RECORD* pExRecord = nullptr;
20082120
CONTEXT* pCtx = nullptr;
20092121
EXCEPTION_POINTERS exPtrs{};
20102122

2123+
const bool haveContext = BuildExceptionContext(exPtrs, pExRecord, pCtx, CPP_EXCEPTION_CODE);
2124+
if (haveContext)
2125+
{
2126+
CaptureAllocationTelemetry(&exPtrs);
2127+
}
2128+
20112129
PFNCHFILTFN callback = g_pfnCrashCallback.load(std::memory_order_acquire);
20122130

2013-
if (callback != nullptr && BuildExceptionContext(exPtrs, pExRecord, pCtx, CPP_EXCEPTION_CODE))
2131+
if (callback != nullptr && haveContext)
20142132
{
20152133
SafeDebugPrintPrefixed(DEBUG_PREFIX_CPP, "Calling crash handler callback\n");
20162134

@@ -2082,6 +2200,12 @@ static void ReportCurrentCppException() noexcept
20822200
SafeDebugOutput("C++ NEW HANDLER: Memory allocation failed\n");
20832201
SafeDebugOutput(DEBUG_SEPARATOR);
20842202

2203+
if (auto telemetryNote = CrashTelemetry::BuildAllocationTelemetryNote(); !telemetryNote.empty())
2204+
{
2205+
SafeDebugOutput(telemetryNote.c_str());
2206+
SafeDebugOutput("\n");
2207+
}
2208+
20852209
std::terminate();
20862210
}
20872211

Client/core/Graphics/CPixelsManager.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,28 @@
1111
#include "StdInc.h"
1212
#include "CFileFormat.h"
1313
#include "CPixelsManager.h"
14+
#include <CrashTelemetry.h>
15+
16+
namespace
17+
{
18+
const char* PixelsFormatToString(EPixelsFormatType format)
19+
{
20+
switch (format)
21+
{
22+
case EPixelsFormat::PLAIN:
23+
return "plain";
24+
case EPixelsFormat::JPEG:
25+
return "jpeg";
26+
case EPixelsFormat::PNG:
27+
return "png";
28+
case EPixelsFormat::DDS:
29+
return "dds";
30+
case EPixelsFormat::UNKNOWN:
31+
default:
32+
return "unknown";
33+
}
34+
}
35+
}
1436

1537
///////////////////////////////////////////////////////////////
1638
// Object creation
@@ -750,6 +772,22 @@ bool CPixelsManager::ChangePixelsFormat(const CPixels& oldPixels, CPixels& newPi
750772
if (oldFormat == EPixelsFormat::UNKNOWN || newFormat == EPixelsFormat::UNKNOWN)
751773
return false;
752774

775+
const uint sourceBytes = oldPixels.GetSize();
776+
uint width = 0;
777+
uint height = 0;
778+
GetPixelsSize(oldPixels, width, height);
779+
780+
SString telemetryDetail;
781+
telemetryDetail.Format("%s->%s q=%d bytes=%u dims=%ux%u",
782+
PixelsFormatToString(oldFormat),
783+
PixelsFormatToString(newFormat),
784+
uiQuality,
785+
sourceBytes,
786+
width,
787+
height);
788+
// Tag conversions here so crashes show which pixel formats/dimensions were being processed.
789+
CrashTelemetry::Scope conversionScope(sourceBytes, oldPixels.GetData(), "Pixels::ChangeFormat", telemetryDetail.c_str());
790+
753791
if (oldFormat == newFormat)
754792
{
755793
// No change

Client/mods/deathmatch/StdInc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#define MTA_CLIENT
55
#define SHARED_UTIL_WITH_FAST_HASH_MAP
66
#include "SharedUtil.h"
7+
#include <CrashTelemetry.h>
78

89
#include <string.h>
910
#include <stdio.h>

Client/mods/deathmatch/logic/CClientEntity.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,14 @@ bool CClientEntity::CallEvent(const char* szName, const CLuaArguments& Arguments
747747
if (!g_pClientGame->GetDebugHookManager()->OnPreEvent(szName, Arguments, this, NULL))
748748
return false;
749749

750+
const SString& thisTypeName = GetTypeName();
751+
const char* thisTypeNameCStr = !thisTypeName.empty() ? thisTypeName.c_str() : "<unknown>";
752+
const ElementID thisId = GetID();
753+
SString telemetryDetail;
754+
telemetryDetail.Format("%s %s(%u)", szName, thisTypeNameCStr, thisId.Value());
755+
// Capture the element+event context so any crash (even core.dll faults) reports the last event being dispatched.
756+
CrashTelemetry::Scope entityScope(0, this, "Entity::CallEvent", telemetryDetail.c_str());
757+
750758
TIMEUS startTime = GetTimeUs();
751759

752760
CEvents* pEvents = g_pClientGame->GetEvents();

Client/mods/deathmatch/logic/CMapEventManager.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,32 @@ bool CMapEventManager::Call(const char* szName, const CLuaArguments& Arguments,
187187
if (!g_pClientGame->GetDebugHookManager()->OnPreEventFunction(szName, Arguments, pSource, nullptr, pMapEvent))
188188
continue;
189189

190+
const char* scriptName = pMapEvent->GetVM()->GetScriptName();
191+
CResource* pEventResource = pMapEvent->GetVM()->GetResource();
192+
const char* resourceName = pEventResource ? pEventResource->GetName() : "<no-resource>";
193+
194+
SString sourceTag = "<null>";
195+
if (pSource)
196+
{
197+
const SString& sourceTypeName = pSource->GetTypeName();
198+
const char* sourceTypeNameCStr = !sourceTypeName.empty() ? sourceTypeName.c_str() : "<unknown>";
199+
sourceTag.Format("%s(%u)", sourceTypeNameCStr, pSource->GetID().Value());
200+
}
201+
202+
SString thisTag = "<null>";
203+
if (pThis)
204+
{
205+
const SString& thisTypeName = pThis->GetTypeName();
206+
const char* thisTypeNameCStr = !thisTypeName.empty() ? thisTypeName.c_str() : "<unknown>";
207+
thisTag.Format("%s(%u)", thisTypeNameCStr, pThis->GetID().Value());
208+
}
209+
210+
SString telemetryDetail;
211+
telemetryDetail.Format("%s::%s src=%s this=%s", scriptName, resourceName, sourceTag.c_str(), thisTag.c_str());
212+
// Tag each Lua event dispatch so crash dumps show which
213+
// resource/event/element executed last
214+
CrashTelemetry::Scope eventScope(0, pThis, "LuaEvent::Call", telemetryDetail.c_str());
215+
190216
// Store the current values of the globals
191217
lua_getglobal(pState, "source");
192218
CLuaArgument OldSource(pState, -1);

0 commit comments

Comments
 (0)