Common: Redo assertions, purge DiagnosticOrigin

This commit is contained in:
Connor McLaughlin 2022-05-24 21:21:31 +10:00 committed by refractionpcsx2
parent a02df6c980
commit 1f802eca46
10 changed files with 188 additions and 159 deletions

View File

@ -27,35 +27,6 @@
#endif #endif
#endif #endif
// --------------------------------------------------------------------------------------
// DiagnosticOrigin
// --------------------------------------------------------------------------------------
struct DiagnosticOrigin
{
const char* srcfile;
const char* function;
const char* condition;
int line;
DiagnosticOrigin(const char* _file, int _line, const char* _func, const char* _cond = nullptr)
: srcfile(_file)
, function(_func)
, condition(_cond)
, line(_line)
{
}
std::string ToString(const char* msg = nullptr) const;
};
// Returns ture if the assertion is to trap into the debugger, or false if execution
// of the program should continue unimpeded.
typedef bool pxDoAssertFnType(const DiagnosticOrigin& origin, const char* msg);
extern pxDoAssertFnType pxAssertImpl_LogIt;
extern pxDoAssertFnType* pxDoAssert;
// ---------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------
// pxAssert / pxAssertDev // pxAssert / pxAssertDev
// ---------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------
@ -93,16 +64,15 @@ extern pxDoAssertFnType* pxDoAssert;
// it can lead to the compiler optimizing out code and leading to crashes in dev/release // it can lead to the compiler optimizing out code and leading to crashes in dev/release
// builds. To have code optimized, explicitly use pxAssume(false) or pxAssumeDev(false,msg); // builds. To have code optimized, explicitly use pxAssume(false) or pxAssumeDev(false,msg);
#define pxDiagSpot DiagnosticOrigin(__FILE__, __LINE__, __pxFUNCTION__)
#define pxAssertSpot(cond) DiagnosticOrigin(__FILE__, __LINE__, __pxFUNCTION__, #cond)
// pxAssertRel -> // pxAssertRel ->
// Special release-mode assertion. Limited use since stack traces in release mode builds // Special release-mode assertion. Limited use since stack traces in release mode builds
// (especially with LTCG) are highly suspect. But when troubleshooting crashes that only // (especially with LTCG) are highly suspect. But when troubleshooting crashes that only
// rear ugly heads in optimized builds, this is one of the few tools we have. // rear ugly heads in optimized builds, this is one of the few tools we have.
#define pxAssertRel(cond, msg) ((likely(cond)) || (pxOnAssert(pxAssertSpot(cond), msg), false)) extern void pxOnAssertFail(const char* file, int line, const char* func, const char* msg);
#define pxAssumeRel(cond, msg) ((void)((!likely(cond)) && (pxOnAssert(pxAssertSpot(cond), msg), false)))
#define pxAssertRel(cond, msg) ((likely(cond)) || (pxOnAssertFail(__FILE__, __LINE__, __pxFUNCTION__, msg), false))
#define pxAssumeRel(cond, msg) ((void)((!likely(cond)) && (pxOnAssertFail(__FILE__, __LINE__, __pxFUNCTION__, msg), false)))
#define pxFailRel(msg) pxAssertRel(false, msg) #define pxFailRel(msg) pxAssertRel(false, msg)
#if defined(PCSX2_DEBUG) #if defined(PCSX2_DEBUG)
@ -168,8 +138,6 @@ extern pxDoAssertFnType* pxDoAssert;
#define pxAssertRelease(cond, msg) #define pxAssertRelease(cond, msg)
extern void pxOnAssert(const DiagnosticOrigin& origin, const char* msg);
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// jNO_DEFAULT -- disables the default case in a switch, which improves switch optimization // jNO_DEFAULT -- disables the default case in a switch, which improves switch optimization
// under MSVC. // under MSVC.

View File

@ -46,11 +46,7 @@ CrashHandlerStackWalker::CrashHandlerStackWalker(HANDLE out_file)
{ {
} }
CrashHandlerStackWalker::~CrashHandlerStackWalker() CrashHandlerStackWalker::~CrashHandlerStackWalker() = default;
{
if (m_out_file)
CloseHandle(m_out_file);
}
void CrashHandlerStackWalker::OnOutput(LPCSTR szText) void CrashHandlerStackWalker::OnOutput(LPCSTR szText)
{ {
@ -105,33 +101,8 @@ static void GenerateCrashFilename(wchar_t* buf, size_t len, const wchar_t* prefi
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, extension); st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, extension);
} }
static LONG NTAPI ExceptionHandler(PEXCEPTION_POINTERS exi) static void WriteMinidumpAndCallstack(PEXCEPTION_POINTERS exi)
{ {
if (s_in_crash_handler)
return EXCEPTION_CONTINUE_SEARCH;
switch (exi->ExceptionRecord->ExceptionCode)
{
case EXCEPTION_ACCESS_VIOLATION:
case EXCEPTION_BREAKPOINT:
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
case EXCEPTION_INT_DIVIDE_BY_ZERO:
case EXCEPTION_INT_OVERFLOW:
case EXCEPTION_PRIV_INSTRUCTION:
case EXCEPTION_ILLEGAL_INSTRUCTION:
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
case EXCEPTION_STACK_OVERFLOW:
case EXCEPTION_GUARD_PAGE:
break;
default:
return EXCEPTION_CONTINUE_SEARCH;
}
// if the debugger is attached, let it take care of it.
if (IsDebuggerPresent())
return EXCEPTION_CONTINUE_SEARCH;
s_in_crash_handler = true; s_in_crash_handler = true;
wchar_t filename[1024] = {}; wchar_t filename[1024] = {};
@ -139,7 +110,7 @@ static LONG NTAPI ExceptionHandler(PEXCEPTION_POINTERS exi)
// might fail // might fail
HANDLE hFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr); HANDLE hFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
if (hFile != INVALID_HANDLE_VALUE) if (exi && hFile != INVALID_HANDLE_VALUE)
{ {
char line[1024]; char line[1024];
DWORD written; DWORD written;
@ -169,11 +140,40 @@ static LONG NTAPI ExceptionHandler(PEXCEPTION_POINTERS exi)
CloseHandle(hMinidumpFile); CloseHandle(hMinidumpFile);
CrashHandlerStackWalker sw(hFile); CrashHandlerStackWalker sw(hFile);
sw.ShowCallstack(GetCurrentThread(), exi->ContextRecord); sw.ShowCallstack(GetCurrentThread(), exi ? exi->ContextRecord : nullptr);
if (hFile != INVALID_HANDLE_VALUE) if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile); CloseHandle(hFile);
}
static LONG NTAPI ExceptionHandler(PEXCEPTION_POINTERS exi)
{
if (s_in_crash_handler)
return EXCEPTION_CONTINUE_SEARCH;
switch (exi->ExceptionRecord->ExceptionCode)
{
case EXCEPTION_ACCESS_VIOLATION:
case EXCEPTION_BREAKPOINT:
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
case EXCEPTION_INT_DIVIDE_BY_ZERO:
case EXCEPTION_INT_OVERFLOW:
case EXCEPTION_PRIV_INSTRUCTION:
case EXCEPTION_ILLEGAL_INSTRUCTION:
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
case EXCEPTION_STACK_OVERFLOW:
case EXCEPTION_GUARD_PAGE:
break;
default:
return EXCEPTION_CONTINUE_SEARCH;
}
// if the debugger is attached, let it take care of it.
if (IsDebuggerPresent())
return EXCEPTION_CONTINUE_SEARCH;
WriteMinidumpAndCallstack(exi);
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
@ -195,6 +195,11 @@ void CrashHandler::SetWriteDirectory(const std::string_view& dump_directory)
s_write_directory = StringUtil::UTF8StringToWideString(dump_directory); s_write_directory = StringUtil::UTF8StringToWideString(dump_directory);
} }
void CrashHandler::WriteDumpForCaller()
{
WriteMinidumpAndCallstack(nullptr);
}
void CrashHandler::Uninstall() void CrashHandler::Uninstall()
{ {
if (s_veh_handle) if (s_veh_handle)
@ -221,6 +226,10 @@ void CrashHandler::SetWriteDirectory(const std::string_view& dump_directory)
{ {
} }
void CrashHandler::WriteDumpForCaller()
{
}
void CrashHandler::Uninstall() void CrashHandler::Uninstall()
{ {
} }

View File

@ -19,5 +19,6 @@ namespace CrashHandler
{ {
bool Install(); bool Install();
void SetWriteDirectory(const std::string_view& dump_directory); void SetWriteDirectory(const std::string_view& dump_directory);
void WriteDumpForCaller();
void Uninstall(); void Uninstall();
} // namespace CrashHandler } // namespace CrashHandler

View File

@ -16,51 +16,21 @@
#include "Threading.h" #include "Threading.h"
#include "General.h" #include "General.h"
#include "Exceptions.h" #include "Exceptions.h"
#include "CrashHandler.h"
#include <mutex>
#include "fmt/core.h" #include "fmt/core.h"
#ifdef _WIN32 #ifdef _WIN32
#include "RedtapeWindows.h" #include "RedtapeWindows.h"
#include <intrin.h>
#include <tlhelp32.h>
#endif #endif
#ifdef __UNIX__ #ifdef __UNIX__
#include <signal.h> #include <signal.h>
#endif #endif
// ------------------------------------------------------------------------
// Force DevAssert to *not* inline for devel builds (allows using breakpoints to trap assertions,
// and force it to inline for release builds (optimizes it out completely since IsDevBuild is a
// const false).
//
#ifdef PCSX2_DEVBUILD
#define DEVASSERT_INLINE __noinline
#else
#define DEVASSERT_INLINE __fi
#endif
pxDoAssertFnType* pxDoAssert = pxAssertImpl_LogIt;
// make life easier for people using VC++ IDE by using this format, which allows double-click
// response times from the Output window...
std::string DiagnosticOrigin::ToString(const char* msg) const
{
std::string message;
fmt::format_to(std::back_inserter(message), "{}({}) : assertion failed:\n", srcfile, line);
if (function)
fmt::format_to(std::back_inserter(message), " Function: {}\n", function);
if (condition)
fmt::format_to(std::back_inserter(message), " Condition: {}\n", condition);
if (msg)
fmt::format_to(std::back_inserter(message), " Message: {}\n", msg);
return message;
}
// Because wxTrap isn't available on Linux builds of wxWidgets (non-Debug, typically) // Because wxTrap isn't available on Linux builds of wxWidgets (non-Debug, typically)
void pxTrap() void pxTrap()
{ {
@ -73,46 +43,105 @@ void pxTrap()
#endif #endif
} }
static std::mutex s_assertion_failed_mutex;
bool pxAssertImpl_LogIt(const DiagnosticOrigin& origin, const char* msg) static inline void FreezeThreads(void** handle)
{ {
//wxLogError( L"%s", origin.ToString( msg ).c_str() ); #if defined(_WIN32) && !defined(_UWP)
std::string full_msg(origin.ToString(msg)); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
#ifdef _WIN32 if (snapshot != INVALID_HANDLE_VALUE)
OutputDebugStringA(full_msg.c_str()); {
OutputDebugStringA("\n"); THREADENTRY32 threadEntry;
if (Thread32First(snapshot, &threadEntry))
{
do
{
if (threadEntry.th32ThreadID == GetCurrentThreadId())
continue;
HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, threadEntry.th32ThreadID);
if (hThread != nullptr)
{
SuspendThread(hThread);
CloseHandle(hThread);
}
} while (Thread32Next(snapshot, &threadEntry));
}
}
*handle = static_cast<void*>(snapshot);
#else
* handle = nullptr;
#endif #endif
std::fprintf(stderr, "%s\n", full_msg.c_str());
pxTrap();
return false;
} }
static inline void ResumeThreads(void* handle)
DEVASSERT_INLINE void pxOnAssert(const DiagnosticOrigin& origin, const char* msg)
{ {
// wxWidgets doesn't come with debug builds on some Linux distros, and other distros make #if defined(_WIN32) && !defined(_UWP)
// it difficult to use the debug build (compilation failures). To handle these I've had to if (handle != INVALID_HANDLE_VALUE)
// bypass the internal wxWidgets assertion handler entirely, since it may not exist even if
// PCSX2 itself is compiled in debug mode (assertions enabled).
bool trapit;
if (pxDoAssert == NULL)
{ {
// Note: Format uses MSVC's syntax for output window hotlinking. THREADENTRY32 threadEntry;
trapit = pxAssertImpl_LogIt(origin, msg); if (Thread32First(reinterpret_cast<HANDLE>(handle), &threadEntry))
} {
else do
{ {
trapit = pxDoAssert(origin, msg); if (threadEntry.th32ThreadID == GetCurrentThreadId())
} continue;
if (trapit) HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, threadEntry.th32ThreadID);
{ if (hThread != nullptr)
pxTrap(); {
ResumeThread(hThread);
CloseHandle(hThread);
}
} while (Thread32Next(reinterpret_cast<HANDLE>(handle), &threadEntry));
}
CloseHandle(reinterpret_cast<HANDLE>(handle));
} }
#else
#endif
}
void pxOnAssertFail(const char* file, int line, const char* func, const char* msg)
{
std::unique_lock guard(s_assertion_failed_mutex);
void* handle;
FreezeThreads(&handle);
char full_msg[512];
std::snprintf(full_msg, sizeof(full_msg), "%s:%d: assertion failed in function %s: %s\n", file, line, func, msg);
#if defined(_WIN32) && !defined(_UWP)
HANDLE error_handle = GetStdHandle(STD_ERROR_HANDLE);
if (error_handle != INVALID_HANDLE_VALUE)
WriteConsoleA(GetStdHandle(STD_ERROR_HANDLE), full_msg, static_cast<DWORD>(std::strlen(full_msg)), NULL, NULL);
OutputDebugStringA(full_msg);
std::snprintf(
full_msg, sizeof(full_msg),
"Assertion failed in function %s (%s:%d):\n\n%s\n\nPress Abort to exit, Retry to break to debugger, or Ignore to attempt to continue.",
func, file, line, msg);
int result = MessageBoxA(NULL, full_msg, NULL, MB_ABORTRETRYIGNORE | MB_ICONERROR);
if (result == IDRETRY)
{
__debugbreak();
}
else if (result != IDIGNORE)
{
// try to save a crash dump before exiting
CrashHandler::WriteDumpForCaller();
TerminateProcess(GetCurrentProcess(), 0xBAADC0DE);
}
#else
fputs(full_msg, stderr);
fputs("\nAborting application.\n", stderr);
fflush(stderr);
abort();
#endif
ResumeThreads(handle);
} }
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------

View File

@ -728,8 +728,6 @@ int AppOpenModalDialog(wxString panel_name, wxWindow* parent = NULL)
return DialogType(parent).ShowModal(); return DialogType(parent).ShowModal();
} }
extern pxDoAssertFnType AppDoAssert;
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// External App-related Globals and Shortcuts // External App-related Globals and Shortcuts
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------

View File

@ -114,10 +114,7 @@ void Pcsx2App::OnAssertFailure( const wxChar *file, int line, const wxChar *func
std::string nfunc(StringUtil::wxStringToUTF8String(func)); std::string nfunc(StringUtil::wxStringToUTF8String(func));
std::string ncond(StringUtil::wxStringToUTF8String(cond)); std::string ncond(StringUtil::wxStringToUTF8String(cond));
std::string nmsg(StringUtil::wxStringToUTF8String(msg)); std::string nmsg(StringUtil::wxStringToUTF8String(msg));
if( AppDoAssert( DiagnosticOrigin( nfile.c_str(), line, nfunc.c_str(), ncond.c_str()), nmsg.c_str())) pxOnAssertFail(nfile.c_str(), line, nfunc.c_str(), nmsg.empty() ? ncond.c_str() : nmsg.c_str());
{
pxTrap();
}
} }
#endif #endif

View File

@ -388,8 +388,6 @@ bool Pcsx2App::OnInit()
InitCPUTicks(); InitCPUTicks();
pxDoAssert = AppDoAssert;
g_Conf = std::make_unique<AppConfig>(); g_Conf = std::make_unique<AppConfig>();
wxInitAllImageHandlers(); wxInitAllImageHandlers();
@ -623,7 +621,6 @@ void Pcsx2App::CleanupOnExit()
// FIXME: performing a wxYield() here may fix that problem. -- air // FIXME: performing a wxYield() here may fix that problem. -- air
pxDoAssert = pxAssertImpl_LogIt;
Console_SetActiveHandler(ConsoleWriter_Stdout); Console_SetActiveHandler(ConsoleWriter_Stdout);
} }
@ -732,7 +729,6 @@ Pcsx2App::Pcsx2App()
Pcsx2App::~Pcsx2App() Pcsx2App::~Pcsx2App()
{ {
pxDoAssert = pxAssertImpl_LogIt;
} }
void Pcsx2App::CleanUp() void Pcsx2App::CleanUp()

View File

@ -107,6 +107,26 @@ static void unmake_curthread_key()
curthread_key = 0; curthread_key = 0;
} }
// make life easier for people using VC++ IDE by using this format, which allows double-click
// response times from the Output window...
std::string DiagnosticOrigin::ToString(const char* msg) const
{
std::string message;
fmt::format_to(std::back_inserter(message), "{}({}) : assertion failed:\n", srcfile, line);
if (function)
fmt::format_to(std::back_inserter(message), " Function: {}\n", function);
if (condition)
fmt::format_to(std::back_inserter(message), " Condition: {}\n", condition);
if (msg)
fmt::format_to(std::back_inserter(message), " Message: {}\n", msg);
return message;
}
void Threading::pxTestCancel() void Threading::pxTestCancel()
{ {
pthread_testcancel(); pthread_testcancel();
@ -204,7 +224,7 @@ bool Threading::pxThread::AffinityAssert_AllowFromSelf(const DiagnosticOrigin& o
return true; return true;
if (IsDevBuild) if (IsDevBuild)
pxOnAssert(origin, pxsFmt(L"Thread affinity violation: Call allowed from '%s' thread only.", WX_STR(GetName())).ToUTF8().data()); pxOnAssertFail(origin.srcfile, origin.line, origin.function, pxsFmt(L"Thread affinity violation: Call allowed from '%s' thread only.", WX_STR(GetName())).ToUTF8().data());
return false; return false;
} }
@ -215,7 +235,7 @@ bool Threading::pxThread::AffinityAssert_DisallowFromSelf(const DiagnosticOrigin
return true; return true;
if (IsDevBuild) if (IsDevBuild)
pxOnAssert(origin, pxsFmt(L"Thread affinity violation: Call is *not* allowed from '%s' thread.", WX_STR(GetName())).ToUTF8().data()); pxOnAssertFail(origin.srcfile, origin.line, origin.function, pxsFmt(L"Thread affinity violation: Call is *not* allowed from '%s' thread.", WX_STR(GetName())).ToUTF8().data());
return false; return false;
} }

View File

@ -30,6 +30,30 @@
#include <semaphore.h> #include <semaphore.h>
#endif #endif
// --------------------------------------------------------------------------------------
// DiagnosticOrigin
// --------------------------------------------------------------------------------------
struct DiagnosticOrigin
{
const char* srcfile;
const char* function;
const char* condition;
int line;
DiagnosticOrigin(const char* _file, int _line, const char* _func, const char* _cond = nullptr)
: srcfile(_file)
, function(_func)
, condition(_cond)
, line(_line)
{
}
std::string ToString(const char* msg = nullptr) const;
};
#define pxDiagSpot DiagnosticOrigin(__FILE__, __LINE__, __pxFUNCTION__)
#define pxAssertSpot(cond) DiagnosticOrigin(__FILE__, __LINE__, __pxFUNCTION__, #cond)
namespace Threading namespace Threading
{ {
class pxThread; class pxThread;

View File

@ -22,20 +22,7 @@ using namespace x86Emitter;
thread_local const char *currentTest; thread_local const char *currentTest;
static void assertHandlerInternal(const DiagnosticOrigin& origin, const char* msg) {
FAIL() << "Assertion failed: " << msg
<< "\n at " << origin.srcfile << ":" << origin.line << ""
<< "\n when trying to assemble " << currentTest;
}
static bool assertHandler(const DiagnosticOrigin& origin, const char* msg) {
assertHandlerInternal(origin, msg);
return false;
}
void runCodegenTest(void (*exec)(void *base), const char* description, const char* expected) { void runCodegenTest(void (*exec)(void *base), const char* description, const char* expected) {
pxDoAssert = assertHandler;
u8 code[4096]; u8 code[4096];
memset(code, 0xcc, sizeof(code)); memset(code, 0xcc, sizeof(code));
char str[4096] = {0}; char str[4096] = {0};