mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-05 04:33:51 +00:00
fix(diagnostics): add render-phase crash markers and improve signal handling
Add signal-safe render-phase markers throughout GameScreen::render() and Application::render() so the crash handler can report which render call was active when a SIGSEGV occurs. The AMD RADV crash backtrace only shows 2 frames due to missing frame pointers, making it impossible to identify the actual crash site. Changes: - Add volatile g_crashRenderPhase marker updated before each major render call - Upgrade Linux signal handler to sigaction with SA_SIGINFO for faulting address - Set ImGui CheckVkResultFn to log silent Vulkan errors in ImGui backend - Enable -fno-omit-frame-pointer in all build configs (not just Debug/RelWithDebInfo)
This commit is contained in:
parent
b092bc2e90
commit
82267320b0
6 changed files with 104 additions and 29 deletions
|
|
@ -935,9 +935,11 @@ else()
|
|||
# -g3 — maximum DWARF debug info (includes macro definitions)
|
||||
# -Og — optimise for debugging (better than -O0, keeps most frames)
|
||||
# -fno-omit-frame-pointer — preserve frame pointers so stack traces are clean
|
||||
# Frame pointers in all configs (negligible perf cost, critical for crash backtraces)
|
||||
target_compile_options(wowee PRIVATE
|
||||
$<$<CONFIG:Debug>:-g3 -Og -fno-omit-frame-pointer>
|
||||
$<$<CONFIG:RelWithDebInfo>:-g -fno-omit-frame-pointer>
|
||||
-fno-omit-frame-pointer
|
||||
$<$<CONFIG:Debug>:-g3 -Og>
|
||||
$<$<CONFIG:RelWithDebInfo>:-g>
|
||||
)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -2133,15 +2133,22 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
// Render-phase marker from game_screen.cpp — updated here for 3D/submit phases
|
||||
} } // close wowee::core temporarily
|
||||
extern volatile const char* g_crashRenderPhase;
|
||||
namespace wowee { namespace core {
|
||||
|
||||
void Application::render() {
|
||||
if (!renderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_crashRenderPhase = "beginFrame";
|
||||
renderer->beginFrame();
|
||||
|
||||
// Only render 3D world when in-game
|
||||
if (state == AppState::IN_GAME) {
|
||||
g_crashRenderPhase = "renderWorld";
|
||||
if (world) {
|
||||
renderer->renderWorld(world.get(), gameHandler.get());
|
||||
} else {
|
||||
|
|
@ -2151,15 +2158,19 @@ void Application::render() {
|
|||
|
||||
// Render performance HUD (within ImGui frame, before UI ends the frame)
|
||||
if (renderer) {
|
||||
g_crashRenderPhase = "renderHUD";
|
||||
renderer->renderHUD();
|
||||
}
|
||||
|
||||
// Render UI on top (ends ImGui frame with ImGui::Render())
|
||||
if (uiManager) {
|
||||
g_crashRenderPhase = "uiRender";
|
||||
uiManager->render(state, authHandler.get(), gameHandler.get());
|
||||
}
|
||||
|
||||
g_crashRenderPhase = "endFrame";
|
||||
renderer->endFrame();
|
||||
g_crashRenderPhase = "idle";
|
||||
}
|
||||
|
||||
void Application::setupUICallbacks() {
|
||||
|
|
|
|||
71
src/main.cpp
71
src/main.cpp
|
|
@ -10,6 +10,7 @@
|
|||
#include <X11/Xlib.h>
|
||||
#include <execinfo.h>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
|
||||
// Keep a persistent X11 connection for emergency mouse release in signal handlers.
|
||||
// XOpenDisplay inside a signal handler is unreliable, so we open it once at startup.
|
||||
|
|
@ -26,32 +27,45 @@ static void releaseMouseGrab() {
|
|||
static void releaseMouseGrab() {}
|
||||
#endif
|
||||
|
||||
// Render-phase marker set by GameScreen::render() — lets crash handler
|
||||
// identify which render call was active when backtrace is incomplete.
|
||||
extern volatile const char* g_crashRenderPhase;
|
||||
|
||||
#ifdef __linux__
|
||||
static void crashHandlerSigaction(int sig, siginfo_t* info, void* /*ucontext*/) {
|
||||
releaseMouseGrab();
|
||||
void* frames[64];
|
||||
int n = backtrace(frames, 64);
|
||||
const char* sigName = (sig == SIGSEGV) ? "SIGSEGV" :
|
||||
(sig == SIGABRT) ? "SIGABRT" :
|
||||
(sig == SIGFPE) ? "SIGFPE" : "UNKNOWN";
|
||||
const char* phase = (const char*)g_crashRenderPhase;
|
||||
void* faultAddr = info ? info->si_addr : nullptr;
|
||||
fprintf(stderr, "\n=== CRASH: signal %s (%d) renderPhase=%s faultAddr=%p ===\n",
|
||||
sigName, sig, phase ? phase : "?", faultAddr);
|
||||
backtrace_symbols_fd(frames, n, STDERR_FILENO);
|
||||
FILE* f = fopen("/tmp/wowee_debug.log", "a");
|
||||
if (f) {
|
||||
fprintf(f, "\n=== CRASH: signal %s (%d) renderPhase=%s faultAddr=%p ===\n",
|
||||
sigName, sig, phase ? phase : "?", faultAddr);
|
||||
fflush(f);
|
||||
backtrace_symbols_fd(frames, n, fileno(f));
|
||||
fclose(f);
|
||||
}
|
||||
// Re-raise with default handler
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = SIG_DFL;
|
||||
sigaction(sig, &sa, nullptr);
|
||||
raise(sig);
|
||||
}
|
||||
#else
|
||||
static void crashHandler(int sig) {
|
||||
releaseMouseGrab();
|
||||
#ifdef __linux__
|
||||
// Dump backtrace to debug log
|
||||
{
|
||||
void* frames[64];
|
||||
int n = backtrace(frames, 64);
|
||||
const char* sigName = (sig == SIGSEGV) ? "SIGSEGV" :
|
||||
(sig == SIGABRT) ? "SIGABRT" :
|
||||
(sig == SIGFPE) ? "SIGFPE" : "UNKNOWN";
|
||||
// Write to stderr and to the debug log file
|
||||
fprintf(stderr, "\n=== CRASH: signal %s (%d) ===\n", sigName, sig);
|
||||
backtrace_symbols_fd(frames, n, STDERR_FILENO);
|
||||
FILE* f = fopen("/tmp/wowee_debug.log", "a");
|
||||
if (f) {
|
||||
fprintf(f, "\n=== CRASH: signal %s (%d) ===\n", sigName, sig);
|
||||
fflush(f);
|
||||
// Also write backtrace to the log file fd
|
||||
backtrace_symbols_fd(frames, n, fileno(f));
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
std::signal(sig, SIG_DFL);
|
||||
std::raise(sig);
|
||||
}
|
||||
#endif
|
||||
|
||||
static wowee::core::LogLevel readLogLevelFromEnv() {
|
||||
const char* raw = std::getenv("WOWEE_LOG_LEVEL");
|
||||
|
|
@ -69,12 +83,25 @@ static wowee::core::LogLevel readLogLevelFromEnv() {
|
|||
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
|
||||
#ifdef __linux__
|
||||
g_emergencyDisplay = XOpenDisplay(nullptr);
|
||||
#endif
|
||||
// Use sigaction for SIGSEGV/SIGABRT/SIGFPE to get si_addr (faulting address)
|
||||
{
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = crashHandlerSigaction;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGSEGV, &sa, nullptr);
|
||||
sigaction(SIGABRT, &sa, nullptr);
|
||||
sigaction(SIGFPE, &sa, nullptr);
|
||||
}
|
||||
std::signal(SIGTERM, [](int) { std::_Exit(1); });
|
||||
std::signal(SIGINT, [](int) { std::_Exit(1); });
|
||||
#else
|
||||
std::signal(SIGSEGV, crashHandler);
|
||||
std::signal(SIGABRT, crashHandler);
|
||||
std::signal(SIGFPE, crashHandler);
|
||||
std::signal(SIGTERM, crashHandler);
|
||||
std::signal(SIGINT, crashHandler);
|
||||
#endif
|
||||
try {
|
||||
wowee::core::Logger::getInstance().setLogLevel(readLogLevelFromEnv());
|
||||
LOG_INFO("=== Wowee Native Client ===");
|
||||
|
|
|
|||
|
|
@ -792,6 +792,10 @@ void Renderer::applyMsaaChange() {
|
|||
initInfo.ImageCount = vkCtx->getSwapchainImageCount();
|
||||
initInfo.PipelineInfoMain.RenderPass = vkCtx->getImGuiRenderPass();
|
||||
initInfo.PipelineInfoMain.MSAASamples = vkCtx->getMsaaSamples();
|
||||
initInfo.CheckVkResultFn = [](VkResult err) {
|
||||
if (err != VK_SUCCESS)
|
||||
LOG_ERROR("ImGui Vulkan error: ", static_cast<int>(err));
|
||||
};
|
||||
ImGui_ImplVulkan_Init(&initInfo);
|
||||
|
||||
LOG_INFO("MSAA change complete");
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@
|
|||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
|
||||
// Signal-safe render-phase marker — crash handler reads this to identify which
|
||||
// render call was active when a SIGSEGV occurs (backtrace is unreliable with
|
||||
// -fomit-frame-pointer).
|
||||
volatile const char* g_crashRenderPhase = "idle";
|
||||
#include <unordered_set>
|
||||
|
||||
namespace {
|
||||
|
|
@ -298,7 +303,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
// Process targeting input before UI windows
|
||||
processTargetInput(gameHandler);
|
||||
|
||||
// Player unit frame (top-left)
|
||||
g_crashRenderPhase = "playerFrame";
|
||||
renderPlayerFrame(gameHandler);
|
||||
|
||||
// Pet frame (below player frame, only when player has an active pet)
|
||||
|
|
@ -353,6 +358,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
}
|
||||
|
||||
// ---- New UI elements ----
|
||||
g_crashRenderPhase = "actionBar";
|
||||
actionBarPanel_.renderActionBar(gameHandler, settingsPanel_, chatPanel_,
|
||||
inventoryScreen, spellbookScreen, questLogScreen,
|
||||
[this](uint32_t id, pipeline::AssetManager* am) { return getSpellIcon(id, am); });
|
||||
|
|
@ -362,31 +368,43 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
actionBarPanel_.renderXpBar(gameHandler, settingsPanel_);
|
||||
actionBarPanel_.renderRepBar(gameHandler, settingsPanel_);
|
||||
auto spellIconFn = [this](uint32_t id, pipeline::AssetManager* am) { return getSpellIcon(id, am); };
|
||||
g_crashRenderPhase = "castBar";
|
||||
combatUI_.renderCastBar(gameHandler, spellIconFn);
|
||||
renderMirrorTimers(gameHandler);
|
||||
combatUI_.renderCooldownTracker(gameHandler, settingsPanel_, spellIconFn);
|
||||
renderQuestObjectiveTracker(gameHandler);
|
||||
g_crashRenderPhase = "nameplates";
|
||||
renderNameplates(gameHandler); // player names always shown; NPC plates gated by showNameplates_
|
||||
g_crashRenderPhase = "bgScore";
|
||||
combatUI_.renderBattlegroundScore(gameHandler);
|
||||
combatUI_.renderRaidWarningOverlay(gameHandler);
|
||||
combatUI_.renderCombatText(gameHandler);
|
||||
combatUI_.renderDPSMeter(gameHandler, settingsPanel_);
|
||||
g_crashRenderPhase = "durability";
|
||||
renderDurabilityWarning(gameHandler);
|
||||
renderUIErrors(gameHandler, ImGui::GetIO().DeltaTime);
|
||||
toastManager_.renderEarlyToasts(ImGui::GetIO().DeltaTime, gameHandler);
|
||||
g_crashRenderPhase = "partyFrames";
|
||||
if (socialPanel_.showRaidFrames_) {
|
||||
socialPanel_.renderPartyFrames(gameHandler, chatPanel_, spellIconFn);
|
||||
}
|
||||
g_crashRenderPhase = "bossFrames";
|
||||
socialPanel_.renderBossFrames(gameHandler, spellbookScreen, spellIconFn);
|
||||
g_crashRenderPhase = "dialogs";
|
||||
dialogManager_.renderDialogs(gameHandler, inventoryScreen, chatPanel_);
|
||||
g_crashRenderPhase = "guildRoster";
|
||||
socialPanel_.renderGuildRoster(gameHandler, chatPanel_);
|
||||
socialPanel_.renderSocialFrame(gameHandler, chatPanel_);
|
||||
g_crashRenderPhase = "buffBar";
|
||||
combatUI_.renderBuffBar(gameHandler, spellbookScreen, spellIconFn);
|
||||
g_crashRenderPhase = "lootWindow";
|
||||
windowManager_.renderLootWindow(gameHandler, inventoryScreen, chatPanel_);
|
||||
windowManager_.renderGossipWindow(gameHandler, chatPanel_);
|
||||
g_crashRenderPhase = "questWindows";
|
||||
windowManager_.renderQuestDetailsWindow(gameHandler, chatPanel_, inventoryScreen);
|
||||
windowManager_.renderQuestRequestItemsWindow(gameHandler, chatPanel_, inventoryScreen);
|
||||
windowManager_.renderQuestOfferRewardWindow(gameHandler, chatPanel_, inventoryScreen);
|
||||
g_crashRenderPhase = "vendorTrainer";
|
||||
windowManager_.renderVendorWindow(gameHandler, inventoryScreen, chatPanel_);
|
||||
windowManager_.renderTrainerWindow(gameHandler,
|
||||
[this](uint32_t id, pipeline::AssetManager* am) { return getSpellIcon(id, am); });
|
||||
|
|
@ -398,39 +416,48 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
windowManager_.renderBankWindow(gameHandler, inventoryScreen, chatPanel_);
|
||||
windowManager_.renderGuildBankWindow(gameHandler, inventoryScreen, chatPanel_);
|
||||
windowManager_.renderAuctionHouseWindow(gameHandler, inventoryScreen, chatPanel_);
|
||||
g_crashRenderPhase = "dungeonFinder";
|
||||
socialPanel_.renderDungeonFinderWindow(gameHandler, chatPanel_);
|
||||
windowManager_.renderInstanceLockouts(gameHandler);
|
||||
socialPanel_.renderWhoWindow(gameHandler, chatPanel_);
|
||||
g_crashRenderPhase = "combatLog";
|
||||
combatUI_.renderCombatLog(gameHandler, spellbookScreen);
|
||||
g_crashRenderPhase = "achievementSkills";
|
||||
windowManager_.renderAchievementWindow(gameHandler);
|
||||
windowManager_.renderSkillsWindow(gameHandler);
|
||||
windowManager_.renderTitlesWindow(gameHandler);
|
||||
windowManager_.renderEquipSetWindow(gameHandler);
|
||||
windowManager_.renderGmTicketWindow(gameHandler);
|
||||
g_crashRenderPhase = "inspectBook";
|
||||
socialPanel_.renderInspectWindow(gameHandler, inventoryScreen);
|
||||
windowManager_.renderBookWindow(gameHandler);
|
||||
g_crashRenderPhase = "threatBg";
|
||||
combatUI_.renderThreatWindow(gameHandler);
|
||||
combatUI_.renderBgScoreboard(gameHandler);
|
||||
g_crashRenderPhase = "minimap";
|
||||
if (showMinimap_) {
|
||||
renderMinimapMarkers(gameHandler);
|
||||
}
|
||||
g_crashRenderPhase = "deathLogout";
|
||||
windowManager_.renderLogoutCountdown(gameHandler);
|
||||
windowManager_.renderDeathScreen(gameHandler);
|
||||
windowManager_.renderReclaimCorpseButton(gameHandler);
|
||||
dialogManager_.renderLateDialogs(gameHandler);
|
||||
chatPanel_.renderBubbles(gameHandler);
|
||||
windowManager_.renderEscapeMenu(settingsPanel_);
|
||||
g_crashRenderPhase = "settings";
|
||||
settingsPanel_.renderSettingsWindow(inventoryScreen, chatPanel_, [this]() { saveSettings(); });
|
||||
toastManager_.renderLateToasts(gameHandler);
|
||||
g_crashRenderPhase = "weather";
|
||||
renderWeatherOverlay(gameHandler);
|
||||
|
||||
// World map (M key toggle handled inside)
|
||||
g_crashRenderPhase = "worldMap";
|
||||
renderWorldMap(gameHandler);
|
||||
|
||||
// Quest Log (L key toggle handled inside)
|
||||
g_crashRenderPhase = "questLog";
|
||||
questLogScreen.render(gameHandler, inventoryScreen);
|
||||
|
||||
// Spellbook (P key toggle handled inside)
|
||||
g_crashRenderPhase = "spellbook";
|
||||
spellbookScreen.render(gameHandler, services_.assetManager);
|
||||
|
||||
// Insert spell link into chat if player shift-clicked a spellbook entry
|
||||
|
|
@ -483,7 +510,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
windowManager_.vendorBagsOpened_ = false;
|
||||
}
|
||||
|
||||
// Bags (B key toggle handled inside)
|
||||
g_crashRenderPhase = "inventory";
|
||||
inventoryScreen.setGameHandler(&gameHandler);
|
||||
inventoryScreen.render(gameHandler.getInventory(), gameHandler.getMoneyCopper());
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ bool UIManager::initialize(core::Window* win) {
|
|||
initInfo.ImageCount = vkCtx->getSwapchainImageCount();
|
||||
initInfo.PipelineInfoMain.RenderPass = vkCtx->getImGuiRenderPass();
|
||||
initInfo.PipelineInfoMain.MSAASamples = vkCtx->getMsaaSamples();
|
||||
initInfo.CheckVkResultFn = [](VkResult err) {
|
||||
if (err != VK_SUCCESS)
|
||||
LOG_ERROR("ImGui Vulkan error: ", static_cast<int>(err));
|
||||
};
|
||||
|
||||
ImGui_ImplVulkan_Init(&initInfo);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue