Permalink
Browse files

Breakpad integration: optionally generate crash dumps for Windows and…

… Linux engine as well as all NaCl VMs
  • Loading branch information...
slipher committed Dec 23, 2015
1 parent 4d900d0 commit 60cc2e57c4520eff9df4d0d9cdf096e1fdfb78cf
@@ -1,3 +1,6 @@
[submodule "src/utils/cbse"]
path = src/utils/cbse
url = https://github.com/DaemonDevelopers/CBSE-Toolchain.git
[submodule "daemon/libs/breakpad"]
path = daemon/libs/breakpad
url = https://github.com/Unvanquished/breakpad.git
@@ -24,16 +24,11 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

cmake_minimum_required (VERSION 2.8)
cmake_minimum_required (VERSION 3.0)
if (POLICY CMP0017)
cmake_policy(SET CMP0017 NEW)
endif()

# Need 2.8.12 for RPATH support
if (APPLE)
cmake_minimum_required(VERSION 2.8.12)
endif()

set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake PARENT_SCOPE)

@@ -85,6 +80,7 @@ if(NOT DAEMON_EXTERNAL_APP)
endif()
cmake_dependent_option(USE_SMP "Compile with support for running the renderer in a separate thread" 1 BUILD_CLIENT 0)
cmake_dependent_option(USE_GEOIP "Use libgeoip" 1 "BUILD_SERVER OR BUILD_CLIENT OR BUILD_TTY_CLIENT" 0)
option(USE_BREAKPAD "Use Breakpad crash logging" 0)
endif()

option(USE_LTO "Use link-time optimization for release builds" 0)
@@ -412,7 +408,8 @@ if (WIN32)
set(LIBS_BASE ${LIBS_BASE} winmm ws2_32)
elseif (NACL)
find_library(NACL_EXCEPTION nacl_exception)
set(LIBS_BASE ${LIBS_BASE} ${NACL_EXCEPTION})
find_library(NACL_MINIDUMP minidump_generator)
set(LIBS_BASE ${LIBS_BASE} ${NACL_MINIDUMP} ${NACL_EXCEPTION} )
else()
find_library(LIBM m)
if (LIBM)
@@ -542,6 +539,52 @@ if (BUILD_CLIENT OR (WIN32 AND (BUILD_TTY_CLIENT OR BUILD_SERVER)))
mark_as_advanced(SDL2MAIN_LIBRARY SDL2_LIBRARY SDL2_INCLUDE_DIR)
endif()

# Breakpad
if (USE_BREAKPAD)
add_definitions(-DUSE_BREAKPAD)
include_directories(${LIB_DIR}/breakpad/src)

if (WIN32)
add_library(srclibs-breakpad-common EXCLUDE_FROM_ALL ${BREAKPAD_COMMON_LIST})
add_library(srclibs-breakpad-crash_generation_server EXCLUDE_FROM_ALL ${BREAKPAD_CRASHGENERATIONSERVER_LIST})
add_library(srclibs-breakpad-exception_handler EXCLUDE_FROM_ALL ${BREAKPAD_EXCEPTIONHANDLER_LIST})
add_library(srclibs-breakpad-crash_generation_client EXCLUDE_FROM_ALL ${BREAKPAD_CRASHGENERATIONCLIENT_LIST})

set(BREAKPAD_LIBRARIES
srclibs-breakpad-common
srclibs-breakpad-crash_generation_server
srclibs-breakpad-exception_handler
srclibs-breakpad-crash_generation_client
)
foreach(breaklib ${BREAKPAD_LIBRARIES})
set_target_properties(${breaklib} PROPERTIES POSITION_INDEPENDENT_CODE 1 FOLDER "libs")

# Breakpad library assumes wide char versions of Windows API functions
target_compile_definitions(${breaklib} PRIVATE UNICODE)

if (CMAKE_BUILD_TYPE MATCHES Debug)
target_compile_definitions(${breaklib} PRIVATE _DEBUG)
endif()
endforeach(breaklib)

add_executable(crash_server src/engine/crash_server/crash_server_windows.cpp)
target_link_libraries(crash_server
srclibs-breakpad-crash_generation_server srclibs-breakpad-common)
target_compile_definitions(crash_server PRIVATE UNICODE)

set(LIBS_ENGINE ${LIBS_ENGINE} srclibs-breakpad-exception_handler
srclibs-breakpad-crash_generation_client srclibs-breakpad-common)

elseif (LINUX)
add_library(srclibs-breakpad EXCLUDE_FROM_ALL ${BREAKPAD_LIST})
set_target_properties(srclibs-breakpad PROPERTIES POSITION_INDEPENDENT_CODE 1 FOLDER "libs")
set(LIBS_ENGINE ${LIBS_ENGINE} srclibs-breakpad)

add_executable(crash_server src/engine/crash_server/crash_server_linux.cpp)
target_link_libraries(crash_server srclibs-breakpad)
endif()
endif()

if (BUILD_CLIENT)
add_library(srclibs-openexr EXCLUDE_FROM_ALL ${OPENEXRLIST})
set_target_properties(srclibs-openexr PROPERTIES POSITION_INDEPENDENT_CODE 1 FOLDER "libs")
@@ -102,6 +102,10 @@ macro(try_linker_flag PROP FLAG)
endif()
endmacro()

if(MINGW AND USE_BREAKPAD)
set_linker_flag("-Wl,--build-id")
endif()

if (MSVC)
set_c_cxx_flag("/MP")
set_c_cxx_flag("/fp:fast")
Submodule breakpad added at 51b008
@@ -151,6 +151,8 @@ set(ENGINELIST
${ENGINE_DIR}/framework/ConsoleField.h
${ENGINE_DIR}/framework/ConsoleHistory.cpp
${ENGINE_DIR}/framework/ConsoleHistory.h
${ENGINE_DIR}/framework/CrashDump.h
${ENGINE_DIR}/framework/CrashDump.cpp
${ENGINE_DIR}/framework/CvarSystem.cpp
${ENGINE_DIR}/framework/CvarSystem.h
${ENGINE_DIR}/framework/LogSystem.cpp
@@ -1475,7 +1475,7 @@ Cmd::CompletionResult CompleteFilename(Str::StringRef prefix, Str::StringRef roo
#ifndef BUILD_VM
namespace RawPath {

static void CreatePathTo(Str::StringRef path, std::error_code& err)
void CreatePathTo(Str::StringRef path, std::error_code& err)
{
#ifdef _WIN32
std::wstring buffer = Str::UTF8To16(path);
@@ -358,6 +358,9 @@ namespace RawPath {
File OpenAppend(Str::StringRef path, std::error_code& err = throws());
File OpenEdit(Str::StringRef path, std::error_code& err = throws());

// Ensure existence of all directories in a path
void CreatePathTo(Str::StringRef path, std::error_code& err);

// Check if a file exists
bool FileExists(Str::StringRef path);

@@ -37,7 +37,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <signal.h>
#ifdef __native_client__
#include <nacl/nacl_exception.h>
#include <nacl/nacl_minidump.h>
#include <nacl/nacl_random.h>
#include "engine/client/cg_api.h"
#else
#include <dlfcn.h>
#endif
@@ -233,15 +235,16 @@ void SetupCrashHandler()
SetUnhandledExceptionFilter(CrashHandler);
}
#elif defined(__native_client__)
static void CrashHandler(struct NaClExceptionContext *)
static void CrashHandler(const void* data, size_t n)
{
// TODO: backtrace

Sys::Error("Crashed with NaCl exception");
trap_CrashDump(static_cast<const uint8_t*>(data), n);
Sys::Error("Crashed with NaCl exception");
}

void SetupCrashHandler()
{
nacl_exception_set_handler(CrashHandler);
nacl_minidump_register_crash_handler();
nacl_minidump_set_callback(CrashHandler);
}
#else
static void CrashHandler(int sig)
@@ -286,4 +286,5 @@ void trap_S_UpdateEntityVelocity( int entityNum, const vec3_t velocit
void trap_S_SetReverb( int slotNum, const char* presetName, float ratio );
void trap_S_BeginRegistration();
void trap_S_EndRegistration();
void trap_CrashDump(const uint8_t* data, size_t size);
#endif
@@ -140,6 +140,7 @@ typedef enum cgameImport_s
CG_NOTIFY_TEAMCHANGE,
CG_PREPAREKEYUP,
CG_GETNEWS,
CG_CRASH_DUMP,

// Sound
CG_S_STARTSOUND,
@@ -344,6 +345,10 @@ typedef IPC::SyncMessage<
IPC::Message<IPC::Id<VM::QVM, CG_GETNEWS>, bool>,
IPC::Reply<bool>
> GetNewsMsg;
typedef IPC::SyncMessage <
IPC::Message<IPC::Id<VM::QVM, CG_CRASH_DUMP>, std::vector<uint8_t> >
> CrashDumpMsg;


// All Sounds

@@ -43,6 +43,7 @@ Maryland 20850 USA.
#include "framework/CommonVMServices.h"
#include "framework/CommandSystem.h"
#include "framework/CvarSystem.h"
#include "framework/CrashDump.h"

#define __(x) Trans_GettextGame(x)
#define C__(x, y) Trans_PgettextGame(x, y)
@@ -1300,6 +1301,12 @@ void CGameVM::QVMSyscall(int index, Util::Reader& reader, IPC::Channel& channel)
});
break;

case CG_CRASH_DUMP:
IPC::HandleMsg<CrashDumpMsg>(channel, std::move(reader), [this](std::vector<uint8_t> dump) {
Sys::NaclCrashDump(dump);
});
break;

// All sounds

case CG_S_REGISTERSOUND:
@@ -0,0 +1,74 @@
/*
===========================================================================
Daemon BSD Source Code
Copyright (c) 2013-2015, Daemon Developers
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Daemon developers nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL DAEMON DEVELOPERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===========================================================================
*/

#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <string>

#include "breakpad/src/client/linux/crash_generation/crash_generation_server.h"

google_breakpad::CrashGenerationServer* server;

void ShutdownServer(int) {
// Destructor waits for crash dump to finish
delete server;
}

/*
Starts a Breakpad crash generation server to enable out-of-process crash dumps.
First argument: file descriptor to listen on
Second argument: crash dump directory
Third argument: PID of engine (child process)
*/
int main(int argc, char** argv) {

if (argc != 4) {
return 1;
}

struct sigaction sa{};
sa.sa_handler = ShutdownServer;
if (sigaction(SIGTERM, &sa, nullptr) != 0) return 1;

int fd = std::stoi(argv[1]);
std::string path = argv[2];
pid_t pid = std::stoi(argv[3]);

server = new google_breakpad::CrashGenerationServer(fd, nullptr, nullptr, nullptr, nullptr, true, &path);
if (!server->Start()) {
return 1;
}

int _;
waitpid(pid, &_, 0); //wait for the game to exit
ShutdownServer(0);
return 0;
}
@@ -0,0 +1,95 @@
/*
===========================================================================
Daemon BSD Source Code
Copyright (c) 2013-2015, Daemon Developers
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Daemon developers nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL DAEMON DEVELOPERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===========================================================================
*/


#include <string>
#include <windows.h>
#include "breakpad/src/client/windows/crash_generation/crash_generation_server.h"


/*
Starts a Breakpad crash generation server to enable out-of-process crash dumps.
First argument: pipe name
Second argument: crash dump directory
Third argument: engine process ID
*/
int main(int argc, char** argv)
{
if (argc != 4)
{
return 1;
}

DWORD pid;
try {
pid = std::stoul(argv[3]);
} catch (...) {
return 1;
}

HANDLE parent = OpenProcess(SYNCHRONIZE, FALSE, pid);
if (!parent) {
return 1;
}

std::wstring pipeName, dumpPath;
// convert args from UTF-8 to UTF-16...
int len0 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, argv[1], -1, NULL, 0); // length includes null char
int len1 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, argv[2], -1, NULL, 0);
if (!len0 || !len1) return 1;
pipeName.resize(len0 - 1);
dumpPath.resize(len1 - 1);
if (MultiByteToWideChar(CP_UTF8, 0, argv[1], strlen(argv[1]), &pipeName[0], len0 - 1) != len0 - 1) return 1;
if (MultiByteToWideChar(CP_UTF8, 0, argv[1], strlen(argv[2]), &dumpPath[0], len1 - 1) != len1 - 1) return 1;

google_breakpad::CrashGenerationServer* server = new google_breakpad::CrashGenerationServer(
pipeName,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
true,
&dumpPath);

if (!server->Start()) {
return 1;
}

WaitForSingleObject(parent, INFINITE); // wait for application to terminate

delete server; // destructor waits for any dump requests to finish

return 0;
}
Oops, something went wrong.

0 comments on commit 60cc2e5

Please sign in to comment.