Skip to content

Commit 938ebbf

Browse files
committed
Experimental support for Windows crash logging
1 parent 4d27d12 commit 938ebbf

4 files changed

Lines changed: 287 additions & 27 deletions

File tree

‎src/Engine/CrossPlatform.cpp‎

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* along with OpenXcom. If not, see <http://www.gnu.org/licenses/>.
1818
*/
1919
#include "CrossPlatform.h"
20+
#include <exception>
2021
#include <algorithm>
2122
#include <sstream>
2223
#include <fstream>
@@ -37,13 +38,17 @@
3738
#include <shlobj.h>
3839
#include <shlwapi.h>
3940
#include <direct.h>
41+
#include <DbgHelp.h>
4042
#ifndef SHGFP_TYPE_CURRENT
4143
#define SHGFP_TYPE_CURRENT 0
4244
#endif
45+
#define MAX_STACK_FRAMES 100
46+
#define EXCEPTION_CODE_CXX 0xe06d7363
4347
#ifndef __GNUC__
4448
#pragma comment(lib, "advapi32.lib")
4549
#pragma comment(lib, "shell32.lib")
4650
#pragma comment(lib, "shlwapi.lib")
51+
#pragma comment(lib, "DbgHelp.lib")
4752
#endif
4853
#else
4954
#include "Language.h"
@@ -56,7 +61,16 @@
5661
#include <sys/param.h>
5762
#include <sys/types.h>
5863
#include <pwd.h>
64+
#ifdef BACKTRACE_LIBUNWIND
65+
#ifndef _GNU_SOURCE
66+
#define _GNU_SOURCE
5967
#endif
68+
#include <dlfcn.h>
69+
#define UNW_LOCAL_ONLY
70+
#include <libunwind.h>
71+
#endif
72+
#endif
73+
#define MAX_SYMBOL_LENGTH 1024
6074
#include <SDL.h>
6175
#include <SDL_syswm.h>
6276

@@ -810,6 +824,13 @@ std::string getDosPath()
810824
#endif
811825
}
812826

827+
/**
828+
* Sets the window titlebar icon.
829+
* For Windows, use the embedded resource icon.
830+
* For other systems, use a PNG icon.
831+
* @param winResource ID for Windows icon.
832+
* @param unixPath Path to PNG icon for Unix.
833+
*/
813834
void setWindowIcon(int winResource, const std::string &unixPath)
814835
{
815836
#ifdef _WIN32
@@ -836,5 +857,232 @@ void setWindowIcon(int winResource, const std::string &unixPath)
836857
#endif
837858
}
838859

860+
/**
861+
* Logs the stack back trace leading up to this function call.
862+
*/
863+
void stackTrace()
864+
{
865+
#ifdef _WIN32
866+
static bool initialised = false;
867+
static HANDLE process;
868+
869+
unsigned int frames;
870+
void *ip[MAX_STACK_FRAMES];
871+
SYMBOL_INFO *sym;
872+
873+
if (!initialised)
874+
{
875+
process = GetCurrentProcess();
876+
SymInitialize(process, NULL, true);
877+
initialised = true;
878+
}
879+
880+
if (!process)
881+
{
882+
Log(LOG_FATAL) << "Failed to get process for backtrace";
883+
return;
884+
}
885+
886+
sym = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + MAX_SYMBOL_LENGTH * (sizeof(char)), 1);
887+
if (!sym)
888+
{
889+
Log(LOG_FATAL) << "Failed to allocate symbol info for backtrace";
890+
return;
891+
}
892+
sym->MaxNameLen = MAX_SYMBOL_LENGTH;
893+
sym->SizeOfStruct = sizeof(SYMBOL_INFO);
894+
895+
/* Skip 1 frame to skip this function */
896+
frames = CaptureStackBackTrace(1, MAX_STACK_FRAMES, ip, NULL);
897+
898+
char buf[MAX_SYMBOL_LENGTH];
899+
for (unsigned int frame = 0; frame < frames; ++frame)
900+
{
901+
SymFromAddr(process, (DWORD64)(ip[frame]), 0, sym);
902+
sprintf(buf, "- 0x%p %s+0x%lx", ip[frame], sym->Name, (uintptr_t)ip[frame] - (uintptr_t)sym->Address);
903+
Log(LOG_FATAL) << buf;
904+
}
905+
906+
free(sym);
907+
#elif defined(BACKTRACE_LIBUNWIND)
908+
unw_cursor_t cursor;
909+
unw_context_t ctx;
910+
unw_word_t ip;
911+
unw_getcontext(&ctx);
912+
unw_init_local(&cursor, &ctx);
913+
914+
Log(LOG_FATAL) << " called by:";
915+
char buf[MAX_SYMBOL_LENGTH];
916+
while (unw_step(&cursor) > 0)
917+
{
918+
Dl_info info;
919+
unw_get_reg(&cursor, UNW_REG_IP, &ip);
920+
if (ip == 0)
921+
break;
922+
dladdr(reinterpret_cast<void *>(ip), &info);
923+
if (info.dli_sname)
924+
{
925+
sprintf(buf, "- 0x%lx %s+0x%lx (%s)", static_cast<uintptr_t>(ip), info.dli_sname, static_cast<uintptr_t>(ip) - reinterpret_cast<uintptr_t>(info.dli_saddr), info.dli_fname);
926+
Log(LOG_FATAL) << buf;
927+
continue;
928+
}
929+
// If dladdr() failed, try libunwind
930+
unw_word_t offsetInFn;
931+
char fnName[MAX_SYMBOL_LENGTH];
932+
if (!unw_get_proc_name(&cursor, fnName, MAX_SYMBOL_LENGTH, &offsetInFn))
933+
{
934+
sprintf(buf, "- 0x%lx %s+0x%lx (%s)", static_cast<uintptr_t>(ip), fnName, offsetInFn, info.dli_fname);
935+
Log(LOG_FATAL) << buf;
936+
}
937+
else
938+
{
939+
sprintf(buf, "- 0x%lx", static_cast<uintptr_t>(ip));
940+
Log(LOG_FATAL) << buf;
941+
}
942+
}
943+
#else
944+
// TODO: Other platform backtrace?
945+
#endif
946+
}
947+
948+
std::string timestamp()
949+
{
950+
const int MAX_LEN = 25, MAX_RESULT = 80;
951+
#ifdef _WIN32
952+
char date[MAX_LEN], time[MAX_LEN];
953+
if (GetDateFormatA(LOCALE_INVARIANT, 0, 0,
954+
"dd'-'MM'-'yyyy", date, MAX_LEN) == 0)
955+
return "Error in Now()";
956+
if (GetTimeFormatA(LOCALE_INVARIANT, TIME_FORCE24HOURFORMAT, 0,
957+
"HH'-'mm'-'ss", time, MAX_LEN) == 0)
958+
return "Error in Now()";
959+
960+
char result[MAX_RESULT] = { 0 };
961+
sprintf(result, "%s_%s", date, time);
962+
#else
963+
char buffer[MAX_LEN];
964+
time_t rawtime;
965+
struct tm *timeinfo;
966+
time(&rawtime);
967+
timeinfo = localtime(&rawtime);
968+
strftime(buffer, MAX_LEN, "%d-%m-%Y_%H-%M-%S", timeinfo);
969+
char result[MAX_RESULT] = { 0 };
970+
sprintf(result, "%s", buffer);
971+
#endif
972+
return result;
973+
}
974+
975+
/**
976+
* Logs the stack back trace and crash dump leading up to this fatal exception.
977+
* @note Based on http://stackoverflow.com/questions/22487887/why-doesnt-stack-walking-work-properly-when-using-setunhandledexceptionfilter
978+
* @return Error code.
979+
*/
980+
std::string crashDump(void *info)
981+
{
982+
std::ostringstream error;
983+
#ifdef _WIN32
984+
PEXCEPTION_POINTERS exception = (PEXCEPTION_POINTERS)info;
985+
if (exception->ExceptionRecord->ExceptionCode == EXCEPTION_CODE_CXX)
986+
{
987+
std::exception *ex = (std::exception *)exception->ExceptionRecord->ExceptionInformation[1];
988+
error << ex->what();
989+
}
990+
else
991+
{
992+
error << "0x" << std::hex << exception->ExceptionRecord->ExceptionCode;
993+
}
994+
CONTEXT context = *(exception->ContextRecord);
995+
HANDLE thread = GetCurrentThread();
996+
HANDLE process = GetCurrentProcess();
997+
STACKFRAME64 frame;
998+
memset(&frame, 0, sizeof(STACKFRAME64));
999+
DWORD image;
1000+
#ifdef _M_IX86
1001+
image = IMAGE_FILE_MACHINE_I386;
1002+
frame.AddrPC.Offset = context.Eip;
1003+
frame.AddrPC.Mode = AddrModeFlat;
1004+
frame.AddrFrame.Offset = context.Ebp;
1005+
frame.AddrFrame.Mode = AddrModeFlat;
1006+
frame.AddrStack.Offset = context.Esp;
1007+
frame.AddrStack.Mode = AddrModeFlat;
1008+
#elif _M_X64
1009+
image = IMAGE_FILE_MACHINE_AMD64;
1010+
frame.AddrPC.Offset = context.Rip;
1011+
frame.AddrPC.Mode = AddrModeFlat;
1012+
frame.AddrFrame.Offset = context.Rbp;
1013+
frame.AddrFrame.Mode = AddrModeFlat;
1014+
frame.AddrStack.Offset = context.Rsp;
1015+
frame.AddrStack.Mode = AddrModeFlat;
1016+
#elif _M_IA64
1017+
image = IMAGE_FILE_MACHINE_IA64;
1018+
frame.AddrPC.Offset = context.StIIP;
1019+
frame.AddrPC.Mode = AddrModeFlat;
1020+
frame.AddrFrame.Offset = context.IntSp;
1021+
frame.AddrFrame.Mode = AddrModeFlat;
1022+
frame.AddrBStore.Offset = context.RsBSP;
1023+
frame.AddrBStore.Mode = AddrModeFlat;
1024+
frame.AddrStack.Offset = context.IntSp;
1025+
frame.AddrStack.Mode = AddrModeFlat;
1026+
#else
1027+
#error This platform is not supported.
1028+
#endif
1029+
SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO) + (MAX_SYMBOL_LENGTH - 1) * sizeof(TCHAR));
1030+
symbol->MaxNameLen = MAX_SYMBOL_LENGTH;
1031+
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
1032+
IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
1033+
line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
1034+
DWORD displacement;
1035+
Log(LOG_FATAL) << "A fatal error has occurred: " << error.str();
1036+
SymInitialize(process, NULL, TRUE);
1037+
while (StackWalk64(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
1038+
{
1039+
if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
1040+
{
1041+
if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
1042+
{
1043+
std::string filename = line->FileName;
1044+
size_t n = filename.find_last_of(PATH_SEPARATOR);
1045+
if (n != std::string::npos)
1046+
{
1047+
filename = filename.substr(n+1);
1048+
}
1049+
Log(LOG_FATAL) << "0x" << std::hex << symbol->Address << std::dec << " " << symbol->Name << " (" << filename << ":" << line->LineNumber << ")";
1050+
}
1051+
else
1052+
{
1053+
Log(LOG_FATAL) << "0x" << std::hex << symbol->Address << std::dec << " " << symbol->Name << " (SymGetLineFromAddr64 failed: " << GetLastError() << ")";
1054+
}
1055+
}
1056+
else
1057+
{
1058+
Log(LOG_FATAL) << "SymFromAddr failed: " << GetLastError();
1059+
}
1060+
}
1061+
DWORD err = GetLastError();
1062+
if (err)
1063+
{
1064+
Log(LOG_FATAL) << "StackWalk64 failed: " << err;
1065+
}
1066+
std::string dumpName = Options::getUserFolder();
1067+
dumpName += timestamp() + ".dmp";
1068+
HANDLE dumpFile = CreateFileA(dumpName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
1069+
MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
1070+
exceptionInformation.ThreadId = GetCurrentThreadId();
1071+
exceptionInformation.ExceptionPointers = exception;
1072+
exceptionInformation.ClientPointers = FALSE;
1073+
if (MiniDumpWriteDump(process, GetProcessId(process), dumpFile, MiniDumpNormal, exception ? &exceptionInformation : NULL, NULL, NULL))
1074+
{
1075+
Log(LOG_FATAL) << "Crash dump generated at " << dumpName;
1076+
}
1077+
else
1078+
{
1079+
Log(LOG_FATAL) << "MiniDumpWriteDump failed: " << GetLastError();
1080+
}
1081+
#else
1082+
// TODO: Other platform backtrace?
1083+
#endif
1084+
return error.str();
1085+
}
1086+
8391087
}
8401088
}

‎src/Engine/CrossPlatform.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ namespace CrossPlatform
8585
std::string getDosPath();
8686
/// Sets the window icon.
8787
void setWindowIcon(int winResource, const std::string &unixPath);
88+
/// Produces a stack trace.
89+
void stackTrace();
90+
/// Produces a crash dump.
91+
std::string crashDump(void *info);
8892
}
8993

9094
}

‎src/Engine/Logger.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ inline std::string& Logger::logFile()
117117

118118
inline std::string Logger::toString(SeverityLevel level)
119119
{
120-
static const char* const buffer[] = {"FATAL", "ERROR", "WARN", "INFO", "DEBUG", "VERBOSE"};
120+
static const char* const buffer[] = {"FATAL", "ERROR", "WARN", "INFO", "DEBUG", "VERB"};
121121
return buffer[level];
122122
}
123123

‎src/main.cpp‎

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
* You should have received a copy of the GNU General Public License
1717
* along with OpenXcom. If not, see <http://www.gnu.org/licenses/>.
1818
*/
19-
#include <exception>
2019
#include <sstream>
2120
#include "version.h"
2221
#include "Engine/Logger.h"
@@ -40,42 +39,51 @@
4039

4140
using namespace OpenXcom;
4241

42+
#ifdef _WIN32
43+
#include <windows.h>
44+
LONG WINAPI crashLogger(PEXCEPTION_POINTERS exception)
45+
{
46+
std::string error = CrossPlatform::crashDump(exception);
47+
std::ostringstream msg;
48+
msg << "OpenXcom has crashed: " << error << std::endl;
49+
msg << "Extra information has been saved to openxcom.log." << std::endl;
50+
msg << "Please report this to the developers.";
51+
CrossPlatform::showError(msg.str());
52+
return EXCEPTION_CONTINUE_SEARCH;
53+
}
54+
#endif
55+
4356
Game *game = 0;
4457

4558
// If you can't tell what the main() is for you should have your
4659
// programming license revoked...
4760
int main(int argc, char *argv[])
4861
{
62+
#ifdef _WIN32
4963
// Uncomment to check memory leaks in VS
5064
//_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
5165

52-
#ifndef _DEBUG
53-
try
54-
{
55-
Logger::reportingLevel() = LOG_INFO;
56-
#else
57-
Logger::reportingLevel() = LOG_DEBUG;
66+
SetUnhandledExceptionFilter(crashLogger);
67+
AddVectoredContinueHandler(1, crashLogger);
5868
#endif
59-
if (!Options::init(argc, argv))
60-
return EXIT_SUCCESS;
61-
std::ostringstream title;
62-
title << "OpenXcom " << OPENXCOM_VERSION_SHORT << OPENXCOM_VERSION_GIT;
63-
if (Options::verboseLogging)
64-
Logger::reportingLevel() = LOG_VERBOSE;
65-
Options::baseXResolution = Options::displayWidth;
66-
Options::baseYResolution = Options::displayHeight;
67-
game = new Game(title.str());
68-
State::setGamePtr(game);
69-
game->setState(new StartState);
70-
game->run();
71-
#ifndef _DEBUG
72-
}
73-
catch (std::exception &e)
74-
{
75-
CrossPlatform::showError(e.what());
76-
exit(EXIT_FAILURE);
77-
}
69+
70+
#ifdef _DEBUG
71+
Logger::reportingLevel() = LOG_DEBUG;
72+
#else
73+
Logger::reportingLevel() = LOG_INFO;
7874
#endif
75+
if (!Options::init(argc, argv))
76+
return EXIT_SUCCESS;
77+
std::ostringstream title;
78+
title << "OpenXcom " << OPENXCOM_VERSION_SHORT << OPENXCOM_VERSION_GIT;
79+
if (Options::verboseLogging)
80+
Logger::reportingLevel() = LOG_VERBOSE;
81+
Options::baseXResolution = Options::displayWidth;
82+
Options::baseYResolution = Options::displayHeight;
83+
game = new Game(title.str());
84+
State::setGamePtr(game);
85+
game->setState(new StartState);
86+
game->run();
7987
Options::save();
8088

8189
// Comment this for faster exit.

0 commit comments

Comments
 (0)