mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-03 15:26:07 +00:00
Bug 875562 - Part 8: Write crash events for main process crashes; r=bsmedberg
This patch makes the crash reporter crash event directory aware and writes main process crash event files at crash time. --HG-- extra : rebase_source : 1d6a817ae401230b3fe8fcd236468ceb3e309a10
This commit is contained in:
parent
bdadbd3fbe
commit
a0a4ade497
@ -93,6 +93,7 @@ try {
|
|||||||
let (crashReporter =
|
let (crashReporter =
|
||||||
Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
|
Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
|
||||||
.getService(Components.interfaces.nsICrashReporter)) {
|
.getService(Components.interfaces.nsICrashReporter)) {
|
||||||
|
crashReporter.UpdateCrashEventsDir();
|
||||||
crashReporter.minidumpPath = do_get_minidumpdir();
|
crashReporter.minidumpPath = do_get_minidumpdir();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1184,6 +1185,15 @@ function do_get_profile() {
|
|||||||
let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
|
let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
|
||||||
getService(Components.interfaces.nsIObserverService);
|
getService(Components.interfaces.nsIObserverService);
|
||||||
|
|
||||||
|
// We need to update the crash events directory when the profile changes.
|
||||||
|
if (runningInParent &&
|
||||||
|
"@mozilla.org/toolkit/crash-reporter;1" in Components.classes) {
|
||||||
|
let crashReporter =
|
||||||
|
Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
|
||||||
|
.getService(Components.interfaces.nsICrashReporter);
|
||||||
|
crashReporter.UpdateCrashEventsDir();
|
||||||
|
}
|
||||||
|
|
||||||
if (!_profileInitialized) {
|
if (!_profileInitialized) {
|
||||||
obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile");
|
obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile");
|
||||||
_profileInitialized = true;
|
_profileInitialized = true;
|
||||||
|
@ -151,11 +151,16 @@ static const XP_CHAR dumpFileExtension[] = {'.', 'd', 'm', 'p',
|
|||||||
static const XP_CHAR extraFileExtension[] = {'.', 'e', 'x', 't',
|
static const XP_CHAR extraFileExtension[] = {'.', 'e', 'x', 't',
|
||||||
'r', 'a', '\0'}; // .extra
|
'r', 'a', '\0'}; // .extra
|
||||||
|
|
||||||
|
static const char kCrashMainID[] = "crash.main.1\n";
|
||||||
|
|
||||||
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
|
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
|
||||||
|
|
||||||
static XP_CHAR* pendingDirectory;
|
static XP_CHAR* pendingDirectory;
|
||||||
static XP_CHAR* crashReporterPath;
|
static XP_CHAR* crashReporterPath;
|
||||||
|
|
||||||
|
// Where crash events should go.
|
||||||
|
static XP_CHAR* eventsDirectory;
|
||||||
|
|
||||||
// If this is false, we don't launch the crash reporter
|
// If this is false, we don't launch the crash reporter
|
||||||
static bool doReport = true;
|
static bool doReport = true;
|
||||||
|
|
||||||
@ -557,6 +562,63 @@ bool MinidumpCallback(
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write crash event file.
|
||||||
|
|
||||||
|
// Minidump IDs are UUIDs (36) + NULL.
|
||||||
|
static char id_ascii[37];
|
||||||
|
#ifdef XP_LINUX
|
||||||
|
const char * index = strrchr(descriptor.path(), '/');
|
||||||
|
MOZ_ASSERT(index);
|
||||||
|
MOZ_ASSERT(strlen(index) == 1 + 36 + 4); // "/" + UUID + ".dmp"
|
||||||
|
for (uint32_t i = 0; i < 36; i++) {
|
||||||
|
id_ascii[i] = *(index + 1 + i);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
MOZ_ASSERT(XP_STRLEN(minidump_id) == 36);
|
||||||
|
for (uint32_t i = 0; i < 36; i++) {
|
||||||
|
id_ascii[i] = *((char *)(minidump_id + i));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (eventsDirectory) {
|
||||||
|
static XP_CHAR crashEventPath[XP_PATH_MAX];
|
||||||
|
int size = XP_PATH_MAX;
|
||||||
|
XP_CHAR* p;
|
||||||
|
p = Concat(crashEventPath, eventsDirectory, &size);
|
||||||
|
p = Concat(p, XP_PATH_SEPARATOR, &size);
|
||||||
|
#ifdef XP_LINUX
|
||||||
|
p = Concat(p, id_ascii, &size);
|
||||||
|
#else
|
||||||
|
p = Concat(p, minidump_id, &size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(XP_WIN32)
|
||||||
|
HANDLE hFile = CreateFile(crashEventPath, GENERIC_WRITE, 0,
|
||||||
|
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
|
||||||
|
nullptr);
|
||||||
|
if (hFile != INVALID_HANDLE_VALUE) {
|
||||||
|
DWORD nBytes;
|
||||||
|
WriteFile(hFile, kCrashMainID, sizeof(kCrashMainID) - 1, &nBytes,
|
||||||
|
nullptr);
|
||||||
|
WriteFile(hFile, crashTimeString, crashTimeStringLen, &nBytes, nullptr);
|
||||||
|
WriteFile(hFile, "\n", 1, &nBytes, nullptr);
|
||||||
|
WriteFile(hFile, id_ascii, strlen(id_ascii), &nBytes, nullptr);
|
||||||
|
CloseHandle(hFile);
|
||||||
|
}
|
||||||
|
#elif defined(XP_UNIX)
|
||||||
|
int fd = sys_open(crashEventPath,
|
||||||
|
O_WRONLY | O_CREAT | O_TRUNC,
|
||||||
|
0600);
|
||||||
|
if (fd != -1) {
|
||||||
|
unused << sys_write(fd, kCrashMainID, sizeof(kCrashMainID) - 1);
|
||||||
|
unused << sys_write(fd, crashTimeString, crashTimeStringLen);
|
||||||
|
unused << sys_write(fd, "\n", 1);
|
||||||
|
unused << sys_write(fd, id_ascii, strlen(id_ascii));
|
||||||
|
sys_close(fd);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(XP_WIN32)
|
#if defined(XP_WIN32)
|
||||||
if (!crashReporterAPIData->IsEmpty()) {
|
if (!crashReporterAPIData->IsEmpty()) {
|
||||||
// write out API data
|
// write out API data
|
||||||
@ -1271,6 +1333,19 @@ InitInstallTime(nsACString& aInstallTime)
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure a directory exists and create it if missing.
|
||||||
|
static nsresult
|
||||||
|
EnsureDirectoryExists(nsIFile* dir)
|
||||||
|
{
|
||||||
|
nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
|
||||||
|
|
||||||
|
if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
// Annotate the crash report with a Unique User ID and time
|
// Annotate the crash report with a Unique User ID and time
|
||||||
// since install. Also do some prep work for recording
|
// since install. Also do some prep work for recording
|
||||||
// time since last crash, which must be calculated at
|
// time since last crash, which must be calculated at
|
||||||
@ -1286,14 +1361,7 @@ nsresult SetupExtraData(nsIFile* aAppDataDirectory,
|
|||||||
rv = dataDirectory->AppendNative(NS_LITERAL_CSTRING("Crash Reports"));
|
rv = dataDirectory->AppendNative(NS_LITERAL_CSTRING("Crash Reports"));
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
bool exists;
|
EnsureDirectoryExists(dataDirectory);
|
||||||
rv = dataDirectory->Exists(&exists);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
|
|
||||||
if (!exists) {
|
|
||||||
rv = dataDirectory->Create(nsIFile::DIRECTORY_TYPE, 0700);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(XP_WIN32)
|
#if defined(XP_WIN32)
|
||||||
nsAutoString dataDirEnv(NS_LITERAL_STRING("MOZ_CRASHREPORTER_DATA_DIRECTORY="));
|
nsAutoString dataDirEnv(NS_LITERAL_STRING("MOZ_CRASHREPORTER_DATA_DIRECTORY="));
|
||||||
@ -1439,6 +1507,11 @@ nsresult UnsetExceptionHandler()
|
|||||||
crashReporterPath = nullptr;
|
crashReporterPath = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventsDirectory) {
|
||||||
|
NS_Free(eventsDirectory);
|
||||||
|
eventsDirectory = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef XP_MACOSX
|
#ifdef XP_MACOSX
|
||||||
posix_spawnattr_destroy(&spawnattr);
|
posix_spawnattr_destroy(&spawnattr);
|
||||||
#endif
|
#endif
|
||||||
@ -1996,6 +2069,68 @@ nsresult SetSubmitReports(bool aSubmitReports)
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UpdateCrashEventsDir()
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIFile> eventsDir;
|
||||||
|
|
||||||
|
// We prefer the following locations in order:
|
||||||
|
//
|
||||||
|
// 1. If environment variable is present, use it. We don't expect
|
||||||
|
// the environment variable except for tests and other atypical setups.
|
||||||
|
// 2. Inside the profile directory.
|
||||||
|
// 3. Inside the user application data directory (no profile available).
|
||||||
|
// 4. A temporary directory (setup likely is invalid / application is buggy).
|
||||||
|
const char *env = PR_GetEnv("CRASHES_EVENTS_DIR");
|
||||||
|
if (env) {
|
||||||
|
eventsDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
|
||||||
|
if (!eventsDir) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
eventsDir->InitWithNativePath(nsDependentCString(env));
|
||||||
|
EnsureDirectoryExists(eventsDir);
|
||||||
|
} else {
|
||||||
|
nsresult rv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(eventsDir));
|
||||||
|
if (NS_SUCCEEDED(rv)) {
|
||||||
|
eventsDir->Append(NS_LITERAL_STRING("crashes"));
|
||||||
|
EnsureDirectoryExists(eventsDir);
|
||||||
|
eventsDir->Append(NS_LITERAL_STRING("events"));
|
||||||
|
EnsureDirectoryExists(eventsDir);
|
||||||
|
} else {
|
||||||
|
rv = NS_GetSpecialDirectory("UAppData", getter_AddRefs(eventsDir));
|
||||||
|
if (NS_SUCCEEDED(rv)) {
|
||||||
|
eventsDir->Append(NS_LITERAL_STRING("Crash Reports"));
|
||||||
|
EnsureDirectoryExists(eventsDir);
|
||||||
|
eventsDir->Append(NS_LITERAL_STRING("events"));
|
||||||
|
EnsureDirectoryExists(eventsDir);
|
||||||
|
} else {
|
||||||
|
NS_WARNING("Couldn't get the user appdata directory. Crash events may not be produced.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef XP_WIN
|
||||||
|
nsString path;
|
||||||
|
eventsDir->GetPath(path);
|
||||||
|
eventsDirectory = reinterpret_cast<wchar_t*>(ToNewUnicode(path));
|
||||||
|
#else
|
||||||
|
nsCString path;
|
||||||
|
eventsDir->GetNativePath(path);
|
||||||
|
eventsDirectory = ToNewCString(path);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetCrashEventsDir(nsAString& aPath)
|
||||||
|
{
|
||||||
|
if (!eventsDirectory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
aPath = CONVERT_XP_CHAR_TO_UTF16(eventsDirectory);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
FindPendingDir()
|
FindPendingDir()
|
||||||
{
|
{
|
||||||
@ -2406,6 +2541,7 @@ OOPInit()
|
|||||||
dumpMapLock = new Mutex("CrashReporter::dumpMapLock");
|
dumpMapLock = new Mutex("CrashReporter::dumpMapLock");
|
||||||
|
|
||||||
FindPendingDir();
|
FindPendingDir();
|
||||||
|
UpdateCrashEventsDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -33,6 +33,20 @@ class nsCStringHashKey;
|
|||||||
namespace CrashReporter {
|
namespace CrashReporter {
|
||||||
nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force=false);
|
nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force=false);
|
||||||
nsresult UnsetExceptionHandler();
|
nsresult UnsetExceptionHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the crash reporter to recalculate where crash events files should go.
|
||||||
|
*
|
||||||
|
* This should be called during crash reporter initialization and when a
|
||||||
|
* profile is activated or deactivated.
|
||||||
|
*/
|
||||||
|
void UpdateCrashEventsDir();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path where crash event files should be written.
|
||||||
|
*/
|
||||||
|
bool GetCrashEventsDir(nsAString& aPath);
|
||||||
|
|
||||||
bool GetEnabled();
|
bool GetEnabled();
|
||||||
bool GetServerURL(nsACString& aServerURL);
|
bool GetServerURL(nsACString& aServerURL);
|
||||||
nsresult SetServerURL(const nsACString& aServerURL);
|
nsresult SetServerURL(const nsACString& aServerURL);
|
||||||
|
@ -12,6 +12,9 @@ let crashReporter =
|
|||||||
Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
|
Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
|
||||||
.getService(Components.interfaces.nsICrashReporter);
|
.getService(Components.interfaces.nsICrashReporter);
|
||||||
|
|
||||||
|
// We need to call this or crash events go in an undefined location.
|
||||||
|
crashReporter.UpdateCrashEventsDir();
|
||||||
|
|
||||||
// Setting the minidump path is not allowed in content processes
|
// Setting the minidump path is not allowed in content processes
|
||||||
let processType = Components.classes["@mozilla.org/xre/runtime;1"].
|
let processType = Components.classes["@mozilla.org/xre/runtime;1"].
|
||||||
getService(Components.interfaces.nsIXULRuntime).processType;
|
getService(Components.interfaces.nsIXULRuntime).processType;
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||||
|
|
||||||
|
function getEventDir() {
|
||||||
|
return OS.Path.join(do_get_tempdir().path, "crash-events");
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Run an xpcshell subprocess and crash it.
|
* Run an xpcshell subprocess and crash it.
|
||||||
*
|
*
|
||||||
@ -55,10 +61,25 @@ function do_crash(setup, callback, canReturnZero)
|
|||||||
args.push('-e', setup);
|
args.push('-e', setup);
|
||||||
}
|
}
|
||||||
args.push('-f', tailfile.path);
|
args.push('-f', tailfile.path);
|
||||||
|
|
||||||
|
let env = Components.classes["@mozilla.org/process/environment;1"]
|
||||||
|
.getService(Components.interfaces.nsIEnvironment);
|
||||||
|
|
||||||
|
let crashD = do_get_tempdir();
|
||||||
|
crashD.append("crash-events");
|
||||||
|
if (!crashD.exists()) {
|
||||||
|
crashD.create(crashD.DIRECTORY_TYPE, 0700);
|
||||||
|
}
|
||||||
|
|
||||||
|
env.set("CRASHES_EVENTS_DIR", crashD.path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
process.run(true, args, args.length);
|
process.run(true, args, args.length);
|
||||||
}
|
}
|
||||||
catch(ex) {} // on Windows we exit with a -1 status when crashing.
|
catch(ex) {} // on Windows we exit with a -1 status when crashing.
|
||||||
|
finally {
|
||||||
|
env.set("CRASHES_EVENTS_DIR", "");
|
||||||
|
}
|
||||||
|
|
||||||
if (!canReturnZero) {
|
if (!canReturnZero) {
|
||||||
// should exit with an error (should have crashed)
|
// should exit with an error (should have crashed)
|
||||||
|
42
toolkit/crashreporter/test/unit/test_event_files.js
Normal file
42
toolkit/crashreporter/test/unit/test_event_files.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {utils: Cu} = Components;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||||
|
Cu.import("resource://testing-common/AppData.jsm", this);
|
||||||
|
|
||||||
|
function run_test() {
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(function* test_setup() {
|
||||||
|
do_get_profile();
|
||||||
|
yield makeFakeAppDir();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_main_process_crash() {
|
||||||
|
let cm = Services.crashmanager;
|
||||||
|
Assert.ok(cm, "CrashManager available.");
|
||||||
|
|
||||||
|
let basename;
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
do_crash("crashType = CrashTestUtils.CRASH_RUNTIMEABORT;",
|
||||||
|
(minidump, extra) => {
|
||||||
|
basename = minidump.leafName;
|
||||||
|
cm._eventsDirs = [getEventDir()];
|
||||||
|
cm.aggregateEventsFiles().then(deferred.resolve, deferred.reject);
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
|
let count = yield deferred.promise;
|
||||||
|
Assert.equal(count, 1, "A single crash event file was seen.");
|
||||||
|
let crashes = yield cm.getCrashes();
|
||||||
|
Assert.equal(crashes.length, 1);
|
||||||
|
let crash = crashes[0];
|
||||||
|
Assert.ok(crash.isMainProcessCrash);
|
||||||
|
Assert.equal(crash.id + ".dmp", basename, "ID recorded properly");
|
||||||
|
});
|
@ -26,3 +26,4 @@ run-if = os == 'win' || os == 'linux'
|
|||||||
skip-if = os=='linux' && bits==32
|
skip-if = os=='linux' && bits==32
|
||||||
|
|
||||||
[test_crash_AsyncShutdown.js]
|
[test_crash_AsyncShutdown.js]
|
||||||
|
[test_event_files.js]
|
||||||
|
@ -1110,6 +1110,13 @@ nsXULAppInfo::SetSubmitReports(bool aEnabled)
|
|||||||
return CrashReporter::SetSubmitReports(aEnabled);
|
return CrashReporter::SetSubmitReports(aEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsXULAppInfo::UpdateCrashEventsDir()
|
||||||
|
{
|
||||||
|
CrashReporter::UpdateCrashEventsDir();
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const nsXULAppInfo kAppInfo;
|
static const nsXULAppInfo kAppInfo;
|
||||||
@ -2991,6 +2998,7 @@ XREMain::XRE_mainInit(bool* aExitFlag)
|
|||||||
if ((mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) &&
|
if ((mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) &&
|
||||||
NS_SUCCEEDED(
|
NS_SUCCEEDED(
|
||||||
CrashReporter::SetExceptionHandler(mAppData->xreDirectory))) {
|
CrashReporter::SetExceptionHandler(mAppData->xreDirectory))) {
|
||||||
|
CrashReporter::UpdateCrashEventsDir();
|
||||||
if (mAppData->crashReporterURL)
|
if (mAppData->crashReporterURL)
|
||||||
CrashReporter::SetServerURL(nsDependentCString(mAppData->crashReporterURL));
|
CrashReporter::SetServerURL(nsDependentCString(mAppData->crashReporterURL));
|
||||||
|
|
||||||
@ -3644,6 +3652,8 @@ XREMain::XRE_mainStartup(bool* aExitFlag)
|
|||||||
#ifdef MOZ_CRASHREPORTER
|
#ifdef MOZ_CRASHREPORTER
|
||||||
if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER)
|
if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER)
|
||||||
MakeOrSetMinidumpPath(mProfD);
|
MakeOrSetMinidumpPath(mProfD);
|
||||||
|
|
||||||
|
CrashReporter::UpdateCrashEventsDir();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
nsAutoCString version;
|
nsAutoCString version;
|
||||||
|
@ -109,4 +109,12 @@ interface nsICrashReporter : nsISupports
|
|||||||
* User preference for submitting crash reports.
|
* User preference for submitting crash reports.
|
||||||
*/
|
*/
|
||||||
attribute boolean submitReports;
|
attribute boolean submitReports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cause the crash reporter to re-evaluate where crash events should go.
|
||||||
|
*
|
||||||
|
* This should be called during application startup and whenever profiles
|
||||||
|
* change.
|
||||||
|
*/
|
||||||
|
void UpdateCrashEventsDir();
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user