feat: finish exception helper.

- finish exception helper.
	- switch to LOCALAPPDATA/CrashDumps as output directory.
- add testbench for exception helper.
- add lost testbench for GetLocalAppData in WinFctHelper.
- link with DbgHelp.lib on Windows in CMake.
This commit is contained in:
yyc12345 2024-06-17 15:10:45 +08:00
parent e20c03a5f1
commit 0319be7e19
4 changed files with 117 additions and 80 deletions

View File

@ -37,13 +37,17 @@ FILES
YYCCInternal.hpp YYCCInternal.hpp
YYCCommonplace.hpp YYCCommonplace.hpp
) )
# Setup header infomations # Setup header infomations
target_include_directories(YYCCommonplace target_include_directories(YYCCommonplace
PUBLIC PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
) )
# Link with DbgHelp.lib on Windows
target_link_libraries(YYCCommonplace
PRIVATE
$<$<BOOL:${WIN32}>:DbgHelp.lib>
)
# Setup C++ standard # Setup C++ standard
set_target_properties(YYCCommonplace set_target_properties(YYCCommonplace
PROPERTIES PROPERTIES

View File

@ -6,6 +6,7 @@
#include "StringHelper.hpp" #include "StringHelper.hpp"
#include "IOHelper.hpp" #include "IOHelper.hpp"
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "FsPathPatch.hpp"
#include <filesystem> #include <filesystem>
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
@ -106,17 +107,17 @@ namespace YYCC::ExceptionHelper {
} }
/** /**
* @brief Backtrace used output function with format feature * @brief Error log (including backtrace) used output function with format feature
* @details * @details
* This function will format message first. * This function will format message first.
* And write them into given file stream and stderr. * And write them into given file stream and stderr.
* @param[in] fs * @param[in] fs
* The file stream where we write. * The file stream where we write.
* If it is nullptr, function will skip writing for file stream. * If it is nullptr, function will skip writing for file stream.
* @param[in] fmt The format string. * @param[in] fmt The format string.
* @param[in] ... The argument to be formatted. * @param[in] ... The argument to be formatted.
*/ */
static void UExceptionBacktraceFormatLine(std::FILE* fs, const char* fmt, ...) { static void UExceptionErrLogFormatLine(std::FILE* fs, const char* fmt, ...) {
// write to file // write to file
if (fs != nullptr) { if (fs != nullptr) {
va_list arg1; va_list arg1;
@ -133,15 +134,15 @@ namespace YYCC::ExceptionHelper {
} }
/** /**
* @brief Backtrace used output function * @brief Error log (including backtrace) used output function
* @details * @details
* This function will write given string into given file stream and stderr. * This function will write given string into given file stream and stderr.
* @param[in] fs * @param[in] fs
* The file stream where we write. * The file stream where we write.
* If it is nullptr, function will skip writing for file stream. * If it is nullptr, function will skip writing for file stream.
* @param[in] strl The string to be written. * @param[in] strl The string to be written.
*/ */
static void UExceptionBacktraceWriteLine(std::FILE* fs, const char* strl) { static void UExceptionErrLogWriteLine(std::FILE* fs, const char* strl) {
// write to file // write to file
if (fs != nullptr) { if (fs != nullptr) {
std::fputs(strl, fs); std::fputs(strl, fs);
@ -162,7 +163,7 @@ namespace YYCC::ExceptionHelper {
// init symbol // init symbol
if (!SymInitialize(process, 0, TRUE)) { if (!SymInitialize(process, 0, TRUE)) {
// fail to init. return // fail to init. return
UExceptionBacktraceWriteLine(fs, "Fail to initialize symbol handle for process!"); UExceptionErrLogWriteLine(fs, "Fail to initialize symbol handle for process!");
return; return;
} }
@ -214,7 +215,7 @@ namespace YYCC::ExceptionHelper {
// depth breaker // depth breaker
--maxdepth; --maxdepth;
if (maxdepth < 0) { if (maxdepth < 0) {
UExceptionBacktraceWriteLine(fs, "...\n"); // indicate there are some frames not listed UExceptionErrLogWriteLine(fs, "..."); // indicate there are some frames not listed
break; break;
} }
@ -240,7 +241,7 @@ namespace YYCC::ExceptionHelper {
} }
// write to file // write to file
UExceptionBacktraceFormatLine(fs, "0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "]\t%s#L%" PRIu64 "\n", UExceptionErrLogFormatLine(fs, "0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "]\t%s#L%" PRIu64,
frame.AddrPC.Offset, // memory adress frame.AddrPC.Offset, // memory adress
module_name, frame.AddrPC.Offset - module_base, // module name + relative address module_name, frame.AddrPC.Offset - module_base, // module name + relative address
source_file, source_file_line // source file + source line source_file, source_file_line // source file + source line
@ -255,13 +256,44 @@ namespace YYCC::ExceptionHelper {
} }
static void UExceptionErrorLog(const std::string& u8_filename, LPEXCEPTION_POINTERS info) { static void UExceptionErrorLog(const std::string& u8_filename, LPEXCEPTION_POINTERS info) {
// open file stream if we have file name
std::FILE* fs = nullptr;
if (!u8_filename.empty()) {
fs = IOHelper::UTF8FOpen(u8_filename.c_str(), "wb");
}
// record exception type first
PEXCEPTION_RECORD rec = info->ExceptionRecord;
UExceptionErrLogFormatLine(fs, "Unhandled exception occured at 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR ": %s (%" PRIu32 ").",
rec->ExceptionAddress,
UExceptionGetCodeName(rec->ExceptionCode),
rec->ExceptionCode
);
// special proc for 2 exceptions
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
if (rec->NumberParameters >= 2) {
const char* op =
rec->ExceptionInformation[0] == 0 ? "read" :
rec->ExceptionInformation[0] == 1 ? "written" : "executed";
UExceptionErrLogFormatLine(fs, "The data at memory address 0x%" PRI_XPTR_LEFT_PADDING PRIxPTR " could not be %s.",
rec->ExceptionInformation[1], op);
}
}
// output stacktrace
UExceptionBacktrace(fs, info->ContextRecord, 1024);
// close file if necessary
if (fs != nullptr) {
std::fclose(fs);
}
} }
static void UExceptionCoreDump(const std::string& u8_filename, LPEXCEPTION_POINTERS info) { static void UExceptionCoreDump(const std::string& u8_filename, LPEXCEPTION_POINTERS info) {
// convert file encoding // convert file encoding
std::wstring filename; std::wstring filename;
if (u8_filename.empty()) if (u8_filename.empty())
return; // if no given file name, return return; // if no given file name, return
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filename.c_str(), filename)) if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filename.c_str(), filename))
return; // if convertion failed, return return; // if convertion failed, return
@ -283,26 +315,55 @@ namespace YYCC::ExceptionHelper {
} }
} }
static void UExceptionFetchRecordPath(std::wstring& log_path, std::wstring& coredump_path) { static bool UExceptionFetchRecordPath(std::string& log_path, std::string& coredump_path) {
// get self name first // build two file names like: "module.dll.1234.log" and "module.dll.1234.dmp".
std::string self_name; // "module.dll" is the name of current module. "1234" is current process id.
// get self module name
std::filesystem::path ironpad_path; std::string u8_self_module_name;
WCHAR module_path[MAX_PATH]; {
std::memset(module_path, 0, sizeof(module_path)); // get module handle
if (GetModuleFileNameW(WinFctHelper::GetCurrentModule(), module_path, MAX_PATH) == 0) { HMODULE hSelfModule = YYCC::WinFctHelper::GetCurrentModule();
//goto failed; if (hSelfModule == nullptr)
return false;
// get full path of self module
std::string u8_self_module_path;
if (!YYCC::WinFctHelper::GetModuleFileName(hSelfModule, u8_self_module_path))
return false;
// extract file name from full path by std::filesystem::path
std::filesystem::path self_module_path(FsPathPatch::FromUTF8Path(u8_self_module_path.c_str()));
u8_self_module_name = FsPathPatch::ToUTF8Path(self_module_path.filename());
} }
ironpad_path = module_path; // then get process id
ironpad_path = ironpad_path.parent_path(); DWORD process_id = GetCurrentProcessId();
// conbine them as a file name prefix
std::string u8_filename_prefix;
if (!YYCC::StringHelper::Printf(u8_filename_prefix, "%s.%" PRIu32, u8_self_module_name.c_str(), process_id))
return false;
// then get file name for log and minidump
std::string u8_log_filename = u8_filename_prefix + ".log";
std::string u8_coredump_filename = u8_filename_prefix + ".dmp";
// create 2 filename // fetch crash report path
auto logfilename = ironpad_path / "IronPad.log"; // get local appdata folder
auto dmpfilename = ironpad_path / "IronPad.dmp"; std::string u8_localappdata_path;
ConsoleHelper::ErrWriteLine(""); if (!WinFctHelper::GetLocalAppData(u8_localappdata_path))
ConsoleHelper::ErrFormatLine("Exception Log: %s\n", logfilename.string().c_str()); return false;
ConsoleHelper::ErrFormatLine("Exception Coredump: %s\n", dmpfilename.string().c_str()); // convert to std::filesystem::path
std::filesystem::path crash_report_path(FsPathPatch::FromUTF8Path(u8_localappdata_path.c_str()));
// slash into crash report folder
crash_report_path /= FsPathPatch::FromUTF8Path("CrashDumps");
// use create function to make sure it is existing
std::filesystem::create_directories(crash_report_path);
// build log path and coredump path
// build std::filesystem::path first
std::filesystem::path log_filepath = crash_report_path / FsPathPatch::FromUTF8Path(u8_log_filename.c_str());
std::filesystem::path coredump_filepath = crash_report_path / FsPathPatch::FromUTF8Path(u8_coredump_filename.c_str());
// output to result
log_path = FsPathPatch::ToUTF8Path(log_filepath);
coredump_path = FsPathPatch::ToUTF8Path(coredump_filepath);
return true;
} }
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) { static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) {
@ -311,60 +372,27 @@ namespace YYCC::ExceptionHelper {
// start process // start process
g_IsProcessing = true; g_IsProcessing = true;
// core implementation
{ {
// get main folder first // fetch error report path first
std::filesystem::path ironpad_path; std::string log_path, coredump_path;
WCHAR module_path[MAX_PATH]; if (!UExceptionFetchRecordPath(log_path, coredump_path)) {
std::memset(module_path, 0, sizeof(module_path)); // fail to fetch path, clear them.
if (GetModuleFileNameW(WinFctHelper::GetCurrentModule(), module_path, MAX_PATH) == 0) { // we still can handle crash without them
goto failed; log_path.clear();
} coredump_path.clear();
ironpad_path = module_path; // and tell user we can not output file
ironpad_path = ironpad_path.parent_path(); ConsoleHelper::ErrWriteLine("Crash occurs, but we can not create crash log and coredump!");
} else {
// create 2 filename // okey. output file path to tell user the path where you can find.
auto logfilename = ironpad_path / "IronPad.log"; ConsoleHelper::ErrFormatLine("Crash Log: %s", log_path.c_str());
auto dmpfilename = ironpad_path / "IronPad.dmp"; ConsoleHelper::ErrFormatLine("Crash Coredump: %s", coredump_path.c_str());
std::fputc('\n', stdout);
std::fprintf(stdout, "Exception Log: %s\n", logfilename.string().c_str());
std::fprintf(stdout, "Exception Coredump: %s\n", dmpfilename.string().c_str());
// output log file
{
std::FILE* fs = _wfopen(logfilename.wstring().c_str(), L"w");
if (fs == nullptr) {
goto failed;
}
// record exception type first
PEXCEPTION_RECORD rec = info->ExceptionRecord;
fprintf(fs, "Unhandled exception occured at 0x%08p: %s (%lu).\n",
rec->ExceptionAddress,
UExceptionGetCodeName(rec->ExceptionCode),
rec->ExceptionCode
);
// special proc for 2 exceptions
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
if (rec->NumberParameters >= 2) {
const char* op =
rec->ExceptionInformation[0] == 0 ? "read" :
rec->ExceptionInformation[0] == 1 ? "written" : "executed";
fprintf(fs, "The data at memory address 0x%016" PRIxPTR " could not be %s.\n",
rec->ExceptionInformation[1], op);
}
}
// output stacktrace
UExceptionBacktrace(fs, info->ContextRecord, 1024);
std::fclose(fs);
} }
// output minidump // write crash log
{ UExceptionErrorLog(log_path, info);
//UExceptionCoreDump(dmpfilename.wstring().c_str(), info); // write crash coredump
} UExceptionCoreDump(coredump_path, info);
} }

View File

@ -5,6 +5,7 @@
#include "StringHelper.hpp" #include "StringHelper.hpp"
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "ConsoleHelper.hpp" #include "ConsoleHelper.hpp"
#include "COMHelper.hpp"
#include "DialogHelper.hpp" #include "DialogHelper.hpp"
#include "ParserHelper.hpp" #include "ParserHelper.hpp"
#include "ExceptionHelper.hpp" #include "ExceptionHelper.hpp"

View File

@ -237,6 +237,10 @@ namespace YYCCTestbench {
std::string test_module_name; std::string test_module_name;
Assert(YYCC::WinFctHelper::GetModuleFileName(YYCC::WinFctHelper::GetCurrentModule(), test_module_name), "YYCC::WinFctHelper::GetModuleFileName"); Assert(YYCC::WinFctHelper::GetModuleFileName(YYCC::WinFctHelper::GetCurrentModule(), test_module_name), "YYCC::WinFctHelper::GetModuleFileName");
Console::FormatLine("Current Module File Name: %s", test_module_name.c_str()); Console::FormatLine("Current Module File Name: %s", test_module_name.c_str());
std::string test_localappdata_path;
Assert(YYCC::WinFctHelper::GetLocalAppData(test_localappdata_path), "YYCC::WinFctHelper::GetLocalAppData");
Console::FormatLine("Local AppData: %s", test_localappdata_path.c_str());
} }
static void FsPathPatch() { static void FsPathPatch() {
@ -266,7 +270,7 @@ int main(int argc, char** args) {
//YYCCTestbench::StringTestbench(); //YYCCTestbench::StringTestbench();
//YYCCTestbench::ParserTestbench(); //YYCCTestbench::ParserTestbench();
//YYCCTestbench::DialogTestbench(); //YYCCTestbench::DialogTestbench();
//YYCCTestbench::ExceptionTestbench(); YYCCTestbench::ExceptionTestbench();
//YYCCTestbench::WinFctTestbench(); //YYCCTestbench::WinFctTestbench();
//YYCCTestbench::FsPathPatch(); //YYCCTestbench::FsPathPatch();
} }