improved crash and exit handlers that restore system settings (monitor gamma ramps, system timer resolution, terminal settings, ...) and crash reports with full QVM stack trace

This commit is contained in:
myT 2017-03-14 05:11:12 +01:00
parent d945904298
commit 1efd5b6d82
23 changed files with 1414 additions and 122 deletions

View file

@ -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 "$<"

View file

@ -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 "$<"

View file

@ -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" }

View file

@ -115,7 +115,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\.bin\debug_x32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalOptions> ..\..\cnq3\code\win32\winquake.res /STACK:8388608 %(AdditionalOptions)</AdditionalOptions>
</Link>
@ -143,7 +143,7 @@ copy "..\..\..\.bin\debug_x32\cnq3-server-x86.pdb" "$(QUAKE3DIR)"</Command>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\.bin\debug_x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalOptions> ..\..\cnq3\code\win32\winquake.res /STACK:8388608 %(AdditionalOptions)</AdditionalOptions>
</Link>
@ -182,7 +182,7 @@ copy "..\..\..\.bin\debug_x64\cnq3-server-x64.pdb" "$(QUAKE3DIR)"</Command>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\.bin\release_x32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalOptions> ..\..\cnq3\code\win32\winquake.res /STACK:8388608 /OPT:REF /OPT:ICF %(AdditionalOptions)</AdditionalOptions>
</Link>
@ -220,7 +220,7 @@ copy "..\..\..\.bin\release_x32\cnq3-server-x86.pdb" "$(QUAKE3DIR)"</Command>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\.bin\release_x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalOptions> ..\..\cnq3\code\win32\winquake.res /STACK:8388608 /OPT:REF /OPT:ICF %(AdditionalOptions)</AdditionalOptions>
</Link>
@ -279,6 +279,7 @@ copy "..\..\..\.bin\release_x64\cnq3-server-x64.pdb" "$(QUAKE3DIR)"</Command>
<ClInclude Include="..\..\code\qcommon\cm_patch.h" />
<ClInclude Include="..\..\code\qcommon\cm_polylib.h" />
<ClInclude Include="..\..\code\qcommon\cm_public.h" />
<ClInclude Include="..\..\code\qcommon\crash.h" />
<ClInclude Include="..\..\code\qcommon\g_public.h" />
<ClInclude Include="..\..\code\qcommon\git.h" />
<ClInclude Include="..\..\code\qcommon\q_platform.h" />
@ -307,10 +308,12 @@ copy "..\..\..\.bin\release_x64\cnq3-server-x64.pdb" "$(QUAKE3DIR)"</Command>
<ClCompile Include="..\..\code\qcommon\cm_trace.cpp" />
<ClCompile Include="..\..\code\qcommon\cmd.cpp" />
<ClCompile Include="..\..\code\qcommon\common.cpp" />
<ClCompile Include="..\..\code\qcommon\crash.cpp" />
<ClCompile Include="..\..\code\qcommon\cvar.cpp" />
<ClCompile Include="..\..\code\qcommon\files.cpp" />
<ClCompile Include="..\..\code\qcommon\huffman.cpp" />
<ClCompile Include="..\..\code\qcommon\huffman_static.cpp" />
<ClCompile Include="..\..\code\qcommon\json.cpp" />
<ClCompile Include="..\..\code\qcommon\md4.cpp" />
<ClCompile Include="..\..\code\qcommon\md5.cpp" />
<ClCompile Include="..\..\code\qcommon\msg.cpp" />
@ -331,6 +334,7 @@ copy "..\..\..\.bin\release_x64\cnq3-server-x64.pdb" "$(QUAKE3DIR)"</Command>
<ClCompile Include="..\..\code\server\sv_net_chan.cpp" />
<ClCompile Include="..\..\code\server\sv_snapshot.cpp" />
<ClCompile Include="..\..\code\server\sv_world.cpp" />
<ClCompile Include="..\..\code\win32\win_exception.cpp" />
<ClCompile Include="..\..\code\win32\win_main.cpp" />
<ClCompile Include="..\..\code\win32\win_shared.cpp" />
<ClCompile Include="..\..\code\win32\win_syscon.cpp" />

View file

@ -159,6 +159,9 @@
<ClInclude Include="..\..\code\qcommon\cm_public.h">
<Filter>qcommon</Filter>
</ClInclude>
<ClInclude Include="..\..\code\qcommon\crash.h">
<Filter>qcommon</Filter>
</ClInclude>
<ClInclude Include="..\..\code\qcommon\g_public.h">
<Filter>qcommon</Filter>
</ClInclude>
@ -239,6 +242,9 @@
<ClCompile Include="..\..\code\qcommon\common.cpp">
<Filter>qcommon</Filter>
</ClCompile>
<ClCompile Include="..\..\code\qcommon\crash.cpp">
<Filter>qcommon</Filter>
</ClCompile>
<ClCompile Include="..\..\code\qcommon\cvar.cpp">
<Filter>qcommon</Filter>
</ClCompile>
@ -251,6 +257,9 @@
<ClCompile Include="..\..\code\qcommon\huffman_static.cpp">
<Filter>qcommon</Filter>
</ClCompile>
<ClCompile Include="..\..\code\qcommon\json.cpp">
<Filter>qcommon</Filter>
</ClCompile>
<ClCompile Include="..\..\code\qcommon\md4.cpp">
<Filter>qcommon</Filter>
</ClCompile>
@ -311,6 +320,9 @@
<ClCompile Include="..\..\code\server\sv_world.cpp">
<Filter>server</Filter>
</ClCompile>
<ClCompile Include="..\..\code\win32\win_exception.cpp">
<Filter>win32</Filter>
</ClCompile>
<ClCompile Include="..\..\code\win32\win_main.cpp">
<Filter>win32</Filter>
</ClCompile>

View file

@ -116,7 +116,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;opengl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;Version.lib;opengl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\.bin\debug_x32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalOptions> ..\..\cnq3\code\win32\winquake.res /STACK:8388608 %(AdditionalOptions)</AdditionalOptions>
</Link>
@ -145,7 +145,7 @@ copy "..\..\..\.bin\debug_x32\cnq3-x86.pdb" "$(QUAKE3DIR)"</Command>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;opengl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;Version.lib;opengl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\.bin\debug_x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalOptions> ..\..\cnq3\code\win32\winquake.res /STACK:8388608 %(AdditionalOptions)</AdditionalOptions>
</Link>
@ -185,7 +185,7 @@ copy "..\..\..\.bin\debug_x64\cnq3-x64.pdb" "$(QUAKE3DIR)"</Command>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;opengl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;Version.lib;opengl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\.bin\release_x32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalOptions> ..\..\cnq3\code\win32\winquake.res /STACK:8388608 /OPT:REF /OPT:ICF %(AdditionalOptions)</AdditionalOptions>
</Link>
@ -224,7 +224,7 @@ copy "..\..\..\.bin\release_x32\cnq3-x86.pdb" "$(QUAKE3DIR)"</Command>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;opengl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Winmm.lib;ws2_32.lib;Version.lib;opengl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\.bin\release_x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalOptions> ..\..\cnq3\code\win32\winquake.res /STACK:8388608 /OPT:REF /OPT:ICF %(AdditionalOptions)</AdditionalOptions>
</Link>
@ -283,6 +283,7 @@ copy "..\..\..\.bin\release_x64\cnq3-x64.pdb" "$(QUAKE3DIR)"</Command>
<ClInclude Include="..\..\code\qcommon\cm_patch.h" />
<ClInclude Include="..\..\code\qcommon\cm_polylib.h" />
<ClInclude Include="..\..\code\qcommon\cm_public.h" />
<ClInclude Include="..\..\code\qcommon\crash.h" />
<ClInclude Include="..\..\code\qcommon\g_public.h" />
<ClInclude Include="..\..\code\qcommon\git.h" />
<ClInclude Include="..\..\code\qcommon\q_platform.h" />
@ -334,10 +335,12 @@ copy "..\..\..\.bin\release_x64\cnq3-x64.pdb" "$(QUAKE3DIR)"</Command>
<ClCompile Include="..\..\code\qcommon\cm_trace.cpp" />
<ClCompile Include="..\..\code\qcommon\cmd.cpp" />
<ClCompile Include="..\..\code\qcommon\common.cpp" />
<ClCompile Include="..\..\code\qcommon\crash.cpp" />
<ClCompile Include="..\..\code\qcommon\cvar.cpp" />
<ClCompile Include="..\..\code\qcommon\files.cpp" />
<ClCompile Include="..\..\code\qcommon\huffman.cpp" />
<ClCompile Include="..\..\code\qcommon\huffman_static.cpp" />
<ClCompile Include="..\..\code\qcommon\json.cpp" />
<ClCompile Include="..\..\code\qcommon\md4.cpp" />
<ClCompile Include="..\..\code\qcommon\md5.cpp" />
<ClCompile Include="..\..\code\qcommon\msg.cpp" />
@ -358,6 +361,7 @@ copy "..\..\..\.bin\release_x64\cnq3-x64.pdb" "$(QUAKE3DIR)"</Command>
<ClCompile Include="..\..\code\server\sv_net_chan.cpp" />
<ClCompile Include="..\..\code\server\sv_snapshot.cpp" />
<ClCompile Include="..\..\code\server\sv_world.cpp" />
<ClCompile Include="..\..\code\win32\win_exception.cpp" />
<ClCompile Include="..\..\code\win32\win_glimp.cpp" />
<ClCompile Include="..\..\code\win32\win_input.cpp" />
<ClCompile Include="..\..\code\win32\win_main.cpp" />

View file

@ -162,6 +162,9 @@
<ClInclude Include="..\..\code\qcommon\cm_public.h">
<Filter>qcommon</Filter>
</ClInclude>
<ClInclude Include="..\..\code\qcommon\crash.h">
<Filter>qcommon</Filter>
</ClInclude>
<ClInclude Include="..\..\code\qcommon\g_public.h">
<Filter>qcommon</Filter>
</ClInclude>
@ -311,6 +314,9 @@
<ClCompile Include="..\..\code\qcommon\common.cpp">
<Filter>qcommon</Filter>
</ClCompile>
<ClCompile Include="..\..\code\qcommon\crash.cpp">
<Filter>qcommon</Filter>
</ClCompile>
<ClCompile Include="..\..\code\qcommon\cvar.cpp">
<Filter>qcommon</Filter>
</ClCompile>
@ -323,6 +329,9 @@
<ClCompile Include="..\..\code\qcommon\huffman_static.cpp">
<Filter>qcommon</Filter>
</ClCompile>
<ClCompile Include="..\..\code\qcommon\json.cpp">
<Filter>qcommon</Filter>
</ClCompile>
<ClCompile Include="..\..\code\qcommon\md4.cpp">
<Filter>qcommon</Filter>
</ClCompile>
@ -383,6 +392,9 @@
<ClCompile Include="..\..\code\server\sv_world.cpp">
<Filter>server</Filter>
</ClCompile>
<ClCompile Include="..\..\code\win32\win_exception.cpp">
<Filter>win32</Filter>
</ClCompile>
<ClCompile Include="..\..\code\win32\win_glimp.cpp">
<Filter>win32</Filter>
</ClCompile>

View file

@ -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;
}

219
code/qcommon/crash.cpp Normal file
View file

@ -0,0 +1,219 @@
#include "crash.h"
#include "git.h"
#include "vm_local.h"
#include <sys/stat.h>
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();
}
}

29
code/qcommon/crash.h Normal file
View file

@ -0,0 +1,29 @@
#include "q_shared.h"
#include "qcommon.h"
#include <stdio.h>
// 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);

View file

@ -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;

365
code/qcommon/json.cpp Normal file
View file

@ -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];
}

View file

@ -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_

View file

@ -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 );

View file

@ -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;

View file

@ -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;

View file

@ -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" );

View file

@ -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;

View file

@ -20,9 +20,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include <signal.h>
#include <execinfo.h>
#include <backtrace.h>
#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

View file

@ -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)

View file

@ -0,0 +1,423 @@
#include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h"
#include "../qcommon/crash.h"
#include "win_local.h"
#include <DbgHelp.h>
#include <strsafe.h>
#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);
}

View file

@ -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

View file

@ -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