diff --git a/Cargo.lock b/Cargo.lock index ac7d061071af..7ed4914f89ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", @@ -4254,11 +4303,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]] @@ -4420,9 +4468,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", @@ -4644,6 +4692,7 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", + "memoffset 0.9.0", ] [[package]] @@ -5137,7 +5186,7 @@ dependencies = [ "memoffset 0.9.0", "mozilla-central-workspace-hack", "scroll", - "thiserror 1.999.999", + "thiserror 2.0.9", "windows-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 1f99b5b07753..16abc04084f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,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/", ] diff --git a/browser/app/macbuild/Contents/MacOS-files.in b/browser/app/macbuild/Contents/MacOS-files.in index 7517c5b42ac7..2f420f773610 100644 --- a/browser/app/macbuild/Contents/MacOS-files.in +++ b/browser/app/macbuild/Contents/MacOS-files.in @@ -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 diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index a95cd98c450d..7b61a77b0123 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -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@ diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh index b7f8e1453089..ec598c3cd1b2 100755 --- a/browser/installer/windows/nsis/shared.nsh +++ b/browser/installer/windows/nsis/shared.nsh @@ -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" diff --git a/build/workspace-hack/Cargo.toml b/build/workspace-hack/Cargo.toml index 2442f7c4afff..350391e59e98 100644 --- a/build/workspace-hack/Cargo.toml +++ b/build/workspace-hack/Cargo.toml @@ -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"] diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index 3ebb9f420d0d..53e3be502232 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -843,20 +843,8 @@ void BrowserParent::ActorDestroy(ActorDestroyReason why) { nsCOMPtr 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); } } } diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index aa3f58360ffd..6b64962ba838 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -1125,13 +1125,18 @@ Result 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(); diff --git a/mobile/android/geckoview/src/main/AndroidManifest.xml b/mobile/android/geckoview/src/main/AndroidManifest.xml index 72f3d2726f5c..ac6e718f3cde 100644 --- a/mobile/android/geckoview/src/main/AndroidManifest.xml +++ b/mobile/android/geckoview/src/main/AndroidManifest.xml @@ -87,6 +87,14 @@ android:isolatedProcess="false" android:process=":ipdlunittest"> + + diff --git a/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/crashhelper/ICrashHelper.aidl b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/crashhelper/ICrashHelper.aidl new file mode 100644 index 000000000000..005f5adf6836 --- /dev/null +++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/crashhelper/ICrashHelper.aidl @@ -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); +} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java index e430d2fff0ab..e24b6ab27c25 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java @@ -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 cls = + (Class) 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 args = new ArrayList<>(); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/crashhelper/CrashHelper.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/crashhelper/CrashHelper.java new file mode 100644 index 000000000000..bee04903efa2 --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/crashhelper/CrashHelper.java @@ -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); +} diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index e91dce96119d..a0fb642e1ed7 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -78,6 +78,7 @@ #endif #ifdef MOZ_CRASHREPORTER +@BINPATH@/@DLL_PREFIX@crashhelper@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@minidump_analyzer@DLL_SUFFIX@ #endif diff --git a/python/mozbuild/mozbuild/artifacts.py b/python/mozbuild/mozbuild/artifacts.py index 97974a723319..73f38c7577c6 100644 --- a/python/mozbuild/mozbuild/artifacts.py +++ b/python/mozbuild/mozbuild/artifacts.py @@ -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", diff --git a/security/mac/hardenedruntime/v2/developer/utility.xml b/security/mac/hardenedruntime/v2/developer/utility.xml index 305323e42a12..63802f0cbf7d 100644 --- a/security/mac/hardenedruntime/v2/developer/utility.xml +++ b/security/mac/hardenedruntime/v2/developer/utility.xml @@ -2,7 +2,8 @@ diff --git a/taskcluster/config.yml b/taskcluster/config.yml index a1d76842174e..39eb9c8aa236 100644 --- a/taskcluster/config.yml +++ b/taskcluster/config.yml @@ -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" diff --git a/toolkit/crashreporter/bionic_missing_funcs.cpp b/toolkit/crashreporter/bionic_missing_funcs.cpp index 81e8bef303b7..2c9daea50f38 100644 --- a/toolkit/crashreporter/bionic_missing_funcs.cpp +++ b/toolkit/crashreporter/bionic_missing_funcs.cpp @@ -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) { diff --git a/toolkit/crashreporter/breakpad-client/moz.build b/toolkit/crashreporter/breakpad-client/moz.build index 9ee42e41364d..89a0a9d98bc4 100644 --- a/toolkit/crashreporter/breakpad-client/moz.build +++ b/toolkit/crashreporter/breakpad-client/moz.build @@ -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') diff --git a/toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp b/toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp new file mode 100644 index 000000000000..194527be4be7 --- /dev/null +++ b/toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp @@ -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 + +#if defined(XP_LINUX) +# include +# include +# 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 +# include +# 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(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(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(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(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(aServer); + delete server; +} diff --git a/toolkit/crashreporter/breakpad_wrapper/moz.build b/toolkit/crashreporter/breakpad_wrapper/moz.build new file mode 100644 index 000000000000..372c5b4cbba7 --- /dev/null +++ b/toolkit/crashreporter/breakpad_wrapper/moz.build @@ -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", + ] diff --git a/toolkit/crashreporter/crash_annotations.rs.in b/toolkit/crashreporter/crash_annotations.rs.in new file mode 100644 index 000000000000..3f6beb6eaff6 --- /dev/null +++ b/toolkit/crashreporter/crash_annotations.rs.in @@ -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 + } +} diff --git a/toolkit/crashreporter/crash_helper/crashhelper.cpp b/toolkit/crashreporter/crash_helper/crashhelper.cpp new file mode 100644 index 000000000000..02b95db8440b --- /dev/null +++ b/toolkit/crashreporter/crash_helper/crashhelper.cpp @@ -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 + +#if defined(XP_WIN) +# include +#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(value); + + if (errno != 0) { + exit(EXIT_FAILURE); + } + + crash_generator_logic(client_pid); + exit(EXIT_SUCCESS); +} diff --git a/toolkit/crashreporter/crash_helper/crashhelper_android.cpp b/toolkit/crashreporter/crash_helper/crashhelper_android.cpp new file mode 100644 index 000000000000..ef452d5a4b85 --- /dev/null +++ b/toolkit/crashreporter/crash_helper/crashhelper_android.cpp @@ -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 + +#include +#include + +#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); +} diff --git a/toolkit/crashreporter/crash_helper/moz.build b/toolkit/crashreporter/crash_helper/moz.build new file mode 100644 index 000000000000..d5af66b076f9 --- /dev/null +++ b/toolkit/crashreporter/crash_helper/moz.build @@ -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) diff --git a/toolkit/crashreporter/crash_helper_client/Cargo.toml b/toolkit/crashreporter/crash_helper_client/Cargo.toml new file mode 100644 index 000000000000..fbc16983f6d3 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/Cargo.toml @@ -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 diff --git a/toolkit/crashreporter/mozannotation_server/cbindgen.toml b/toolkit/crashreporter/crash_helper_client/cbindgen.toml similarity index 62% rename from toolkit/crashreporter/mozannotation_server/cbindgen.toml rename to toolkit/crashreporter/crash_helper_client/cbindgen.toml index 532919626274..99d6d2e1649a 100644 --- a/toolkit/crashreporter/mozannotation_server/cbindgen.toml +++ b/toolkit/crashreporter/crash_helper_client/cbindgen.toml @@ -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"] diff --git a/toolkit/crashreporter/mozannotation_server/moz.build b/toolkit/crashreporter/crash_helper_client/moz.build similarity index 58% rename from toolkit/crashreporter/mozannotation_server/moz.build rename to toolkit/crashreporter/crash_helper_client/moz.build index 21762b95b197..4eb427592e77 100644 --- a/toolkit/crashreporter/mozannotation_server/moz.build +++ b/toolkit/crashreporter/crash_helper_client/moz.build @@ -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", ] diff --git a/toolkit/crashreporter/crash_helper_client/src/lib.rs b/toolkit/crashreporter/crash_helper_client/src/lib.rs new file mode 100644 index 000000000000..ac801dae9b77 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/lib.rs @@ -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::()?; + Ok(()) + } + + fn transfer_crash_report(&self, pid: Pid) -> Result { + 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::()?; + + if reply.path.is_empty() { + // TODO: We should return Result> 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 = ::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 = ::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::()); + } +} + +// 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 { + let mut exception_records = Vec::::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); + } +} diff --git a/toolkit/crashreporter/crash_helper_client/src/platform.rs b/toolkit/crashreporter/crash_helper_client/src/platform.rs new file mode 100644 index 000000000000..ef3e4b47063f --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform.rs @@ -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; diff --git a/toolkit/crashreporter/crash_helper_client/src/platform/unix.rs b/toolkit/crashreporter/crash_helper_client/src/platform/unix.rs new file mode 100644 index 000000000000..33ba7ea23c62 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform/unix.rs @@ -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 { + 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 { + #[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) + } +} diff --git a/toolkit/crashreporter/crash_helper_client/src/platform/windows.rs b/toolkit/crashreporter/crash_helper_client/src/platform/windows.rs new file mode 100644 index 000000000000..fdef31ad56d0 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform/windows.rs @@ -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, PROCESS_INFORMATION, + STARTUPINFOW, + }, +}; + +use crate::CrashHelperClient; + +impl CrashHelperClient { + pub(crate) fn new(program: *const BreakpadChar) -> Result { + 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 { + let pid = unsafe { GetCurrentProcessId() }; + let program = ::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 = cmd_line.encode_wide().collect(); + + let mut pi = unsafe { zeroed::() }; + let si = STARTUPINFOW { + cb: size_of::().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, // TODO: 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) + } +} diff --git a/toolkit/crashreporter/crash_helper_server/Cargo.toml b/toolkit/crashreporter/crash_helper_server/Cargo.toml new file mode 100644 index 000000000000..2b65e4974427 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "crash_helper_server" +version = "0.1.0" +authors = ["Gabriele Svelto "] +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" diff --git a/toolkit/crashreporter/crash_helper_server/build.rs b/toolkit/crashreporter/crash_helper_server/build.rs new file mode 100644 index 000000000000..a376dca369df --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/build.rs @@ -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, + skip_if: Option, +} + +impl Annotation { + fn new(key: &Yaml, value: &Yaml) -> Result { + 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 { + 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, 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::::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 { + 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 { + 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 { + 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 { + 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(()) +} diff --git a/toolkit/crashreporter/crash_helper_server/cbindgen.toml b/toolkit/crashreporter/crash_helper_server/cbindgen.toml new file mode 100644 index 000000000000..99d6d2e1649a --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/cbindgen.toml @@ -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"] diff --git a/toolkit/crashreporter/crash_helper_server/moz.build b/toolkit/crashreporter/crash_helper_server/moz.build new file mode 100644 index 000000000000..1267269db89f --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/moz.build @@ -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", + ] diff --git a/toolkit/crashreporter/crash_helper_server/src/breakpad_crash_generator.rs b/toolkit/crashreporter/crash_helper_server/src/breakpad_crash_generator.rs new file mode 100644 index 000000000000..5d3cf987286c --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/breakpad_crash_generator.rs @@ -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, +} + +// 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 { + 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 { ::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()); + } + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/crash_generation.rs b/toolkit/crashreporter/crash_helper_server/src/crash_generation.rs new file mode 100644 index 000000000000..e1fbb1efcf0f --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/crash_generation.rs @@ -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, +} + +impl CrashReport { + fn new(path: &OsStr, error: &Option) -> 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>>> = 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, + release_channel: String, + client_pid: Pid, + state: CrashGeneratorState, +} + +impl CrashGenerator { + pub(crate) fn new(client_pid: Pid) -> Result { + 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, + pid: Pid, + ) -> Result>> { + 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(::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, + 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> { + #[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, 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 { + let mut escaped = Vec::::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) + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/crash_generation/windows.rs b/toolkit/crashreporter/crash_helper_server/src/crash_generation/windows.rs new file mode 100644 index 000000000000..df34b6540ac8 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/crash_generation/windows.rs @@ -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) -> *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::().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 { + // SAFETY: No pointers involved, worst case we get an error + match unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) } { + 0 => Err(()), + handle => Ok(handle), + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/ipc_server.rs b/toolkit/crashreporter/crash_helper_server/src/ipc_server.rs new file mode 100644 index 000000000000..b37de5036bb8 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/ipc_server.rs @@ -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, + client_pid: Pid, +} + +impl IPCServer { + pub(crate) fn new(client_pid: Pid) -> Result { + 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 { + 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(()) + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/lib.rs b/toolkit/crashreporter/crash_helper_server/src/lib.rs new file mode 100644 index 000000000000..1ea28ebc63df --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/lib.rs @@ -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 + } + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/phc.rs b/toolkit/crashreporter/crash_helper_server/src/phc.rs new file mode 100644 index 000000000000..c5da6a204724 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/phc.rs @@ -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 { + if buff.len() != size_of::() { + bail!( + "PHC AddrInfo structure size {} doesn't match expected size {}", + buff.len(), + size_of::() + ); + } + + let mut addr_info = MaybeUninit::::uninit(); + // SAFETY: MaybeUninit 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, + size_of::(), + ) + }; + + 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 + } +} diff --git a/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc b/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc index 701a4f070f28..aaf5feb4aa07 100644 --- a/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc +++ b/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc @@ -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 { diff --git a/toolkit/crashreporter/moz.build b/toolkit/crashreporter/moz.build index f3cec30b0627..88bc933eefe6 100644 --- a/toolkit/crashreporter/moz.build +++ b/toolkit/crashreporter/moz.build @@ -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", diff --git a/toolkit/crashreporter/mozannotation_client/cbindgen.toml b/toolkit/crashreporter/mozannotation_client/cbindgen.toml index 63f85bd4f4c5..6802953fc5f7 100644 --- a/toolkit/crashreporter/mozannotation_client/cbindgen.toml +++ b/toolkit/crashreporter/mozannotation_client/cbindgen.toml @@ -11,7 +11,5 @@ language = "C++" include_guard = "mozannotation_client_ffi_generated_h" includes = ["nsStringFwd.h"] -[export.rename] -"ThinVec" = "nsTArray" [export] exclude = ["nsCString"] diff --git a/toolkit/crashreporter/mozannotation_server/Cargo.toml b/toolkit/crashreporter/mozannotation_server/Cargo.toml index bd3fb283bf17..11bc257189e0 100644 --- a/toolkit/crashreporter/mozannotation_server/Cargo.toml +++ b/toolkit/crashreporter/mozannotation_server/Cargo.toml @@ -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" diff --git a/toolkit/crashreporter/mozannotation_server/src/lib.rs b/toolkit/crashreporter/mozannotation_server/src/lib.rs index b814ad0e506e..3fef0dcf5e13 100644 --- a/toolkit/crashreporter/mozannotation_server/src/lib.rs +++ b/toolkit/crashreporter/mozannotation_server/src/lib.rs @@ -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), + ByteBuffer(Vec), + 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 { - 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) { - // 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>, AnnotationsRetrievalError> { +) -> Result, 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::::with_capacity(min(max_annotations, length)); + let mut annotations = Vec::::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 { @@ -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, process_reader::error::ReadError> { +) -> Result { let buffer_address = reader.copy_object::(address)?; - copy_null_terminated_string(reader, buffer_address) -} - -fn copy_null_terminated_string( - reader: &ProcessReader, - address: usize, -) -> Result, process_reader::error::ReadError> { - let string = reader.copy_null_terminated_string(address)?; - Ok(ThinVec::::from(string.as_bytes())) + reader.copy_null_terminated_string(buffer_address) } fn copy_nscstring( reader: &ProcessReader, address: usize, -) -> Result, process_reader::error::ReadError> { +) -> Result { // HACK: This assumes the layout of the nsCString object let length_address = address + size_of::(); let length = reader.copy_object::(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::::new()) + Ok(CString::default()) } } @@ -224,7 +184,6 @@ fn copy_bytebuffer( reader: &ProcessReader, address: usize, size: u32, -) -> Result, process_reader::error::ReadError> { - let value = reader.copy_array::(address, size as _)?; - Ok(ThinVec::::from_iter(value.into_iter())) +) -> Result, process_reader::error::ReadError> { + reader.copy_array::(address, size as _) } diff --git a/toolkit/crashreporter/mozwer-rust/Cargo.toml b/toolkit/crashreporter/mozwer-rust/Cargo.toml index 89cf3d43561b..fe86aea692c7 100644 --- a/toolkit/crashreporter/mozwer-rust/Cargo.toml +++ b/toolkit/crashreporter/mozwer-rust/Cargo.toml @@ -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", ] diff --git a/toolkit/crashreporter/mozwer-rust/lib.rs b/toolkit/crashreporter/mozwer-rust/lib.rs index 61fc89446229..4579350a35f8 100644 --- a/toolkit/crashreporter/mozwer-rust/lib.rs +++ b/toolkit/crashreporter/mozwer-rust/lib.rs @@ -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(process: HANDLE, data: T) -> Result<*mut T> { - let address = unsafe { - VirtualAllocEx( - process, - null(), - size_of::(), - 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::(), - 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 { } } +fn get_thread_id(thread: HANDLE) -> Result { + match unsafe { GetThreadId(thread) } { + 0 => Err(()), + tid => Ok(tid), + } +} + fn get_process_handle(pid: DWORD) -> Result { 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") diff --git a/toolkit/crashreporter/mozwer-rust/moz.build b/toolkit/crashreporter/mozwer-rust/moz.build index 980547f8e845..241efaf75ab7 100644 --- a/toolkit/crashreporter/mozwer-rust/moz.build +++ b/toolkit/crashreporter/mozwer-rust/moz.build @@ -1,11 +1,11 @@ RustLibrary("mozwer_s") OS_LIBS += [ + "advapi32", "dbghelp", "kernel32", "ntdll", "ole32", - "psapi", "shell32", "user32", "userenv", diff --git a/toolkit/crashreporter/nsExceptionHandler.cpp b/toolkit/crashreporter/nsExceptionHandler.cpp index 52a97a3e39ae..6a054ba5286f 100644 --- a/toolkit/crashreporter/nsExceptionHandler.cpp +++ b/toolkit/crashreporter/nsExceptionHandler.cpp @@ -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 // 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 # include # include "sys/sysinfo.h" @@ -109,7 +110,6 @@ #include #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 defaultMemoryReportPath = {}; static const char kCrashMainID[] = "crash.main.3\n"; +static Maybe gCrashHelperClient; static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr; static mozilla::Atomic 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 minidump; - UniquePtr annotations; -}; - -typedef nsTHashtable 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& 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& 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 buffer((size_t)rv); + + nsCOMPtr 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* 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.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(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(buffer), length); - break; - case AnnotationType::Boolean: - if (length == sizeof(bool)) { - value.Assign(*reinterpret_cast(buffer) ? "1" : "0"); - } - break; - case AnnotationType::U32: - if (length == sizeof(uint32_t)) { - value.AppendInt(*reinterpret_cast(buffer)); - } - break; - case AnnotationType::U64: - if (length == sizeof(uint64_t)) { - value.AppendInt(*reinterpret_cast(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(buffer)); -#else - value.AppendInt(*reinterpret_cast(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 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 memoryReport; - if (!memoryReportPath.empty()) { - CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport)); - MOZ_ASSERT(memoryReport); - } - MoveToPending(minidump, nullptr, memoryReport); - } - -#if XP_WIN - nsTArray* child_annotations = mozannotation_retrieve( - reinterpret_cast(aClientInfo.process_handle()), - static_cast(Annotation::Count)); -#elif defined(XP_MACOSX) - nsTArray* child_annotations = mozannotation_retrieve( - aClientInfo.task(), static_cast(Annotation::Count)); -#else - nsTArray* child_annotations = - mozannotation_retrieve(pid, static_cast(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& 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(::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(::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(getpid())) - .release(); - const std::string dumpPath = gExceptionHandler->dump_path(); + childCrashNotifyPipe = nsCString("gecko-crash-server-pipe."); + childCrashNotifyPipe.AppendInt(static_cast(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(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 extra = nullptr; + NS_ENSURE_TRUE(GetExtraFileForMinidump(*dump, getter_AddRefs(extra)), false); + + if (ShouldReport()) { + nsCOMPtr 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(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 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(); - (*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* child_annotations = - mozannotation_retrieve(reinterpret_cast(aTargetHandle), - static_cast(Annotation::Count)); -#else - nsTArray* child_annotations = mozannotation_retrieve( - aTargetHandle, static_cast(Annotation::Count)); -#endif - AddChildProcessAnnotations(aTargetAnnotations, child_annotations); - if (child_annotations) { - mozannotation_free(child_annotations); - } + // TODO: Retrieve annotations from child process targetMinidump.forget(aMainDumpOut); diff --git a/toolkit/crashreporter/nsExceptionHandler.h b/toolkit/crashreporter/nsExceptionHandler.h index 32ea02d3c398..02b1e5a69c02 100644 --- a/toolkit/crashreporter/nsExceptionHandler.h +++ b/toolkit/crashreporter/nsExceptionHandler.h @@ -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 @@ -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 diff --git a/toolkit/crashreporter/process_reader/Cargo.toml b/toolkit/crashreporter/process_reader/Cargo.toml index 4bbf105e0d26..c242f2ba16ea 100644 --- a/toolkit/crashreporter/process_reader/Cargo.toml +++ b/toolkit/crashreporter/process_reader/Cargo.toml @@ -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" diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index e0f2e2c18073..e17f61e6f1e4 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -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" } @@ -141,7 +142,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"] diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index dba877ad37b0..daa1d31c867d 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -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; diff --git a/toolkit/xre/GeckoArgs.h b/toolkit/xre/GeckoArgs.h index 5bb83ad51cfc..a3f18dfdcfdf 100644 --- a/toolkit/xre/GeckoArgs.h +++ b/toolkit/xre/GeckoArgs.h @@ -209,13 +209,14 @@ static CommandLineArg sNotForBrowser{"-notForBrowser", "notforbrowser"}; static CommandLineArg sPluginPath{"-pluginPath", "pluginpath"}; static CommandLineArg sPluginNativeEvent{"-pluginNativeEvent", "pluginnativeevent"}; - -#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) -static CommandLineArg sCrashReporter{"-crashReporter", - "crashreporter"}; -#elif defined(XP_UNIX) +#if defined(XP_LINUX) static CommandLineArg sCrashReporter{"-crashReporter", "crashreporter"}; +static CommandLineArg sCrashHelperPid{"-crashHelperPid", + "crashhelperpid"}; +#else +static CommandLineArg sCrashReporter{"-crashReporter", + "crashreporter"}; #endif #if defined(XP_WIN) diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp index eeab0cf10455..acb9e18883c3 100644 --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -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(*crashHelperPidArg); +#endif + exceptionHandlerIsSet = CrashReporter::SetRemoteExceptionHandler( - std::move(*crashReporterArg)); + std::move(*crashReporterArg), crashHelperPid); MOZ_ASSERT(exceptionHandlerIsSet, "Should have been able to set remote exception handler");