carb/crashreporter/CrashReporterUtils.h
File members: carb/crashreporter/CrashReporterUtils.h
// Copyright (c) 2019-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "../Framework.h"
#include "../InterfaceUtils.h"
#include "../logging/Log.h"
#include "../settings/ISettings.h"
#include "ICrashReporter.h"
#if CARB_PLATFORM_WINDOWS && !defined(_DLL)
# include "../CarbWindows.h"
# include "../extras/Library.h"
#elif CARB_PLATFORM_LINUX
# include <argz.h>
#endif
#include <signal.h>
#include <string.h>
#include <future>
#include <string>
#include <fstream>
#include <ios>
CARB_WEAKLINK carb::crashreporter::ICrashReporter* g_carbCrashReporter;
#ifdef DOXYGEN_BUILD
# define CARB_CRASH_REPORTER_GLOBALS()
#else
// only install the signal handler for modules that have been statically linked to the
// Windows CRT (OVCC-1379). This is done because plugins that statically link to the
// CRT have their own copies of the signal handlers table and the crash reporter is
// unable to directly manipulate those. By setting the signal handler here in the
// context of the statically linked plugin, we provide a way to relay that abort
// signal to the crash reporter.
# if CARB_PLATFORM_WINDOWS && !defined(_DLL)
# if !CARB_COMPILER_MSC
static_assert(false, "Unsupported compiler!");
# endif
# define CARB_CRASH_REPORTER_GLOBALS() \
bool g_carbSignalHandlerInstalled = carb::crashreporter::detail::installSignalHandler();
namespace carb
{
namespace crashreporter
{
namespace detail
{
inline bool installSignalHandler()
{
using SignalHandlerFn = void (*)(int);
static bool disableHandler = []() {
WCHAR envVarValue[32] = { 0 };
return GetEnvironmentVariableW(L"CARB_DISABLE_ABORT_HANDLER", envVarValue, CARB_COUNTOF32(envVarValue)) != 0 &&
envVarValue[0] == '1' && envVarValue[1] == '\0';
}();
if (disableHandler)
return false;
static SignalHandlerFn handler = []() -> SignalHandlerFn {
SignalHandlerFn fn;
carb::extras::LibraryHandle handle = carb::extras::loadLibrary(
"carb", carb::extras::fLibFlagMakeFullLibName | carb::extras::fLibFlagLoadExisting);
if (handle == carb::extras::kInvalidLibraryHandle)
return nullptr;
fn = carb::extras::getLibrarySymbol<SignalHandlerFn>(handle, "carbSignalHandler");
if (fn == nullptr)
{
carb::extras::unloadLibrary(handle);
return nullptr;
}
return fn;
}();
if (handler == nullptr)
return false;
// install the signal handler for this thread in this module. Since signals on Windows
// are a bonus feature and rarely used, we don't care about preserving the previous
// signal handler.
signal(SIGABRT, handler);
return true;
}
} // namespace detail
} // namespace crashreporter
} // namespace carb
# else
# define CARB_CRASH_REPORTER_GLOBALS()
# endif
#endif
namespace carb
{
namespace crashreporter
{
constexpr uintptr_t kBaseMagicSignature = 0xc7a547e907137700ull;
constexpr uintptr_t kMagicSignature = kBaseMagicSignature;
#if CARB_PLATFORM_LINUX || defined(DOXYGEN_BUILD)
static int kExternalTerminationSignal = SIGRTMAX - 1;
#elif CARB_PLATFORM_MACOS
constexpr int kExternalTerminationSignal = SIGUSR2;
#endif
inline void registerCrashReporterForClient()
{
g_carbCrashReporter = getFramework()->tryAcquireInterface<ICrashReporter>();
}
inline void deregisterCrashReporterForClient()
{
if (g_carbCrashReporter)
{
getFramework()->releaseInterface(g_carbCrashReporter);
g_carbCrashReporter = nullptr;
}
}
inline std::future<void> sendAndRemoveLeftOverDumpsAsync()
{
std::unique_ptr<std::promise<void>> sentPromise(new std::promise<void>());
std::future<void> sentFuture(sentPromise->get_future());
if (g_carbCrashReporter)
{
const auto finishCallback = [](void* promisePtr) {
auto sentPromise = reinterpret_cast<std::promise<void>*>(promisePtr);
sentPromise->set_value();
delete sentPromise;
};
g_carbCrashReporter->sendAndRemoveLeftOverDumpsAsync(finishCallback, sentPromise.release());
}
else
{
CARB_LOG_WARN("No crash reporter present, dumps uploading isn't available.");
sentPromise->set_value();
}
return sentFuture;
}
namespace detail
{
inline std::string sanitizeExtraCrashFileKey(const char* keyName)
{
std::string key = keyName;
// sanitize the key name so that it contains only database friendly characters.
for (auto& c : key)
{
if (c <= ' ' || c >= 127 || strchr("\"'\\/,#$%^&*()!~`[]{}|<>?;:=+.\t\b\n\r ", c) != nullptr)
{
c = '_';
}
}
return key;
}
} // namespace detail
template <typename T>
inline bool addCrashMetadata(const char* keyName, T value)
{
return addCrashMetadata(keyName, std::to_string(value).c_str());
}
template <>
inline bool addCrashMetadata(const char* keyName, const char* value)
{
carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
std::string key;
if (settings == nullptr)
return false;
key = detail::sanitizeExtraCrashFileKey(keyName);
settings->setString((std::string("/crashreporter/data/") + key).c_str(), value);
return true;
}
inline const char* getCrashMetadataValue(const char* keyName)
{
carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
std::string key;
if (settings == nullptr)
return nullptr;
key = "/crashreporter/data/" + detail::sanitizeExtraCrashFileKey(keyName);
return settings->getStringBuffer(key.c_str());
}
inline bool addExtraCrashFile(const char* keyName, const char* filename)
{
carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
std::string key;
if (settings == nullptr)
return false;
// sanitize the key name so that it contains only database friendly characters.
key = detail::sanitizeExtraCrashFileKey(keyName);
settings->setString(("/crashreporter/files/" + key).c_str(), filename);
return true;
}
inline bool isExtraCrashFileKeyUsed(const char* keyName)
{
carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
std::string key;
if (settings == nullptr)
return false;
// sanitize the key name so that it contains only database friendly characters.
key = detail::sanitizeExtraCrashFileKey(keyName);
return settings->isAccessibleAs(carb::dictionary::ItemType::eString, ("/crashreporter/files/" + key).c_str());
}
inline std::string getCommandLineArgsAsString()
{
std::string out;
#if CARB_PLATFORM_WINDOWS
out = carb::extras::convertWideToUtf8(GetCommandLineW());
#elif CARB_POSIX
auto escapeString = [](const char* str) -> std::string {
std::string out;
const char* last = str;
for (size_t i = 0; str[i] != '\0'; i++)
{
if (str[i] == ' ' || str[i] == '\"' || str[i] == '\\')
{
out += std::string(last, &str[i] - last) + "\\";
last = &str[i];
}
}
// add any remaining chunk of the string.
out += last;
return out;
};
auto getCmdLineString = [&](size_t argc, char* argv[]) -> std::string {
std::string out;
for (size_t i = 0; i < argc; i++)
{
out += escapeString(argv[i]) + std::string(" ");
}
out.pop_back();
return out;
};
# if CARB_PLATFORM_LINUX
std::string cmdLine;
std::getline(std::fstream("/proc/self/cmdline", std::ios::in), cmdLine);
size_t argc = argz_count(cmdLine.c_str(), cmdLine.size());
auto argv = std::make_unique<char*[]>(argc + 1);
argz_extract(cmdLine.c_str(), cmdLine.size(), argv.get());
out = getCmdLineString(argc, argv.get());
# elif CARB_PLATFORM_MACOS
int argc = *_NSGetArgc();
char** argv = *_NSGetArgv();
out = getCmdLineString(argc, argv);
# endif
#else
CARB_UNSUPPORTED_PLATFORM();
#endif
return out;
}
} // namespace crashreporter
} // namespace carb