Fix ImGui assert: ensure null-terminated buffer with +1

Fix host debugger crash when no debugger is attached

Added safe break implementation: replaced unsafe __debugbreak() with a guarded check using IsDebuggerPresent(). Prevents crashes when triggering host debugger without an active debugger attached.

Fix host debugger crash when no debugger is attached

Added safe break implementation: replaced unsafe __debugbreak() with a guarded check using IsDebuggerPresent(). Prevents crashes when triggering host debugger without an active debugger attached.

[Logging]Disable logging in Release via XE_OPTION_ENABLE_LOGGING=0 [hybrid mode]

Implement hybrid logging system (XE_OPTION_ENABLE_LOGGING):
- Disables all log macros and sinks in release mode
- Keeps interface & headers safe for use
- Prevents xenia.log creation in Release
- Improves performance on Steam Deck (~+10% CPU)

x+Logging] Improve ImGui safety & disable logging in Release

- Fixed ImGui assert: ensure null-terminated buffer (+1 size)
- Safe fallback for __debugbreak() when no debugger is attached
- Implemented hybrid logging system (XE_OPTION_ENABLE_LOGGING):
- Disables all XELOG macros and sinks in release mode
- Keeps interface & headers safe for use
- Prevents xenia.log creation in release
- Improves performance on Steam Deck (~+10% CPU)
This commit is contained in:
Alexander Freeman 2025-04-14 16:15:25 +03:00
parent 1a356f7344
commit be9eb30fd0
13 changed files with 116 additions and 32 deletions

View File

@ -79,11 +79,16 @@ filter({"configurations:Debug", "platforms:Linux"})
"_GLIBCXX_DEBUG", -- make dbg symbols work on some distros
})
filter("configurations:Release")
filter("configurations:Release")
runtime("Release")
defines({
"NDEBUG",
"_NO_DEBUG_HEAP=1",
-- AlexFreeman -> отключаем всё лишнее в Release-сборке
"XENIA_DISABLE_LOGGING",
"XENIA_ENABLE_TRACING=0",
"XENIA_ENABLE_ASSERTIONS=0",
"XE_OPTION_ENABLE_LOGGING=0"
})
optimize("Speed")
inlining("Auto")

View File

@ -2072,7 +2072,7 @@ void EmulatorWindow::LoadRecentlyLaunchedTitles() {
toml::parse_result parsed_file;
try {
parsed_file = toml::parse(file);
} catch (toml::parse_error& exception) {
} catch (toml::parse_error& exception [[maybe_unused]]) {
XELOGE("Cannot parse file: recent.toml. Error: {}", exception.what());
return;
}

View File

@ -26,6 +26,9 @@ namespace debugging {
bool IsDebuggerAttached();
// AlexFreeman -> Safe version of Break() that only triggers if debugger is attached.
void SafeBreakIntoDebugger();
// Breaks into the debugger if it is attached.
// If no debugger is present, a signal will be raised.
void Break();

View File

@ -20,7 +20,19 @@ bool IsDebuggerAttached() {
__readgsqword(0x60))[2]; // get BeingDebugged field of PEB
}
void Break() { __debugbreak(); }
void Break() {
if (IsDebuggerPresent()) {
__debugbreak();
}
// AlexFreeman -> Debugger not attached, skip silently
}
// AlexFreeman -> Safe wrapper to avoid crashing if debugger is not attached
void SafeBreakIntoDebugger() {
if (IsDebuggerAttached()) {
xe::debugging::SafeBreakIntoDebugger();
}
}
namespace internal {
void DebugPrint(const char* s) { OutputDebugStringA(s); }

View File

@ -429,7 +429,7 @@ class Logger {
claim_strategy_.publish(range);
}
};
#if XE_OPTION_ENABLE_LOGGING
void InitializeLogging(const std::string_view app_name) {
auto mem = memory::AlignedAlloc<Logger>(0x10);
logger_ = new (mem) Logger(app_name);
@ -462,32 +462,54 @@ void InitializeLogging(const std::string_view app_name) {
}
#endif // XE_PLATFORM_ANDROID
}
#else
void InitializeLogging(const std::string_view app_name) {}
#endif
void ShutdownLogging() {
#if XE_OPTION_ENABLE_LOGGING
void ShutdownLogging() {
Logger* logger = logger_;
logger_ = nullptr;
logger->~Logger();
memory::AlignedFree(logger);
}
#else
void ShutdownLogging() {}
#endif
static int g_saved_loglevel = static_cast<int>(LogLevel::Disabled);
#if XE_OPTION_ENABLE_LOGGING
void logging::internal::ToggleLogLevel() {
auto swap = g_saved_loglevel;
g_saved_loglevel = cvars::log_level;
cvars::log_level = swap;
}
#else
void logging::internal::ToggleLogLevel() {}
#endif
#if XE_OPTION_ENABLE_LOGGING
bool logging::internal::ShouldLog(LogLevel log_level, uint32_t log_mask) {
return static_cast<int32_t>(log_level) <= cvars::log_level &&
(log_mask & cvars::log_mask) == 0;
}
uint32_t logging::internal::GetLogLevel() { return cvars::log_level; }
#else
bool logging::internal::ShouldLog(LogLevel log_level, uint32_t log_mask) {
return false;
}
#endif
uint32_t logging::internal::GetLogLevel() { return cvars::log_level; }
std::pair<char*, size_t> logging::internal::GetThreadBuffer() {
return {thread_log_buffer_, sizeof(thread_log_buffer_)};
}
XE_NOALIAS
#if XE_OPTION_ENABLE_LOGGING
void logging::internal::AppendLogLine(LogLevel log_level,
const char prefix_char, size_t written) {
if (!logger_ || !ShouldLog(log_level) || !written) {
@ -496,7 +518,12 @@ void logging::internal::AppendLogLine(LogLevel log_level,
logger_->AppendLine(xe::threading::current_thread_id(), prefix_char,
thread_log_buffer_, written);
}
#else
void logging::internal::AppendLogLine(LogLevel log_level,
const char prefix_char, size_t written) {}
#endif
#if XE_OPTION_ENABLE_LOGGING
void logging::AppendLogLine(LogLevel log_level, const char prefix_char,
const std::string_view str, uint32_t log_mask) {
if (!internal::ShouldLog(log_level, log_mask) || !str.size()) {
@ -505,7 +532,10 @@ void logging::AppendLogLine(LogLevel log_level, const char prefix_char,
logger_->AppendLine(xe::threading::current_thread_id(), prefix_char,
str.data(), str.size());
}
#else
void logging::AppendLogLine(LogLevel log_level, const char prefix_char,
const std::string_view str, uint32_t log_mask) {}
#endif
void FatalError(const std::string_view str) {
logging::AppendLogLine(LogLevel::Error, 'x', str);
@ -523,4 +553,4 @@ void FatalError(const std::string_view str) {
#endif // XE_PLATFORM_ANDROID
}
} // namespace xe
} // namespace xe

View File

@ -19,9 +19,9 @@
#include "xenia/base/string.h"
namespace xe {
#ifndef XE_OPTION_ENABLE_LOGGING
#define XE_OPTION_ENABLE_LOGGING 1
#endif
// Log level is a general indication of the importance of a given log line.
//
// While log levels are named, they are a rough correlation of what the log line
@ -235,6 +235,15 @@ void XELOGFS(std::string_view format, const Args&... args) {
#define XELOGKERNEL(...) __XELOGDUMMY
#define XELOGFS(...) __XELOGDUMMY
#endif // ENABLE_LOGGING
// AlexFreeman -> stub for external version of AppendLogLine
inline void AppendLogLine(xe::LogLevel, const char, const std::string_view,
uint32_t = xe::LogSrc::Uncategorized) {}
#endif // XENIA_BASE_LOGGING_H_
/// AlexFreeman -> stub for FatalError fallback
[[noreturn]] inline void FatalError(const std::string_view) {
std::exit(1);
}
#endif // XE_OPTION_ENABLE_LOGGING
#endif // XENIA_BASE_LOGGING_H_

View File

@ -423,8 +423,11 @@ void X64Emitter::EmitGetCurrentThreadId() {
void X64Emitter::EmitTraceUserCallReturn() {}
void X64Emitter::DebugBreak() {
// TODO(benvanik): notify debugger.
db(0xCC);
if (::IsDebuggerPresent()) {
db(0xCC); // AlexFreeman -> native debug break
} else {
XELOGW("X64Emitter::DebugBreak() called without debugger"); // AlexFreeman ->
}
}
uint64_t TrapDebugPrint(void* raw_context, uint64_t address) {
@ -477,8 +480,12 @@ void X64Emitter::Trap(uint16_t trap_type) {
}
void X64Emitter::UnimplementedInstr(const hir::Instr* i) {
// TODO(benvanik): notify debugger.
db(0xCC);
// AlexFreeman -> Handle unimplemented instruction safely
if (::IsDebuggerPresent()) {
db(0xCC); // INT 3: stop for debugging
} else {
XELOGE("Unimplemented HIR instruction encountered"); // Optional, but useful
}
assert_always();
}

View File

@ -56,4 +56,4 @@ D3D12GraphicsSystem::CreateCommandProcessor() {
} // namespace d3d12
} // namespace gpu
} // namespace xe
} // namespace xe

View File

@ -418,7 +418,12 @@ void Shader::GatherVertexFetchInformation(
}
}
if (!attrib) {
assert_not_zero(fetch_instr.attributes.stride);
// AlexFreeman -> Protect against zero stride crash in shader translation
if (fetch_instr.attributes.stride == 0) {
XELOGE("ShaderTranslator: fetch_instr with stride == 0 at fetch_constant={}, offset={}",
op.fetch_constant_index(), fetch_instr.attributes.offset);
return; // Skip faulty instruction instead of crashing
}
VertexBinding vertex_binding;
vertex_binding.binding_index = int(vertex_bindings_.size());
vertex_binding.fetch_constant = op.fetch_constant_index();

View File

@ -54,6 +54,7 @@ class ShaderTranslator {
bool is_vertex_shader() const {
return current_shader().type() == xenos::ShaderType::kVertex;
}
// True if the current shader is a pixel shader.
bool is_pixel_shader() const {
return current_shader().type() == xenos::ShaderType::kPixel;
@ -77,6 +78,7 @@ class ShaderTranslator {
// Handles post-translation tasks when the shader has been fully translated.
virtual void PostTranslation() {}
// Sets the host disassembly on a shader.
void set_host_disassembly(Shader::Translation& translation,
std::string value) {
@ -94,41 +96,48 @@ class ShaderTranslator {
// Handles translation for control flow nop instructions.
virtual void ProcessControlFlowNopInstruction(uint32_t cf_index) {}
// Handles the start of a control flow instruction at the given address.
virtual void ProcessControlFlowInstructionBegin(uint32_t cf_index) {}
// Handles the end of a control flow instruction that began at the given
// address.
virtual void ProcessControlFlowInstructionEnd(uint32_t cf_index) {}
// Handles translation for control flow exec instructions prior to their
// contained ALU/fetch instructions.
virtual void ProcessExecInstructionBegin(const ParsedExecInstruction& instr) {
}
// Handles translation for control flow exec instructions after their
// contained ALU/fetch instructions.
virtual void ProcessExecInstructionEnd(const ParsedExecInstruction& instr) {}
// Handles translation for loop start instructions.
virtual void ProcessLoopStartInstruction(
const ParsedLoopStartInstruction& instr) {}
virtual void ProcessLoopStartInstruction(const ParsedLoopStartInstruction& instr) {}
// Handles translation for loop end instructions.
virtual void ProcessLoopEndInstruction(
const ParsedLoopEndInstruction& instr) {}
virtual void ProcessLoopEndInstruction(const ParsedLoopEndInstruction& instr) {}
// Handles translation for function call instructions.
virtual void ProcessCallInstruction(const ParsedCallInstruction& instr) {}
// Handles translation for function return instructions.
virtual void ProcessReturnInstruction(const ParsedReturnInstruction& instr) {}
// Handles translation for jump instructions.
virtual void ProcessJumpInstruction(const ParsedJumpInstruction& instr) {}
// Handles translation for alloc instructions. Memory exports for eM#
// indicated by export_eM must be performed, regardless of the alloc type.
virtual void ProcessAllocInstruction(const ParsedAllocInstruction& instr,
uint8_t export_eM) {}
virtual void ProcessAllocInstruction(const ParsedAllocInstruction& instr, uint8_t export_eM) {}
// Handles translation for vertex fetch instructions.
virtual void ProcessVertexFetchInstruction(
const ParsedVertexFetchInstruction& instr) {}
virtual void ProcessVertexFetchInstruction(const ParsedVertexFetchInstruction& instr) {}
// Handles translation for texture fetch instructions.
virtual void ProcessTextureFetchInstruction(
const ParsedTextureFetchInstruction& instr) {}
virtual void ProcessTextureFetchInstruction(const ParsedTextureFetchInstruction& instr) {}
// Handles translation for ALU instructions.
// memexport_eM_potentially_written_before needs to be handled by `kill`
// instruction to make sure memory exports for the eM# writes earlier in

View File

@ -1,4 +1,4 @@
/**
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************

View File

@ -220,4 +220,4 @@ class D3D12Provider : public GraphicsProvider {
} // namespace ui
} // namespace xe
#endif // XENIA_UI_D3D12_D3D12_PROVIDER_H_
#endif // XENIA_UI_D3D12_D3D12_PROVIDER_H_

View File

@ -62,9 +62,13 @@ class MessageBoxDialog final : public ImGuiDialog {
}
if (ImGui::BeginPopupModal(title_.c_str(), nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
char* text = const_cast<char*>(body_.c_str());
// AlexFreeman -> Fix ImGui assert: ensure null-terminated buffer with +1
// size
std::vector<char> text_buf(body_.begin(), body_.end());
text_buf.push_back('\0');
ImGui::InputTextMultiline(
"##body", text, body_.size(), ImVec2(600, 0),
"##body", text_buf.data(), text_buf.size(), ImVec2(600, 0),
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_ReadOnly);
if (ImGui::Button("OK")) {
ImGui::CloseCurrentPopup();