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