Bug 1620998 - Out-of-process crash generation for child processes r=afranchuk,geckoview-reviewers,glandium,browser-installer-reviewers,nalexander,owlish

This implements the crash helper service used to move child process crash
report generation out of the main process and into its own process. This is
implemented as a separate executable that is launched on startup by the main
process on the desktop platforms and as a service hosted by a separate process
on Android.

One limitation of the current code is that the crash helper process needs to
be running before we can start setting exception handlers in child processes.
This limitation is due to how Breakpad exception handlers register themselves
with the crash generator and prevents us from lazily starting the helper (or
restarting it on Android).

IPC with the crash helper is implemented using Unix sockets on Linux and macOS
with the former using sequential packets and the latter using stream sockets.
On Windows we use named pipes. In all cases the choice of IPC was dictated both
by the requirement to eventually talk directly to child processes from within
the sandbox, and to external processes in case of Windows as the Windows
Error Reporting exception handler must be able to reach out to the helper from
within a restricted context. These particular requirements are not used yet but
will be as we move more logic out of the main process logic.

Differential Revision: https://phabricator.services.mozilla.com/D231083
This commit is contained in:
Gabriele Svelto
2025-02-12 14:06:10 +00:00
parent 380f943c54
commit e2f7a6a1c8
56 changed files with 2489 additions and 639 deletions

59
Cargo.lock generated
View File

@@ -1087,6 +1087,54 @@ dependencies = [
"mach2",
]
[[package]]
name = "crash_helper_client"
version = "0.1.0"
dependencies = [
"anyhow",
"crash_helper_common",
"nix 0.29.0",
"num-derive",
"num-traits",
"windows-sys",
]
[[package]]
name = "crash_helper_common"
version = "0.1.0"
dependencies = [
"nix 0.29.0",
"num-derive",
"num-traits",
"thiserror 2.0.9",
"windows-sys",
]
[[package]]
name = "crash_helper_server"
version = "0.1.0"
dependencies = [
"anyhow",
"cc",
"cfg-if",
"crash_helper_common",
"env_logger",
"linked-hash-map",
"log",
"mozannotation_server",
"mozbuild",
"mozilla-central-workspace-hack",
"nix 0.29.0",
"num-derive",
"num-traits",
"once_cell",
"rust_minidump_writer_linux",
"thiserror 2.0.9",
"uuid",
"windows-sys",
"yaml-rust",
]
[[package]]
name = "crashreporter"
version = "1.0.0"
@@ -2403,6 +2451,7 @@ dependencies = [
"cert_storage",
"chardetng_c",
"cose-c",
"crash_helper_client",
"crypto_hash",
"cubeb-coreaudio",
"cubeb-pulse",
@@ -4174,11 +4223,10 @@ version = "0.1.0"
name = "mozannotation_server"
version = "0.1.0"
dependencies = [
"memoffset 0.8.999",
"memoffset 0.9.0",
"mozannotation_client",
"process_reader",
"thin-vec",
"thiserror 1.999.999",
"thiserror 2.0.9",
]
[[package]]
@@ -4340,9 +4388,9 @@ dependencies = [
name = "mozwer_s"
version = "0.1.0"
dependencies = [
"crash_helper_client",
"libc",
"mozilla-central-workspace-hack",
"process_reader",
"rust-ini",
"serde",
"serde_json",
@@ -4565,6 +4613,7 @@ dependencies = [
"cfg-if",
"cfg_aliases",
"libc",
"memoffset 0.9.0",
]
[[package]]
@@ -5058,7 +5107,7 @@ dependencies = [
"memoffset 0.9.0",
"mozilla-central-workspace-hack",
"scroll",
"thiserror 1.999.999",
"thiserror 2.0.9",
"windows-sys",
]

View File

@@ -17,9 +17,10 @@ members = [
"testing/geckodriver",
"toolkit/components/uniffi-bindgen-gecko-js",
"toolkit/crashreporter/client/app",
"toolkit/crashreporter/crash_helper_server",
"toolkit/crashreporter/crash_helper_client",
"toolkit/crashreporter/minidump-analyzer/android/export",
"toolkit/crashreporter/mozwer-rust",
"toolkit/crashreporter/rust_minidump_writer_linux",
"toolkit/library/gtest/rust",
"toolkit/library/rust/",
]

View File

@@ -13,6 +13,9 @@
#if defined(MOZ_ASAN) || defined(MOZ_TSAN) || defined(FUZZING)
/llvm-symbolizer
#endif
#if defined(MOZ_CRASHREPORTER)
/crashhelper
#endif
/nmhproxy
/pingsender
/pk12util

View File

@@ -396,6 +396,12 @@ bin/libfreebl_64int_3.so
#endif
#endif
; [ crashhelper ]
;
#ifdef MOZ_CRASHREPORTER
@BINPATH@/crashhelper@BIN_SUFFIX@
#endif
; [ Ping Sender ]
;
@BINPATH@/pingsender@BIN_SUFFIX@

View File

@@ -1586,6 +1586,7 @@ ${RemoveDefaultBrowserAgentShortcut}
Push "nssdbm3.dll"
Push "mozsqlite3.dll"
Push "xpcom.dll"
Push "crashhelper.exe"
Push "crashreporter.exe"
Push "default-browser-agent.exe"
Push "nmhproxy.exe"

View File

@@ -264,6 +264,7 @@ optional = true
[features]
builtins-static = ["dep:bindgen", "dep:bitflags", "dep:itertools", "dep:memchr", "dep:nom", "dep:regex", "dep:smallvec"]
crash_helper_server = ["dep:cc", "dep:num-traits", "dep:log", "dep:once_cell", "dep:uuid", "dep:windows-sys"]
crashreporter = ["dep:allocator-api2", "dep:arrayvec", "dep:bindgen", "dep:bitflags", "dep:byteorder", "dep:bytes", "dep:cc", "dep:chrono", "dep:crossbeam-utils", "dep:env_logger", "dep:flate2", "dep:fluent", "dep:fluent-langneg", "dep:fnv", "dep:form_urlencoded", "dep:futures-channel", "dep:futures-core", "dep:futures-executor", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:hex", "dep:hyper", "dep:icu_locid", "dep:icu_properties", "dep:idna", "dep:indexmap", "dep:itertools", "dep:lmdb-rkv-sys", "dep:log", "dep:memchr", "dep:mio", "dep:nom", "dep:num-integer", "dep:num-traits", "dep:object", "dep:once_cell", "dep:percent-encoding", "dep:phf", "dep:regex", "dep:rkv", "dep:scroll", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:stable_deref_trait", "dep:time", "dep:time-macros", "dep:tinystr", "dep:tokio", "dep:tokio-util", "dep:toml", "dep:tracing", "dep:unic-langid", "dep:unic-langid-impl", "dep:unicode-bidi", "dep:uniffi", "dep:url", "dep:uuid", "dep:windows-sys", "dep:yoke", "dep:zerocopy", "dep:zerofrom", "dep:zerovec", "dep:zip"]
geckodriver = ["dep:allocator-api2", "dep:bitflags", "dep:byteorder", "dep:bytes", "dep:cc", "dep:chrono", "dep:clap", "dep:crossbeam-utils", "dep:flate2", "dep:fnv", "dep:form_urlencoded", "dep:futures-channel", "dep:futures-core", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:hyper", "dep:icu_locid", "dep:icu_properties", "dep:idna", "dep:indexmap", "dep:log", "dep:memchr", "dep:mio", "dep:num-integer", "dep:num-traits", "dep:once_cell", "dep:percent-encoding", "dep:regex", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:stable_deref_trait", "dep:strsim", "dep:time", "dep:time-macros", "dep:tinystr", "dep:tokio", "dep:tokio-util", "dep:tracing", "dep:unicode-bidi", "dep:url", "dep:uuid", "dep:windows-sys", "dep:xml-rs", "dep:yoke", "dep:zerocopy", "dep:zerofrom", "dep:zerovec", "dep:zip"]
gkrust = ["dep:allocator-api2", "dep:arrayvec", "dep:bindgen", "dep:bitflags", "dep:byteorder", "dep:bytes", "dep:cc", "dep:chrono", "dep:core-foundation-sys", "dep:crossbeam-utils", "dep:env_logger", "dep:flate2", "dep:fluent", "dep:fluent-langneg", "dep:fnv", "dep:form_urlencoded", "dep:futures", "dep:futures-channel", "dep:futures-core", "dep:futures-executor", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:hex", "dep:icu_locid", "dep:icu_properties", "dep:idna", "dep:indexmap", "dep:itertools", "dep:lmdb-rkv-sys", "dep:log", "dep:memchr", "dep:nom", "dep:num-integer", "dep:num-traits", "dep:object", "dep:once_cell", "dep:percent-encoding", "dep:phf", "dep:regex", "dep:rkv", "dep:scopeguard", "dep:scroll", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:stable_deref_trait", "dep:strsim", "dep:time", "dep:time-macros", "dep:tinystr", "dep:toml", "dep:unic-langid", "dep:unic-langid-impl", "dep:unicode-bidi", "dep:uniffi", "dep:url", "dep:uuid", "dep:windows", "dep:windows-sys", "dep:xml-rs", "dep:yoke", "dep:zerocopy", "dep:zerofrom", "dep:zerovec"]

View File

@@ -843,20 +843,8 @@ void BrowserParent::ActorDestroy(ActorDestroyReason why) {
nsCOMPtr<nsIPrincipal> principal = GetContentPrincipal();
if (principal) {
nsAutoCString crash_reason;
CrashReporter::GetAnnotation(OtherPid(),
CrashReporter::Annotation::MozCrashReason,
crash_reason);
// FIXME(arenevier): Find a less fragile way to identify that a crash
// was caused by OOM
bool is_oom = false;
if (crash_reason == "OOM" || crash_reason == "OOM!" ||
StringBeginsWith(crash_reason, "[unhandlable oom]"_ns) ||
StringBeginsWith(crash_reason, "Unhandlable OOM"_ns)) {
is_oom = true;
}
CrashReport::Deliver(principal, is_oom);
// TODO: Flag out-of-memory crashes appropriately.
CrashReport::Deliver(principal, /* aIsOOM */ false);
}
}
}

View File

@@ -1125,13 +1125,18 @@ Result<Ok, LaunchError> BaseProcessLauncher::DoSetup() {
#if defined(MOZ_WIDGET_COCOA) || defined(XP_WIN)
geckoargs::sCrashReporter.Put(CrashReporter::GetChildNotificationPipe(),
mChildArgs);
#elif defined(XP_UNIX)
#elif defined(XP_UNIX) && !defined(XP_IOS)
UniqueFileHandle childCrashFd = CrashReporter::GetChildNotificationPipe();
if (!childCrashFd) {
return Err(LaunchError("DuplicateFileHandle failed"));
}
geckoargs::sCrashReporter.Put(std::move(childCrashFd), mChildArgs);
#endif
CrashReporter::ProcessId pid = CrashReporter::GetCrashHelperPid();
if (pid != base::kInvalidProcessId) {
geckoargs::sCrashHelperPid.Put(pid, mChildArgs);
}
#endif // XP_UNIX && !XP_IOS
}
return Ok();

View File

@@ -87,6 +87,14 @@
android:isolatedProcess="false"
android:process=":ipdlunittest">
</service>
<service
android:name="org.mozilla.gecko.crashhelper.CrashHelper"
android:enabled="true"
android:exported="false"
android:isolatedProcess="false"
android:process=":crashhelper"
android:stopWithTask="true">
</service>
</application>
</manifest>

View File

@@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.crashhelper;
interface ICrashHelper {
boolean start(in int mainProcessPid);
}

View File

@@ -5,7 +5,9 @@
package org.mozilla.gecko;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
@@ -29,6 +31,7 @@ import java.util.Map;
import java.util.StringTokenizer;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.crashhelper.CrashHelper;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.process.GeckoProcessManager;
import org.mozilla.gecko.process.GeckoProcessType;
@@ -326,11 +329,26 @@ public class GeckoThread extends Thread {
if (!isChildProcess()) {
GeckoSystemStateListener.getInstance().initialize(context);
startCrashHelper(context);
}
loadGeckoLibs(context);
}
private static void startCrashHelper(final Context context) {
try {
// Native crashes are reported through pipes, so we don't have to
// do anything special for that.
@SuppressWarnings("unchecked")
final Class<? extends Service> cls =
(Class<? extends Service>) Class.forName("org.mozilla.gecko.crashhelper.CrashHelper");
final Intent i = new Intent(context, cls);
context.bindService(i, CrashHelper.createConnection(), Context.BIND_AUTO_CREATE);
} catch (final ClassNotFoundException e) {
Log.w(LOGTAG, "Couldn't find the crash helper class");
}
}
private String[] getMainProcessArgs() {
final Context context = GeckoAppShell.getApplicationContext();
final ArrayList<String> args = new ArrayList<>();

View File

@@ -0,0 +1,91 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.crashhelper;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.geckoview.BuildConfig;
public final class CrashHelper extends Service {
private static final String LOGTAG = "GeckoCrashHelper";
private static final boolean DEBUG = !BuildConfig.MOZILLA_OFFICIAL;
private final Binder mBinder = new CrashHelperBinder();
private static boolean sNativeLibLoaded;
@Override
public synchronized void onCreate() {
if (!sNativeLibLoaded) {
GeckoLoader.doLoadLibrary(this, "crashhelper");
sNativeLibLoaded = true;
}
}
private static class CrashHelperBinder extends ICrashHelper.Stub {
@Override
public boolean start(final int clientPid) {
// Launch the crash helper code, this will spin out a thread which will
// handle the IPC with Firefox' other processes. When running junit tests
// we might have several main processes connecting to the service. Each
// will get its own thread, but the generated crashes live in a shared
// space. This means that technically one main process instance might
// "steal" the crash report of another main process. We should add
// additional separation within the crash generation code to prevent this
// from happening even though it's very unlikely.
CrashHelper.crash_generator(clientPid);
return false;
}
}
@Override
public IBinder onBind(final Intent intent) {
if (intent == null) {
Log.d(LOGTAG, "Intent is empty, crash helper will not start");
return null;
}
return mBinder;
}
public static ServiceConnection createConnection() {
final ServiceConnection connection =
new ServiceConnection() {
@Override
public void onServiceConnected(final ComponentName name, final IBinder service) {
final ICrashHelper helper = ICrashHelper.Stub.asInterface(service);
try {
helper.start(Process.myPid());
} catch (final DeadObjectException e) {
// TODO: Cleanup?
return;
} catch (final RemoteException e) {
throw new RuntimeException(e);
}
}
@Override
public void onServiceDisconnected(final ComponentName name) {
// Nothing to do here
}
};
return connection;
}
// Note: we don't implement onDestroy() because the service has the
// `stopWithTask` flag set in the manifest, so the service manager will
// tear it down for us.
protected static native void crash_generator(int clientPid);
}

View File

@@ -78,6 +78,7 @@
#endif
#ifdef MOZ_CRASHREPORTER
@BINPATH@/@DLL_PREFIX@crashhelper@DLL_SUFFIX@
@BINPATH@/@DLL_PREFIX@minidump_analyzer@DLL_SUFFIX@
#endif

View File

@@ -556,6 +556,7 @@ class LinuxArtifactJob(ArtifactJob):
product = "firefox"
_package_artifact_patterns = {
"{product}/crashhelper",
"{product}/crashreporter",
"{product}/dependentlibs.list",
"{product}/{product}",
@@ -654,6 +655,7 @@ class MacArtifactJob(ArtifactJob):
(
"Contents/MacOS",
[
"crashhelper",
"crashreporter.app/Contents/MacOS/crashreporter",
"{product}",
"{product}-bin",

View File

@@ -2,7 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
Entitlements to apply to non-browser executables such as updater,
pingsender, and crashreporter during codesigning of developer builds.
pingsender, crashhelper and crashreporter during codesigning of developer
builds.
-->
<plist version="1.0">
<dict>

View File

@@ -977,6 +977,7 @@ mac-signing:
force: true
# These files are signed without entitlements
globs:
- "/Contents/MacOS/crashhelper"
- "/Contents/MacOS/crashreporter.app"
- "/Contents/MacOS/updater.app/Contents/Frameworks/UpdateSettings.framework"
- "/Contents/MacOS/updater.app"
@@ -1021,6 +1022,7 @@ mac-signing:
force: true
entitlements: security/mac/hardenedruntime/v2/developer/utility.xml
globs:
- "/Contents/MacOS/crashhelper"
- "/Contents/MacOS/crashreporter.app"
- "/Contents/MacOS/updater.app/Contents/Frameworks/UpdateSettings.framework"
- "/Contents/MacOS/updater.app"

View File

@@ -12,14 +12,29 @@
extern "C" {
#if defined(__ANDROID_API__) && (__ANDROID_API__ < 28)
// Bionic introduced support for syncfs only in version 28 (that is
// Android Pie / 9). Since GeckoView is built with version 21, those functions
// aren't defined, but nix needs them and the crash helper relies on nix. These
// functions should never be called in practice hence we implement them only to
// satisfy nix linking requirements but we crash if we accidentally enter them.
int syncfs(int fd) {
MOZ_CRASH("syncfs() is not available");
return EPERM;
}
#endif // __ANDROID_API__ && (__ANDROID_API__ < 28)
#if defined(__ANDROID_API__) && (__ANDROID_API__ < 24)
// Bionic introduced support for getgrgid_r() and getgrnam_r() only in version
// 24 (that is Android Nougat / 7.0). Since GeckoView is built with version 21,
// those functions aren't defined, but nix needs them and minidump-writer
// relies on nix. These functions should never be called in practice hence we
// implement them only to satisfy nix linking requirements but we crash if we
// accidentally enter them.
// those functions aren't defined, but the nix crate needs them and
// minidump-writer relies on nix. These functions should never be called in
// practice hence we implement them only to satisfy nix linking requirements
// but we crash if we accidentally enter them.
int getgrgid_r(gid_t gid, struct group* grp, char* buf, size_t buflen,
struct group** result) {

View File

@@ -8,8 +8,6 @@ SOURCES += [
'minidump_file_writer.cc',
]
Library('breakpad_client')
USE_LIBS += [
'breakpad_common_s',
]
@@ -19,15 +17,19 @@ if CONFIG['OS_ARCH'] == 'Darwin':
'breakpad_mac_common_s',
]
elif CONFIG['OS_ARCH'] == 'Linux':
UNIFIED_SOURCES += [
'../linux_utils.cc',
]
USE_LIBS += [
'breakpad_linux_common_s',
]
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/toolkit/crashreporter/google-breakpad/src',
]
if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
CXXFLAGS += ['-Wno-error=stack-protector']
Library('breakpad_client')

View File

@@ -0,0 +1,171 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <string>
#if defined(XP_LINUX)
# include <sys/signalfd.h>
# include <sys/ucontext.h>
# include "linux/crash_generation/client_info.h"
# include "linux/crash_generation/crash_generation_server.h"
using breakpad_char = char;
using breakpad_string = std::string;
using breakpad_init_type = int;
using breakpad_pid = pid_t;
#elif defined(XP_WIN)
# include "windows/crash_generation/client_info.h"
# include "windows/crash_generation/crash_generation_server.h"
using breakpad_char = wchar_t;
using breakpad_string = std::wstring;
using breakpad_init_type = wchar_t*;
using breakpad_pid = DWORD;
#elif defined(XP_MACOSX)
# include <mach/mach_types.h>
# include <unistd.h>
# include "mac/crash_generation/client_info.h"
# include "mac/crash_generation/crash_generation_server.h"
using breakpad_char = char;
using breakpad_string = std::string;
using breakpad_init_type = const char*;
using breakpad_pid = pid_t;
#else
# error "Unsupported platform"
#endif
#ifdef MOZ_PHC
# include "PHC.h"
namespace mozilla::phc {
// HACK: The breakpad code expects this global variable even though we don't
// use it in the wrapper.
MOZ_RUNINIT mozilla::phc::AddrInfo gAddrInfo;
} // namespace mozilla::phc
#endif // defined(MOZ_PHC)
using google_breakpad::ClientInfo;
using google_breakpad::CrashGenerationServer;
// This struct and the callback that uses it need to be kept in sync with the
// corresponding Rust code in src/crash_generation.rs.
struct BreakpadProcessId {
breakpad_pid pid;
#if defined(XP_MACOSX)
task_t task;
#elif defined(XP_WIN)
HANDLE handle;
#endif
};
using RustCallback = void (*)(BreakpadProcessId, const char*,
const breakpad_char*);
void onClientDumpRequestCallback(void* context, const ClientInfo& client_info,
const breakpad_string& file_path) {
RustCallback callback = reinterpret_cast<RustCallback>(context);
BreakpadProcessId process_id = {
.pid = client_info.pid(),
#if defined(XP_MACOSX)
.task = client_info.task(),
#elif defined(XP_WIN)
.handle = client_info.process_handle(),
#endif
};
const char* error_msg =
#if defined(XP_LINUX)
client_info.error_msg();
#else
nullptr;
#endif // XP_LINUX
callback(process_id, error_msg, file_path.c_str());
}
#ifdef XP_WIN
extern "C" void* CrashGenerationServer_init(breakpad_init_type aBreakpadData,
const breakpad_char* aMinidumpPath,
RustCallback aDumpCallback) {
breakpad_string minidumpPath(aMinidumpPath);
breakpad_string breakpadData(aBreakpadData);
CrashGenerationServer* server = new CrashGenerationServer(
breakpadData,
/* pipe_sec_attrs */ nullptr,
/* connect_callback */ nullptr,
/* connect_context */ nullptr, onClientDumpRequestCallback,
reinterpret_cast<void*>(aDumpCallback),
/* written_callback */ nullptr,
/* exit_callback */ nullptr,
/* exit_context */ nullptr,
/* upload_request_callback */ nullptr,
/* upload_context */ nullptr,
/* generate_dumps */ true, &minidumpPath);
if (!server->Start()) {
delete server;
return nullptr;
}
return server;
}
#elif defined(XP_MACOSX)
extern "C" void* CrashGenerationServer_init(breakpad_init_type aBreakpadData,
const breakpad_char* aMinidumpPath,
RustCallback aDumpCallback) {
breakpad_string minidumpPath(aMinidumpPath);
breakpad_init_type breakpadData = aBreakpadData;
CrashGenerationServer* server = new CrashGenerationServer(
breakpadData,
/* filter */ nullptr,
/* filter_context */ nullptr, onClientDumpRequestCallback,
reinterpret_cast<void*>(aDumpCallback),
/* exit_callback */ nullptr,
/* exit_context */ nullptr,
/* generate_dumps */ true, minidumpPath);
if (!server->Start()) {
delete server;
return nullptr;
}
return server;
}
#elif defined(XP_LINUX)
extern "C" void* CrashGenerationServer_init(breakpad_init_type aBreakpadData,
const breakpad_char* aMinidumpPath,
RustCallback aDumpCallback) {
breakpad_string minidumpPath(aMinidumpPath);
breakpad_init_type breakpadData = aBreakpadData;
CrashGenerationServer* server =
new CrashGenerationServer(breakpadData, onClientDumpRequestCallback,
reinterpret_cast<void*>(aDumpCallback),
/* exit_callback */ nullptr,
/* exit_context */ nullptr,
/* generate_dumps */ true, &minidumpPath);
if (!server->Start()) {
delete server;
return nullptr;
}
return server;
}
#endif
extern "C" void CrashGenerationServer_shutdown(void* aServer) {
CrashGenerationServer* server = static_cast<CrashGenerationServer*>(aServer);
delete server;
}

View File

@@ -0,0 +1,37 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Library("breakpad_wrapper")
if CONFIG["MOZ_PHC"]:
DEFINES["MOZ_PHC"] = True
SOURCES = [
"breakpad_wrapper.cpp",
]
LOCAL_INCLUDES += [
"../breakpad-client/",
"../google-breakpad/src/",
]
if CONFIG["OS_ARCH"] == "WINNT":
USE_LIBS += [
"google_breakpad_libxul_s",
]
OS_LIBS += [
"advapi32",
"bcrypt",
"dbghelp",
"ntdll",
"userenv",
"ws2_32",
]
else:
USE_LIBS += [
"breakpad_client",
]

View File

@@ -0,0 +1,80 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use num_derive::FromPrimitive;
// Enumeration representing all crash annotations
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[repr(u32)]
#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq)]
pub(crate) enum CrashAnnotation {
${enum}
}
impl std::fmt::Display for CrashAnnotation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", CRASH_ANNOTATION_STRINGS[*self as usize])
}
}
// Type of each annotation
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum CrashAnnotationType {
String = 0, // Any type of string, const char*, nsCString, etc...
Boolean = 1, // Stored as a byte
U32 = 2, // C/C++'s uint32_t or Rust's u32
U64 = 3, // C/C++'s uint64_t or Rust's u64
USize = 4, // C/C++'s size_t or Rust's usize
}
// Type of each annotation
static CRASH_ANNOTATION_TYPES: &[CrashAnnotationType] = &[
${types}
];
// Stringified representation of each annotation. Most of these will just match
// the corresponding enum values, but for historical reasons some of them are
// different in string form when stored in the .extra file.
static CRASH_ANNOTATION_STRINGS: &[&str] = &[
${names}
];
// Annotations which should be skipped when they have specific values
struct CrashAnnotationSkipValue {
annotation: CrashAnnotation,
value: &'static [u8],
}
static CRASH_ANNOTATIONS_SKIP_VALUES: &[CrashAnnotationSkipValue] = &[
${skiplist}
];
/// Returns the type of a crash annotation.
///
/// # Arguments
///
/// * `annotation` - a crash annotation
pub(crate) fn type_of_annotation(annotation: CrashAnnotation) -> CrashAnnotationType {
CRASH_ANNOTATION_TYPES[annotation as usize]
}
/// Checks if the annotation should be included. Some annotations are skipped
/// if their value matches a specific one (like the value 0).
///
/// # Arguments
///
/// * `annotation` - the crash annotation to be checked
/// * `value` - the contents of the annotation as a string
pub(crate) fn should_include_annotation(annotation: CrashAnnotation, value: &[u8]) -> bool {
if let Some(skip_value) = CRASH_ANNOTATIONS_SKIP_VALUES
.iter()
.find(|&a| a.annotation == annotation)
{
skip_value.value != value
} else {
true
}
}

View File

@@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <cstdlib>
#if defined(XP_WIN)
# include <windows.h>
#endif // defined(XP_WIN)
#include "mozilla/crash_helper_ffi_generated.h"
int main(int argc, char* argv[]) {
if (argc < 2) {
exit(EXIT_FAILURE);
}
errno = 0;
long value = strtol(argv[1], nullptr, 10);
Pid client_pid = static_cast<Pid>(value);
if (errno != 0) {
exit(EXIT_FAILURE);
}
crash_generator_logic(client_pid);
exit(EXIT_SUCCESS);
}

View File

@@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <jni.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "mozilla/crash_helper_ffi_generated.h"
extern "C" JNIEXPORT void JNICALL
Java_org_mozilla_gecko_crashhelper_CrashHelper_crash_1generator(
JNIEnv*, jclass, jint client_pid) {
crash_generator_logic(client_pid);
}

View File

@@ -0,0 +1,42 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
LOCAL_INCLUDES = [
"/toolkit/crashreporter",
]
USE_LIBS += [
"breakpad_wrapper",
"crash_helper_server",
]
if CONFIG["OS_TARGET"] == "Android":
UNIFIED_SOURCES = [
"../bionic_missing_funcs.cpp",
"crashhelper_android.cpp",
]
GeckoSharedLibrary("crashhelper", linkage=None)
else:
if CONFIG["OS_ARCH"] == "Darwin":
OS_LIBS += [
"-framework Foundation",
]
elif CONFIG["OS_ARCH"] == "WINNT":
OS_LIBS += [
"advapi32",
"bcrypt",
"dbghelp",
"ntdll",
"userenv",
"ws2_32",
]
UNIFIED_SOURCES += [
"crashhelper.cpp",
]
GeckoProgram("crashhelper", linkage=None)

View File

@@ -0,0 +1,18 @@
[package]
name = "crash_helper_client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1"
crash_helper_common = { path = "../crash_helper_common" }
num-derive = "0.4"
num-traits = "0.2"
[target."cfg(any(target_os = \"android\", target_os = \"linux\", target_os = \"macos\"))".dependencies]
nix = { version = "0.29", features = ["process"] }
[target."cfg(target_os = \"windows\")".dependencies]
windows-sys = { version = "0.52" } # All features inherited from crash_helper_common

View File

@@ -8,8 +8,14 @@ braces = "SameLine"
line_length = 100
tab_width = 2
language = "C++"
include_guard = "mozannotation_server_ffi_generated_h"
includes = ["nsString.h", "nsTArrayForwardDeclare.h"]
include_guard = "crash_helper_ffi_generated_h"
[export.rename]
"ThinVec" = "nsTArray"
[defines]
"target_os = android" = "MOZ_WIDGET_ANDROID"
"target_os = linux" = "XP_LINUX"
"target_os = macos" = "XP_MACOSX"
"target_os = windows" = "XP_WIN"
[parse]
parse_deps = true
include = ["crash_helper_common"]

View File

@@ -7,11 +7,11 @@
if CONFIG["COMPILE_ENVIRONMENT"]:
# This tells mach to run cbindgen and that this header-file should be created
CbindgenHeader(
"mozannotation_server_ffi_generated.h",
inputs=["/toolkit/crashreporter/mozannotation_server"],
"crash_helper_client_ffi_generated.h",
inputs=["/toolkit/crashreporter/crash_helper_client"],
)
# This tells mach to copy that generated file to obj/dist/include/mozilla/toolkit/crashreporter
EXPORTS.mozilla.toolkit.crashreporter += [
"!mozannotation_server_ffi_generated.h",
# This tells mach to copy that generated file to obj/dist/mozilla
EXPORTS.mozilla += [
"!crash_helper_client_ffi_generated.h",
]

View File

@@ -0,0 +1,223 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
use anyhow::{bail, Result};
use crash_helper_common::{
messages::{self},
BreakpadData, BreakpadString, IPCConnector,
};
use std::{
ffi::{c_char, CStr, CString, OsString},
ptr::null_mut,
};
#[cfg(target_os = "windows")]
use windows_sys::Win32::System::Diagnostics::Debug::{CONTEXT, EXCEPTION_RECORD};
extern crate num_traits;
pub use crash_helper_common::{BreakpadChar, BreakpadRawData, Pid};
mod platform;
pub struct CrashHelperClient {
connector: IPCConnector,
pid: Pid,
}
impl CrashHelperClient {
fn send_initialize(
&self,
minidump_path: OsString,
breakpad_data: BreakpadData,
release_channel: &str,
) -> Result<()> {
let message = messages::Initialize::new(minidump_path, breakpad_data, release_channel);
self.connector.send_message(&message)?;
self.connector.recv_reply::<messages::InitializeReply>()?;
Ok(())
}
fn transfer_crash_report(&self, pid: Pid) -> Result<CrashReport> {
let message = messages::TransferMinidump::new(pid);
self.connector.send_message(&message)?;
// HACK: Workaround for a macOS-specific bug
#[cfg(target_os = "macos")]
self.connector.poll(nix::poll::PollFlags::POLLIN)?;
let reply = self
.connector
.recv_reply::<messages::TransferMinidumpReply>()?;
if reply.path.is_empty() {
// TODO: We should return Result<Option<CrashReport>> instead of
// this. Semantics would be better once we interact with Rust
bail!("Minidump for pid {pid:} was not found");
}
Ok(CrashReport {
// SAFETY: path is a valid C string we generated ourselves
path: unsafe { reply.path.into_raw() },
error: reply.error.map_or(null_mut(), |error| error.into_raw()),
})
}
}
/// Launch the crash helper process, initialize it and connect to it. Returns
/// a pointer to the client connection or `null` upon failure.
///
/// # Safety
///
/// The `helper_name` and `minidump_path` arguments must point to byte or wide
/// strings where appropriate. The `breakpad_raw_data` argument must either
/// point to a string or must contain a valid file descriptor create via
/// `CrashGenerationServer::CreateReportChannel()` depending on the platform.
#[no_mangle]
pub unsafe extern "C" fn crash_helper_launch(
helper_name: *const BreakpadChar,
minidump_path: *const BreakpadChar,
breakpad_raw_data: BreakpadRawData,
release_channel: *const c_char,
) -> *mut CrashHelperClient {
if let Ok(crash_helper) = CrashHelperClient::new(helper_name) {
let minidump_path = <OsString as BreakpadString>::from_ptr(minidump_path);
let breakpad_data = BreakpadData::new(breakpad_raw_data);
let release_channel = CStr::from_ptr(release_channel);
let res = crash_helper.send_initialize(
minidump_path,
breakpad_data,
&release_channel.to_string_lossy(),
);
if res.is_err() {
return null_mut();
}
let crash_helper_box = Box::new(crash_helper);
// The object will be owned by the C++ code from now on, until it is
// passed back in `crash_helper_shutdown`.
Box::into_raw(crash_helper_box)
} else {
null_mut()
}
}
/// Shutdown the crash helper and dispose of the client object.
///
/// # Safety
///
/// The `client` parameter must be a valid pointer to the crash helper client
/// object returned by the [`crash_helper_launch()`] function.
#[no_mangle]
pub unsafe extern "C" fn crash_helper_shutdown(client: *mut CrashHelperClient) {
// The CrashHelperClient object will be automatically destroyed when the
// contents of this box are automatically dropped at the end of the function
let _crash_helper_box = Box::from_raw(client);
}
/// Return the pid of the crash helper process.
///
/// # Safety
///
/// The `client` parameter must be a valid pointer to the crash helper client
/// object returned by the [`crash_helper_launch()`] function.
#[no_mangle]
pub unsafe extern "C" fn crash_helper_pid(client: *const CrashHelperClient) -> Pid {
(*client).pid
}
#[repr(C)]
pub struct CrashReport {
path: *mut BreakpadChar,
error: *mut c_char,
}
/// Request the crash report generated for the process associated with `pid`.
/// If the crash report is found an object holding a pointer to the minidump
/// and a potential error message will be returned. Otherwise the function will
/// return `null`.
///
/// # Safety
///
/// The `client` parameter must be a valid pointer to the crash helper client
/// object returned by the [`crash_helper_launch()`] function.
#[no_mangle]
pub unsafe extern "C" fn transfer_crash_report(
client: *mut CrashHelperClient,
pid: Pid,
) -> *mut CrashReport {
let client = client.as_ref().unwrap();
if let Ok(crash_report) = client.transfer_crash_report(pid) {
// The object will be owned by the C++ code from now on, until it is
// passed back in `release_crash_report`.
Box::into_raw(Box::new(crash_report))
} else {
null_mut()
}
}
/// Release an object obtained via [`transfer_crash_report()`].
///
/// # Safety
///
/// The `crash_report` argument must be a pointer returned by the
/// [`transfer_crash_report()`] function.
#[no_mangle]
pub unsafe extern "C" fn release_crash_report(crash_report: *mut CrashReport) {
let crash_report = Box::from_raw(crash_report);
// Release the strings, we just get back ownership of the raw objects and let the drop logic get rid of them
let _path = <OsString as BreakpadString>::from_raw(crash_report.path);
if !crash_report.error.is_null() {
let _error = CString::from_raw(crash_report.error);
}
}
/// Report an exception to the crash manager that has been captured outside of
/// Firefox processes, typically within the Windows Error Reporting runtime
/// exception module.
///
/// # Safety
///
/// The `exception_record_ptr` and `context_ptr` parameters must point to valid
/// objects of the corresponding types.
#[cfg(target_os = "windows")]
pub unsafe fn report_external_exception(
main_process_pid: Pid,
pid: Pid,
thread: Pid, // TODO: This should be a different type, but it's the same on Windows
exception_record_ptr: *mut EXCEPTION_RECORD,
context_ptr: *mut CONTEXT,
) {
let exception_records = collect_exception_records(exception_record_ptr);
let context = unsafe { context_ptr.read() };
let message =
messages::WindowsErrorReportingMinidump::new(pid, thread, exception_records, context);
// In the code below we connect to the crash helper, send our message and wait for a reply before returning, but we ignore errors because we can't do anything about them in the calling code
if let Ok(connector) = IPCConnector::connect(main_process_pid) {
let _ = connector
.send_message(&message)
.and_then(|_| connector.recv_reply::<messages::WindowsErrorReportingMinidumpReply>());
}
}
// Collect a linked-list of exception records and turn it into a vector
#[cfg(target_os = "windows")]
fn collect_exception_records(
mut exception_record_ptr: *mut EXCEPTION_RECORD,
) -> Vec<EXCEPTION_RECORD> {
let mut exception_records = Vec::<EXCEPTION_RECORD>::with_capacity(1);
loop {
if exception_record_ptr.is_null() {
return exception_records;
}
let mut exception_record = unsafe { exception_record_ptr.read() };
exception_record_ptr = exception_record.ExceptionRecord;
exception_record.ExceptionRecord = null_mut();
exception_records.push(exception_record);
}
}

View File

@@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#[cfg(target_os = "windows")]
pub(crate) mod windows;
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
pub(crate) mod unix;

View File

@@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
use anyhow::Result;
use std::{ffi::c_char, process};
use crate::CrashHelperClient;
use crash_helper_common::{IPCConnector, Pid};
impl CrashHelperClient {
pub(crate) fn new(program: *const c_char) -> Result<CrashHelperClient> {
let pid = CrashHelperClient::spawn_crash_helper(program)?;
let connector = IPCConnector::connect(process::id() as Pid)?;
Ok(CrashHelperClient { connector, pid })
}
#[allow(unused_variables)]
fn spawn_crash_helper(program: *const c_char) -> Result<nix::libc::pid_t> {
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
use nix::unistd::{execv, fork, getpid, ForkResult};
use std::ffi::{CStr, CString};
let parent_pid = getpid().to_string();
let parent_pid_arg = unsafe { CString::from_vec_unchecked(parent_pid.into_bytes()) };
let pid = unsafe { fork() }?;
// TODO: daemonize the helper by double fork()'ing and waiting on the child
match pid {
ForkResult::Child => {
let program = unsafe { CStr::from_ptr(program) };
let _ = execv(program, &[program, &parent_pid_arg]);
// This point should be unreachable, but let's play it safe
unsafe { nix::libc::_exit(1) };
}
ForkResult::Parent { child } => Ok(child.as_raw()),
}
}
// This is a no-op on Android because we spawn the crash helper as an
// Android service directly from the Java code, before loading libxul.
#[cfg(target_os = "android")]
Ok(0)
}
}

View File

@@ -0,0 +1,69 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use anyhow::{bail, Result};
use crash_helper_common::{BreakpadChar, BreakpadString, IPCConnector, Pid};
use std::{
ffi::OsString,
mem::{size_of, zeroed},
os::windows::ffi::OsStrExt,
process,
ptr::{null, null_mut},
};
use windows_sys::Win32::{
Foundation::FALSE,
System::Threading::{
CreateProcessW, GetCurrentProcessId, CREATE_UNICODE_ENVIRONMENT, DETACHED_PROCESS,
PROCESS_INFORMATION, STARTUPINFOW,
},
};
use crate::CrashHelperClient;
impl CrashHelperClient {
pub(crate) fn new(program: *const BreakpadChar) -> Result<CrashHelperClient> {
let pid = CrashHelperClient::spawn_crash_helper(program)?;
let connector = IPCConnector::connect(process::id())?;
Ok(CrashHelperClient { connector, pid })
}
fn spawn_crash_helper(program: *const u16) -> Result<Pid> {
let pid = unsafe { GetCurrentProcessId() };
let program = <OsString as BreakpadString>::from_ptr(program);
let mut cmd_line = OsString::from("\"");
cmd_line.push(program);
cmd_line.push("\" \"");
cmd_line.push(pid.to_string());
cmd_line.push("\"\0");
let mut cmd_line: Vec<u16> = cmd_line.encode_wide().collect();
let mut pi = unsafe { zeroed::<PROCESS_INFORMATION>() };
let si = STARTUPINFOW {
cb: size_of::<STARTUPINFOW>().try_into().unwrap(),
..unsafe { zeroed() }
};
let res = unsafe {
CreateProcessW(
null(),
cmd_line.as_mut_ptr(),
/* lpProcessAttributes */ null_mut(),
/* lpThreadAttributes */ null_mut(),
/* bInheritHandles */ FALSE,
CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS,
/* lpEnvironment */ null_mut(),
/* lpCurrentDirectory */ null_mut(),
&si,
&mut pi,
)
};
if res == FALSE {
bail!("Could not create the crash helper process");
}
Ok(pi.dwProcessId)
}
}

View File

@@ -0,0 +1,49 @@
[package]
name = "crash_helper_server"
version = "0.1.0"
authors = ["Gabriele Svelto <gsvelto@mozilla.com>"]
edition = "2018"
[dependencies]
anyhow = "1"
cfg-if = "1"
crash_helper_common = { path = "../crash_helper_common" }
env_logger = { version = "0.10", default-features = false }
log = "0.4"
mozannotation_server = { path = "../mozannotation_server" }
mozilla-central-workspace-hack = { version = "0.1", features = [
"crash_helper_server",
], optional = true }
num-derive = "0.4"
num-traits = "0.2"
once_cell = "1"
thiserror = "2"
uuid = { version = "1.0", features = ["v4"] }
[target."cfg(any(target_os = \"android\", target_os = \"linux\"))".dependencies]
nix = { version = "0.29", features = ["poll", "socket", "uio"] }
rust_minidump_writer_linux = { path = "../rust_minidump_writer_linux" }
[target."cfg(target_os = \"windows\")".dependencies]
windows-sys = { version = "0.52", features = [
"Win32_Foundation",
"Win32_Storage_FileSystem",
"Win32_System_Kernel",
"Win32_System_Memory",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
] }
[target."cfg(target_os = \"macos\")".dependencies]
nix = { version = "0.29", features = ["fs", "poll", "socket", "uio"] }
[build-dependencies]
cc = "1"
linked-hash-map = "0.5"
mozbuild = "0.1"
yaml-rust = "0.4"
[lib]
name = "crash_helper_server"
crate-type = ["staticlib"]
path = "src/lib.rs"

View File

@@ -0,0 +1,166 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::env;
use std::fs::{self, File};
use std::io::{Error, Write};
use std::path::Path;
use std::vec::Vec;
use yaml_rust::{Yaml, YamlLoader};
fn main() -> Result<(), Error> {
generate_annotations()?;
Ok(())
}
struct Annotation {
name: String,
type_string: String,
altname: Option<String>,
skip_if: Option<String>,
}
impl Annotation {
fn new(key: &Yaml, value: &Yaml) -> Result<Annotation, Error> {
let name = key.as_str().ok_or(Error::other("Not a string"))?;
let raw_type = value["type"].as_str().ok_or(Error::other("Missing type"))?;
let type_string = Annotation::type_str_to_value(raw_type)?;
let altname = value["altname"].as_str().map(str::to_owned);
// We don't care about the contents of the `ping` field for the time being
let skip_if = value["skip_if"].as_str().map(str::to_owned);
Ok(Annotation {
name: name.to_owned(),
type_string,
altname,
skip_if,
})
}
fn type_str_to_value(raw_type: &str) -> Result<String, Error> {
match raw_type {
"string" => Ok("String".to_owned()),
"boolean" => Ok("Boolean".to_owned()),
"u32" => Ok("U32".to_owned()),
"u64" => Ok("U64".to_owned()),
"usize" => Ok("USize".to_owned()),
_ => Err(Error::other("Invalid type")),
}
}
}
fn read_annotations(doc: &Yaml) -> Result<Vec<Annotation>, Error> {
let raw_annotations = doc.as_hash().ok_or(Error::other(
"Invalid data in YAML file, expected a hash".to_string(),
))?;
let mut annotations = Vec::<Annotation>::new();
for annotation in raw_annotations {
let annotation = Annotation::new(annotation.0, annotation.1)?;
annotations.push(annotation);
}
annotations.sort_by(|a, b| {
let a_lower = a.name.to_ascii_lowercase();
let b_lower = b.name.to_ascii_lowercase();
a_lower.cmp(&b_lower)
});
Ok(annotations)
}
fn generate_annotation_enum(annotations: &[Annotation]) -> Result<String, Error> {
let mut annotations_enum = String::new();
for (index, value) in annotations.iter().enumerate() {
let entry = format!(" {} = {index:},\n", value.name);
annotations_enum.push_str(&entry);
}
let count = format!(" Count = {},", annotations.len());
annotations_enum.push_str(&count);
Ok(annotations_enum)
}
fn generate_annotation_types(annotations: &[Annotation]) -> Result<String, Error> {
let mut types_array = String::new();
for value in annotations.iter() {
let entry = format!(" CrashAnnotationType::{},\n", value.type_string);
types_array.push_str(&entry);
}
// Pop the last newline
types_array.pop();
Ok(types_array)
}
fn generate_annotation_names(annotations: &[Annotation]) -> Result<String, Error> {
let mut names_array = String::new();
for value in annotations.iter() {
let name = value.altname.as_ref().unwrap_or(&value.name);
let entry = format!(" \"{}\",\n", name);
names_array.push_str(&entry);
}
// Pop the last newline
names_array.pop();
Ok(names_array)
}
fn generate_annotation_skiplist(annotations: &[Annotation]) -> Result<String, Error> {
let mut skiplist = String::new();
for annotation in annotations.iter() {
if let Some(skip_if) = &annotation.skip_if {
let entry = format!(
" CrashAnnotationSkipValue {{ annotation: CrashAnnotation::{}, value: b\"{}\" }},\n",
&annotation.name, skip_if
);
skiplist.push_str(&entry);
}
}
// Pop the last newline
skiplist.pop();
Ok(skiplist)
}
// Generate Rust code to manipulate crash annotations
fn generate_annotations() -> Result<(), Error> {
const CRASH_ANNOTATIONS_YAML: &str = "../CrashAnnotations.yaml";
const CRASH_ANNOTATIONS_TEMPLATE: &str = "../crash_annotations.rs.in";
let out_dir = env::var("OUT_DIR").unwrap();
let annotations_path = Path::new(&out_dir).join("crash_annotations.rs");
let mut annotations_file = File::create(annotations_path)?;
let template = fs::read_to_string(CRASH_ANNOTATIONS_TEMPLATE)?;
let yaml_str = fs::read_to_string(CRASH_ANNOTATIONS_YAML)?;
let yaml_doc = YamlLoader::load_from_str(&yaml_str)
.map_err(|e| Error::other(format!("Failed to parse YAML file: {}", e)))?;
let doc = &yaml_doc[0];
let annotations = read_annotations(doc)?;
let annotations_enum = generate_annotation_enum(&annotations)?;
let annotations_types = generate_annotation_types(&annotations)?;
let annotations_names = generate_annotation_names(&annotations)?;
let skiplist = generate_annotation_skiplist(&annotations)?;
let compiled_template = template
.replace("${enum}", &annotations_enum)
.replace("${types}", &annotations_types)
.replace("${names}", &annotations_names)
.replace("${skiplist}", &skiplist);
write!(&mut annotations_file, "{}", compiled_template)?;
println!("cargo:rerun-if-changed={CRASH_ANNOTATIONS_YAML}");
println!("cargo:rerun-if-changed={CRASH_ANNOTATIONS_TEMPLATE}");
Ok(())
}

View File

@@ -0,0 +1,21 @@
header = """/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
"""
include_version = true
braces = "SameLine"
line_length = 100
tab_width = 2
language = "C++"
include_guard = "crash_helper_ffi_generated_h"
[defines]
"target_os = android" = "MOZ_WIDGET_ANDROID"
"target_os = linux" = "XP_LINUX"
"target_os = macos" = "XP_MACOSX"
"target_os = windows" = "XP_WIN"
[parse]
parse_deps = true
include = ["crash_helper_common"]

View File

@@ -0,0 +1,19 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
RustLibrary("crash_helper_server")
if CONFIG["COMPILE_ENVIRONMENT"]:
# This tells mach to run cbindgen and that this header-file should be created
CbindgenHeader(
"crash_helper_ffi_generated.h",
inputs=["/toolkit/crashreporter/crash_helper_server"],
)
# This tells mach to copy that generated file to obj/dist/mozilla
EXPORTS.mozilla += [
"!crash_helper_ffi_generated.h",
]

View File

@@ -0,0 +1,86 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/******************************************************************************
* Wrappers used to call into Breakpad code *
******************************************************************************/
use std::{
ffi::{c_char, c_void, OsString},
ptr::NonNull,
};
use anyhow::{bail, Result};
use crash_helper_common::{BreakpadChar, BreakpadData, BreakpadString};
use crate::crash_generation::BreakpadProcessId;
#[cfg(target_os = "windows")]
type BreakpadInitType = *const u16;
#[cfg(target_os = "macos")]
type BreakpadInitType = *const c_char;
#[cfg(any(target_os = "linux", target_os = "android"))]
type BreakpadInitType = std::os::fd::RawFd;
extern "C" {
fn CrashGenerationServer_init(
breakpad_data: BreakpadInitType,
minidump_path: *const BreakpadChar,
cb: extern "C" fn(BreakpadProcessId, *const c_char, *const BreakpadChar),
) -> *mut c_void;
fn CrashGenerationServer_shutdown(server: *mut c_void);
}
pub(crate) struct BreakpadCrashGenerator {
ptr: NonNull<c_void>,
}
// Safety: We own the pointer to the Breakpad C++ CrashGeneration server object
// so we can safely transfer this object to another thread.
unsafe impl Send for BreakpadCrashGenerator {}
// Safety: All mutations to the pointer to the Breakpad C++ CrashGeneration
// server happen within this object meaning it's safe to read it from different
// threads.
unsafe impl Sync for BreakpadCrashGenerator {}
impl BreakpadCrashGenerator {
pub(crate) fn new(
breakpad_data: BreakpadData,
path: OsString,
finalize_callback: extern "C" fn(BreakpadProcessId, *const c_char, *const BreakpadChar),
) -> Result<BreakpadCrashGenerator> {
let breakpad_raw_data = breakpad_data.into_raw();
// SAFETY: We're converting a valid string for use within C++ code.
let path_ptr = unsafe { path.into_raw() };
// SAFETY: Calling into breakpad code with parameters that have been previously validated.
let breakpad_server =
unsafe { CrashGenerationServer_init(breakpad_raw_data, path_ptr, finalize_callback) };
// Retake ownership of the raw data & strings so we don't leak them.
let _breakpad_data = BreakpadData::new(breakpad_raw_data);
// SAFETY: We've allocated this string within this same block.
let _path = unsafe { <OsString as BreakpadString>::from_raw(path_ptr) };
if breakpad_server.is_null() {
bail!("Could not initialize Breakpad crash generator");
}
Ok(BreakpadCrashGenerator {
// SAFETY: We already verified that the pointer is non-null.
ptr: unsafe { NonNull::new(breakpad_server).unwrap_unchecked() },
})
}
}
impl Drop for BreakpadCrashGenerator {
fn drop(&mut self) {
// SAFETY: The pointer we're passing is guaranteed to be non-null and
// valid since we created it ourselves during construction.
unsafe {
CrashGenerationServer_shutdown(self.ptr.as_ptr());
}
}
}

View File

@@ -0,0 +1,436 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
pub mod crash_annotations {
include!(concat!(env!("OUT_DIR"), "/crash_annotations.rs"));
}
#[cfg(target_os = "windows")]
mod windows;
use anyhow::{bail, Result};
use crash_annotations::{
should_include_annotation, type_of_annotation, CrashAnnotation, CrashAnnotationType,
};
use crash_helper_common::{
messages::{self, Message},
AncillaryData, BreakpadChar, BreakpadData, BreakpadString, Pid,
};
use mozannotation_server::{AnnotationData, CAnnotation};
use num_traits::FromPrimitive;
use once_cell::sync::Lazy;
use std::{
collections::HashMap,
convert::TryInto,
ffi::{c_char, CStr, CString, OsStr, OsString},
fs::File,
io::{Seek, SeekFrom, Write},
mem::size_of,
path::{Path, PathBuf},
sync::Mutex,
};
#[cfg(target_os = "windows")]
use windows_sys::Win32::Foundation::HANDLE;
use crate::{
breakpad_crash_generator::BreakpadCrashGenerator,
phc::{self, StackTrace},
};
struct CrashReport {
path: OsString,
error: Option<CString>,
}
impl CrashReport {
fn new(path: &OsStr, error: &Option<CString>) -> CrashReport {
CrashReport {
path: path.to_owned(),
error: error.to_owned(),
}
}
}
// Table holding all the crash reports we've generated. It's indexed by PID and
// new crash reports are insterted in the corresponding vector in order of
// arrival. When crashes are retrieved they're similarly pulled out in the
// order they've arrived.
static CRASH_REPORTS: Lazy<Mutex<HashMap<Pid, Vec<CrashReport>>>> = Lazy::new(Default::default);
/******************************************************************************
* Crash generator *
******************************************************************************/
#[derive(PartialEq)]
enum MinidumpOrigin {
Breakpad,
WindowsErrorReporting,
}
#[derive(PartialEq)]
enum CrashGeneratorState {
Disconnected,
Connected,
Initialized,
}
pub(crate) struct CrashGenerator {
minidump_path: OsString,
breakpad_server: Option<BreakpadCrashGenerator>,
release_channel: String,
client_pid: Pid,
state: CrashGeneratorState,
}
impl CrashGenerator {
pub(crate) fn new(client_pid: Pid) -> Result<CrashGenerator> {
Ok(CrashGenerator {
minidump_path: OsString::default(),
breakpad_server: None,
release_channel: String::new(),
client_pid,
state: CrashGeneratorState::Disconnected,
})
}
pub(crate) fn client_connect(&mut self, pid: Pid) -> bool {
match self.state {
CrashGeneratorState::Disconnected => {
if self.client_pid != pid {
// We accept connections only from the main process
return false;
}
self.state = CrashGeneratorState::Connected;
}
CrashGeneratorState::Connected | CrashGeneratorState::Initialized => {
// Once we're connected we'll accept further incoming connections but limit what the corresponding clients can do
}
}
true
}
// Process a message received from the client. Return an optional reply
// that will be sent back to the client.
pub(crate) fn client_message(
&mut self,
kind: messages::Kind,
data: &[u8],
ancillary_data: Option<AncillaryData>,
pid: Pid,
) -> Result<Option<Box<dyn Message>>> {
match kind {
messages::Kind::Initialize => {
if (self.state != CrashGeneratorState::Connected) || (pid != self.client_pid) {
panic!("Already initialized or received a message from the wrong process");
}
let message = messages::Initialize::decode(data, ancillary_data)?;
self.initialize(message.breakpad_data, message.path, message.release_channel)?;
Ok(Some(Box::new(messages::InitializeReply::new())))
}
messages::Kind::TransferMinidump => {
if (self.state != CrashGeneratorState::Initialized) || (pid != self.client_pid) {
panic!(
"Not initialized or attempting to request a minidump from a child process"
);
}
let message = messages::TransferMinidump::decode(data, ancillary_data)?;
Ok(Some(Box::new(self.transfer_minidump(message.pid))))
}
messages::Kind::GenerateMinidump => {
todo!("Implement all messages");
}
#[cfg(target_os = "windows")]
messages::Kind::WindowsErrorReporting => {
let message =
messages::WindowsErrorReportingMinidump::decode(data, ancillary_data)?;
let _ = self.generate_wer_minidump(message);
Ok(Some(Box::new(
messages::WindowsErrorReportingMinidumpReply::new(),
)))
}
kind => {
bail!("Unexpected message {:?}", kind);
}
}
}
fn initialize(
&mut self,
breakpad_data: BreakpadData,
path: OsString,
release_channel: String,
) -> Result<()> {
self.minidump_path = path.clone();
self.release_channel = release_channel;
self.breakpad_server = Some(BreakpadCrashGenerator::new(
breakpad_data,
path,
finalize_breakpad_minidump,
)?);
self.state = CrashGeneratorState::Initialized;
Ok(())
}
fn transfer_minidump(&self, pid: Pid) -> messages::TransferMinidumpReply {
let mut map = CRASH_REPORTS.lock().unwrap();
if let Some(mut entry) = map.remove(&pid) {
let crash_report = entry.remove(0);
if !entry.is_empty() {
map.insert(pid, entry);
}
messages::TransferMinidumpReply::new(crash_report.path, crash_report.error)
} else {
// Report not found, reply with a zero length path
messages::TransferMinidumpReply::new(OsString::new(), None)
}
}
}
/******************************************************************************
* Crash annotations *
******************************************************************************/
macro_rules! read_numeric_annotation {
($t:ty,$d:expr) => {
if let AnnotationData::ByteBuffer(buff) = $d {
if buff.len() == size_of::<$t>() {
let value = buff.get(0..size_of::<$t>()).map(|bytes| {
let bytes: [u8; size_of::<$t>()] = bytes.try_into().unwrap();
<$t>::from_ne_bytes(bytes)
});
value.map(|value| value.to_string().into_bytes())
} else {
None
}
} else {
None
}
};
}
fn write_phc_annotations(file: &mut File, buff: &[u8]) -> Result<()> {
let addr_info = phc::AddrInfo::from_bytes(buff)?;
if addr_info.kind == phc::Kind::Unknown {
return Ok(());
}
write!(
file,
"\"PHCKind\":\"{}\",\
\"PHCBaseAddress\":\"{}\",\
\"PHCUsableSize\":\"{}\",",
addr_info.kind_as_str(),
addr_info.base_addr as usize,
addr_info.usable_size,
)?;
if addr_info.alloc_stack.has_stack != 0 {
write!(
file,
"\"PHCAllocStack\":\"{}\",",
serialize_phc_stack(&addr_info.alloc_stack)
)?;
}
if addr_info.free_stack.has_stack != 0 {
write!(
file,
"\"PHCFreeStack\":\"{}\",",
serialize_phc_stack(&addr_info.free_stack)
)?;
}
Ok(())
}
fn serialize_phc_stack(stack_trace: &StackTrace) -> String {
let mut string = String::new();
for i in 0..stack_trace.length {
string.push_str(&(stack_trace.pcs[i] as usize).to_string());
string.push(',');
}
string.pop();
string
}
#[repr(C)]
pub struct BreakpadProcessId {
pub pid: Pid,
#[cfg(target_os = "macos")]
pub task: u32,
#[cfg(target_os = "windows")]
pub handle: HANDLE,
}
/// This reads the crash annotations, writes them to the .extra file and
/// finally stores the resulting minidump in the global hash table.
extern "C" fn finalize_breakpad_minidump(
process_id: BreakpadProcessId,
error_ptr: *const c_char,
minidump_path_ptr: *const BreakpadChar,
) {
let minidump_path = PathBuf::from(<OsString as BreakpadString>::from_ptr(minidump_path_ptr));
let error = if !error_ptr.is_null() {
// SAFETY: The string is a valid C string we passed in ourselves.
Some(unsafe { CStr::from_ptr(error_ptr) }.to_owned())
} else {
None
};
finalize_crash_report(process_id, error, &minidump_path, MinidumpOrigin::Breakpad);
}
fn finalize_crash_report(
process_id: BreakpadProcessId,
error: Option<CString>,
minidump_path: &Path,
origin: MinidumpOrigin,
) {
let mut extra_path = PathBuf::from(minidump_path);
extra_path.set_extension("extra");
let annotations = retrieve_annotations(&process_id, origin);
let extra_file_written = annotations
.map(|annotations| write_extra_file(&annotations, &extra_path))
.is_ok();
let path = minidump_path.as_os_str();
let error = if !extra_file_written {
Some(CString::new("MissingAnnotations").unwrap())
} else {
error
};
let map = &mut CRASH_REPORTS.lock().unwrap();
let entry = map.entry(process_id.pid);
entry
.and_modify(|entry| entry.push(CrashReport::new(path, &error)))
.or_insert_with(|| vec![CrashReport::new(path, &error)]);
}
fn retrieve_annotations(
process_id: &BreakpadProcessId,
origin: MinidumpOrigin,
) -> Result<Vec<CAnnotation>> {
#[cfg(target_os = "windows")]
let res = mozannotation_server::retrieve_annotations(
process_id.handle,
CrashAnnotation::Count as usize,
);
#[cfg(any(target_os = "linux", target_os = "android"))]
let res =
mozannotation_server::retrieve_annotations(process_id.pid, CrashAnnotation::Count as usize);
#[cfg(target_os = "macos")]
let res = mozannotation_server::retrieve_annotations(
process_id.task,
CrashAnnotation::Count as usize,
);
let mut annotations = res?;
if origin == MinidumpOrigin::WindowsErrorReporting {
annotations.push(CAnnotation {
id: CrashAnnotation::WindowsErrorReporting as u32,
data: AnnotationData::ByteBuffer(vec![1]),
});
}
Ok(annotations)
}
fn write_extra_file(annotations: &Vec<CAnnotation>, path: &Path) -> Result<()> {
let mut annotations_written: usize = 0;
let mut file = File::create(path)?;
write!(&mut file, "{{")?;
for annotation in annotations {
if let Some(annotation_id) = CrashAnnotation::from_u32(annotation.id) {
if annotation_id == CrashAnnotation::PHCBaseAddress {
if let AnnotationData::ByteBuffer(buff) = &annotation.data {
write_phc_annotations(&mut file, buff)?;
}
continue;
}
let value = match type_of_annotation(annotation_id) {
CrashAnnotationType::String => match &annotation.data {
AnnotationData::String(string) => Some(escape_value(string.as_bytes())),
AnnotationData::ByteBuffer(buffer) => Some(escape_value(buffer)),
_ => None,
},
CrashAnnotationType::Boolean => {
if let AnnotationData::ByteBuffer(buff) = &annotation.data {
if buff.len() == 1 {
Some(vec![if buff[0] != 0 { b'1' } else { b'0' }])
} else {
None
}
} else {
None
}
}
CrashAnnotationType::U32 => {
read_numeric_annotation!(u32, &annotation.data)
}
CrashAnnotationType::U64 => {
read_numeric_annotation!(u64, &annotation.data)
}
CrashAnnotationType::USize => {
read_numeric_annotation!(usize, &annotation.data)
}
};
if let Some(value) = value {
if !value.is_empty() && should_include_annotation(annotation_id, &value) {
write!(&mut file, "\"{annotation_id:}\":\"")?;
file.write_all(&value)?;
write!(&mut file, "\",")?;
annotations_written += 1;
}
}
}
}
if annotations_written > 0 {
// Drop the last comma
file.seek(SeekFrom::Current(-1))?;
}
writeln!(&mut file, "}}")?;
Ok(())
}
// Escapes the characters of a crash annotation so that they appear correctly
// within the JSON output, escaping non-visible characters and the like. This
// does not try to make the output valid UTF-8 because the input might be
// corrupted so there's no point in that.
fn escape_value(input: &[u8]) -> Vec<u8> {
let mut escaped = Vec::<u8>::with_capacity(input.len() + 2);
for &c in input {
if c <= 0x1f || c == b'\\' || c == b'"' {
escaped.extend(b"\\u00");
escaped.push(hex_digit_as_ascii_char((c & 0x00f0) >> 4));
escaped.push(hex_digit_as_ascii_char(c & 0x000f));
} else {
escaped.push(c)
}
}
escaped
}
fn hex_digit_as_ascii_char(value: u8) -> u8 {
if value < 10 {
b'0' + value
} else {
b'a' + (value - 10)
}
}

View File

@@ -0,0 +1,166 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
use super::{finalize_crash_report, BreakpadProcessId, CrashGenerator};
use crash_helper_common::{messages, Pid};
use std::{
convert::TryInto,
fs::{create_dir_all, File},
mem::{size_of, zeroed},
os::windows::io::AsRawHandle,
path::PathBuf,
ptr::{null, null_mut},
};
use uuid::Uuid;
use windows_sys::Win32::{
Foundation::{FALSE, HANDLE},
System::{
Diagnostics::Debug::{
MiniDumpWithFullMemoryInfo, MiniDumpWithIndirectlyReferencedMemory,
MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules, MiniDumpWriteDump,
EXCEPTION_POINTERS, EXCEPTION_RECORD, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE,
},
SystemInformation::{
VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION,
VER_MINORVERSION, VER_SERVICEPACKMAJOR, VER_SERVICEPACKMINOR,
},
SystemServices::VER_GREATER_EQUAL,
Threading::{OpenProcess, PROCESS_ALL_ACCESS},
},
};
impl CrashGenerator {
pub(super) fn generate_wer_minidump(
&self,
message: messages::WindowsErrorReportingMinidump,
) -> Result<(), ()> {
let (minidump_file, path) = self.create_minidump_file()?;
let minidump_type: MINIDUMP_TYPE = self.get_minidump_type();
let mut context = message.context;
let mut exception_records = message.exception_records;
let exception_records_ptr = link_exception_records(&mut exception_records);
let handle = open_process(message.pid)?;
let mut exception_pointers = EXCEPTION_POINTERS {
ExceptionRecord: exception_records_ptr,
ContextRecord: &mut context as *mut _,
};
let exception = MINIDUMP_EXCEPTION_INFORMATION {
ThreadId: message.tid,
ExceptionPointers: &mut exception_pointers,
ClientPointers: FALSE,
};
let res = unsafe {
MiniDumpWriteDump(
handle,
message.pid,
minidump_file.as_raw_handle() as _,
minidump_type,
&exception,
/* UserStreamParam */ null(),
/* CallbackParam */ null(),
)
};
if res != FALSE {
let process_id = BreakpadProcessId {
pid: message.pid,
handle,
};
finalize_crash_report(
process_id,
None,
&path,
super::MinidumpOrigin::WindowsErrorReporting,
);
}
Ok(())
}
fn get_minidump_type(&self) -> MINIDUMP_TYPE {
let mut minidump_type = MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules;
if self.release_channel.eq("nightly") {
// This is Nightly only because this doubles the size of minidumps based
// on the experimental data.
minidump_type |= MiniDumpWithProcessThreadData;
// dbghelp.dll on Win7 can't handle overlapping memory regions so we only
// enable this feature on Win8 or later.
if is_windows8_or_later() {
// This allows us to examine heap objects referenced from stack objects
// at the cost of further doubling the size of minidumps.
minidump_type |= MiniDumpWithIndirectlyReferencedMemory;
}
}
minidump_type
}
fn create_minidump_file(&self) -> Result<(File, PathBuf), ()> {
// Make sure that the target directory is present
create_dir_all(&self.minidump_path).map_err(|_| ())?;
let uuid = Uuid::new_v4()
.as_hyphenated()
.encode_lower(&mut Uuid::encode_buffer())
.to_string();
let path = PathBuf::from(self.minidump_path.clone()).join(uuid + ".dmp");
let file = File::create(&path).map_err(|_| ())?;
Ok((file, path))
}
}
fn link_exception_records(exception_records: &mut Vec<EXCEPTION_RECORD>) -> *mut EXCEPTION_RECORD {
let mut iter = exception_records.iter_mut().peekable();
while let Some(exception_record) = iter.next() {
exception_record.ExceptionRecord = null_mut();
if let Some(next) = iter.peek_mut() {
exception_record.ExceptionRecord = *next as *mut _;
}
}
if exception_records.is_empty() {
null_mut()
} else {
exception_records.as_mut_ptr()
}
}
fn is_windows8_or_later() -> bool {
let mut info = OSVERSIONINFOEXW {
dwOSVersionInfoSize: size_of::<OSVERSIONINFOEXW>().try_into().unwrap(),
dwMajorVersion: 6,
dwMinorVersion: 2,
..unsafe { zeroed() }
};
unsafe {
let mut mask: u64 = 0;
let ge: u8 = VER_GREATER_EQUAL.try_into().unwrap();
mask = VerSetConditionMask(mask, VER_MAJORVERSION, ge);
mask = VerSetConditionMask(mask, VER_MINORVERSION, ge);
mask = VerSetConditionMask(mask, VER_SERVICEPACKMAJOR, ge);
mask = VerSetConditionMask(mask, VER_SERVICEPACKMINOR, ge);
VerifyVersionInfoW(
&mut info,
VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR,
mask,
) != 0
}
}
fn open_process(pid: Pid) -> Result<HANDLE, ()> {
// SAFETY: No pointers involved, worst case we get an error
match unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) } {
0 => Err(()),
handle => Ok(handle),
}
}

View File

@@ -0,0 +1,97 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
use anyhow::Result;
use crash_helper_common::{
errors::IPCError, messages, wait_for_events, IPCConnector, IPCEvent, IPCListener, Pid,
};
use crate::crash_generation::CrashGenerator;
#[derive(PartialEq)]
pub enum IPCServerState {
Running,
ClientDisconnected,
}
pub(crate) struct IPCServer {
listener: IPCListener,
connectors: Vec<IPCConnector>,
client_pid: Pid,
}
impl IPCServer {
pub(crate) fn new(client_pid: Pid) -> Result<IPCServer, IPCError> {
let mut listener = IPCListener::new(client_pid)?;
listener.listen()?;
Ok(IPCServer {
listener,
connectors: Vec::with_capacity(1),
client_pid,
})
}
pub(crate) fn run(
&mut self,
generator: &mut CrashGenerator,
) -> Result<IPCServerState, IPCError> {
let events = wait_for_events(&mut self.listener, &mut self.connectors)?;
for event in events {
match event {
IPCEvent::Connect(connector) => {
if generator.client_connect(connector.endpoint_pid()) {
self.connectors.push(connector);
}
}
IPCEvent::Header(index, header) => {
let connector = self
.connectors
.get_mut(index)
.expect("Invalid connector index");
let _res = Self::handle_message(connector, &header, generator);
// TODO: Errors at this level are always survivable, but we
// should probably log them.
}
IPCEvent::Disconnect(index) => {
let connector = self
.connectors
.get_mut(index)
.expect("Invalid connector index");
if connector.endpoint_pid() == self.client_pid {
// The main process disconnected, leave
return Ok(IPCServerState::ClientDisconnected);
} else {
// This closes the connection
let _ = self.connectors.remove(index);
}
}
}
}
Ok(IPCServerState::Running)
}
fn handle_message(
connector: &mut IPCConnector,
header: &messages::Header,
generator: &mut CrashGenerator,
) -> Result<()> {
let (data, ancillary_data) = connector.recv(header.size)?;
let reply = generator.client_message(
header.kind,
&data,
ancillary_data,
connector.endpoint_pid(),
)?;
if let Some(reply) = reply {
connector.send_message(reply.as_ref())?;
}
Ok(())
}
}

View File

@@ -0,0 +1,60 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#[cfg(any(target_os = "linux", target_os = "android"))]
extern crate rust_minidump_writer_linux;
mod breakpad_crash_generator;
mod crash_generation;
mod ipc_server;
mod phc;
use cfg_if::cfg_if;
use crash_helper_common::Pid;
use std::{
ffi::{c_char, CStr},
path::PathBuf,
str::FromStr,
};
use crash_generation::CrashGenerator;
use ipc_server::{IPCServer, IPCServerState};
#[no_mangle]
pub extern "C" fn crash_generator_logic(client_pid: Pid) -> i32 {
let crash_generator = CrashGenerator::new(client_pid)
.unwrap();
let ipc_server = IPCServer::new(client_pid)
.unwrap();
cfg_if! {
if #[cfg(target_os = "android")] {
// On Android the main thread is used to respond to the intents so
// we can't block it. Run the crash generation loop in a separate
// thread.
let _ = std::thread::spawn(move || {
main_loop(ipc_server, crash_generator)
});
0
} else {
main_loop(ipc_server, crash_generator)
}
}
}
fn main_loop(mut ipc_server: IPCServer, mut crash_generator: CrashGenerator) -> i32 {
loop {
match ipc_server.run(&mut crash_generator) {
Ok(_result @ IPCServerState::ClientDisconnected) => {
return 0;
}
Err(error) => {
return -1;
}
_ => {} // Go on
}
}
}

View File

@@ -0,0 +1,100 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
// The types here must match the ones in memory/build/PHC.h
use anyhow::{bail, Result};
use std::{
ffi::{c_char, c_void},
mem::{size_of, MaybeUninit},
slice,
};
#[repr(C)]
#[derive(Clone, Copy, PartialEq)]
#[allow(dead_code)]
pub(crate) enum Kind {
Unknown = 0,
NeverAllocatedPage = 1,
InUsePage = 2,
FreedPage = 3,
GuardPage = 4,
}
const MAX_FRAMES: usize = 16;
#[repr(C)]
pub(crate) struct StackTrace {
pub(crate) length: usize,
pub(crate) pcs: [*const c_void; MAX_FRAMES],
pub(crate) has_stack: c_char,
}
#[repr(C)]
pub(crate) struct AddrInfo {
pub(crate) kind: Kind,
pub(crate) base_addr: *const c_void,
pub(crate) usable_size: usize,
pub(crate) alloc_stack: StackTrace,
pub(crate) free_stack: StackTrace,
pub(crate) phc_was_locked: c_char,
}
impl AddrInfo {
pub(crate) fn from_bytes(buff: &[u8]) -> Result<AddrInfo> {
if buff.len() != size_of::<AddrInfo>() {
bail!(
"PHC AddrInfo structure size {} doesn't match expected size {}",
buff.len(),
size_of::<AddrInfo>()
);
}
let mut addr_info = MaybeUninit::<AddrInfo>::uninit();
// SAFETY: MaybeUninit<u8> is always valid, even for padding bytes
let uninit_addr_info = unsafe {
slice::from_raw_parts_mut(
addr_info.as_mut_ptr() as *mut MaybeUninit<u8>,
size_of::<AddrInfo>(),
)
};
for (index, &value) in buff.iter().enumerate() {
uninit_addr_info[index].write(value);
}
let addr_info = unsafe { addr_info.assume_init() };
if !addr_info.check_consistency() {
bail!("PHC AddrInfo structure is inconsistent");
}
Ok(addr_info)
}
pub(crate) fn kind_as_str(&self) -> &'static str {
match self.kind {
Kind::Unknown => "Unknown(?!)",
Kind::NeverAllocatedPage => "NeverAllocatedPage",
Kind::InUsePage => "InUsePage(?!)",
Kind::FreedPage => "FreedPage",
Kind::GuardPage => "GuardPage",
}
}
fn check_consistency(&self) -> bool {
let kind_value = self.kind as u32;
if (kind_value > Kind::GuardPage as u32)
|| (self.alloc_stack.length > MAX_FRAMES)
|| (self.free_stack.length > MAX_FRAMES)
|| (self.alloc_stack.has_stack > 1)
|| (self.free_stack.has_stack > 1)
|| (self.phc_was_locked > 1)
{
return false;
}
true
}
}

View File

@@ -46,6 +46,7 @@
#include "common/linux/linux_libc_support.h"
#include "common/linux/memory_mapped_file.h"
#include "common/using_std_string.h"
#include "google_breakpad/common/minidump_format.h"
#include "third_party/lss/linux_syscall_support.h"
namespace google_breakpad {

View File

@@ -25,6 +25,11 @@ UNIFIED_SOURCES = [
FINAL_LIBRARY = "xul"
if CONFIG["MOZ_CRASHREPORTER"]:
if CONFIG["OS_ARCH"] != "WINNT":
USE_LIBS += [
"breakpad_client",
]
if CONFIG["OS_ARCH"] == "WINNT":
DIRS += [
"breakpad-windows-libxul",
@@ -53,10 +58,6 @@ if CONFIG["MOZ_CRASHREPORTER"]:
"google-breakpad/src/processor",
]
UNIFIED_SOURCES += [
"linux_utils.cc",
]
EXPORTS += [
"linux_utils.h",
]
@@ -66,13 +67,15 @@ if CONFIG["MOZ_CRASHREPORTER"]:
if CONFIG["OS_TARGET"] != "Android":
DIRS += ["client/app"]
if CONFIG["OS_TARGET"] == "Android":
else:
DIRS += ["minidump-analyzer/android"]
DIRS += [
"breakpad_wrapper",
"crash_helper",
"crash_helper_client",
"crash_helper_server",
"mozannotation_client",
"mozannotation_server",
]
TEST_DIRS += ["test"]
@@ -115,7 +118,9 @@ if CONFIG["MOZ_CRASHREPORTER"]:
"google-breakpad/src",
]
USE_LIBS += ["jsoncpp"]
USE_LIBS += [
"jsoncpp",
]
PYTHON_UNITTEST_MANIFESTS += [
"tools/python.toml",

View File

@@ -11,7 +11,5 @@ language = "C++"
include_guard = "mozannotation_client_ffi_generated_h"
includes = ["nsStringFwd.h"]
[export.rename]
"ThinVec" = "nsTArray"
[export]
exclude = ["nsCString"]

View File

@@ -10,8 +10,7 @@ license = "MPL-2.0"
[dependencies]
mozannotation_client = { path = "../mozannotation_client/" }
process_reader = { path = "../process_reader/" }
thin-vec = { version = "0.2.7", features = ["gecko-ffi"] }
thiserror = "1.0.38"
thiserror = "2"
[target."cfg(any(target_os = \"linux\", target_os = \"android\"))".dependencies]
memoffset = "0.8"
memoffset = "0.9"

View File

@@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
mod errors;
pub mod errors;
use crate::errors::*;
#[cfg(any(target_os = "linux", target_os = "android"))]
@@ -16,20 +16,15 @@ use mozannotation_client::{Annotation, AnnotationContents, AnnotationMutex};
#[cfg(any(target_os = "linux", target_os = "android"))]
use mozannotation_client::{MozAnnotationNote, ANNOTATION_NOTE_NAME, ANNOTATION_TYPE};
use std::cmp::min;
use std::iter::FromIterator;
use std::ffi::CString;
use std::mem::{size_of, ManuallyDrop};
use std::ptr::null_mut;
use thin_vec::ThinVec;
#[repr(C)]
#[derive(Debug)]
pub enum AnnotationData {
Empty,
ByteBuffer(ThinVec<u8>),
ByteBuffer(Vec<u8>),
String(CString),
}
#[repr(C)]
#[derive(Debug)]
pub struct CAnnotation {
#[allow(dead_code)] // This is implicitly stored to and used externally
pub id: u32,
@@ -38,39 +33,10 @@ pub struct CAnnotation {
pub type ProcessHandle = process_reader::ProcessHandle;
/// Return the annotations of a given process.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_retrieve(
process: usize,
max_annotations: usize,
) -> *mut ThinVec<CAnnotation> {
let result = retrieve_annotations(process as _, max_annotations);
match result {
// Leak the object as it will be owned by the C++ code from now on
Ok(annotations) => Box::into_raw(annotations) as *mut _,
Err(_) => null_mut(),
}
}
/// Free the annotations returned by `mozannotation_retrieve()`.
///
/// # Safety
///
/// `ptr` must contain the value returned by a call to
/// `mozannotation_retrieve()` and be called only once.
#[no_mangle]
pub unsafe extern "C" fn mozannotation_free(ptr: *mut ThinVec<CAnnotation>) {
// The annotation vector will be automatically destroyed when the contents
// of this box are automatically dropped at the end of the function.
let _box = Box::from_raw(ptr);
}
pub fn retrieve_annotations(
process: ProcessHandle,
max_annotations: usize,
) -> Result<Box<ThinVec<CAnnotation>>, AnnotationsRetrievalError> {
) -> Result<Vec<CAnnotation>, AnnotationsRetrievalError> {
let reader = ProcessReader::new(process)?;
let address = find_annotations(&reader)?;
@@ -91,7 +57,7 @@ pub fn retrieve_annotations(
let vec_pointer = annotation_table.get_ptr();
let length = annotation_table.len();
let mut annotations = ThinVec::<CAnnotation>::with_capacity(min(max_annotations, length));
let mut annotations = Vec::<CAnnotation>::with_capacity(min(max_annotations, length));
for i in 0..length {
let annotation_address = unsafe { vec_pointer.add(i) };
@@ -100,7 +66,7 @@ pub fn retrieve_annotations(
}
}
Ok(Box::new(annotations))
Ok(annotations)
}
fn find_annotations(reader: &ProcessReader) -> Result<usize, AnnotationsRetrievalError> {
@@ -159,19 +125,19 @@ fn read_annotation(
AnnotationContents::Empty => {}
AnnotationContents::NSCStringPointer => {
let string = copy_nscstring(reader, raw_annotation.address)?;
annotation.data = AnnotationData::ByteBuffer(string);
annotation.data = AnnotationData::String(string);
}
AnnotationContents::CStringPointer => {
let string = copy_null_terminated_string_pointer(reader, raw_annotation.address)?;
annotation.data = AnnotationData::ByteBuffer(string);
annotation.data = AnnotationData::String(string);
}
AnnotationContents::CString => {
let string = copy_null_terminated_string(reader, raw_annotation.address)?;
annotation.data = AnnotationData::ByteBuffer(string);
annotation.data =
AnnotationData::String(reader.copy_null_terminated_string(raw_annotation.address)?);
}
AnnotationContents::ByteBuffer(size) | AnnotationContents::OwnedByteBuffer(size) => {
let string = copy_bytebuffer(reader, raw_annotation.address, size)?;
annotation.data = AnnotationData::ByteBuffer(string);
let buffer = copy_bytebuffer(reader, raw_annotation.address, size)?;
annotation.data = AnnotationData::ByteBuffer(buffer);
}
};
@@ -181,23 +147,15 @@ fn read_annotation(
fn copy_null_terminated_string_pointer(
reader: &ProcessReader,
address: usize,
) -> Result<ThinVec<u8>, process_reader::error::ReadError> {
) -> Result<CString, process_reader::error::ReadError> {
let buffer_address = reader.copy_object::<usize>(address)?;
copy_null_terminated_string(reader, buffer_address)
}
fn copy_null_terminated_string(
reader: &ProcessReader,
address: usize,
) -> Result<ThinVec<u8>, process_reader::error::ReadError> {
let string = reader.copy_null_terminated_string(address)?;
Ok(ThinVec::<u8>::from(string.as_bytes()))
reader.copy_null_terminated_string(buffer_address)
}
fn copy_nscstring(
reader: &ProcessReader,
address: usize,
) -> Result<ThinVec<u8>, process_reader::error::ReadError> {
) -> Result<CString, process_reader::error::ReadError> {
// HACK: This assumes the layout of the nsCString object
let length_address = address + size_of::<usize>();
let length = reader.copy_object::<u32>(length_address)?;
@@ -214,9 +172,11 @@ fn copy_nscstring(
vec.truncate(nul_byte_pos);
}
Ok(ThinVec::from(vec))
// SAFETY: This is safe because we verified that there are no nul
// characters inside the string.
Ok(unsafe { CString::from_vec_unchecked(vec) })
} else {
Ok(ThinVec::<u8>::new())
Ok(CString::default())
}
}
@@ -224,7 +184,6 @@ fn copy_bytebuffer(
reader: &ProcessReader,
address: usize,
size: u32,
) -> Result<ThinVec<u8>, process_reader::error::ReadError> {
let value = reader.copy_array::<u8>(address, size as _)?;
Ok(ThinVec::<u8>::from_iter(value.into_iter()))
) -> Result<Vec<u8>, process_reader::error::ReadError> {
reader.copy_array::<u8>(address, size as _)
}

View File

@@ -6,11 +6,11 @@ edition = "2018"
license = "MPL-2.0"
[dependencies]
crash_helper_client = { path = "../crash_helper_client" }
libc = "0.2.0"
mozilla-central-workspace-hack = { version = "0.1", features = [
"mozwer_s",
], optional = true }
process_reader = { path = "../process_reader/" }
rust-ini = "0.10"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
@@ -22,6 +22,7 @@ features = [
"Wdk_System_Threading",
"Win32_Foundation",
"Win32_Security",
"Win32_Storage_FileSystem",
"Win32_System_Com",
"Win32_System_Diagnostics_Debug",
"Win32_System_ErrorReporting",
@@ -29,7 +30,6 @@ features = [
"Win32_System_ProcessStatus",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging",
]

View File

@@ -2,20 +2,20 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crash_helper_client::report_external_exception;
use ini::Ini;
use libc::time;
use process_reader::ProcessReader;
use serde::Serialize;
use serde_json::ser::to_writer;
use std::convert::TryInto;
use std::ffi::{c_void, OsString};
use std::fs::{read_to_string, DirBuilder, File, OpenOptions};
use std::io::{BufRead, BufReader, Write};
use std::mem::{size_of, transmute, zeroed};
use std::mem::{size_of, zeroed};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle};
use std::os::windows::io::AsRawHandle;
use std::path::{Path, PathBuf};
use std::ptr::{addr_of, null, null_mut};
use std::ptr::{null, null_mut};
use std::slice::from_raw_parts;
use uuid::Uuid;
use windows_sys::core::{HRESULT, PWSTR};
@@ -24,7 +24,7 @@ use windows_sys::Win32::{
Foundation::{
CloseHandle, GetLastError, SetLastError, BOOL, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS,
EXCEPTION_BREAKPOINT, E_UNEXPECTED, FALSE, FILETIME, HANDLE, HWND, LPARAM, MAX_PATH,
STATUS_SUCCESS, S_OK, TRUE, WAIT_OBJECT_0,
STATUS_SUCCESS, S_OK, TRUE,
},
Security::{
GetSidSubAuthority, GetSidSubAuthorityCount, GetTokenInformation, IsTokenRestricted,
@@ -34,12 +34,9 @@ use windows_sys::Win32::{
System::Diagnostics::Debug::{
GetThreadContext, MiniDumpWithFullMemoryInfo, MiniDumpWithIndirectlyReferencedMemory,
MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules, MiniDumpWriteDump,
WriteProcessMemory, EXCEPTION_POINTERS, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE,
EXCEPTION_POINTERS, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE,
},
System::ErrorReporting::WER_RUNTIME_EXCEPTION_INFORMATION,
System::Memory::{
VirtualAllocEx, VirtualFreeEx, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE,
},
System::ProcessStatus::K32GetModuleFileNameExW,
System::SystemInformation::{
VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION,
@@ -47,9 +44,8 @@ use windows_sys::Win32::{
},
System::SystemServices::{SECURITY_MANDATORY_MEDIUM_RID, VER_GREATER_EQUAL},
System::Threading::{
CreateProcessW, CreateRemoteThread, GetProcessId, GetProcessTimes, GetThreadId,
OpenProcess, OpenProcessToken, OpenThread, TerminateProcess, WaitForSingleObject,
CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, LPTHREAD_START_ROUTINE,
CreateProcessW, GetProcessId, GetProcessTimes, GetThreadId, OpenProcess, OpenProcessToken,
OpenThread, TerminateProcess, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT,
NORMAL_PRIORITY_CLASS, PROCESS_ALL_ACCESS, PROCESS_BASIC_INFORMATION, PROCESS_INFORMATION,
STARTUPINFOW, THREAD_GET_CONTEXT,
},
@@ -67,15 +63,6 @@ type PDWORD = *mut DWORD;
#[allow(non_camel_case_types)]
type PWER_RUNTIME_EXCEPTION_INFORMATION = *mut WER_RUNTIME_EXCEPTION_INFORMATION;
/* The following struct must be kept in sync with the identically named one in
* nsExceptionHandler.h. WER will use it to communicate with the main process
* when a child process is encountered. */
#[repr(C)]
struct WindowsErrorReportingData {
child_pid: DWORD,
minidump_name: [u8; 40],
}
// This value comes from GeckoProcessTypes.h
static MAIN_PROCESS_TYPE: u32 = 0;
@@ -174,14 +161,17 @@ fn out_of_process_exception_event_callback(
}
let process = exception_information.hProcess;
let application_info = ApplicationInformation::from_process(process)?;
let process_type: u32 = (context as usize).try_into().map_err(|_| ())?;
let startup_time = get_startup_time(process)?;
let crash_report = CrashReport::new(&application_info, startup_time, is_ui_hang);
crash_report.write_minidump(exception_information)?;
if process_type == MAIN_PROCESS_TYPE {
match is_sandboxed_process(process) {
Ok(false) => handle_main_process_crash(crash_report, &application_info),
Ok(false) => {
let application_info = ApplicationInformation::from_process(process)?;
let startup_time = get_startup_time(process)?;
let crash_report = CrashReport::new(&application_info, startup_time, is_ui_hang);
crash_report.write_minidump(exception_information)?;
handle_main_process_crash(crash_report, &application_info)
}
_ => {
// The parent process should never be sandboxed, bail out so the
// process which is impersonating it gets killed right away. Also
@@ -190,7 +180,7 @@ fn out_of_process_exception_event_callback(
}
}
} else {
handle_child_process_crash(crash_report, process)
handle_child_process_crash(exception_information)
}
}
@@ -245,87 +235,24 @@ fn handle_main_process_crash(
Ok(())
}
fn handle_child_process_crash(crash_report: CrashReport, child_process: HANDLE) -> Result<()> {
let parent_process = get_parent_process(child_process)?;
let process_reader = ProcessReader::new(parent_process).map_err(|_e| ())?;
let libxul_address = process_reader.find_module("xul.dll").map_err(|_e| ())?;
let wer_notify_proc = process_reader
.find_section(libxul_address, b"mozwerpt")
.map_err(|_e| ())?;
let wer_notify_proc = unsafe { transmute::<_, LPTHREAD_START_ROUTINE>(wer_notify_proc) };
let wer_data = WindowsErrorReportingData {
child_pid: get_process_id(child_process)?,
minidump_name: crash_report.get_minidump_name(),
};
let address = copy_object_into_process(parent_process, wer_data)?;
notify_main_process(parent_process, wer_notify_proc, address)
}
fn copy_object_into_process<T>(process: HANDLE, data: T) -> Result<*mut T> {
let address = unsafe {
VirtualAllocEx(
process,
null(),
size_of::<T>(),
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE,
)
};
if address.is_null() {
return Err(());
}
let res = unsafe {
WriteProcessMemory(
process,
address,
addr_of!(data) as *const _,
size_of::<T>(),
null_mut(),
)
};
if res == 0 {
unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) };
Err(())
} else {
Ok(address as *mut T)
}
}
fn notify_main_process(
process: HANDLE,
wer_notify_proc: LPTHREAD_START_ROUTINE,
address: *mut WindowsErrorReportingData,
fn handle_child_process_crash(
exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
) -> Result<()> {
let thread = unsafe {
CreateRemoteThread(
process,
null_mut(),
0,
wer_notify_proc,
address as LPVOID,
0,
null_mut(),
)
};
let process = unsafe { (*exception_information).hProcess };
let process_id = get_process_id(process)?;
let thread = unsafe { (*exception_information).hThread };
let thread_id = get_thread_id(thread)?;
let parent_process = get_parent_process(process)?;
let parent_pid = get_process_id(parent_process)?;
if thread == 0 {
unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) };
return Err(());
}
// From this point on the memory pointed to by address is owned by the
// thread we've created in the main process, so we don't free it.
let thread = unsafe { OwnedHandle::from_raw_handle(thread as RawHandle) };
// Don't wait forever as we want the process to get killed eventually
let res = unsafe { WaitForSingleObject(thread.as_raw_handle() as HANDLE, 5000) };
if res != WAIT_OBJECT_0 {
return Err(());
unsafe {
report_external_exception(
parent_pid,
process_id,
thread_id,
&raw mut (*exception_information).exceptionRecord,
&raw mut (*exception_information).context,
);
}
Ok(())
@@ -366,6 +293,13 @@ fn get_process_id(process: HANDLE) -> Result<DWORD> {
}
}
fn get_thread_id(thread: HANDLE) -> Result<DWORD> {
match unsafe { GetThreadId(thread) } {
0 => Err(()),
tid => Ok(tid),
}
}
fn get_process_handle(pid: DWORD) -> Result<HANDLE> {
let handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) };
if handle != 0 {
@@ -693,11 +627,6 @@ impl CrashReport {
self.get_pending_path().join(self.uuid.to_string() + ".dmp")
}
fn get_minidump_name(&self) -> [u8; 40] {
let bytes = (self.uuid.to_string() + ".dmp").into_bytes();
bytes[0..40].try_into().unwrap()
}
fn get_extra_file_path(&self) -> PathBuf {
self.get_pending_path()
.join(self.uuid.to_string() + ".extra")

View File

@@ -1,11 +1,11 @@
RustLibrary("mozwer_s")
OS_LIBS += [
"advapi32",
"dbghelp",
"kernel32",
"ntdll",
"ole32",
"psapi",
"shell32",
"user32",
"userenv",

View File

@@ -13,20 +13,18 @@
#include "nsComponentManagerUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryService.h"
#include "nsIFileStreams.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsTHashMap.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/EnumeratedRange.h"
#include "mozilla/Services.h"
#include "nsIObserverService.h"
#include "mozilla/Unused.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Printf.h"
#include "mozilla/RuntimeExceptionModule.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
@@ -35,12 +33,11 @@
#include "nsThreadUtils.h"
#include "nsThread.h"
#include "jsfriendapi.h"
#include "private/pprio.h"
#include "base/process_util.h"
#include "common/basictypes.h"
#include "mozilla/toolkit/crashreporter/mozannotation_client_ffi_generated.h"
#include "mozilla/toolkit/crashreporter/mozannotation_server_ffi_generated.h"
#include "mozilla/crash_helper_client_ffi_generated.h"
#ifdef MOZ_BACKGROUNDTASKS
# include "mozilla/BackgroundTasks.h"
@@ -82,12 +79,16 @@
# include "mac_utils.h"
#elif defined(XP_LINUX)
# include "nsIINIParser.h"
# if defined(MOZ_WIDGET_ANDROID)
# include "common/linux/eintr_wrapper.h"
# else
# include <sys/prctl.h> // For prctl() and PR_SET_PTRACER
# endif // defined(MOZ_WIDGET_ANDROID)
# include "common/linux/linux_libc_support.h"
# include "third_party/lss/linux_syscall_support.h"
# include "breakpad-client/linux/crash_generation/client_info.h"
# include "breakpad-client/linux/crash_generation/crash_generation_server.h"
# include "breakpad-client/linux/handler/exception_handler.h"
# include "common/linux/eintr_wrapper.h"
# include <fcntl.h>
# include <sys/types.h>
# include "sys/sysinfo.h"
@@ -109,7 +110,6 @@
#include <prio.h>
#include "mozilla/Mutex.h"
#include "nsDebug.h"
#include "nsCRT.h"
#include "nsIFile.h"
#include "mozilla/IOInterposer.h"
@@ -137,6 +137,8 @@ using google_breakpad::PageAllocator;
#endif
using namespace mozilla;
#ifdef MOZ_PHC
namespace mozilla::phc {
// Global instance that is retrieved by the process generating the crash report
@@ -144,6 +146,8 @@ MOZ_GLOBINIT mozilla::phc::AddrInfo gAddrInfo;
} // namespace mozilla::phc
#endif // defined(MOZ_PHC)
namespace CrashReporter {
#ifdef XP_WIN
@@ -154,6 +158,7 @@ typedef std::wstring xpstring;
# define XP_STRLEN(x) wcslen(x)
# define my_strlen strlen
# define my_memchr memchr
# define CRASH_HELPER_FILENAME u"crashhelper.exe"_ns
# define CRASH_REPORTER_FILENAME u"crashreporter.exe"_ns
# define XP_PATH_SEPARATOR L"\\"
# define XP_PATH_SEPARATOR_CHAR L'\\'
@@ -167,6 +172,7 @@ typedef char XP_CHAR;
typedef std::string xpstring;
# define XP_TEXT(x) x
# define CONVERT_XP_CHAR_TO_UTF16(x) NS_ConvertUTF8toUTF16(x)
# define CRASH_HELPER_FILENAME u"crashhelper"_ns
# define CRASH_REPORTER_FILENAME u"crashreporter"_ns
# define XP_PATH_SEPARATOR "/"
# define XP_PATH_SEPARATOR_CHAR '/'
@@ -207,12 +213,14 @@ MOZ_RUNINIT static std::optional<xpstring> defaultMemoryReportPath = {};
static const char kCrashMainID[] = "crash.main.3\n";
static Maybe<CrashHelperClient*> gCrashHelperClient;
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
static mozilla::Atomic<bool> gEncounteredChildException(false);
MOZ_CONSTINIT static nsCString gServerURL;
MOZ_RUNINIT static xpstring pendingDirectory;
MOZ_RUNINIT static xpstring crashReporterPath;
MOZ_RUNINIT static xpstring crashHelperPath;
MOZ_RUNINIT static xpstring memoryReportPath;
// Where crash events should go.
@@ -240,6 +248,8 @@ static char* androidUserSerial = nullptr;
static const char* androidStartServiceCommand = nullptr;
#endif
static ProcessId gCrashHelperPid = 0;
// this holds additional data sent via the API
static Mutex* notesFieldLock;
static nsCString* notesField = nullptr;
@@ -268,7 +278,7 @@ static CrashGenerationServer* crashServer; // chrome process has this
static std::terminate_handler oldTerminateHandler = nullptr;
#if defined(XP_WIN) || defined(XP_MACOSX)
static char* childCrashNotifyPipe;
MOZ_RUNINIT static nsCString childCrashNotifyPipe;
#elif defined(XP_LINUX)
static int serverSocketFd = -1;
@@ -276,18 +286,6 @@ static int clientSocketFd = -1;
#endif
// |dumpMapLock| must protect all access to |pidToMinidump|.
static Mutex* dumpMapLock;
struct ChildProcessData : public nsUint32HashKey {
explicit ChildProcessData(KeyTypePointer aKey)
: nsUint32HashKey(aKey), annotations(nullptr) {}
nsCOMPtr<nsIFile> minidump;
UniquePtr<AnnotationTable> annotations;
};
typedef nsTHashtable<ChildProcessData> ChildMinidumpMap;
static ChildMinidumpMap* pidToMinidump;
static bool OOPInitialized();
void RecordMainThreadId() {
@@ -728,7 +726,7 @@ static void PHCStackTraceToString(char* aBuffer, size_t aBufferLen,
strcat(aBuffer, ",");
}
XP_STOA(uintptr_t(aStack.mPcs[i]), addrString);
strncat(aBuffer, addrString, aBufferLen);
strncat(aBuffer, addrString, aBufferLen - 1);
}
}
@@ -785,55 +783,6 @@ static void WritePHCAddrInfo(AnnotationWriter& writer,
}
}
static void PopulatePHCStackTraceAnnotation(
AnnotationTable& aAnnotations, const Annotation aName,
const Maybe<phc::StackTrace>& aStack) {
if (aStack.isNothing()) {
return;
}
char addrsString[phcStringifiedAnnotationSize];
PHCStackTraceToString(addrsString, sizeof(addrsString), *aStack);
aAnnotations[aName] = addrsString;
}
static void PopulatePHCAnnotations(AnnotationTable& aAnnotations,
const phc::AddrInfo* aAddrInfo) {
// Is this a PHC allocation needing special treatment?
if (aAddrInfo && aAddrInfo->mKind != phc::AddrInfo::Kind::Unknown) {
const char* kindString;
switch (aAddrInfo->mKind) {
case phc::AddrInfo::Kind::Unknown:
kindString = "Unknown(?!)";
break;
case phc::AddrInfo::Kind::NeverAllocatedPage:
kindString = "NeverAllocatedPage";
break;
case phc::AddrInfo::Kind::InUsePage:
kindString = "InUsePage(?!)";
break;
case phc::AddrInfo::Kind::FreedPage:
kindString = "FreedPage";
break;
case phc::AddrInfo::Kind::GuardPage:
kindString = "GuardPage";
break;
default:
kindString = "Unmatched(?!)";
break;
}
aAnnotations[Annotation::PHCKind] = kindString;
aAnnotations[Annotation::PHCBaseAddress] =
nsPrintfCString("%zu", uintptr_t(aAddrInfo->mBaseAddr));
aAnnotations[Annotation::PHCUsableSize] =
nsPrintfCString("%zu", aAddrInfo->mUsableSize);
PopulatePHCStackTraceAnnotation(aAnnotations, Annotation::PHCAllocStack,
aAddrInfo->mAllocStack);
PopulatePHCStackTraceAnnotation(aAnnotations, Annotation::PHCFreeStack,
aAddrInfo->mFreeStack);
}
}
#endif
/**
@@ -1739,7 +1688,13 @@ static void PrepareForMinidump() {
# if defined(DEBUG) && defined(HAS_DLL_BLOCKLIST)
DllBlocklist_Shutdown();
# endif
#endif // XP_WIN
#elif defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
if (gCrashHelperPid) {
// Ignore the return value because we're in the exception handler, so
// there's not much we can do safely, not even log the error.
Unused << prctl(PR_SET_PTRACER, gCrashHelperPid);
}
#endif
}
#ifdef XP_WIN
@@ -1815,22 +1770,6 @@ static bool ChildFilter(void* context) {
#endif // !defined(XP_WIN)
static bool ChildMinidumpCallback(
#if defined(XP_WIN)
const wchar_t* dump_path, const wchar_t* minidump_id,
#elif defined(XP_LINUX)
const MinidumpDescriptor& descriptor,
#else // defined(XP_MACOSX)
const char* dump_dir, const char* minidump_id,
#endif
void* context,
#if defined(XP_WIN)
EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion,
#endif // defined(XP_WIN)
const mozilla::phc::AddrInfo* addr_info, bool succeeded) {
return succeeded;
}
static bool ShouldReport() {
// this environment variable prevents us from launching
// the crash reporter client
@@ -1860,10 +1799,12 @@ static nsresult LocateExecutable(nsIFile* aXREDirectory, const nsAString& aName,
NS_ENSURE_SUCCESS(rv, rv);
# ifdef XP_MACOSX
exePath->SetNativeLeafName("MacOS"_ns);
exePath->Append(u"crashreporter.app"_ns);
exePath->Append(u"Contents"_ns);
exePath->Append(u"MacOS"_ns);
if (aName.Equals(CRASH_REPORTER_FILENAME)) {
exePath->SetNativeLeafName("MacOS"_ns);
exePath->Append(u"crashreporter.app"_ns);
exePath->Append(u"Contents"_ns);
exePath->Append(u"MacOS"_ns);
}
# endif
exePath->Append(aName);
@@ -1955,8 +1896,16 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Locate the crash helper executable
PathString crashHelperPath_temp;
rv = LocateExecutable(aXREDirectory, CRASH_HELPER_FILENAME,
crashHelperPath_temp);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
crashReporterPath = crashReporterPath_temp.get();
crashHelperPath = crashHelperPath_temp.get();
#else
// On Android, we launch a service defined via MOZ_ANDROID_CRASH_HANDLER
const char* androidCrashHandler = PR_GetEnv("MOZ_ANDROID_CRASH_HANDLER");
@@ -1976,6 +1925,10 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) {
androidStartServiceCommand = (char*)"startservice";
}
}
const char* crashHelperPathEnv = PR_GetEnv("MOZ_ANDROID_PACKAGE_NAME");
MOZ_ASSERT(crashHelperPathEnv, "The application package name is required");
crashHelperPath = crashHelperPathEnv;
#endif // !defined(MOZ_WIDGET_ANDROID)
// get temp path to use for minidump path
@@ -3093,6 +3046,34 @@ bool GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile) {
return true;
}
static nsresult ReadExtraFile(nsCOMPtr<nsIFile>& aFile,
AnnotationTable& aAnnotations) {
const int64_t kExtraFileMaxSize = 1024 * 1024 * 1024;
int64_t fileSize;
nsresult rv = aFile->GetFileSize(&fileSize);
NS_ENSURE_SUCCESS(rv, rv);
// Reject humongous extra files, Socorro will discard them anyway
NS_ENSURE_TRUE((fileSize > 0) && (fileSize < kExtraFileMaxSize),
NS_ERROR_OUT_OF_MEMORY);
nsTArray<uint8_t> buffer((size_t)rv);
nsCOMPtr<nsIInputStream> stream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFile);
NS_ENSURE_SUCCESS(rv, rv);
nsCString json;
rv = NS_ReadInputStreamToString(stream, json, fileSize);
NS_ENSURE_SUCCESS(rv, rv);
auto annotations = ExtraFileParser::Parse(json);
if (!annotations) {
return NS_ERROR_FAILURE;
}
aAnnotations = *annotations;
return NS_OK;
}
static bool WriteExtraFile(PlatformWriter& pw,
const AnnotationTable& aAnnotations) {
if (!pw.Valid()) {
@@ -3210,79 +3191,6 @@ static void AddSharedAnnotations(AnnotationTable& aAnnotations) {
AddCommonAnnotations(aAnnotations);
}
static void AddChildProcessAnnotations(
AnnotationTable& aAnnotations, nsTArray<CAnnotation>* aChildAnnotations) {
if (!aChildAnnotations) {
// TODO: We should probably make a list of errors that occurred when
// generating a crash report as more than one can occurr.
aAnnotations[Annotation::DumperError] = "MissingAnnotations";
return;
}
for (const auto& annotation : *aChildAnnotations) {
Annotation id = static_cast<Annotation>(annotation.id);
const AnnotationData& data = annotation.data;
if ((id == Annotation::PHCBaseAddress) &&
(data.tag == AnnotationData::Tag::ByteBuffer)) {
// PHC is special for now, let's deal with it here
#ifdef MOZ_PHC
const auto& buffer = data.byte_buffer._0;
alignas(mozilla::phc::AddrInfo) char mem[sizeof(mozilla::phc::AddrInfo)];
memcpy(mem, buffer.Elements(), sizeof(mozilla::phc::AddrInfo));
const auto* addr_info =
reinterpret_cast<const mozilla::phc::AddrInfo*>(mem);
PopulatePHCAnnotations(aAnnotations, addr_info);
#endif
continue;
}
if (data.tag == AnnotationData::Tag::Empty) {
continue;
}
nsAutoCString value;
const uint8_t* buffer = data.byte_buffer._0.Elements();
const size_t length = data.byte_buffer._0.Length();
switch (TypeOfAnnotation(id)) {
case AnnotationType::String:
value.Assign(reinterpret_cast<const char*>(buffer), length);
break;
case AnnotationType::Boolean:
if (length == sizeof(bool)) {
value.Assign(*reinterpret_cast<const bool*>(buffer) ? "1" : "0");
}
break;
case AnnotationType::U32:
if (length == sizeof(uint32_t)) {
value.AppendInt(*reinterpret_cast<const uint32_t*>(buffer));
}
break;
case AnnotationType::U64:
if (length == sizeof(uint64_t)) {
value.AppendInt(*reinterpret_cast<const uint64_t*>(buffer));
}
break;
case AnnotationType::USize:
if (length == sizeof(size_t)) {
#ifdef XP_MACOSX
// macOS defines size_t as unsigned long, which causes ambiguity
// when it comes to function overload, use a 64-bit integer instead
value.AppendInt(*reinterpret_cast<const uint64_t*>(buffer));
#else
value.AppendInt(*reinterpret_cast<const size_t*>(buffer));
#endif
}
break;
}
if (!value.IsEmpty() && ShouldIncludeAnnotation(id, value.get())) {
aAnnotations[id] = value;
}
}
}
// It really only makes sense to call this function when
// ShouldReport() is true.
// Uses dumpFile's filename to generate memoryReport's filename (same name
@@ -3318,74 +3226,7 @@ static bool MoveToPending(nsIFile* dumpFile, nsIFile* extraFile,
return true;
}
static void MaybeAnnotateDumperError(const ClientInfo& aClientInfo,
AnnotationTable& aAnnotations) {
#if defined(MOZ_OXIDIZED_BREAKPAD)
if (aClientInfo.had_error()) {
aAnnotations[Annotation::DumperError] =
nsDependentCString(aClientInfo.error_msg());
}
#endif
}
static void OnChildProcessDumpRequested(
void* aContext, const ClientInfo& aClientInfo,
const xpstring& aFilePath) MOZ_NO_THREAD_SAFETY_ANALYSIS {
nsCOMPtr<nsIFile> minidump;
// Hold the mutex until the current dump request is complete, to
// prevent UnsetExceptionHandler() from pulling the rug out from
// under us.
MutexAutoLock lock(*dumpSafetyLock);
if (!isSafeToDump) return;
CreateFileFromPath(aFilePath, getter_AddRefs(minidump));
MOZ_ASSERT(minidump);
ProcessId pid = aClientInfo.pid();
if (ShouldReport()) {
nsCOMPtr<nsIFile> memoryReport;
if (!memoryReportPath.empty()) {
CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport));
MOZ_ASSERT(memoryReport);
}
MoveToPending(minidump, nullptr, memoryReport);
}
#if XP_WIN
nsTArray<CAnnotation>* child_annotations = mozannotation_retrieve(
reinterpret_cast<uintptr_t>(aClientInfo.process_handle()),
static_cast<size_t>(Annotation::Count));
#elif defined(XP_MACOSX)
nsTArray<CAnnotation>* child_annotations = mozannotation_retrieve(
aClientInfo.task(), static_cast<size_t>(Annotation::Count));
#else
nsTArray<CAnnotation>* child_annotations =
mozannotation_retrieve(pid, static_cast<size_t>(Annotation::Count));
#endif
// TODO: Write a minimal set of annotations if we fail to read them, and
// add an error to the minidump to highlight this fact.
{
MutexAutoLock lock(*dumpMapLock);
ChildProcessData* pd = pidToMinidump->PutEntry(pid);
MOZ_ASSERT(!pd->minidump);
pd->minidump = minidump;
pd->annotations = MakeUnique<AnnotationTable>();
AnnotationTable& annotations = *(pd->annotations);
AddSharedAnnotations(annotations);
AddChildProcessAnnotations(annotations, child_annotations);
MaybeAnnotateDumperError(aClientInfo, annotations);
}
if (child_annotations) {
mozannotation_free(child_annotations);
}
}
static bool OOPInitialized() { return pidToMinidump != nullptr; }
static bool OOPInitialized() { return gCrashHelperClient.isSome(); }
void OOPInit() {
class ProxyToMainThread : public Runnable {
@@ -3412,56 +3253,48 @@ void OOPInit() {
"attempt to initialize OOP crash reporter before in-process "
"crashreporter!");
CrashHelperClient* crashHelperClient;
#if defined(XP_WIN)
childCrashNotifyPipe =
mozilla::Smprintf("\\\\.\\pipe\\gecko-crash-server-pipe.%i",
static_cast<int>(::GetCurrentProcessId()))
.release();
const std::wstring dumpPath = gExceptionHandler->dump_path();
crashServer = new CrashGenerationServer(
std::wstring(NS_ConvertASCIItoUTF16(childCrashNotifyPipe).get()),
nullptr, // default security attributes
nullptr, nullptr, // we don't care about process connect here
OnChildProcessDumpRequested, nullptr, nullptr, nullptr,
nullptr, // we don't care about process exit here
nullptr, nullptr, // we don't care about upload request here
true, // automatically generate dumps
&dumpPath);
if (sIncludeContextHeap) {
crashServer->set_include_context_heap(sIncludeContextHeap);
}
childCrashNotifyPipe = nsCString("\\\\.\\pipe\\gecko-crash-server-pipe.");
childCrashNotifyPipe.AppendInt(static_cast<int>(::GetCurrentProcessId()));
// TODO: Create the crash server and set include_context_heap based on the
// value of sIncludeContextHeap. Also pass the release channel so we can set
// the appropriate type of minidump in the crash helper.
crashHelperClient = crash_helper_launch(
(const BreakpadChar*)crashHelperPath.c_str(),
(const BreakpadChar*)gExceptionHandler->dump_path().c_str(),
(const BreakpadChar*)NS_ConvertUTF8toUTF16(childCrashNotifyPipe)
.BeginReading(),
MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL));
#elif defined(XP_LINUX)
if (!CrashGenerationServer::CreateReportChannel(&serverSocketFd,
&clientSocketFd))
&clientSocketFd)) {
MOZ_CRASH("can't create crash reporter socketpair()");
}
const std::string dumpPath =
gExceptionHandler->minidump_descriptor().directory();
crashServer =
new CrashGenerationServer(serverSocketFd, OnChildProcessDumpRequested,
nullptr, nullptr, nullptr, true, &dumpPath);
crashHelperClient =
crash_helper_launch(crashHelperPath.c_str(), dumpPath.c_str(),
serverSocketFd, MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL));
close(serverSocketFd);
#elif defined(XP_MACOSX)
childCrashNotifyPipe = mozilla::Smprintf("gecko-crash-server-pipe.%i",
static_cast<int>(getpid()))
.release();
const std::string dumpPath = gExceptionHandler->dump_path();
childCrashNotifyPipe = nsCString("gecko-crash-server-pipe.");
childCrashNotifyPipe.AppendInt(static_cast<int>(getpid()));
crashServer = new CrashGenerationServer(childCrashNotifyPipe, nullptr,
nullptr, OnChildProcessDumpRequested,
nullptr, nullptr, nullptr,
true, // automatically generate dumps
dumpPath);
crashHelperClient = crash_helper_launch(
crashHelperPath.c_str(), gExceptionHandler->dump_path().c_str(),
(BreakpadRawData)childCrashNotifyPipe.get(),
MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL));
#endif
if (!crashServer->Start()) MOZ_CRASH("can't start crash reporter server()");
pidToMinidump = new ChildMinidumpMap();
dumpMapLock = new Mutex("CrashReporter::dumpMapLock");
// After this point we'll have a value for the crash helper client, but this
// value may be null in case we failed to start and initialize the external
// process. This is fine as we only need to know if we've already tried
// starting it or not.
gCrashHelperClient = Some(crashHelperClient);
FindPendingDir();
UpdateCrashEventsDir();
@@ -3476,16 +3309,9 @@ static void OOPDeinit() {
delete crashServer;
crashServer = nullptr;
delete dumpMapLock;
dumpMapLock = nullptr;
delete pidToMinidump;
pidToMinidump = nullptr;
#if defined(XP_WIN) || defined(XP_MACOSX)
free(childCrashNotifyPipe);
childCrashNotifyPipe = nullptr;
#endif
childCrashNotifyPipe = ""_ns;
#endif // defined(XP_WIN) || defined(XP_MACOSX)
}
// Parent-side API for children
@@ -3497,14 +3323,28 @@ CrashPipeType GetChildNotificationPipe() {
MOZ_ASSERT(OOPInitialized());
#if defined(XP_WIN) || defined(XP_MACOSX)
return childCrashNotifyPipe;
return childCrashNotifyPipe.get();
#elif defined(XP_LINUX)
return DuplicateFileHandle(clientSocketFd);
#endif
}
bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) {
#if defined(XP_LINUX)
ProcessId GetCrashHelperPid() {
if (gCrashHelperClient.isSome() && *gCrashHelperClient) {
return crash_helper_pid(*gCrashHelperClient);
}
return base::kInvalidProcessId;
}
#endif // defined(XP_LINUX)
bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe,
ProcessId aCrashHelperPid) {
MOZ_ASSERT(!gExceptionHandler, "crash client already init'd");
gCrashHelperPid = aCrashHelperPid;
RegisterRuntimeExceptionModule();
InitializeAppNotes();
RegisterAnnotations();
@@ -3519,13 +3359,15 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) {
static_cast<uint32_t>(Annotation::PHCBaseAddress),
&mozilla::phc::gAddrInfo, sizeof(mozilla::phc::gAddrInfo));
#endif
#if defined(XP_WIN)
gExceptionHandler = new google_breakpad::ExceptionHandler(
L"", ChildFilter, ChildMinidumpCallback,
L"", ChildFilter,
nullptr, // no callback
nullptr, // no callback context
google_breakpad::ExceptionHandler::HANDLER_ALL, GetMinidumpType(),
NS_ConvertASCIItoUTF16(aCrashPipe).get(), nullptr);
(const wchar_t*)NS_ConvertUTF8toUTF16(aCrashPipe).BeginReading(),
nullptr // no custom info
);
gExceptionHandler->set_handle_debug_exceptions(true);
# if defined(HAVE_64BIT_BUILD)
@@ -3535,17 +3377,19 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) {
// MinidumpDescriptor requires a non-empty path.
google_breakpad::MinidumpDescriptor path(".");
gExceptionHandler = new google_breakpad::ExceptionHandler(
path, ChildFilter, ChildMinidumpCallback,
nullptr, // no callback context
true, // install signal handlers
aCrashPipe.release());
gExceptionHandler =
new google_breakpad::ExceptionHandler(path, ChildFilter,
nullptr, // no callback
nullptr, // no callback context
true, // install signal handlers
aCrashPipe.release());
#elif defined(XP_MACOSX)
gExceptionHandler = new google_breakpad::ExceptionHandler(
"", ChildFilter, ChildMinidumpCallback,
nullptr, // no callback context
true, // install signal handlers
aCrashPipe);
gExceptionHandler =
new google_breakpad::ExceptionHandler("", ChildFilter,
nullptr, // no callback
nullptr, // no callback context
true, // install signal handlers
aCrashPipe);
#endif
RecordMainThreadId();
@@ -3556,37 +3400,59 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) {
return gExceptionHandler->IsOutOfProcess();
}
void GetAnnotation(ProcessId childPid, Annotation annotation,
nsACString& outStr) {
if (!GetEnabled()) {
return;
}
MutexAutoLock lock(*dumpMapLock);
ChildProcessData* pd = pidToMinidump->GetEntry(childPid);
if (!pd) {
return;
}
outStr = (*pd->annotations)[annotation];
}
bool TakeMinidumpForChild(ProcessId childPid, nsIFile** dump,
AnnotationTable& aAnnotations) {
if (!GetEnabled()) return false;
if (!GetEnabled()) {
return false;
}
MutexAutoLock lock(*dumpMapLock);
CrashReport* crash_report = nullptr;
ChildProcessData* pd = pidToMinidump->GetEntry(childPid);
if (!pd) return false;
if (gCrashHelperClient.isSome() && *gCrashHelperClient) {
crash_report = transfer_crash_report(*gCrashHelperClient, childPid);
}
NS_IF_ADDREF(*dump = pd->minidump);
aAnnotations = *(pd->annotations);
if (!crash_report) {
return false;
}
pidToMinidump->RemoveEntry(pd);
CreateFileFromPath(xpstring((XP_CHAR*)crash_report->path), dump);
nsCString error =
crash_report->error ? nsCString(crash_report->error) : ""_ns;
release_crash_report(crash_report);
return !!*dump;
nsCOMPtr<nsIFile> extra = nullptr;
NS_ENSURE_TRUE(GetExtraFileForMinidump(*dump, getter_AddRefs(extra)), false);
if (ShouldReport()) {
nsCOMPtr<nsIFile> memoryReport;
if (!memoryReportPath.empty()) {
CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport));
MOZ_ASSERT(memoryReport);
}
MoveToPending(*dump, extra, memoryReport);
}
nsresult rv = ReadExtraFile(extra, aAnnotations);
// Unconditionally remove the temporary .extra file, it will be regenarated
// later when we finalize the crash report.
extra->Remove(false);
if (rv != NS_OK) {
// TODO: We failed to read the annotations, this will leave an orphaned
// crash that we won't be able to submit. Clean everything up instead?
return false;
}
AddSharedAnnotations(aAnnotations);
if (error.Length() > 0) {
aAnnotations[Annotation::DumperError] = error;
}
return true;
}
bool FinalizeOrphanedMinidump(ProcessId aChildPid, GeckoProcessType aType,
@@ -3613,58 +3479,6 @@ bool FinalizeOrphanedMinidump(ProcessId aChildPid, GeckoProcessType aType,
return WriteExtraFile(id, annotations);
}
#ifdef XP_WIN
// Function invoked by the WER runtime exception handler running in an
// external process. This function isn't used anywhere inside Gecko directly
// but rather invoked via CreateRemoteThread() in the main process.
// Store this global in a section called mozwerpt where we can find it by just
// looking at the program headers.
# pragma section("mozwerpt", read, executable, shared)
__declspec(allocate("mozwerpt")) MOZ_EXPORT DWORD WINAPI
WerNotifyProc(LPVOID aParameter) {
const WindowsErrorReportingData* werData =
static_cast<const WindowsErrorReportingData*>(aParameter);
auto freeParameterOnExit = MakeScopeExit([&aParameter] {
VirtualFree(aParameter, sizeof(WindowsErrorReportingData), MEM_RELEASE);
});
// Hold the mutex until the current dump request is complete, to
// prevent UnsetExceptionHandler() from pulling the rug out from
// under us.
MutexAutoLock safetyLock(*dumpSafetyLock);
if (!isSafeToDump || !ShouldReport()) {
return S_OK;
}
ProcessId pid = werData->mChildPid;
nsCOMPtr<nsIFile> minidump;
if (!GetPendingDir(getter_AddRefs(minidump))) {
return S_OK;
}
xpstring minidump_native_name(werData->mMinidumpFile,
werData->mMinidumpFile + 40);
nsString minidump_name(minidump_native_name.c_str());
minidump->Append(minidump_name);
{
MutexAutoLock lock(*dumpMapLock);
ChildProcessData* pd = pidToMinidump->PutEntry(pid);
MOZ_ASSERT(!pd->minidump);
pd->minidump = minidump;
pd->annotations = MakeUnique<AnnotationTable>();
(*pd->annotations)[Annotation::WindowsErrorReporting] = "1"_ns;
AddSharedAnnotations(*(pd->annotations));
}
return S_OK;
}
#endif // XP_WIN
//-----------------------------------------------------------------------------
// CreateMinidumpsAndPair() and helpers
//
@@ -3838,18 +3652,7 @@ bool CreateMinidumpsAndPair(ProcessHandle aTargetHandle,
#endif
AddSharedAnnotations(aTargetAnnotations);
#if XP_WIN
nsTArray<CAnnotation>* child_annotations =
mozannotation_retrieve(reinterpret_cast<uintptr_t>(aTargetHandle),
static_cast<size_t>(Annotation::Count));
#else
nsTArray<CAnnotation>* child_annotations = mozannotation_retrieve(
aTargetHandle, static_cast<size_t>(Annotation::Count));
#endif
AddChildProcessAnnotations(aTargetAnnotations, child_annotations);
if (child_annotations) {
mozannotation_free(child_annotations);
}
// TODO: Retrieve annotations from child process
targetMinidump.forget(aMainDumpOut);

View File

@@ -165,9 +165,6 @@ nsresult UnregisterAppMemory(void* ptr);
// Include heap regions of the crash context.
void SetIncludeContextHeap(bool aValue);
void GetAnnotation(ProcessId childPid, Annotation annotation,
nsACString& outStr);
// Functions for working with minidumps and .extras
typedef mozilla::EnumeratedArray<Annotation, nsCString,
size_t(Annotation::Count)>
@@ -200,18 +197,6 @@ nsresult AppendObjCExceptionInfoToAppNotes(void* inException);
nsresult GetSubmitReports(bool* aSubmitReport);
nsresult SetSubmitReports(bool aSubmitReport);
#ifdef XP_WIN
// This data is stored in the parent process, there is one copy for each child
// process. The mChildPid and mMinidumpFile fields are filled by the WER runtime
// exception module when the associated child process crashes.
struct WindowsErrorReportingData {
// PID of the child process that crashed.
DWORD mChildPid;
// Filename of the generated minidump; this is not a 0-terminated string
char mMinidumpFile[40];
};
#endif // XP_WIN
// Out-of-process crash reporter API.
// Initializes out-of-process crash reporting. This method must be called
@@ -272,7 +257,7 @@ bool CreateMinidumpsAndPair(ProcessHandle aTargetPid,
AnnotationTable& aTargetAnnotations,
nsIFile** aTargetDumpOut);
#if defined(XP_WIN) || defined(XP_MACOSX)
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_IOS)
using CrashPipeType = const char*;
#else
using CrashPipeType = mozilla::UniqueFileHandle;
@@ -281,8 +266,18 @@ using CrashPipeType = mozilla::UniqueFileHandle;
// Parent-side API for children
CrashPipeType GetChildNotificationPipe();
#if defined(XP_LINUX)
// Return the pid of the crash helper process. This only works in Linux, not
// Android where the crash helper is an Android service and is not under the
// main process' control.
MOZ_EXPORT ProcessId GetCrashHelperPid();
#endif // XP_LINUX
// Child-side API
bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe);
MOZ_EXPORT bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe,
ProcessId aCrashHelperPid = 0);
bool UnsetRemoteExceptionHandler(bool wasSet = true);
} // namespace CrashReporter

View File

@@ -10,7 +10,7 @@ license = "MPL-2.0"
[dependencies]
goblin = { version = "0.7", features = ["elf32", "elf64", "pe32", "pe64"] }
memoffset = "0.9"
thiserror = "1.0"
thiserror = "2"
[target."cfg(any(target_os = \"linux\", target_os = \"android\"))".dependencies]
libc = "0.2"

View File

@@ -70,6 +70,7 @@ rure = "0.2.2"
rust_minidump_writer_linux = { path = "../../../crashreporter/rust_minidump_writer_linux", optional = true }
mozannotation_client = { path = "../../../crashreporter/mozannotation_client", optional = true }
mozannotation_server = { path = "../../../crashreporter/mozannotation_server", optional = true }
crash_helper_client = { path = "../../../crashreporter/crash_helper_client", optional = true }
gecko-profiler = { path = "../../../../tools/profiler/rust-api"}
midir_impl = { path = "../../../../dom/midi/midir_impl", optional = true }
dom = { path = "../../../../dom/base/rust" }
@@ -140,7 +141,7 @@ webrtc = ["mdns_service"]
glean_disable_upload = ["fog_control/disable_upload"]
glean_with_gecko = ["fog_control/with_gecko", "jog/with_gecko"]
oxidized_breakpad = ["rust_minidump_writer_linux"]
crashreporter = ["mozannotation_client", "mozannotation_server"]
crashreporter = ["crash_helper_client", "mozannotation_client"]
with_dbus = ["audio_thread_priority/with_dbus"]
thread_sanitizer = ["xpcom/thread_sanitizer"]
uniffi_fixtures = ["gkrust-uniffi-fixtures"]

View File

@@ -109,8 +109,9 @@ extern crate rust_minidump_writer_linux;
#[cfg(feature = "crashreporter")]
extern crate mozannotation_client;
#[cfg(feature = "crashreporter")]
extern crate mozannotation_server;
extern crate crash_helper_client;
#[cfg(feature = "webmidi_midir_impl")]
extern crate midir_impl;

View File

@@ -209,13 +209,14 @@ static CommandLineArg<bool> sNotForBrowser{"-notForBrowser", "notforbrowser"};
static CommandLineArg<const char*> sPluginPath{"-pluginPath", "pluginpath"};
static CommandLineArg<bool> sPluginNativeEvent{"-pluginNativeEvent",
"pluginnativeevent"};
#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
static CommandLineArg<const char*> sCrashReporter{"-crashReporter",
"crashreporter"};
#elif defined(XP_UNIX)
#if defined(XP_LINUX)
static CommandLineArg<UniqueFileHandle> sCrashReporter{"-crashReporter",
"crashreporter"};
static CommandLineArg<uint64_t> sCrashHelperPid{"-crashHelperPid",
"crashhelperpid"};
#else
static CommandLineArg<const char*> sCrashReporter{"-crashReporter",
"crashreporter"};
#endif
#if defined(XP_WIN)

View File

@@ -352,8 +352,16 @@ nsresult XRE_InitChildProcess(int aArgc, char* aArgv[],
if (!CrashReporter::IsDummy()) {
auto crashReporterArg = geckoargs::sCrashReporter.Get(aArgc, aArgv);
if (crashReporterArg) {
CrashReporter::ProcessId crashHelperPid = base::kInvalidProcessId;
#if defined(XP_LINUX)
auto crashHelperPidArg = geckoargs::sCrashHelperPid.Get(aArgc, aArgv);
MOZ_ASSERT(crashHelperPidArg);
crashHelperPid =
static_cast<CrashReporter::ProcessId>(*crashHelperPidArg);
#endif
exceptionHandlerIsSet = CrashReporter::SetRemoteExceptionHandler(
std::move(*crashReporterArg));
std::move(*crashReporterArg), crashHelperPid);
MOZ_ASSERT(exceptionHandlerIsSet,
"Should have been able to set remote exception handler");