diff --git a/build/gmake/cnq3-server.make b/build/gmake/cnq3-server.make index 20f05b2..036f6d1 100644 --- a/build/gmake/cnq3-server.make +++ b/build/gmake/cnq3-server.make @@ -22,7 +22,7 @@ ifeq ($(config),debug_x32) ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m32 -g -Wno-unused-parameter -Wno-write-strings -x c++ ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CFLAGS) -fno-exceptions -fno-rtti ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) - LIBS += ../../../.bin/debug_x32/libbotlib.a -ldl -lm + LIBS += ../../../.bin/debug_x32/libbotlib.a -ldl -lm -lbacktrace LDDEPS += ../../../.bin/debug_x32/libbotlib.a ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib32 -L../../../.bin/debug_x32 -m32 LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) @@ -53,7 +53,7 @@ ifeq ($(config),debug_x64) ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m64 -g -Wno-unused-parameter -Wno-write-strings -x c++ ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CFLAGS) -fno-exceptions -fno-rtti ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) - LIBS += ../../../.bin/debug_x64/libbotlib.a -ldl -lm + LIBS += ../../../.bin/debug_x64/libbotlib.a -ldl -lm -lbacktrace LDDEPS += ../../../.bin/debug_x64/libbotlib.a ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib64 -L../../../.bin/debug_x64 -m64 LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) @@ -84,7 +84,7 @@ ifeq ($(config),release_x32) ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m32 -fomit-frame-pointer -ffast-math -Os -g -msse2 -Wno-unused-parameter -Wno-write-strings -g1 -x c++ ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CFLAGS) -fno-exceptions -fno-rtti ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) - LIBS += ../../../.bin/release_x32/libbotlib.a -ldl -lm + LIBS += ../../../.bin/release_x32/libbotlib.a -ldl -lm -lbacktrace LDDEPS += ../../../.bin/release_x32/libbotlib.a ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib32 -L../../../.bin/release_x32 -m32 LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) @@ -115,7 +115,7 @@ ifeq ($(config),release_x64) ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m64 -fomit-frame-pointer -ffast-math -Os -g -msse2 -Wno-unused-parameter -Wno-write-strings -g1 -x c++ ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CFLAGS) -fno-exceptions -fno-rtti ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) - LIBS += ../../../.bin/release_x64/libbotlib.a -ldl -lm + LIBS += ../../../.bin/release_x64/libbotlib.a -ldl -lm -lbacktrace LDDEPS += ../../../.bin/release_x64/libbotlib.a ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib64 -L../../../.bin/release_x64 -m64 LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) @@ -142,10 +142,12 @@ OBJECTS := \ $(OBJDIR)/cm_trace.o \ $(OBJDIR)/cmd.o \ $(OBJDIR)/common.o \ + $(OBJDIR)/crash.o \ $(OBJDIR)/cvar.o \ $(OBJDIR)/files.o \ $(OBJDIR)/huffman.o \ $(OBJDIR)/huffman_static.o \ + $(OBJDIR)/json.o \ $(OBJDIR)/md4.o \ $(OBJDIR)/md5.o \ $(OBJDIR)/msg.o \ @@ -247,6 +249,9 @@ $(OBJDIR)/cmd.o: ../../code/qcommon/cmd.cpp $(OBJDIR)/common.o: ../../code/qcommon/common.cpp @echo $(notdir $<) $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/crash.o: ../../code/qcommon/crash.cpp + @echo $(notdir $<) + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(OBJDIR)/cvar.o: ../../code/qcommon/cvar.cpp @echo $(notdir $<) $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" @@ -259,6 +264,9 @@ $(OBJDIR)/huffman.o: ../../code/qcommon/huffman.cpp $(OBJDIR)/huffman_static.o: ../../code/qcommon/huffman_static.cpp @echo $(notdir $<) $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/json.o: ../../code/qcommon/json.cpp + @echo $(notdir $<) + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(OBJDIR)/md4.o: ../../code/qcommon/md4.cpp @echo $(notdir $<) $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" diff --git a/build/gmake/cnq3.make b/build/gmake/cnq3.make index 88913d6..95acd6d 100644 --- a/build/gmake/cnq3.make +++ b/build/gmake/cnq3.make @@ -22,7 +22,7 @@ ifeq ($(config),debug_x32) ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m32 -g -Wno-unused-parameter -Wno-write-strings -pthread -x c++ ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CFLAGS) -fno-exceptions -fno-rtti ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) - LIBS += ../../../.bin/debug_x32/libbotlib.a ../../../.bin/debug_x32/librenderer.a ../../../.bin/debug_x32/libfreetype.a ../../../.bin/debug_x32/liblibjpeg-turbo.a -ldl -lm -lX11 -lpthread + LIBS += ../../../.bin/debug_x32/libbotlib.a ../../../.bin/debug_x32/librenderer.a ../../../.bin/debug_x32/libfreetype.a ../../../.bin/debug_x32/liblibjpeg-turbo.a -ldl -lm -lbacktrace -lX11 -lpthread LDDEPS += ../../../.bin/debug_x32/libbotlib.a ../../../.bin/debug_x32/librenderer.a ../../../.bin/debug_x32/libfreetype.a ../../../.bin/debug_x32/liblibjpeg-turbo.a ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib32 -L../../../.bin/debug_x32 -m32 LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) @@ -53,7 +53,7 @@ ifeq ($(config),debug_x64) ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m64 -g -Wno-unused-parameter -Wno-write-strings -pthread -x c++ ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CFLAGS) -fno-exceptions -fno-rtti ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) - LIBS += ../../../.bin/debug_x64/libbotlib.a ../../../.bin/debug_x64/librenderer.a ../../../.bin/debug_x64/libfreetype.a ../../../.bin/debug_x64/liblibjpeg-turbo.a -ldl -lm -lX11 -lpthread + LIBS += ../../../.bin/debug_x64/libbotlib.a ../../../.bin/debug_x64/librenderer.a ../../../.bin/debug_x64/libfreetype.a ../../../.bin/debug_x64/liblibjpeg-turbo.a -ldl -lm -lbacktrace -lX11 -lpthread LDDEPS += ../../../.bin/debug_x64/libbotlib.a ../../../.bin/debug_x64/librenderer.a ../../../.bin/debug_x64/libfreetype.a ../../../.bin/debug_x64/liblibjpeg-turbo.a ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib64 -L../../../.bin/debug_x64 -m64 LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) @@ -84,7 +84,7 @@ ifeq ($(config),release_x32) ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m32 -fomit-frame-pointer -ffast-math -Os -g -msse2 -Wno-unused-parameter -Wno-write-strings -g1 -pthread -x c++ ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CFLAGS) -fno-exceptions -fno-rtti ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) - LIBS += ../../../.bin/release_x32/libbotlib.a ../../../.bin/release_x32/librenderer.a ../../../.bin/release_x32/libfreetype.a ../../../.bin/release_x32/liblibjpeg-turbo.a -ldl -lm -lX11 -lpthread + LIBS += ../../../.bin/release_x32/libbotlib.a ../../../.bin/release_x32/librenderer.a ../../../.bin/release_x32/libfreetype.a ../../../.bin/release_x32/liblibjpeg-turbo.a -ldl -lm -lbacktrace -lX11 -lpthread LDDEPS += ../../../.bin/release_x32/libbotlib.a ../../../.bin/release_x32/librenderer.a ../../../.bin/release_x32/libfreetype.a ../../../.bin/release_x32/liblibjpeg-turbo.a ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib32 -L../../../.bin/release_x32 -m32 LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) @@ -115,7 +115,7 @@ ifeq ($(config),release_x64) ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m64 -fomit-frame-pointer -ffast-math -Os -g -msse2 -Wno-unused-parameter -Wno-write-strings -g1 -pthread -x c++ ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CFLAGS) -fno-exceptions -fno-rtti ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) - LIBS += ../../../.bin/release_x64/libbotlib.a ../../../.bin/release_x64/librenderer.a ../../../.bin/release_x64/libfreetype.a ../../../.bin/release_x64/liblibjpeg-turbo.a -ldl -lm -lX11 -lpthread + LIBS += ../../../.bin/release_x64/libbotlib.a ../../../.bin/release_x64/librenderer.a ../../../.bin/release_x64/libfreetype.a ../../../.bin/release_x64/liblibjpeg-turbo.a -ldl -lm -lbacktrace -lX11 -lpthread LDDEPS += ../../../.bin/release_x64/libbotlib.a ../../../.bin/release_x64/librenderer.a ../../../.bin/release_x64/libfreetype.a ../../../.bin/release_x64/liblibjpeg-turbo.a ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib64 -L../../../.bin/release_x64 -m64 LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) @@ -161,10 +161,12 @@ OBJECTS := \ $(OBJDIR)/cm_trace.o \ $(OBJDIR)/cmd.o \ $(OBJDIR)/common.o \ + $(OBJDIR)/crash.o \ $(OBJDIR)/cvar.o \ $(OBJDIR)/files.o \ $(OBJDIR)/huffman.o \ $(OBJDIR)/huffman_static.o \ + $(OBJDIR)/json.o \ $(OBJDIR)/md4.o \ $(OBJDIR)/md5.o \ $(OBJDIR)/msg.o \ @@ -327,6 +329,9 @@ $(OBJDIR)/cmd.o: ../../code/qcommon/cmd.cpp $(OBJDIR)/common.o: ../../code/qcommon/common.cpp @echo $(notdir $<) $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/crash.o: ../../code/qcommon/crash.cpp + @echo $(notdir $<) + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(OBJDIR)/cvar.o: ../../code/qcommon/cvar.cpp @echo $(notdir $<) $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" @@ -339,6 +344,9 @@ $(OBJDIR)/huffman.o: ../../code/qcommon/huffman.cpp $(OBJDIR)/huffman_static.o: ../../code/qcommon/huffman_static.cpp @echo $(notdir $<) $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/json.o: ../../code/qcommon/json.cpp + @echo $(notdir $<) + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(OBJDIR)/md4.o: ../../code/qcommon/md4.cpp @echo $(notdir $<) $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" diff --git a/build/premake5.lua b/build/premake5.lua index a94578e..2a18b25 100644 --- a/build/premake5.lua +++ b/build/premake5.lua @@ -281,10 +281,12 @@ local function ApplyExeProjectSettings(exeName, server) "qcommon/cm_test.cpp", "qcommon/cm_trace.cpp", "qcommon/common.cpp", + "qcommon/crash.cpp", "qcommon/cvar.cpp", "qcommon/files.cpp", "qcommon/huffman.cpp", "qcommon/huffman_static.cpp", + "qcommon/json.cpp", "qcommon/md4.cpp", "qcommon/md5.cpp", "qcommon/msg.cpp", @@ -311,6 +313,7 @@ local function ApplyExeProjectSettings(exeName, server) { "win32/win_main.cpp", "win32/win_shared.cpp", + "win32/win_exception.cpp", "win32/win_syscon.cpp" } @@ -349,10 +352,12 @@ local function ApplyExeProjectSettings(exeName, server) "qcommon/cm_test.cpp", "qcommon/cm_trace.cpp", "qcommon/common.cpp", + "qcommon/crash.cpp", "qcommon/cvar.cpp", "qcommon/files.cpp", "qcommon/huffman.cpp", "qcommon/huffman_static.cpp", + "qcommon/json.cpp", "qcommon/md4.cpp", "qcommon/md5.cpp", "qcommon/msg.cpp", @@ -380,6 +385,7 @@ local function ApplyExeProjectSettings(exeName, server) "win32/win_input.cpp", "win32/win_main.cpp", "win32/win_shared.cpp", + "win32/win_exception.cpp", "win32/win_snd.cpp", "win32/win_syscon.cpp", "win32/win_wndproc.cpp", @@ -457,13 +463,13 @@ local function ApplyExeProjectSettings(exeName, server) debugdir(abs_path_q3) filter "system:windows" - links { "Winmm", "ws2_32" } + links { "Winmm", "ws2_32", "Version" } if (server == 0) then links { "opengl32" } end filter "system:not windows" - links { "dl", "m" } + links { "dl", "m", "backtrace" } if (server == 0) then buildoptions { "-pthread" } links { "X11", "pthread" } diff --git a/build/vs2013/cnq3-server.vcxproj b/build/vs2013/cnq3-server.vcxproj index 8ec92b2..086f5c1 100644 --- a/build/vs2013/cnq3-server.vcxproj +++ b/build/vs2013/cnq3-server.vcxproj @@ -115,7 +115,7 @@ Windows true - Winmm.lib;ws2_32.lib;%(AdditionalDependencies) + Winmm.lib;ws2_32.lib;Version.lib;%(AdditionalDependencies) ..\..\..\.bin\debug_x32;%(AdditionalLibraryDirectories) ..\..\cnq3\code\win32\winquake.res /STACK:8388608 %(AdditionalOptions) @@ -143,7 +143,7 @@ copy "..\..\..\.bin\debug_x32\cnq3-server-x86.pdb" "$(QUAKE3DIR)" Windows true - Winmm.lib;ws2_32.lib;%(AdditionalDependencies) + Winmm.lib;ws2_32.lib;Version.lib;%(AdditionalDependencies) ..\..\..\.bin\debug_x64;%(AdditionalLibraryDirectories) ..\..\cnq3\code\win32\winquake.res /STACK:8388608 %(AdditionalOptions) @@ -182,7 +182,7 @@ copy "..\..\..\.bin\debug_x64\cnq3-server-x64.pdb" "$(QUAKE3DIR)" true true true - Winmm.lib;ws2_32.lib;%(AdditionalDependencies) + Winmm.lib;ws2_32.lib;Version.lib;%(AdditionalDependencies) ..\..\..\.bin\release_x32;%(AdditionalLibraryDirectories) ..\..\cnq3\code\win32\winquake.res /STACK:8388608 /OPT:REF /OPT:ICF %(AdditionalOptions) @@ -220,7 +220,7 @@ copy "..\..\..\.bin\release_x32\cnq3-server-x86.pdb" "$(QUAKE3DIR)" true true true - Winmm.lib;ws2_32.lib;%(AdditionalDependencies) + Winmm.lib;ws2_32.lib;Version.lib;%(AdditionalDependencies) ..\..\..\.bin\release_x64;%(AdditionalLibraryDirectories) ..\..\cnq3\code\win32\winquake.res /STACK:8388608 /OPT:REF /OPT:ICF %(AdditionalOptions) @@ -279,6 +279,7 @@ copy "..\..\..\.bin\release_x64\cnq3-server-x64.pdb" "$(QUAKE3DIR)" + @@ -307,10 +308,12 @@ copy "..\..\..\.bin\release_x64\cnq3-server-x64.pdb" "$(QUAKE3DIR)" + + @@ -331,6 +334,7 @@ copy "..\..\..\.bin\release_x64\cnq3-server-x64.pdb" "$(QUAKE3DIR)" + diff --git a/build/vs2013/cnq3-server.vcxproj.filters b/build/vs2013/cnq3-server.vcxproj.filters index 11d0778..d7f7a19 100644 --- a/build/vs2013/cnq3-server.vcxproj.filters +++ b/build/vs2013/cnq3-server.vcxproj.filters @@ -159,6 +159,9 @@ qcommon + + qcommon + qcommon @@ -239,6 +242,9 @@ qcommon + + qcommon + qcommon @@ -251,6 +257,9 @@ qcommon + + qcommon + qcommon @@ -311,6 +320,9 @@ server + + win32 + win32 diff --git a/build/vs2013/cnq3.vcxproj b/build/vs2013/cnq3.vcxproj index 8e9f746..41be75f 100644 --- a/build/vs2013/cnq3.vcxproj +++ b/build/vs2013/cnq3.vcxproj @@ -116,7 +116,7 @@ Windows true - Winmm.lib;ws2_32.lib;opengl32.lib;%(AdditionalDependencies) + Winmm.lib;ws2_32.lib;Version.lib;opengl32.lib;%(AdditionalDependencies) ..\..\..\.bin\debug_x32;%(AdditionalLibraryDirectories) ..\..\cnq3\code\win32\winquake.res /STACK:8388608 %(AdditionalOptions) @@ -145,7 +145,7 @@ copy "..\..\..\.bin\debug_x32\cnq3-x86.pdb" "$(QUAKE3DIR)" Windows true - Winmm.lib;ws2_32.lib;opengl32.lib;%(AdditionalDependencies) + Winmm.lib;ws2_32.lib;Version.lib;opengl32.lib;%(AdditionalDependencies) ..\..\..\.bin\debug_x64;%(AdditionalLibraryDirectories) ..\..\cnq3\code\win32\winquake.res /STACK:8388608 %(AdditionalOptions) @@ -185,7 +185,7 @@ copy "..\..\..\.bin\debug_x64\cnq3-x64.pdb" "$(QUAKE3DIR)" true true true - Winmm.lib;ws2_32.lib;opengl32.lib;%(AdditionalDependencies) + Winmm.lib;ws2_32.lib;Version.lib;opengl32.lib;%(AdditionalDependencies) ..\..\..\.bin\release_x32;%(AdditionalLibraryDirectories) ..\..\cnq3\code\win32\winquake.res /STACK:8388608 /OPT:REF /OPT:ICF %(AdditionalOptions) @@ -224,7 +224,7 @@ copy "..\..\..\.bin\release_x32\cnq3-x86.pdb" "$(QUAKE3DIR)" true true true - Winmm.lib;ws2_32.lib;opengl32.lib;%(AdditionalDependencies) + Winmm.lib;ws2_32.lib;Version.lib;opengl32.lib;%(AdditionalDependencies) ..\..\..\.bin\release_x64;%(AdditionalLibraryDirectories) ..\..\cnq3\code\win32\winquake.res /STACK:8388608 /OPT:REF /OPT:ICF %(AdditionalOptions) @@ -283,6 +283,7 @@ copy "..\..\..\.bin\release_x64\cnq3-x64.pdb" "$(QUAKE3DIR)" + @@ -334,10 +335,12 @@ copy "..\..\..\.bin\release_x64\cnq3-x64.pdb" "$(QUAKE3DIR)" + + @@ -358,6 +361,7 @@ copy "..\..\..\.bin\release_x64\cnq3-x64.pdb" "$(QUAKE3DIR)" + diff --git a/build/vs2013/cnq3.vcxproj.filters b/build/vs2013/cnq3.vcxproj.filters index 7637b52..d5771bd 100644 --- a/build/vs2013/cnq3.vcxproj.filters +++ b/build/vs2013/cnq3.vcxproj.filters @@ -162,6 +162,9 @@ qcommon + + qcommon + qcommon @@ -311,6 +314,9 @@ qcommon + + qcommon + qcommon @@ -323,6 +329,9 @@ qcommon + + qcommon + qcommon @@ -383,6 +392,9 @@ server + + win32 + win32 diff --git a/code/qcommon/common.cpp b/code/qcommon/common.cpp index edce9d6..8a90103 100644 --- a/code/qcommon/common.cpp +++ b/code/qcommon/common.cpp @@ -1892,7 +1892,7 @@ int Com_Milliseconds() /////////////////////////////////////////////////////////////// -#if defined(DEBUG) +#if defined(DEBUG) || defined(CNQ3_DEV) // throw a fatal error to test error shutdown procedures @@ -2223,7 +2223,7 @@ void Com_Init( char *commandLine ) } } -#if defined(DEBUG) +#if defined(DEBUG) || defined(CNQ3_DEV) Cmd_AddCommand( "error", Com_Error_f ); Cmd_AddCommand( "crash", Com_Crash_f ); Cmd_AddCommand( "freeze", Com_Freeze_f ); @@ -2932,3 +2932,33 @@ void Field_AutoCompleteKeyName( int startArg, int compArg ) } #endif + + +const char* Q_itohex( uint64_t number, qbool uppercase, qbool prefix ) +{ + static const char* luts[2] = { "0123456789abcdef", "0123456789ABCDEF" }; + static char buffer[19]; + const int maxLength = 16; + + const char* const lut = luts[uppercase == 0 ? 0 : 1]; + uint64_t x = number; + int i = maxLength; + buffer[i] = '\0'; + while ( i-- ) { + buffer[i] = lut[x & 15]; + x >>= 4; + } + + int startOffset = 0; + for ( i = 0; i < maxLength - 1; i++, startOffset++ ) { + if ( buffer[i] != '0' ) + break; + } + + if ( prefix ) { + startOffset -= 2; + buffer[startOffset + 1] = 'x'; + } + + return buffer + startOffset; +} diff --git a/code/qcommon/crash.cpp b/code/qcommon/crash.cpp new file mode 100644 index 0000000..0dad42b --- /dev/null +++ b/code/qcommon/crash.cpp @@ -0,0 +1,219 @@ +#include "crash.h" +#include "git.h" +#include "vm_local.h" +#include + + +typedef struct { + char gitHeadHash[24]; // SHA-1 -> 160 bits -> 20 hex chars + vm_t* vm; + unsigned int crc32; // CRC32 +} vmCrashInfo_t; + +static vmCrashInfo_t crash_vm[VM_COUNT]; +static char crash_modName[64]; +static char crash_modVersion[64]; + +#if defined(_WIN32) +# define NEWLINE "\n" +#else +# define NEWLINE "\r\n" +#endif + + +static qbool IsVMIndexValid(vmIndex_t vmIndex) +{ + return vmIndex >= 0 && vmIndex < VM_COUNT; +} + +static const char* GetVMName(vmIndex_t vmIndex) +{ + switch (vmIndex) { + case VM_CGAME: return "cgame"; + case VM_GAME: return "game"; + case VM_UI: return "ui"; + default: return "unknown"; + } +} + +void Crash_SaveQVMPointer(vmIndex_t vmIndex, vm_t* vm) +{ + if (IsVMIndexValid(vmIndex)) + crash_vm[vmIndex].vm = vm; +} + +void Crash_SaveQVMChecksum(vmIndex_t vmIndex, unsigned int crc32) +{ + if (IsVMIndexValid(vmIndex)) + crash_vm[vmIndex].crc32 = crc32; +} + +void Crash_SaveQVMGitString(const char* varName, const char* varValue) +{ + if (strstr(varName, "gitHeadHash") == NULL) + return; + + vmIndex_t index; + if (strstr(varName, "cg_") == varName) + index = VM_CGAME; + else if (strstr(varName, "g_") == varName) + index = VM_GAME; + else if (strstr(varName, "ui_") == varName) + index = VM_UI; + else + return; + + Q_strncpyz(crash_vm[index].gitHeadHash, varValue, sizeof(crash_vm[index].gitHeadHash)); +} + +void Crash_SaveModName(const char* modName) +{ + if (modName && *modName != '\0') + Q_strncpyz(crash_modName, modName, sizeof(crash_modName)); +} + +void Crash_SaveModVersion(const char* modVersion) +{ + if (modVersion && *modVersion != '\0') + Q_strncpyz(crash_modVersion, modVersion, sizeof(crash_modVersion)); +} + +static void PrintQVMInfo(vmIndex_t vmIndex) +{ + static char callStack[MAX_VM_CALL_STACK_DEPTH * 12]; + + vmCrashInfo_t* vm = &crash_vm[vmIndex]; + if (vm->crc32 == 0) { + return; + } + + JSONW_BeginObject(); + + JSONW_IntegerValue("index", vmIndex); + JSONW_StringValue("name", GetVMName(vmIndex)); + JSONW_BooleanValue("loaded", vm->vm != NULL); + if (vm->crc32) + JSONW_HexValue("crc32", vm->crc32); + JSONW_StringValue("git_head_hash", vm->gitHeadHash); + + if (vm->vm != NULL) { + vm_t* const vmp = vm->vm; + JSONW_IntegerValue("call_stack_depth_current", vmp->callStackDepth); + JSONW_IntegerValue("call_stack_depth_previous", vmp->lastCallStackDepth); + + int d = vmp->callStackDepth; + qbool current = qtrue; + if (d <= 0 || d > MAX_VM_CALL_STACK_DEPTH) { + d = vmp->lastCallStackDepth; + current = qfalse; + } + + if (d <= 0 || d > MAX_VM_CALL_STACK_DEPTH) + d = 0; + + if (d > 0) { + JSONW_BooleanValue("call_stack_current", current); + JSONW_BooleanValue("call_stack_limit_reached", d == MAX_VM_CALL_STACK_DEPTH); + + callStack[0] = '\0'; + for (int i = 0; i < d; i++) { + Q_strcat(callStack, sizeof(callStack), Q_itohex(vmp->callStack[i], qtrue, qtrue)); + if (i - 1 < d) + Q_strcat(callStack, sizeof(callStack), " "); + } + JSONW_StringValue("call_stack", callStack); + } + } + + JSONW_EndObject(); +} + +static qbool IsAnyVMLoaded() +{ + for (int i = 0; i < VM_COUNT; i++) { + if (crash_vm[i].crc32 != 0) + return qtrue; + } + + return qfalse; +} + +static unsigned int CRC32_HashFile(const char* filePath) +{ + enum { BUFFER_SIZE = 16 << 10 }; // 16 KB + static byte buffer[BUFFER_SIZE]; + + struct stat st; + if (stat(filePath, &st) != 0 || st.st_size == 0) + return 0; + + FILE* const file = fopen(filePath, "rb"); + if (file == NULL) + return 0; + + const unsigned int fileSize = (unsigned int)st.st_size; + const unsigned int fullBlocks = fileSize / (unsigned int)BUFFER_SIZE; + const unsigned int lastBlockSize = fileSize - fullBlocks * (unsigned int)BUFFER_SIZE; + + unsigned int crc32 = 0; + crc32_init(&crc32); + + for(unsigned int i = 0; i < fullBlocks; ++i) { + if (fread(buffer, BUFFER_SIZE, 1, file) != 1) { + fclose(file); + return 0; + } + crc32_update(&crc32, buffer, BUFFER_SIZE); + } + + if(lastBlockSize > 0) { + if (fread(buffer, lastBlockSize, 1, file) != 1) { + fclose(file); + return 0; + } + crc32_update(&crc32, buffer, lastBlockSize); + } + + crc32_final(&crc32); + + fclose(file); + + return crc32; +} + +void Crash_PrintToFile(const char* engineFilePath) +{ + JSONW_StringValue("engine_version", Q3_VERSION); + JSONW_StringValue("engine_build_date", __DATE__); + JSONW_StringValue("engine_arch", ARCH_STRING); +#ifdef DEDICATED + JSONW_BooleanValue("engine_ded_server", qtrue); +#else + JSONW_BooleanValue("engine_ded_server", qfalse); +#endif +#ifdef DEBUG + JSONW_BooleanValue("engine_debug", qtrue); +#else + JSONW_BooleanValue("engine_debug", qfalse); +#endif +#ifdef CNQ3_DEV + JSONW_BooleanValue("engine_dev_build", qtrue); +#else + JSONW_BooleanValue("engine_dev_build", qfalse); +#endif + JSONW_StringValue("engine_git_branch", GIT_BRANCH); + JSONW_StringValue("engine_git_commit", GIT_COMMIT); + const unsigned int crc32 = CRC32_HashFile(engineFilePath); + if (crc32) + JSONW_HexValue("engine_crc32", crc32); + JSONW_StringValue("mod_name", crash_modName); + JSONW_StringValue("mod_version", crash_modVersion); + + if (IsAnyVMLoaded()) { + JSONW_BeginNamedArray("vms"); + for (int i = 0; i < VM_COUNT; i++) { + PrintQVMInfo((vmIndex_t)i); + } + JSONW_EndArray(); + } +} diff --git a/code/qcommon/crash.h b/code/qcommon/crash.h new file mode 100644 index 0000000..5945f69 --- /dev/null +++ b/code/qcommon/crash.h @@ -0,0 +1,29 @@ +#include "q_shared.h" +#include "qcommon.h" + +#include + + +// json.cpp +void JSONW_BeginFile(FILE* file); +void JSONW_EndFile(); +void JSONW_BeginObject(); +void JSONW_BeginNamedObject(const char* name); +void JSONW_EndObject(); +void JSONW_BeginArray(); +void JSONW_BeginNamedArray(const char* name); +void JSONW_EndArray(); +void JSONW_IntegerValue(const char* name, int number); +void JSONW_HexValue(const char* name, uint64_t number); +void JSONW_BooleanValue(const char* name, qbool value); +void JSONW_StringValue(const char* name, const char* format, ...); +void JSONW_UnnamedHex(uint64_t number); +void JSONW_UnnamedString(const char* format, ...); + +// crash.cpp +void Crash_SaveQVMPointer(vmIndex_t vmIndex, vm_t* vm); +void Crash_SaveQVMChecksum(vmIndex_t vmIndex, unsigned int crc32); +void Crash_SaveQVMGitString(const char* varName, const char* varValue); +void Crash_SaveModName(const char* modName); +void Crash_SaveModVersion(const char* modVersion); +void Crash_PrintToFile(const char* engineFilePath); diff --git a/code/qcommon/cvar.cpp b/code/qcommon/cvar.cpp index efe7e5e..fca68ae 100644 --- a/code/qcommon/cvar.cpp +++ b/code/qcommon/cvar.cpp @@ -23,6 +23,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "q_shared.h" #include "qcommon.h" +#include "crash.h" #include "git.h" static cvar_t* cvar_vars; @@ -633,6 +634,14 @@ void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { cvar_t* cv = Cvar_Get( varName, defaultValue, flags ); + + if ( (flags & CVAR_SERVERINFO) && !Q_stricmp(varName, "gamename") ) + Crash_SaveModName( defaultValue ); + else if ( (flags & CVAR_SERVERINFO) && !Q_stricmp(varName, "gameversion") ) + Crash_SaveModVersion( defaultValue ); + else + Crash_SaveQVMGitString( varName, defaultValue ); + if ( !vmCvar ) return; diff --git a/code/qcommon/json.cpp b/code/qcommon/json.cpp new file mode 100644 index 0000000..479957a --- /dev/null +++ b/code/qcommon/json.cpp @@ -0,0 +1,365 @@ +#include "crash.h" + + +#if defined(_MSC_VER) +typedef unsigned __int8 u8; +typedef unsigned __int32 u32; +#else +typedef uint8_t u8; +typedef uint32_t u32; +#endif + + +static qbool UTF8_NextCodePoint(u32* codePoint, u32* byteCount, const char* input) +{ + if (!codePoint || !byteCount || !input || *input == '\0') + return qfalse; + + u8 byte0 = (u8)input[0]; + if (byte0 <= 127) { + *codePoint = (u32)byte0; + *byteCount = 1; + return qtrue; + } + + // Starts with 110? + if ((byte0 >> 5) == 6) { + const u8 byte1 = (u8)input[1]; + *codePoint = ((u32)byte1 & 63) | (((u32)byte0 & 31) << 6); + *byteCount = 2; + return qtrue; + } + + // Starts with 1110? + if ((byte0 >> 4) == 14) { + const u8 byte1 = (u8)input[1]; + const u8 byte2 = (u8)input[2]; + *codePoint = ((u32)byte2 & 63) | (((u32)byte1 & 63) << 6) | (((u32)byte0 & 15) << 12); + *byteCount = 3; + return qtrue; + } + + // Starts with 11110? + if ((byte0 >> 3) == 30) { + const u8 byte1 = (u8)input[1]; + const u8 byte2 = (u8)input[2]; + const u8 byte3 = (u8)input[3]; + *codePoint = ((u32)byte3 & 63) | (((u32)byte2 & 63) << 6) | (((u32)byte1 & 63) << 12) | (((u32)byte0 & 7) << 18); + *byteCount = 4; + return qtrue; + } + + return qfalse; +} + +typedef struct { + u32 CodePoint; + char OutputChar; +} ShortEscape; + +#define ENTRY(HexValue, Char) { 0x##HexValue, Char } +static const ShortEscape ShortEscapeCodePoints[] = { + ENTRY(0022, '"'), + ENTRY(005C, '\\'), + ENTRY(002F, '/'), + ENTRY(0008, 'b'), + ENTRY(000C, 'f'), + ENTRY(000A, 'n'), + ENTRY(000D, 'r'), + ENTRY(0009, 't') +}; +#undef ENTRY + +static const char HexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +static qbool UTF8_NeedsEscaping(u32* newLength, u32 codePoint) +{ + if (!newLength) + return qfalse; + + const u32 count = (u32)ARRAY_LEN(ShortEscapeCodePoints); + for (u32 i = 0; i < count; i++) { + if (codePoint == ShortEscapeCodePoints[i].CodePoint) { + *newLength = 2; // Of form: "\A". + return qtrue; + } + } + + // Range: 0x0000 - 0x001F + if (codePoint <= 0x001F) { + *newLength = 6; // Of form: "\uABCD". + return qtrue; + } + + return qfalse; +} + +static void UTF8_WriteCodePoint(u32* newLength, char* output, u32 codePoint, const char* input) +{ + if (!newLength || !output || !input) + return; + + const u32 count = (u32)ARRAY_LEN(ShortEscapeCodePoints); + for (u32 i = 0; i < count; i++) { + if (codePoint == ShortEscapeCodePoints[i].CodePoint) { + *newLength = 2; + output[0] = '\\'; + output[1] = ShortEscapeCodePoints[i].OutputChar; + return; + } + } + + // Range: 0x0000 - 0x001F + if (codePoint <= 0x001F) { + *newLength = 6; + output[0] = '\\'; + output[1] = 'u'; + output[2] = HexDigits[(codePoint >> 12) & 15]; + output[3] = HexDigits[(codePoint >> 8) & 15]; + output[4] = HexDigits[(codePoint >> 4) & 15]; + output[5] = HexDigits[codePoint & 15]; + return; + } + + for (u32 i = 0; i < *newLength; ++i) { + output[i] = input[i]; + } +} + +static u32 UTF8_LengthForJSON(const char* input) +{ + if (!input) + return 0; + + const char* s = input; + u32 codePoint = 0; + u32 byteCount = 0; + u32 newLength = 0; + while (UTF8_NextCodePoint(&codePoint, &byteCount, s)) { + s += byteCount; + UTF8_NeedsEscaping(&byteCount, codePoint); + newLength += byteCount; + } + + return newLength; +} + +static void UTF8_CleanForJSON(char* output, const char* input) +{ + if (!output || !input) + return; + + const char* in = input; + char* out = output; + u32 codePoint = 0; + u32 inputByteCount = 0; + while (UTF8_NextCodePoint(&codePoint, &inputByteCount, in)) { + u32 outputByteCount = inputByteCount; + UTF8_WriteCodePoint(&outputByteCount, out, codePoint, in); + in += inputByteCount; + out += outputByteCount; + } + + *out = '\0'; +} + +typedef struct { + unsigned int itemIndices[16]; + FILE* file; + unsigned int level; +} JSONWriter; + +static JSONWriter writer; + +static void JSONW_Write(const char* string) +{ + if (!string) + return; + + fwrite(string, strlen(string), 1, writer.file); +} + +static void JSONW_WriteNewLine() +{ + JSONW_Write("\n"); + for (u32 i = 0; i < writer.level; i++) { + JSONW_Write("\t"); + } +} + +static void JSONW_WriteClean(const char* string) +{ + static char buffer[4096]; + + if (!string) + return; + + const u32 length = UTF8_LengthForJSON(string); + if (length >= sizeof(buffer)) { + JSONW_Write("bufferSizeTooSmall"); + return; + } + + UTF8_CleanForJSON(buffer, string); + JSONW_Write(buffer); +} + +void JSONW_BeginFile(FILE* file) +{ + memset(&writer, 0, sizeof(writer)); + writer.file = file; + + JSONW_Write("{"); + ++writer.level; +} + +void JSONW_EndFile() +{ + JSONW_Write("\r\n}"); +} + +void JSONW_BeginObject() +{ + if (writer.itemIndices[writer.level] > 0) + JSONW_Write(","); + + JSONW_WriteNewLine(); + JSONW_Write("{"); + ++writer.level; + writer.itemIndices[writer.level] = 0; +} + +void JSONW_BeginNamedObject(const char* name) +{ + if (!name) + return; + + if (writer.itemIndices[writer.level] > 0) + JSONW_Write(","); + + JSONW_WriteNewLine(); + JSONW_Write("\""); + JSONW_WriteClean(name); + JSONW_Write("\":"); + JSONW_WriteNewLine(); + JSONW_Write("{"); + ++writer.level; + writer.itemIndices[writer.level] = 0; +} + +void JSONW_EndObject() +{ + --writer.level; + JSONW_WriteNewLine(); + JSONW_Write("}"); + ++writer.itemIndices[writer.level]; +} + +void JSONW_BeginArray() +{ + if (writer.itemIndices[writer.level] > 0) + JSONW_Write(","); + + JSONW_WriteNewLine(); + JSONW_Write("["); + ++writer.level; + writer.itemIndices[writer.level] = 0; +} + +void JSONW_BeginNamedArray(const char* name) +{ + if (!name) + return; + + if (writer.itemIndices[writer.level] > 0) + JSONW_Write(","); + + JSONW_WriteNewLine(); + JSONW_Write("\""); + JSONW_WriteClean(name); + JSONW_Write("\":"); + JSONW_WriteNewLine(); + JSONW_Write("["); + ++writer.level; + writer.itemIndices[writer.level] = 0; +} + +void JSONW_EndArray() +{ + --writer.level; + JSONW_WriteNewLine(); + JSONW_Write("]"); + ++writer.itemIndices[writer.level]; +} + +void JSONW_IntegerValue(const char* name, int number) +{ + if (!name) + return; + + JSONW_StringValue(name, "%d", number); +} + +void JSONW_HexValue(const char* name, uint64_t number) +{ + if (!name) + return; + + JSONW_StringValue(name, Q_itohex(number, qtrue, qtrue)); +} + +void JSONW_BooleanValue(const char* name, qbool value) +{ + JSONW_StringValue(name, value ? "true" : "false"); +} + +void JSONW_StringValue(const char* name, const char* format, ...) +{ + static char buffer[4096]; + + if (!name || !format || *format == '\0') + return; + + va_list ap; + va_start(ap, format); + Q_vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if (writer.itemIndices[writer.level] > 0) + JSONW_Write(", "); + + JSONW_WriteNewLine(); + JSONW_Write("\""); + JSONW_Write(name); + JSONW_Write("\": \""); + JSONW_WriteClean(buffer); + JSONW_Write("\""); + ++writer.itemIndices[writer.level]; +} + +void JSONW_UnnamedHex(uint64_t number) +{ + JSONW_UnnamedString(Q_itohex(number, qtrue, qtrue)); +} + +void JSONW_UnnamedString(const char* format, ...) +{ + static char buffer[4096]; + + if (!format || *format == '\0') + return; + + va_list ap; + va_start(ap, format); + Q_vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if (writer.itemIndices[writer.level] > 0) + JSONW_Write(", "); + + JSONW_WriteNewLine(); + JSONW_Write("\""); + JSONW_WriteClean(buffer); + JSONW_Write("\""); + ++writer.itemIndices[writer.level]; +} diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index bb4cfbe..2e5add9 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -993,4 +993,8 @@ int StatHuff_WriteSymbol( int symbol, byte* buffer, int bitIndex ); // returns #define SV_DECODE_START CL_ENCODE_START #define CL_DECODE_START SV_ENCODE_START + +const char* Q_itohex( uint64_t number, qbool uppercase, qbool prefix ); + + #endif // _QCOMMON_H_ diff --git a/code/qcommon/vm.cpp b/code/qcommon/vm.cpp index be35418..8218987 100644 --- a/code/qcommon/vm.cpp +++ b/code/qcommon/vm.cpp @@ -34,6 +34,7 @@ and one exported function: Perform */ #include "vm_local.h" +#include "crash.h" opcode_info_t ops[ OP_MAX ] = { @@ -435,6 +436,7 @@ vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc ) { } vm->crc32sum = crc32sum; + Crash_SaveQVMChecksum( vm->index, crc32sum ); dataLength = header->dataLength + header->litLength + header->bssLength; vm->dataLength = dataLength; @@ -989,6 +991,8 @@ vm_t *VM_Create( vmIndex_t index, syscall_t systemCalls, vmInterpret_t interpret // load the map file VM_LoadSymbols( vm ); + Crash_SaveQVMPointer( index, vm ); + Com_Printf( "%s loaded in %d bytes on the hunk\n", vm->name, remaining - Hunk_MemoryRemaining() ); return vm; @@ -1006,6 +1010,8 @@ void VM_Free( vm_t *vm ) { return; } + Crash_SaveQVMPointer( vm->index, NULL ); + if ( vm->destroy ) vm->destroy( vm ); diff --git a/code/qcommon/vm_interpreted.cpp b/code/qcommon/vm_interpreted.cpp index 4300da3..4327047 100644 --- a/code/qcommon/vm_interpreted.cpp +++ b/code/qcommon/vm_interpreted.cpp @@ -138,6 +138,22 @@ qboolean VM_PrepareInterpreter2( vm_t *vm, vmHeader_t *header ) } +static void CallStackPush( vm_t *vm, int *callStackDepth, int ip ) +{ + const int clampedDepth = min( vm->callStackDepth, MAX_VM_CALL_STACK_DEPTH - 1 ); + + vm->callStack[clampedDepth] = ip; + vm->callStackDepth++; + *callStackDepth = max( *callStackDepth, clampedDepth + 1 ); +} + + +static void CallStackPop( vm_t *vm ) +{ + vm->callStackDepth--; +} + + /* ============== VM_CallInterpreted2 @@ -173,6 +189,7 @@ int VM_CallInterpreted2( vm_t *vm, int *args ) { int *opStack, *opStackTop; int programStack; int stackOnEntry; + int callStackDepth = 0; byte *image; int v1, v0; int dataMask; @@ -238,9 +255,12 @@ nextInstruction2: if ( opStack + ((ci-1)->opStack/4) >= opStackTop ) { Com_Error( ERR_DROP, "VM opStack overflow" ); } + CallStackPush( vm, &callStackDepth, (int)(ci - (instruction_t*)vm->codeBase.ptr) - 1 ); break; case OP_LEAVE: + CallStackPop( vm ); + // remove our stack frame programStack += v0; @@ -274,9 +294,13 @@ nextInstruction2: for ( argn = 0; argn < ARRAY_LEN( argarr ); ++argn ) { argarr[ argn ] = *(int*)&image[ programStack + 4 + 4*argn ]; } + CallStackPush( vm, &callStackDepth, r0.i ); v0 = vm->systemCall( &argarr[0] ); + CallStackPop( vm ); #else + CallStackPush( vm, &callStackDepth, r0.i ); v0 = vm->systemCall( (intptr_t *)&image[ programStack + 4 ] ); + CallStackPop( vm ); #endif } @@ -607,6 +631,7 @@ done: } vm->programStack = stackOnEntry; + vm->lastCallStackDepth = callStackDepth; // return the result return *opStack; diff --git a/code/qcommon/vm_local.h b/code/qcommon/vm_local.h index e5ef473..fe02f08 100644 --- a/code/qcommon/vm_local.h +++ b/code/qcommon/vm_local.h @@ -154,6 +154,8 @@ typedef union vmFunc_u { void (*func)(void); } vmFunc_t; +#define MAX_VM_CALL_STACK_DEPTH 64 + struct vm_s { // DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES // USED BY THE ASM CODE @@ -202,6 +204,11 @@ struct vm_s { uint32_t crc32sum; vmIndex_t index; + + int callStackDepth; + int lastCallStackDepth; + int callStackDepthTemp; // only for vm_x86.cpp + int callStack[MAX_VM_CALL_STACK_DEPTH]; }; extern vm_t *currentVM; diff --git a/code/qcommon/vm_x86.cpp b/code/qcommon/vm_x86.cpp index cd7a23f..436376a 100644 --- a/code/qcommon/vm_x86.cpp +++ b/code/qcommon/vm_x86.cpp @@ -1553,6 +1553,85 @@ static qboolean EmitMOPs(vm_t *vm, int op) } +static void EmitCallStackPush( vm_t *vm ) +{ + const int instOffset = (int)(ci - inst); + +#if id386 + // clampedDepth = MIN_UNSIGNED(vm->callStackDepth, MAX_VM_CALL_STACK_DEPTH - 1); + EmitString( "8B 0D" ); // mov ecx, dword ptr [&vm->callStackDepth] + EmitPtr( &vm->callStackDepth ); + EmitString( "BA" ); // mov edx, MAX_VM_CALL_STACK_DEPTH - 1 + Emit4( MAX_VM_CALL_STACK_DEPTH - 1 ); + EmitString( "39 D1" ); // cmp ecx, edx + EmitString( "0F 47 CA" ); // cmova ecx, edx + + // vm->callStack[clampedDepth] = instOffset; + EmitString( "C7 04 8D" ); // mov dword ptr [vm->callStack + ecx*4], instOffset + EmitPtr( vm->callStack ); + Emit4( instOffset ); + + // vm->callStackDepth++; + EmitString( "83 05" ); // add dword ptr [&vm->callStackDepth], 1 + EmitPtr( &vm->callStackDepth ); + Emit1( 1 ); + + // vm->callStackDepthTemp = MAX(vm->callStackDepthTemp, clampedDepth + 1); + EmitString( "83 C1 01" ); // add ecx, 1 + EmitString( "8B 15" ); // mov edx, dword ptr [&vm->callStackDepthTemp] + EmitPtr( &vm->callStackDepthTemp ); + EmitString( "39 D1" ); // cmp ecx, edx + EmitString( "0F 47 D1" ); // cmova edx, ecx + EmitString( "89 15" ); // mov dword ptr [&vm->callStackDepthTemp], edx + EmitPtr( &vm->callStackDepthTemp ); +#elif idx64 + // clampedDepth = MIN_UNSIGNED(vm->callStackDepth, MAX_VM_CALL_STACK_DEPTH - 1); + EmitString( "A1" ); // mov eax, dword ptr [&vm->callStackDepth] + EmitPtr( &vm->callStackDepth ); + EmitString( "BA" ); // mov edx, MAX_VM_CALL_STACK_DEPTH - 1 + Emit4( MAX_VM_CALL_STACK_DEPTH - 1 ); + EmitString( "39 D0" ); // cmp eax, edx + EmitString( "0F 46 D0" ); // cmovbe edx, eax + + // vm->callStack[clampedDepth] = instOffset; + EmitString( "48 B8" ); // mov rax, vm->callStack + EmitPtr( vm->callStack ); + EmitString( "C7 04 90" ); // mov dword ptr [rax + rdx*4], instOffset + Emit4( instOffset ); + + // vm->callStackDepth++; + EmitString( "48 B8" ); // mov rax, &vm->callStackDepth + EmitPtr( &vm->callStackDepth ); + EmitString( "83 00 01" ); // add dword ptr [rax], 1 + + // vm->callStackDepthTemp = MAX(vm->callStackDepthTemp, clampedDepth + 1); + EmitString( "48 B8" ); // mov rax, &vm->callStackDepthTemp + EmitPtr( &vm->callStackDepthTemp ); + EmitString( "83 C2 01" ); // add edx, 1 + EmitString( "8B 08" ); // mov ecx, dword ptr [rax] + EmitString( "39 D1" ); // cmp ecx, edx + EmitString( "0F 47 D1" ); // cmova edx, ecx + EmitString( "89 10" ); // mov dword ptr [rax], edx +#endif +} + + +static void EmitCallStackPop( vm_t *vm ) +{ +#if id386 + // vm->callStackDepth--; + EmitString( "83 2D" ); // sub dword ptr [&vm->callStackDepth], 1 + EmitPtr( &vm->callStackDepth ); + Emit1( 1 ); +#elif idx64 + // vm->callStackDepth--; + EmitString( "48 B9" ); // mov rcx, &vm->callStackDepth + EmitPtr( &vm->callStackDepth ); + EmitString( "83 29 01" ); // sub dword ptr [rcx], 1 +#endif +} + + /* ================= VM_Compile @@ -1715,6 +1794,8 @@ __compile: break; case OP_ENTER: + EmitCallStackPush( vm ); + v = ci->value; if ( ISS8( v ) ) { EmitString( "83 EE" ); // sub esi, 0x12 @@ -1862,8 +1943,12 @@ __compile: break; case OP_CALL: + EmitCallStackPush( vm ); + EmitMovEAXEDI( vm ); // mov eax, dword ptr [edi] EmitCallOffset( FUNC_CALL ); // call +FUNC_CALL + + EmitCallStackPop( vm ); break; case OP_PUSH: @@ -1885,6 +1970,7 @@ __compile: Emit4( v ); } #endif + EmitCallStackPop( vm ); EmitString( "C3" ); // ret break; @@ -2433,7 +2519,10 @@ int VM_CallCompiled( vm_t *vm, int *args ) vm->opStack = opStack; vm->opStackTop = opStack + ARRAY_LEN( opStack ) - 1; + vm->callStackDepth = 0; // theoretically not necessary... + vm->callStackDepthTemp = 0; vm->codeBase.func(); // go into generated code + vm->lastCallStackDepth = vm->callStackDepthTemp; if ( vm->opStack != &opStack[1] ) { Com_Error( ERR_DROP, "opStack corrupted in compiled code" ); diff --git a/code/unix/linux_local.h b/code/unix/linux_local.h index fc8c36e..1419f6e 100644 --- a/code/unix/linux_local.h +++ b/code/unix/linux_local.h @@ -23,7 +23,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ); void Sys_SendKeyEvents (void); -void Sys_Exit(int); +void Sys_ConsoleInputShutdown(void); // Input subsystem void IN_Init (void); @@ -42,3 +42,6 @@ void SIG_Init(void); // hardware gamma ramp void LIN_RestoreGamma(void); + +extern int q_argc; +extern const char** q_argv; diff --git a/code/unix/linux_signals.cpp b/code/unix/linux_signals.cpp index bee37b5..f0a5104 100644 --- a/code/unix/linux_signals.cpp +++ b/code/unix/linux_signals.cpp @@ -20,9 +20,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include +#include +#include #include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" +#include "../qcommon/crash.h" #ifndef DEDICATED #include "../renderer/tr_local.h" #endif @@ -76,6 +79,81 @@ static const char* Sig_GetName(int sig) } +static void Sig_WriteJSON(int sig) +{ + FILE* const file = fopen(va("%s-crash.json", q_argv[0]), "w"); + if (file == NULL) + return; + + JSONW_BeginFile(file); + JSONW_IntegerValue("signal", sig); + JSONW_StringValue("signal_name", Sig_GetName(sig)); + JSONW_StringValue("signal_description", Sig_GetDescription(sig)); + Crash_PrintToFile(q_argv[0]); + JSONW_EndFile(); + fclose(file); +} + + +// only uses functions safe to call in a signal handler +static void Sig_WriteBacktraceSafe() +{ + FILE* const file = fopen(va("%s-crash.bt", q_argv[0]), "w"); + if (file == NULL) + return; + + void* addresses[64]; + const int addresscount = backtrace(addresses, sizeof(addresses)); + if (addresscount > 0) + { + fprintf(file, "backtrace_symbols_fd stack trace:\r\n"); + fflush(file); + backtrace_symbols_fd(addresses, addresscount, fileno(file)); + } + else + { + fprintf(file, "The call to backtrace failed\r\n"); + } + + fclose(file); +} + + +static void libbt_ErrorCallback(void* data, const char* msg, int errnum) +{ + fprintf((FILE*)data, "libbacktrace error: %s (%d)\r\n", msg, errnum); +} + + +// might not be safe in a signal handler +static void Sig_WriteBacktraceUnsafe() +{ + FILE* const file = fopen(va("%s-crash.bt", q_argv[0]), "a"); + if (file == NULL) + return; + + fprintf(file, "\r\n\r\n"); + + backtrace_state* const state = backtrace_create_state(q_argv[0], 0, libbt_ErrorCallback, file); + if (state) + { + fprintf(file, "libbacktrace stack trace:\r\n"); + fflush(file); + backtrace_print(state, 0, file); + } + else + { + fprintf(file, "The call to backtrace_create_state failed\r\n"); + } + + fclose(file); +} + + +static qbool sig_crashed = qfalse; +static int sig_signal = 0; + + // Every call in there needs to be safe when called more than once. static void SIG_HandleCrash() { @@ -84,6 +162,14 @@ static void SIG_HandleCrash() #ifndef DEDICATED LIN_RestoreGamma(); #endif + Sys_ConsoleInputShutdown(); + + if (!sig_crashed) + return; + + Sig_WriteJSON(sig_signal); + Sig_WriteBacktraceSafe(); + Sig_WriteBacktraceUnsafe(); } @@ -91,7 +177,8 @@ static void Sig_HandleSignal(int sig) { static int faultCounter = 0; static qbool crashHandled = qfalse; - + + sig_signal = sig; faultCounter++; if (faultCounter >= 3) @@ -102,23 +189,23 @@ static void Sig_HandleSignal(int sig) exit(3); } - const qbool crashed = Sig_IsCrashSignal(sig); + sig_crashed = Sig_IsCrashSignal(sig); if (faultCounter == 2) { // The termination handler failed which means that if we exit right now, // some system settings might still be in a bad state. printf("DOUBLE SIGNAL FAULT: Received signal %d (%s), exiting...\n", sig, Sig_GetName(sig)); - if (crashed && !crashHandled) + if (sig_crashed && !crashHandled) { SIG_HandleCrash(); } exit(2); } - fprintf(crashed ? stderr : stdout, "Received %s signal %d: %s (%s), exiting...\n", - crashed ? "crash" : "termination", sig, Sig_GetName(sig), Sig_GetDescription(sig)); + fprintf(sig_crashed ? stderr : stdout, "Received %s signal %d: %s (%s), exiting...\n", + sig_crashed ? "crash" : "termination", sig, Sig_GetName(sig), Sig_GetDescription(sig)); - if (crashed) + if (sig_crashed) { SIG_HandleCrash(); crashHandled = qtrue; @@ -131,12 +218,13 @@ static void Sig_HandleSignal(int sig) CL_Shutdown(); #endif SV_Shutdown("Signal caught"); - Sys_Exit(0); + Sys_ConsoleInputShutdown(); + exit(0); } } -void SIG_Init(void) +void SIG_Init() { // This is unfortunately needed because some code might // call exit and bypass all the clean-up work without diff --git a/code/unix/unix_main.cpp b/code/unix/unix_main.cpp index fbea9c8..503c13a 100644 --- a/code/unix/unix_main.cpp +++ b/code/unix/unix_main.cpp @@ -63,6 +63,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA /////////////////////////////////////////////////////////////// +int q_argc = 0; +const char** q_argv = NULL; + static qboolean stdin_active = qtrue; // enable/disable tty input mode @@ -274,6 +277,9 @@ static void Sys_ConsoleInputInit() } } else ttycon_on = qfalse; + + // make stdin non-blocking + fcntl( 0, F_SETFL, fcntl(0, F_GETFL, 0) | FNDELAY ); } @@ -442,12 +448,16 @@ const char* Sys_ConsoleInput() // never exit without calling this, or your terminal will be left in a pretty bad state -static void Sys_ConsoleInputShutdown() +void Sys_ConsoleInputShutdown() { - if (!ttycon_on) - return; - Com_Printf("Shutdown tty console\n"); - tcsetattr (0, TCSADRAIN, &tty_tc); + if (ttycon_on) + { + tcsetattr (0, TCSADRAIN, &tty_tc); + ttycon_on = qfalse; + } + + // make stdin blocking + fcntl( 0, F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY ); } @@ -462,23 +472,11 @@ qboolean Sys_LowPhysicalMemory() } -// single exit point (regular exit or in case of signal fault) -void Sys_Exit( int ex ) -{ - Sys_ConsoleInputShutdown(); - exit(ex); -} - - void Sys_Error( const char *error, ... ) { va_list argptr; char string[1024]; - // change stdin to nonblocking - // NOTE TTimo not sure how well that goes with tty console mode - fcntl( 0, F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY ); - if (ttycon_on) tty_Hide(); @@ -491,18 +489,15 @@ void Sys_Error( const char *error, ... ) va_end (argptr); fprintf(stderr, "Sys_Error: %s\n", string); - Sys_Exit( 1 ); + Sys_ConsoleInputShutdown(); + exit(1); } void Sys_Quit() { -#ifndef DEDICATED - CL_Shutdown(); -#endif - - fcntl( 0, F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY ); - Sys_Exit(0); + Sys_ConsoleInputShutdown(); + exit(0); } @@ -835,8 +830,11 @@ void Sys_Init() } -int main( int argc, char* argv[] ) +int main( int argc, const char** argv ) { + q_argc = argc; + q_argv = argv; + SIG_Init(); // merge the command line: we need it in a single chunk @@ -858,8 +856,6 @@ int main( int argc, char* argv[] ) Sys_ConsoleInputInit(); - fcntl( 0, F_SETFL, fcntl(0, F_GETFL, 0) | FNDELAY ); - while (qtrue) { // if running as a client but not focused, sleep a bit // (servers have their own sleep path) diff --git a/code/win32/win_exception.cpp b/code/win32/win_exception.cpp new file mode 100644 index 0000000..c2752e1 --- /dev/null +++ b/code/win32/win_exception.cpp @@ -0,0 +1,423 @@ +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../qcommon/crash.h" +#include "win_local.h" +#include +#include + + +#if !id386 && !idx64 +# error "This architecture is not supported." +#endif + + +typedef void (WINAPI *FptrGeneric)(); +typedef BOOL (WINAPI *FptrSymInitialize)(HANDLE, PCSTR, BOOL); +typedef PVOID (WINAPI *FptrSymFunctionTableAccess64)(HANDLE, DWORD64); +typedef DWORD64 (WINAPI *FptrSymGetModuleBase64)(HANDLE, DWORD64); +typedef BOOL (WINAPI *FptrStackWalk64)(DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID, PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64, PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64); +typedef BOOL (WINAPI *FptrSymGetSymFromAddr64)(HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64); +typedef BOOL (WINAPI *FptrMiniDumpWriteDump)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, CONST PMINIDUMP_EXCEPTION_INFORMATION, CONST PMINIDUMP_USER_STREAM_INFORMATION, CONST PMINIDUMP_CALLBACK_INFORMATION); + + +typedef struct { + HMODULE libraryHandle; + FptrSymInitialize SymInitialize; + FptrSymFunctionTableAccess64 SymFunctionTableAccess64; + FptrSymGetModuleBase64 SymGetModuleBase64; + FptrStackWalk64 StackWalk64; + FptrSymGetSymFromAddr64 SymGetSymFromAddr64; + FptrMiniDumpWriteDump MiniDumpWriteDump; +} debug_help_t; + + +static void WIN_CloseDebugHelp( debug_help_t* debugHelp ) +{ + if (debugHelp->libraryHandle == NULL) + return; + + FreeLibrary(debugHelp->libraryHandle); + debugHelp->libraryHandle = NULL; +} + +static qboolean WIN_OpenDebugHelp( debug_help_t* debugHelp ) +{ + debugHelp->libraryHandle = LoadLibraryA("dbghelp.dll"); + if (debugHelp->libraryHandle == NULL) + return qfalse; + +#define GET_FUNCTION(func) \ + debugHelp->func = (Fptr##func)GetProcAddress(debugHelp->libraryHandle, #func); \ + if (debugHelp->func == NULL) { \ + WIN_CloseDebugHelp(debugHelp); \ + return qfalse; \ + } + + GET_FUNCTION(SymInitialize) + GET_FUNCTION(SymFunctionTableAccess64) + GET_FUNCTION(SymGetModuleBase64) + GET_FUNCTION(StackWalk64) + GET_FUNCTION(SymGetSymFromAddr64) + GET_FUNCTION(MiniDumpWriteDump) + +#undef GET_FUNCTION + + return qtrue; +} + +static void WIN_DumpStackTrace( debug_help_t* debugHelp ) +{ + enum { + BUFFER_SIZE = 1024, + MAX_LEVELS = 256 + }; + + if (!debugHelp->SymInitialize(GetCurrentProcess(), NULL, TRUE)) + return; + + CONTEXT context; +#if id386 + ZeroMemory(&context, sizeof(CONTEXT)); + context.ContextFlags = CONTEXT_CONTROL; + __asm { + Label: + mov[context.Ebp], ebp; + mov[context.Esp], esp; + mov eax, [Label]; + mov[context.Eip], eax; + } +#else // idx64 + RtlCaptureContext(&context); +#endif + + // Init the stack frame for this function + STACKFRAME64 stackFrame; + ZeroMemory(&stackFrame, sizeof(stackFrame)); +#if id386 + const DWORD machineType = IMAGE_FILE_MACHINE_I386; + stackFrame.AddrPC.Offset = context.Eip; + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrFrame.Offset = context.Ebp; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrStack.Offset = context.Esp; + stackFrame.AddrStack.Mode = AddrModeFlat; +#else // idx64 + const DWORD machineType = IMAGE_FILE_MACHINE_AMD64; + stackFrame.AddrPC.Offset = context.Rip; + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrFrame.Offset = context.Rsp; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrStack.Offset = context.Rsp; + stackFrame.AddrStack.Mode = AddrModeFlat; +#endif + + JSONW_BeginNamedArray("stack_trace"); + + unsigned char buffer[sizeof(IMAGEHLP_SYMBOL64) + BUFFER_SIZE]; + IMAGEHLP_SYMBOL64* const symbol = (IMAGEHLP_SYMBOL64*)buffer; + + int level = 1; + while (level++ < (MAX_LEVELS + 1)) { + BOOL result = debugHelp->StackWalk64( + machineType, GetCurrentProcess(), GetCurrentThread(), &stackFrame, &context, + NULL, debugHelp->SymFunctionTableAccess64, debugHelp->SymGetModuleBase64, NULL); + if (!result || stackFrame.AddrPC.Offset == 0) + break; + + ZeroMemory(symbol, sizeof(IMAGEHLP_SYMBOL64) + BUFFER_SIZE); + symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + symbol->MaxNameLength = BUFFER_SIZE; + + JSONW_BeginObject(); + JSONW_HexValue("program_counter", stackFrame.AddrPC.Offset); + JSONW_HexValue("stack_pointer", stackFrame.AddrStack.Offset); + JSONW_HexValue("frame_pointer", stackFrame.AddrFrame.Offset); + JSONW_HexValue("return_address", stackFrame.AddrReturn.Offset); + + DWORD64 displacement; + result = debugHelp->SymGetSymFromAddr64(GetCurrentProcess(), stackFrame.AddrPC.Offset, &displacement, symbol); + if (result) + JSONW_StringValue("name", symbol->Name); + + JSONW_EndObject(); + } + + JSONW_EndArray(); +} + +static BOOL WINAPI WIN_MiniDumpCallback( + IN PVOID CallbackParam, + IN CONST PMINIDUMP_CALLBACK_INPUT CallbackInput, + IN OUT PMINIDUMP_CALLBACK_OUTPUT CallbackOutput ) +{ + // Keep everything except... + if (CallbackInput->CallbackType != ModuleCallback) + return TRUE; + + // ...modules unreferenced by memory. + if ((CallbackOutput->ModuleWriteFlags & ModuleReferencedByMemory) == 0) { + CallbackOutput->ModuleWriteFlags &= ~ModuleWriteModule; + return TRUE; + } + + return TRUE; +} + +static qboolean WIN_CreateDirectoryIfNeeded( const char* path ) +{ + const BOOL success = CreateDirectoryA(path, NULL); + if (success) + return qtrue; + + return GetLastError() == ERROR_ALREADY_EXISTS; +} + +static char exc_reportFolderPath[MAX_PATH]; + +static void WIN_CreateDumpFilePath( char* buffer, const char* fileName, SYSTEMTIME* time ) +{ + char* const temp = getenv("TEMP"); + if (temp == NULL || !WIN_CreateDirectoryIfNeeded(va("%s\\cnq3_crash", temp))) + return; + + Q_strncpyz(exc_reportFolderPath, va("%s\\cnq3_crash\\", temp), sizeof(exc_reportFolderPath)); + StringCchPrintfA( + buffer, MAX_PATH, "%s%s_%04d.%02d.%02d_%02d.%02d.%02d", + exc_reportFolderPath, fileName, time->wYear, time->wMonth, time->wDay, + time->wHour, time->wMinute, time->wSecond); +} + +static qboolean WIN_GetOSVersion( int* major, int* minor, int* revision ) +{ + enum { FILE_INFO_SIZE = 4096 }; + const DWORD fileInfoSize = min(FILE_INFO_SIZE, GetFileVersionInfoSizeA("kernel32.dll", NULL)); + if (fileInfoSize == 0) + return qfalse; + + char fileInfo[FILE_INFO_SIZE]; + if (!GetFileVersionInfoA("kernel32.dll", 0, fileInfoSize, fileInfo)) + return qfalse; + + LPVOID osInfo = NULL; + UINT osInfoSize = 0; + if (!VerQueryValueA(&fileInfo[0], "\\", &osInfo, &osInfoSize) || + osInfoSize < sizeof(VS_FIXEDFILEINFO)) + return qfalse; + + const VS_FIXEDFILEINFO* const versionInfo = (const VS_FIXEDFILEINFO*)osInfo; + *major = HIWORD(versionInfo->dwProductVersionMS); + *minor = LOWORD(versionInfo->dwProductVersionMS); + *revision = HIWORD(versionInfo->dwProductVersionLS); + + return qtrue; +} + +static const char* WIN_GetExceptionCodeString( DWORD exceptionCode ) +{ + switch (exceptionCode) { + case EXCEPTION_ACCESS_VIOLATION: return "The thread tried to read from or write to a virtual address for which it does not have the appropriate access."; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "The thread tried to access an array element that is out of bounds and the underlying hardware supports bounds checking."; + case EXCEPTION_BREAKPOINT: return "A breakpoint was encountered."; + case EXCEPTION_DATATYPE_MISALIGNMENT: return "The thread tried to read or write data that is misaligned on hardware that does not provide alignment."; + case EXCEPTION_FLT_DENORMAL_OPERAND: return "One of the operands in a floating-point operation is denormal."; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "The thread tried to divide a floating-point value by a floating-point divisor of zero."; + case EXCEPTION_FLT_INEXACT_RESULT: return "The result of a floating-point operation cannot be represented exactly as a decimal fraction."; + case EXCEPTION_FLT_INVALID_OPERATION: return "This exception represents any floating-point exception not described by other codes."; + case EXCEPTION_FLT_OVERFLOW: return "The exponent of a floating-point operation is greater than the magnitude allowed by the corresponding type."; + case EXCEPTION_FLT_STACK_CHECK: return "The stack overflowed or underflowed as the result of a floating-point operation."; + case EXCEPTION_FLT_UNDERFLOW: return "The exponent of a floating-point operation is less than the magnitude allowed by the corresponding type."; + case EXCEPTION_ILLEGAL_INSTRUCTION: return "The thread tried to execute an invalid instruction."; + case EXCEPTION_IN_PAGE_ERROR: return "The thread tried to access a page that was not present and the system was unable to load the page."; + case EXCEPTION_INT_DIVIDE_BY_ZERO: return "The thread tried to divide an integer value by an integer divisor of zero."; + case EXCEPTION_INT_OVERFLOW: return "The result of an integer operation caused a carry out of the most significant bit of the result."; + case EXCEPTION_INVALID_DISPOSITION: return "An exception handler returned an invalid disposition to the exception dispatcher."; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "The thread tried to continue execution after a noncontinuable exception occurred."; + case EXCEPTION_PRIV_INSTRUCTION: return "The thread tried to execute an instruction whose operation is not allowed in the current machine mode."; + case EXCEPTION_SINGLE_STEP: return "A trace trap or other single-instruction mechanism signaled that one instruction has been executed."; + case EXCEPTION_STACK_OVERFLOW: return "The thread used up its stack."; + default: return "Unknown exception code"; + } +} + +static const char* WIN_GetAccessViolationCodeString( DWORD avCode ) +{ + switch (avCode) { + case 0: return "Read access violation"; + case 1: return "Write access violation"; + case 8: return "User-mode data execution prevention (DEP) violation"; + default: return "Unknown violation"; + } +} + +static void WIN_WriteTextData( const char* filePath, debug_help_t* debugHelp, EXCEPTION_RECORD* pExceptionRecord ) +{ + FILE* const file = fopen(filePath, "w"); + if (file == NULL) + return; + + JSONW_BeginFile(file); + + WIN_DumpStackTrace(debugHelp); + JSONW_HexValue("exception_code", pExceptionRecord->ExceptionCode); + JSONW_StringValue("exception_description", WIN_GetExceptionCodeString(pExceptionRecord->ExceptionCode)); + + if (pExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && + pExceptionRecord->NumberParameters >= 2) { + JSONW_StringValue("exception_details", "%s at address %s", + WIN_GetAccessViolationCodeString(pExceptionRecord->ExceptionInformation[0]), + Q_itohex(pExceptionRecord->ExceptionInformation[1], qtrue, qtrue)); + } + + int osVersion[3]; + if (WIN_GetOSVersion(osVersion, osVersion + 1, osVersion + 2)) { + JSONW_StringValue("windows_version", "%d.%d.%d", osVersion[0], osVersion[1], osVersion[2]); + } + + Crash_PrintToFile(__argv[0]); + + JSONW_EndFile(); + + fclose(file); +} + +static void WIN_WriteMiniDump( const char* filePath, debug_help_t* debugHelp, EXCEPTION_POINTERS* pExceptionPointers ) +{ + const HANDLE dumpFile = CreateFileA( + filePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, + 0, CREATE_ALWAYS, 0, 0); + + if (dumpFile == INVALID_HANDLE_VALUE) + return; + + MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; + ZeroMemory(&exceptionInfo, sizeof(exceptionInfo)); + exceptionInfo.ThreadId = GetCurrentThreadId(); + exceptionInfo.ExceptionPointers = pExceptionPointers; + exceptionInfo.ClientPointers = TRUE; + + MINIDUMP_CALLBACK_INFORMATION callbackInfo; + ZeroMemory(&callbackInfo, sizeof(callbackInfo)); + callbackInfo.CallbackRoutine = &WIN_MiniDumpCallback; + callbackInfo.CallbackParam = NULL; + + debugHelp->MiniDumpWriteDump( + GetCurrentProcess(), GetCurrentProcessId(), dumpFile, + (MINIDUMP_TYPE)(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory), + &exceptionInfo, NULL, &callbackInfo); + + CloseHandle(dumpFile); +} + +static const char* WIN_GetFileName( const char* path ) +{ + const char* name = strrchr(path, '\\'); + if (name != path) + return name + 1; + + name = strrchr(path, '/'); + if (name != path) + return name + 1; + + return path; +} + +static qbool exc_reportWritten = qfalse; + +static void WIN_WriteExceptionFilesImpl( EXCEPTION_POINTERS* pExceptionPointers ) +{ + debug_help_t debugHelp; + if (!WIN_OpenDebugHelp(&debugHelp)) + return; + + SYSTEMTIME time; + GetSystemTime(&time); + + char modulePath[MAX_PATH]; + GetModuleFileNameA(GetModuleHandle(NULL), modulePath, sizeof(modulePath)); + + char dumpFilePath[MAX_PATH]; + WIN_CreateDumpFilePath(dumpFilePath, WIN_GetFileName(modulePath), &time); + + WIN_WriteTextData(va("%s.json", dumpFilePath), &debugHelp, pExceptionPointers->ExceptionRecord); + WIN_WriteMiniDump(va("%s.dmp", dumpFilePath), &debugHelp, pExceptionPointers); + exc_reportWritten = qtrue; + + WIN_CloseDebugHelp(&debugHelp); +} + +static int WINAPI WIN_WriteExceptionFiles( EXCEPTION_POINTERS* pExceptionPointers ) +{ + // No exception info? + if (!pExceptionPointers) { + __try { + // Generate an exception to get a proper context. + RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL); + } __except (WIN_WriteExceptionFiles(GetExceptionInformation()), EXCEPTION_CONTINUE_EXECUTION) {} + + return EXCEPTION_EXECUTE_HANDLER; + } + + // We have exception information now, so let's proceed. + WIN_WriteExceptionFilesImpl(pExceptionPointers); + + return EXCEPTION_EXECUTE_HANDLER; +} + +// +// The exception handler's job is to reset system settings that won't get reset +// as part of the normal process clean-up by the OS. +// It can't do any memory allocation or use any synchronization objects. +// Ideally, we want it to be called before every abrupt application exit +// and right after any legitimate crash. +// +// There are 2 cases where the function won't be called: +// +// 1. Termination through the debugger. +// Our atexit handler never gets called. +// +// Work-around: Quit normally. +// +// 2. Breakpoints. The debugger has first-chance access and handles them. +// Our exception handler doesn't get called. +// +// Work-around: None for debugging. Quit normally. +// + +static qbool exc_exitCalled = qfalse; + +LONG CALLBACK WIN_HandleException( EXCEPTION_POINTERS* ep ) +{ +#if !DEDICATED + __try { + GLW_RestoreGamma(); + } __except(EXCEPTION_EXECUTE_HANDLER) {} +#endif + + __try { + WIN_EndTimePeriod(); + } __except(EXCEPTION_EXECUTE_HANDLER) {} + + if (exc_exitCalled || IsDebuggerPresent()) + return EXCEPTION_CONTINUE_SEARCH; + + static const char* mbTitle = "CNQ3 Crash"; + static const char* mbMsg = "CNQ3 crashed!\n\nYes to generate a crash report\nNo to continue after attaching a debugger\nCancel to quit"; + const int result = MessageBoxA(NULL, mbMsg, mbTitle, MB_YESNOCANCEL | MB_ICONERROR); + if (result == IDYES) { + WIN_WriteExceptionFiles(ep); + if (exc_reportWritten) + ShellExecute(NULL, "open", exc_reportFolderPath, NULL, NULL, SW_SHOW); + else + MessageBoxA(NULL, "CNQ3's crash report generation failed!\nExiting now", mbTitle, MB_OK | MB_ICONERROR); + } else if (result == IDNO && IsDebuggerPresent()) { + return EXCEPTION_CONTINUE_SEARCH; + } + + ExitProcess(666); +} + +void WIN_HandleExit( void ) +{ + exc_exitCalled = qtrue; + WIN_HandleException(NULL); +} diff --git a/code/win32/win_local.h b/code/win32/win_local.h index 2bab90b..1e623e6 100644 --- a/code/win32/win_local.h +++ b/code/win32/win_local.h @@ -49,7 +49,11 @@ void SNDDMA_Activate(); LRESULT CALLBACK MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); +// crash handling +LONG CALLBACK WIN_HandleException( EXCEPTION_POINTERS* ep ); +void WIN_HandleExit(); void GLW_RestoreGamma(); +void WIN_EndTimePeriod(); #define MAX_MONITOR_COUNT 16 diff --git a/code/win32/win_main.cpp b/code/win32/win_main.cpp index 88e010d..9999c23 100644 --- a/code/win32/win_main.cpp +++ b/code/win32/win_main.cpp @@ -41,7 +41,7 @@ WinVars_t g_wv; static qbool win_timePeriodActive = qfalse; -static void Win_BeginTimePeriod() +static void WIN_BeginTimePeriod() { if ( win_timePeriodActive ) return; @@ -51,7 +51,7 @@ static void Win_BeginTimePeriod() } -static void Win_EndTimePeriod() +void WIN_EndTimePeriod() { if ( !win_timePeriodActive ) return; @@ -88,7 +88,7 @@ void QDECL Sys_Error( const char *error, ... ) Sys_SetErrorText( text ); Sys_ShowConsole( 1, qtrue ); - Win_EndTimePeriod(); + WIN_EndTimePeriod(); #ifndef DEDICATED IN_Shutdown(); @@ -111,7 +111,7 @@ void QDECL Sys_Error( const char *error, ... ) void Sys_Quit() { - Win_EndTimePeriod(); + WIN_EndTimePeriod(); #ifndef DEDICATED IN_Shutdown(); #endif @@ -537,7 +537,7 @@ static void Sys_Net_Restart_f( void ) void Sys_Init() { // make sure the timer is high precision, otherwise NT gets 18ms resolution - Win_BeginTimePeriod(); + WIN_BeginTimePeriod(); #ifndef DEDICATED Cmd_AddCommand( "in_restart", Sys_In_Restart_f ); @@ -697,75 +697,16 @@ int WINAPI WinMainImpl( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCm } -// -// The exception handler's job is to reset system settings that won't get reset -// as part of the normal process clean-up by the OS. -// It can't do any memory allocation or use any synchronization objects. -// Ideally, we want it to be called before every abrupt application exit -// and right after any legitimate crash. -// -// There are 2 cases where the function won't be called: -// -// 1. Termination through the debugger. -// Our atexit handler never gets called. -// -// Work-around: Quit normally. -// -// 2. Breakpoints. The debugger has first-chance access and handles them. -// Our exception handler doesn't get called. -// -// Work-around: None for debugging. Quit normally. -// - - -static qbool exitCalled = qfalse; - - -LONG CALLBACK Win_HandleException( EXCEPTION_POINTERS* ep ) -{ - static const char* mbMsg = "CNQ3 crashed!\n\nOK to continue after attaching a debugger\nCancel to quit"; - -#if !DEDICATED - __try { - GLW_RestoreGamma(); - } __except( EXCEPTION_EXECUTE_HANDLER ) {} -#endif - - __try { - Win_EndTimePeriod(); - } __except( EXCEPTION_EXECUTE_HANDLER ) {} - - if ( exitCalled || IsDebuggerPresent() ) - return EXCEPTION_CONTINUE_SEARCH; - -#if defined(_DEBUG) - // ask if we want to debug the app - if ( MessageBoxA( NULL, mbMsg, "Crash", MB_OKCANCEL | MB_ICONERROR ) == IDOK && - IsDebuggerPresent() ) - return EXCEPTION_CONTINUE_SEARCH; -#endif - - ExitProcess( 666 ); -} - - -static void Win_HandleExit( void ) -{ - exitCalled = qtrue; - Win_HandleException( NULL ); -} - - int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { // Register the exception handler for all threads present and future in this process. // 1 means we're inserting the handler at the front of the queue. // The debugger does still get first-chance access though. // The handler is always called in the context of the thread raising the exception. - AddVectoredExceptionHandler( 1, Win_HandleException ); + AddVectoredExceptionHandler( 1, WIN_HandleException ); // Make sure we reset system settings even when someone calls exit. - atexit( Win_HandleExit ); + atexit( WIN_HandleExit ); // SetErrorMode(0) gets the current flags // SEM_FAILCRITICALERRORS -> no abort/retry/fail errors