Common: Add CrashHandler

This commit is contained in:
Connor McLaughlin 2022-04-19 00:58:13 +10:00 committed by refractionpcsx2
parent f46b406cbd
commit abde47fa18
7 changed files with 1674 additions and 0 deletions

View File

@ -175,7 +175,11 @@ if(WIN32)
target_sources(common PRIVATE FastJmp.asm) target_sources(common PRIVATE FastJmp.asm)
target_link_libraries(common PUBLIC WIL::WIL D3D12MemAlloc pthreads4w Winmm.lib) target_link_libraries(common PUBLIC WIL::WIL D3D12MemAlloc pthreads4w Winmm.lib)
target_sources(common PRIVATE target_sources(common PRIVATE
CrashHandler.cpp
CrashHandler.h
FastJmp.asm FastJmp.asm
StackWalker.cpp
StackWalker.h
D3D11/ShaderCache.cpp D3D11/ShaderCache.cpp
D3D11/ShaderCache.h D3D11/ShaderCache.h
D3D11/ShaderCompiler.cpp D3D11/ShaderCompiler.cpp

228
common/CrashHandler.cpp Normal file
View File

@ -0,0 +1,228 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "Pcsx2Defs.h"
#include "CrashHandler.h"
#include "FileSystem.h"
#include "StringUtil.h"
#include <cinttypes>
#include <cstdio>
#include <ctime>
#if defined(_WIN32) && !defined(_UWP)
#include "RedtapeWindows.h"
#include "StackWalker.h"
#include <DbgHelp.h>
class CrashHandlerStackWalker : public StackWalker
{
public:
explicit CrashHandlerStackWalker(HANDLE out_file);
~CrashHandlerStackWalker();
protected:
void OnOutput(LPCSTR szText) override;
private:
HANDLE m_out_file;
};
CrashHandlerStackWalker::CrashHandlerStackWalker(HANDLE out_file)
: StackWalker(RetrieveVerbose, nullptr, GetCurrentProcessId(), GetCurrentProcess())
, m_out_file(out_file)
{
}
CrashHandlerStackWalker::~CrashHandlerStackWalker()
{
if (m_out_file)
CloseHandle(m_out_file);
}
void CrashHandlerStackWalker::OnOutput(LPCSTR szText)
{
if (m_out_file)
{
DWORD written;
WriteFile(m_out_file, szText, static_cast<DWORD>(std::strlen(szText)), &written, nullptr);
}
OutputDebugStringA(szText);
}
static bool WriteMinidump(HMODULE hDbgHelp, HANDLE hFile, HANDLE hProcess, DWORD process_id, DWORD thread_id,
PEXCEPTION_POINTERS exception, MINIDUMP_TYPE type)
{
using PFNMINIDUMPWRITEDUMP =
BOOL(WINAPI*)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType,
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
PFNMINIDUMPWRITEDUMP minidump_write_dump = hDbgHelp ?
reinterpret_cast<PFNMINIDUMPWRITEDUMP>(GetProcAddress(hDbgHelp, "MiniDumpWriteDump")) :
nullptr;
if (!minidump_write_dump)
return false;
MINIDUMP_EXCEPTION_INFORMATION mei;
PMINIDUMP_EXCEPTION_INFORMATION mei_ptr = nullptr;
if (exception)
{
mei.ThreadId = thread_id;
mei.ExceptionPointers = exception;
mei.ClientPointers = FALSE;
mei_ptr = &mei;
}
return minidump_write_dump(hProcess, process_id, hFile, type, mei_ptr, nullptr, nullptr);
}
static std::wstring s_write_directory;
static HMODULE s_dbghelp_module = nullptr;
static PVOID s_veh_handle = nullptr;
static bool s_in_crash_handler = false;
static void GenerateCrashFilename(wchar_t* buf, size_t len, const wchar_t* prefix, const wchar_t* extension)
{
SYSTEMTIME st = {};
GetLocalTime(&st);
_snwprintf_s(buf, len, _TRUNCATE, L"%s%scrash-%04u-%02u-%02u-%02u-%02u-%02u-%03u.%s",
prefix ? prefix : L"", prefix ? L"\\" : L"",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, extension);
}
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;
s_in_crash_handler = true;
wchar_t filename[1024] = {};
GenerateCrashFilename(filename, std::size(filename), s_write_directory.empty() ? nullptr : s_write_directory.c_str(), L"txt");
// might fail
HANDLE hFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
if (hFile != INVALID_HANDLE_VALUE)
{
char line[1024];
DWORD written;
std::snprintf(line, std::size(line), "Exception 0x%08X at 0x%p\n", exi->ExceptionRecord->ExceptionCode,
exi->ExceptionRecord->ExceptionAddress);
WriteFile(hFile, line, static_cast<DWORD>(std::strlen(line)), &written, nullptr);
}
GenerateCrashFilename(filename, std::size(filename), s_write_directory.empty() ? nullptr : s_write_directory.c_str(), L"dmp");
const MINIDUMP_TYPE minidump_type =
static_cast<MINIDUMP_TYPE>(MiniDumpNormal | MiniDumpWithHandleData | MiniDumpWithProcessThreadData |
MiniDumpWithThreadInfo | MiniDumpWithIndirectlyReferencedMemory);
const HANDLE hMinidumpFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
if (hMinidumpFile == INVALID_HANDLE_VALUE ||
!WriteMinidump(s_dbghelp_module, hMinidumpFile, GetCurrentProcess(), GetCurrentProcessId(),
GetCurrentThreadId(), exi, minidump_type))
{
static const char error_message[] = "Failed to write minidump file.\n";
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD written;
WriteFile(hFile, error_message, sizeof(error_message) - 1, &written, nullptr);
}
}
if (hMinidumpFile != INVALID_HANDLE_VALUE)
CloseHandle(hMinidumpFile);
CrashHandlerStackWalker sw(hFile);
sw.ShowCallstack(GetCurrentThread(), exi->ContextRecord);
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
return EXCEPTION_CONTINUE_SEARCH;
}
bool CrashHandler::Install()
{
// load dbghelp at install/startup, that way we're not LoadLibrary()'ing after a crash
// .. because that probably wouldn't go down well.
s_dbghelp_module = StackWalker::LoadDbgHelpLibrary();
s_veh_handle = AddVectoredExceptionHandler(0, ExceptionHandler);
return (s_veh_handle != nullptr);
}
void CrashHandler::SetWriteDirectory(const std::string_view& dump_directory)
{
if (!s_veh_handle)
return;
s_write_directory = StringUtil::UTF8StringToWideString(dump_directory);
}
void CrashHandler::Uninstall()
{
if (s_veh_handle)
{
RemoveVectoredExceptionHandler(s_veh_handle);
s_veh_handle = nullptr;
}
if (s_dbghelp_module)
{
FreeLibrary(s_dbghelp_module);
s_dbghelp_module = nullptr;
}
}
#else
bool CrashHandler::Install()
{
return false;
}
void CrashHandler::SetWriteDirectory(const std::string_view& dump_directory)
{
}
void CrashHandler::Uninstall()
{
}
#endif

23
common/CrashHandler.h Normal file
View File

@ -0,0 +1,23 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <string_view>
namespace CrashHandler
{
bool Install();
void SetWriteDirectory(const std::string_view& dump_directory);
void Uninstall();
} // namespace CrashHandler

1222
common/StackWalker.cpp Normal file

File diff suppressed because it is too large Load Diff

181
common/StackWalker.h Normal file
View File

@ -0,0 +1,181 @@
/**********************************************************************
*
* StackWalker.h
*
*
*
* LICENSE (http://www.opensource.org/licenses/bsd-license.php)
*
* Copyright (c) 2005-2009, Jochen Kalmbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* Neither the name of Jochen Kalmbach nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* **********************************************************************/
#pragma once
#include "RedtapeWindows.h"
class StackWalkerInternal; // forward
class StackWalker
{
public:
typedef enum StackWalkOptions
{
// No addition info will be retrieved
// (only the address is available)
RetrieveNone = 0,
// Try to get the symbol-name
RetrieveSymbol = 1,
// Try to get the line for this symbol
RetrieveLine = 2,
// Try to retrieve the module-infos
RetrieveModuleInfo = 4,
// Also retrieve the version for the DLL/EXE
RetrieveFileVersion = 8,
// Contains all the above
RetrieveVerbose = 0xF,
// Generate a "good" symbol-search-path
SymBuildPath = 0x10,
// Also use the public Microsoft-Symbol-Server
SymUseSymSrv = 0x20,
// Contains all the above "Sym"-options
SymAll = 0x30,
// Contains all options (default)
OptionsAll = 0x3F
} StackWalkOptions;
StackWalker(int options = OptionsAll, // 'int' is by design, to combine the enum-flags
LPCSTR szSymPath = NULL,
DWORD dwProcessId = GetCurrentProcessId(),
HANDLE hProcess = GetCurrentProcess());
StackWalker(DWORD dwProcessId, HANDLE hProcess);
StackWalker(const StackWalker&) = delete;
virtual ~StackWalker();
static HMODULE LoadDbgHelpLibrary();
typedef BOOL(__stdcall* PReadProcessMemoryRoutine)(
HANDLE hProcess,
DWORD64 qwBaseAddress,
PVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead,
LPVOID pUserData // optional data, which was passed in "ShowCallstack"
);
BOOL LoadModules();
BOOL ShowCallstack(
HANDLE hThread = GetCurrentThread(),
const CONTEXT* context = NULL,
PReadProcessMemoryRoutine readMemoryFunction = NULL,
LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback
);
BOOL ShowObject(LPVOID pObject);
#if _MSC_VER >= 1300
// due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public"
// in older compilers in order to use it... starting with VC7 we can declare it as "protected"
protected:
#endif
enum
{
STACKWALK_MAX_NAMELEN = 1024
}; // max name length for found symbols
protected:
// Entry for each Callstack-Entry
typedef struct CallstackEntry
{
DWORD64 offset; // if 0, we have no valid entry
CHAR name[STACKWALK_MAX_NAMELEN];
CHAR undName[STACKWALK_MAX_NAMELEN];
CHAR undFullName[STACKWALK_MAX_NAMELEN];
DWORD64 offsetFromSmybol;
DWORD offsetFromLine;
DWORD lineNumber;
CHAR lineFileName[STACKWALK_MAX_NAMELEN];
DWORD symType;
LPCSTR symTypeString;
CHAR moduleName[STACKWALK_MAX_NAMELEN];
DWORD64 baseOfImage;
CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
} CallstackEntry;
typedef enum CallstackEntryType
{
firstEntry,
nextEntry,
lastEntry
} CallstackEntryType;
virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
virtual void OnLoadModule(LPCSTR img,
LPCSTR mod,
DWORD64 baseAddr,
DWORD size,
DWORD result,
LPCSTR symType,
LPCSTR pdbName,
ULONGLONG fileVersion);
virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry& entry);
virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);
virtual void OnOutput(LPCSTR szText);
StackWalkerInternal* m_sw;
HANDLE m_hProcess;
DWORD m_dwProcessId;
BOOL m_modulesLoaded;
LPSTR m_szSymPath;
int m_options;
int m_MaxRecursionCount;
static BOOL __stdcall myReadProcMem(HANDLE hProcess,
DWORD64 qwBaseAddress,
PVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead);
friend StackWalkerInternal;
}; // class StackWalker
// The following is defined for x86 (XP and higher), x64 and IA64:
#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \
do \
{ \
memset(&c, 0, sizeof(CONTEXT)); \
c.ContextFlags = contextFlags; \
RtlCaptureContext(&c); \
} while (0);

View File

@ -45,6 +45,7 @@
<ItemGroup> <ItemGroup>
<ClCompile Include="AlignedMalloc.cpp" /> <ClCompile Include="AlignedMalloc.cpp" />
<ClCompile Include="Console.cpp" /> <ClCompile Include="Console.cpp" />
<ClCompile Include="CrashHandler.cpp" />
<ClCompile Include="D3D11\ShaderCache.cpp" /> <ClCompile Include="D3D11\ShaderCache.cpp" />
<ClCompile Include="D3D11\ShaderCompiler.cpp" /> <ClCompile Include="D3D11\ShaderCompiler.cpp" />
<ClCompile Include="D3D12\Builders.cpp" /> <ClCompile Include="D3D12\Builders.cpp" />
@ -68,6 +69,7 @@
<ClCompile Include="MD5Digest.cpp" /> <ClCompile Include="MD5Digest.cpp" />
<ClCompile Include="ProgressCallback.cpp" /> <ClCompile Include="ProgressCallback.cpp" />
<ClCompile Include="pxTranslate.cpp" /> <ClCompile Include="pxTranslate.cpp" />
<ClCompile Include="StackWalker.cpp" />
<ClCompile Include="StringUtil.cpp" /> <ClCompile Include="StringUtil.cpp" />
<ClCompile Include="SettingsWrapper.cpp" /> <ClCompile Include="SettingsWrapper.cpp" />
<ClCompile Include="Timer.cpp" /> <ClCompile Include="Timer.cpp" />
@ -122,6 +124,7 @@
<ClInclude Include="Align.h" /> <ClInclude Include="Align.h" />
<ClInclude Include="AlignedMalloc.h" /> <ClInclude Include="AlignedMalloc.h" />
<ClInclude Include="BitCast.h" /> <ClInclude Include="BitCast.h" />
<ClInclude Include="CrashHandler.h" />
<ClInclude Include="D3D11\ShaderCache.h" /> <ClInclude Include="D3D11\ShaderCache.h" />
<ClInclude Include="D3D11\ShaderCompiler.h" /> <ClInclude Include="D3D11\ShaderCompiler.h" />
<ClInclude Include="D3D12\Builders.h" /> <ClInclude Include="D3D12\Builders.h" />
@ -144,6 +147,7 @@
<ClInclude Include="MD5Digest.h" /> <ClInclude Include="MD5Digest.h" />
<ClInclude Include="ProgressCallback.h" /> <ClInclude Include="ProgressCallback.h" />
<ClInclude Include="ScopedGuard.h" /> <ClInclude Include="ScopedGuard.h" />
<ClInclude Include="StackWalker.h" />
<ClInclude Include="StringUtil.h" /> <ClInclude Include="StringUtil.h" />
<ClInclude Include="SettingsInterface.h" /> <ClInclude Include="SettingsInterface.h" />
<ClInclude Include="SettingsWrapper.h" /> <ClInclude Include="SettingsWrapper.h" />

View File

@ -199,6 +199,12 @@
<ClCompile Include="D3D12\Builders.cpp"> <ClCompile Include="D3D12\Builders.cpp">
<Filter>Source Files\D3D12</Filter> <Filter>Source Files\D3D12</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="StackWalker.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CrashHandler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="AlignedMalloc.h"> <ClInclude Include="AlignedMalloc.h">
@ -456,6 +462,12 @@
<ClInclude Include="D3D12\Builders.h"> <ClInclude Include="D3D12\Builders.h">
<Filter>Header Files\D3D12</Filter> <Filter>Header Files\D3D12</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="CrashHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="StackWalker.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Filter Include="Source Files"> <Filter Include="Source Files">