Bug 1620998 - Out-of-process crash generation for child processes r=afranchuk,geckoview-reviewers,glandium,browser-installer-reviewers,nalexander,owlish
This implements the crash helper service used to move child process crash report generation out of the main process and into its own process. This is implemented as a separate executable that is launched on startup by the main process on the desktop platforms and as a service hosted by a separate process on Android. One limitation of the current code is that the crash helper process needs to be running before we can start setting exception handlers in child processes. This limitation is due to how Breakpad exception handlers register themselves with the crash generator and prevents us from lazily starting the helper (or restarting it on Android). IPC with the crash helper is implemented using Unix sockets on Linux and macOS with the former using sequential packets and the latter using stream sockets. On Windows we use named pipes. In all cases the choice of IPC was dictated both by the requirement to eventually talk directly to child processes from within the sandbox, and to external processes in case of Windows as the Windows Error Reporting exception handler must be able to reach out to the helper from within a restricted context. These particular requirements are not used yet but will be as we move more logic out of the main process logic. Differential Revision: https://phabricator.services.mozilla.com/D231083
This commit is contained in:
59
Cargo.lock
generated
59
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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/",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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@
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -843,20 +843,8 @@ void BrowserParent::ActorDestroy(ActorDestroyReason why) {
|
||||
nsCOMPtr<nsIPrincipal> principal = GetContentPrincipal();
|
||||
|
||||
if (principal) {
|
||||
nsAutoCString crash_reason;
|
||||
CrashReporter::GetAnnotation(OtherPid(),
|
||||
CrashReporter::Annotation::MozCrashReason,
|
||||
crash_reason);
|
||||
// FIXME(arenevier): Find a less fragile way to identify that a crash
|
||||
// was caused by OOM
|
||||
bool is_oom = false;
|
||||
if (crash_reason == "OOM" || crash_reason == "OOM!" ||
|
||||
StringBeginsWith(crash_reason, "[unhandlable oom]"_ns) ||
|
||||
StringBeginsWith(crash_reason, "Unhandlable OOM"_ns)) {
|
||||
is_oom = true;
|
||||
}
|
||||
|
||||
CrashReport::Deliver(principal, is_oom);
|
||||
// TODO: Flag out-of-memory crashes appropriately.
|
||||
CrashReport::Deliver(principal, /* aIsOOM */ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1125,13 +1125,18 @@ Result<Ok, LaunchError> BaseProcessLauncher::DoSetup() {
|
||||
#if defined(MOZ_WIDGET_COCOA) || defined(XP_WIN)
|
||||
geckoargs::sCrashReporter.Put(CrashReporter::GetChildNotificationPipe(),
|
||||
mChildArgs);
|
||||
#elif defined(XP_UNIX)
|
||||
#elif defined(XP_UNIX) && !defined(XP_IOS)
|
||||
UniqueFileHandle childCrashFd = CrashReporter::GetChildNotificationPipe();
|
||||
if (!childCrashFd) {
|
||||
return Err(LaunchError("DuplicateFileHandle failed"));
|
||||
}
|
||||
geckoargs::sCrashReporter.Put(std::move(childCrashFd), mChildArgs);
|
||||
#endif
|
||||
|
||||
CrashReporter::ProcessId pid = CrashReporter::GetCrashHelperPid();
|
||||
if (pid != base::kInvalidProcessId) {
|
||||
geckoargs::sCrashHelperPid.Put(pid, mChildArgs);
|
||||
}
|
||||
#endif // XP_UNIX && !XP_IOS
|
||||
}
|
||||
|
||||
return Ok();
|
||||
|
||||
@@ -87,6 +87,14 @@
|
||||
android:isolatedProcess="false"
|
||||
android:process=":ipdlunittest">
|
||||
</service>
|
||||
<service
|
||||
android:name="org.mozilla.gecko.crashhelper.CrashHelper"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:isolatedProcess="false"
|
||||
android:process=":crashhelper"
|
||||
android:stopWithTask="true">
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -5,7 +5,9 @@
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
@@ -29,6 +31,7 @@ import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import org.mozilla.gecko.annotation.RobocopTarget;
|
||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||
import org.mozilla.gecko.crashhelper.CrashHelper;
|
||||
import org.mozilla.gecko.mozglue.GeckoLoader;
|
||||
import org.mozilla.gecko.process.GeckoProcessManager;
|
||||
import org.mozilla.gecko.process.GeckoProcessType;
|
||||
@@ -326,11 +329,26 @@ public class GeckoThread extends Thread {
|
||||
|
||||
if (!isChildProcess()) {
|
||||
GeckoSystemStateListener.getInstance().initialize(context);
|
||||
startCrashHelper(context);
|
||||
}
|
||||
|
||||
loadGeckoLibs(context);
|
||||
}
|
||||
|
||||
private static void startCrashHelper(final Context context) {
|
||||
try {
|
||||
// Native crashes are reported through pipes, so we don't have to
|
||||
// do anything special for that.
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<? extends Service> cls =
|
||||
(Class<? extends Service>) Class.forName("org.mozilla.gecko.crashhelper.CrashHelper");
|
||||
final Intent i = new Intent(context, cls);
|
||||
context.bindService(i, CrashHelper.createConnection(), Context.BIND_AUTO_CREATE);
|
||||
} catch (final ClassNotFoundException e) {
|
||||
Log.w(LOGTAG, "Couldn't find the crash helper class");
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getMainProcessArgs() {
|
||||
final Context context = GeckoAppShell.getApplicationContext();
|
||||
final ArrayList<String> args = new ArrayList<>();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -78,6 +78,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
@BINPATH@/@DLL_PREFIX@crashhelper@DLL_SUFFIX@
|
||||
@BINPATH@/@DLL_PREFIX@minidump_analyzer@DLL_SUFFIX@
|
||||
#endif
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!--
|
||||
Entitlements to apply to non-browser executables such as updater,
|
||||
pingsender, and crashreporter during codesigning of developer builds.
|
||||
pingsender, crashhelper and crashreporter during codesigning of developer
|
||||
builds.
|
||||
-->
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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')
|
||||
|
||||
171
toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp
Normal file
171
toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <string>
|
||||
|
||||
#if defined(XP_LINUX)
|
||||
# include <sys/signalfd.h>
|
||||
# include <sys/ucontext.h>
|
||||
# include "linux/crash_generation/client_info.h"
|
||||
# include "linux/crash_generation/crash_generation_server.h"
|
||||
using breakpad_char = char;
|
||||
using breakpad_string = std::string;
|
||||
using breakpad_init_type = int;
|
||||
using breakpad_pid = pid_t;
|
||||
#elif defined(XP_WIN)
|
||||
# include "windows/crash_generation/client_info.h"
|
||||
# include "windows/crash_generation/crash_generation_server.h"
|
||||
using breakpad_char = wchar_t;
|
||||
using breakpad_string = std::wstring;
|
||||
using breakpad_init_type = wchar_t*;
|
||||
using breakpad_pid = DWORD;
|
||||
#elif defined(XP_MACOSX)
|
||||
# include <mach/mach_types.h>
|
||||
# include <unistd.h>
|
||||
# include "mac/crash_generation/client_info.h"
|
||||
# include "mac/crash_generation/crash_generation_server.h"
|
||||
using breakpad_char = char;
|
||||
using breakpad_string = std::string;
|
||||
using breakpad_init_type = const char*;
|
||||
using breakpad_pid = pid_t;
|
||||
#else
|
||||
# error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
|
||||
# include "PHC.h"
|
||||
|
||||
namespace mozilla::phc {
|
||||
|
||||
// HACK: The breakpad code expects this global variable even though we don't
|
||||
// use it in the wrapper.
|
||||
MOZ_RUNINIT mozilla::phc::AddrInfo gAddrInfo;
|
||||
|
||||
} // namespace mozilla::phc
|
||||
|
||||
#endif // defined(MOZ_PHC)
|
||||
|
||||
using google_breakpad::ClientInfo;
|
||||
using google_breakpad::CrashGenerationServer;
|
||||
|
||||
// This struct and the callback that uses it need to be kept in sync with the
|
||||
// corresponding Rust code in src/crash_generation.rs.
|
||||
struct BreakpadProcessId {
|
||||
breakpad_pid pid;
|
||||
#if defined(XP_MACOSX)
|
||||
task_t task;
|
||||
#elif defined(XP_WIN)
|
||||
HANDLE handle;
|
||||
#endif
|
||||
};
|
||||
|
||||
using RustCallback = void (*)(BreakpadProcessId, const char*,
|
||||
const breakpad_char*);
|
||||
|
||||
void onClientDumpRequestCallback(void* context, const ClientInfo& client_info,
|
||||
const breakpad_string& file_path) {
|
||||
RustCallback callback = reinterpret_cast<RustCallback>(context);
|
||||
BreakpadProcessId process_id = {
|
||||
.pid = client_info.pid(),
|
||||
#if defined(XP_MACOSX)
|
||||
.task = client_info.task(),
|
||||
#elif defined(XP_WIN)
|
||||
.handle = client_info.process_handle(),
|
||||
#endif
|
||||
};
|
||||
const char* error_msg =
|
||||
#if defined(XP_LINUX)
|
||||
client_info.error_msg();
|
||||
#else
|
||||
nullptr;
|
||||
#endif // XP_LINUX
|
||||
|
||||
callback(process_id, error_msg, file_path.c_str());
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
|
||||
extern "C" void* CrashGenerationServer_init(breakpad_init_type aBreakpadData,
|
||||
const breakpad_char* aMinidumpPath,
|
||||
RustCallback aDumpCallback) {
|
||||
breakpad_string minidumpPath(aMinidumpPath);
|
||||
breakpad_string breakpadData(aBreakpadData);
|
||||
|
||||
CrashGenerationServer* server = new CrashGenerationServer(
|
||||
breakpadData,
|
||||
/* pipe_sec_attrs */ nullptr,
|
||||
/* connect_callback */ nullptr,
|
||||
/* connect_context */ nullptr, onClientDumpRequestCallback,
|
||||
reinterpret_cast<void*>(aDumpCallback),
|
||||
/* written_callback */ nullptr,
|
||||
/* exit_callback */ nullptr,
|
||||
/* exit_context */ nullptr,
|
||||
/* upload_request_callback */ nullptr,
|
||||
/* upload_context */ nullptr,
|
||||
/* generate_dumps */ true, &minidumpPath);
|
||||
|
||||
if (!server->Start()) {
|
||||
delete server;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
#elif defined(XP_MACOSX)
|
||||
|
||||
extern "C" void* CrashGenerationServer_init(breakpad_init_type aBreakpadData,
|
||||
const breakpad_char* aMinidumpPath,
|
||||
RustCallback aDumpCallback) {
|
||||
breakpad_string minidumpPath(aMinidumpPath);
|
||||
breakpad_init_type breakpadData = aBreakpadData;
|
||||
|
||||
CrashGenerationServer* server = new CrashGenerationServer(
|
||||
breakpadData,
|
||||
/* filter */ nullptr,
|
||||
/* filter_context */ nullptr, onClientDumpRequestCallback,
|
||||
reinterpret_cast<void*>(aDumpCallback),
|
||||
/* exit_callback */ nullptr,
|
||||
/* exit_context */ nullptr,
|
||||
/* generate_dumps */ true, minidumpPath);
|
||||
|
||||
if (!server->Start()) {
|
||||
delete server;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
#elif defined(XP_LINUX)
|
||||
|
||||
extern "C" void* CrashGenerationServer_init(breakpad_init_type aBreakpadData,
|
||||
const breakpad_char* aMinidumpPath,
|
||||
RustCallback aDumpCallback) {
|
||||
breakpad_string minidumpPath(aMinidumpPath);
|
||||
breakpad_init_type breakpadData = aBreakpadData;
|
||||
|
||||
CrashGenerationServer* server =
|
||||
new CrashGenerationServer(breakpadData, onClientDumpRequestCallback,
|
||||
reinterpret_cast<void*>(aDumpCallback),
|
||||
/* exit_callback */ nullptr,
|
||||
/* exit_context */ nullptr,
|
||||
/* generate_dumps */ true, &minidumpPath);
|
||||
|
||||
if (!server->Start()) {
|
||||
delete server;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
extern "C" void CrashGenerationServer_shutdown(void* aServer) {
|
||||
CrashGenerationServer* server = static_cast<CrashGenerationServer*>(aServer);
|
||||
delete server;
|
||||
}
|
||||
37
toolkit/crashreporter/breakpad_wrapper/moz.build
Normal file
37
toolkit/crashreporter/breakpad_wrapper/moz.build
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Library("breakpad_wrapper")
|
||||
|
||||
if CONFIG["MOZ_PHC"]:
|
||||
DEFINES["MOZ_PHC"] = True
|
||||
|
||||
SOURCES = [
|
||||
"breakpad_wrapper.cpp",
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"../breakpad-client/",
|
||||
"../google-breakpad/src/",
|
||||
]
|
||||
|
||||
if CONFIG["OS_ARCH"] == "WINNT":
|
||||
USE_LIBS += [
|
||||
"google_breakpad_libxul_s",
|
||||
]
|
||||
|
||||
OS_LIBS += [
|
||||
"advapi32",
|
||||
"bcrypt",
|
||||
"dbghelp",
|
||||
"ntdll",
|
||||
"userenv",
|
||||
"ws2_32",
|
||||
]
|
||||
else:
|
||||
USE_LIBS += [
|
||||
"breakpad_client",
|
||||
]
|
||||
80
toolkit/crashreporter/crash_annotations.rs.in
Normal file
80
toolkit/crashreporter/crash_annotations.rs.in
Normal file
@@ -0,0 +1,80 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use num_derive::FromPrimitive;
|
||||
|
||||
// Enumeration representing all crash annotations
|
||||
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
|
||||
#[repr(u32)]
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq)]
|
||||
pub(crate) enum CrashAnnotation {
|
||||
${enum}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CrashAnnotation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", CRASH_ANNOTATION_STRINGS[*self as usize])
|
||||
}
|
||||
}
|
||||
|
||||
// Type of each annotation
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub(crate) enum CrashAnnotationType {
|
||||
String = 0, // Any type of string, const char*, nsCString, etc...
|
||||
Boolean = 1, // Stored as a byte
|
||||
U32 = 2, // C/C++'s uint32_t or Rust's u32
|
||||
U64 = 3, // C/C++'s uint64_t or Rust's u64
|
||||
USize = 4, // C/C++'s size_t or Rust's usize
|
||||
}
|
||||
|
||||
// Type of each annotation
|
||||
static CRASH_ANNOTATION_TYPES: &[CrashAnnotationType] = &[
|
||||
${types}
|
||||
];
|
||||
|
||||
// Stringified representation of each annotation. Most of these will just match
|
||||
// the corresponding enum values, but for historical reasons some of them are
|
||||
// different in string form when stored in the .extra file.
|
||||
static CRASH_ANNOTATION_STRINGS: &[&str] = &[
|
||||
${names}
|
||||
];
|
||||
|
||||
// Annotations which should be skipped when they have specific values
|
||||
struct CrashAnnotationSkipValue {
|
||||
annotation: CrashAnnotation,
|
||||
value: &'static [u8],
|
||||
}
|
||||
|
||||
static CRASH_ANNOTATIONS_SKIP_VALUES: &[CrashAnnotationSkipValue] = &[
|
||||
${skiplist}
|
||||
];
|
||||
|
||||
|
||||
/// Returns the type of a crash annotation.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `annotation` - a crash annotation
|
||||
pub(crate) fn type_of_annotation(annotation: CrashAnnotation) -> CrashAnnotationType {
|
||||
CRASH_ANNOTATION_TYPES[annotation as usize]
|
||||
}
|
||||
|
||||
/// Checks if the annotation should be included. Some annotations are skipped
|
||||
/// if their value matches a specific one (like the value 0).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `annotation` - the crash annotation to be checked
|
||||
/// * `value` - the contents of the annotation as a string
|
||||
pub(crate) fn should_include_annotation(annotation: CrashAnnotation, value: &[u8]) -> bool {
|
||||
if let Some(skip_value) = CRASH_ANNOTATIONS_SKIP_VALUES
|
||||
.iter()
|
||||
.find(|&a| a.annotation == annotation)
|
||||
{
|
||||
skip_value.value != value
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
28
toolkit/crashreporter/crash_helper/crashhelper.cpp
Normal file
28
toolkit/crashreporter/crash_helper/crashhelper.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#if defined(XP_WIN)
|
||||
# include <windows.h>
|
||||
#endif // defined(XP_WIN)
|
||||
|
||||
#include "mozilla/crash_helper_ffi_generated.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
long value = strtol(argv[1], nullptr, 10);
|
||||
Pid client_pid = static_cast<Pid>(value);
|
||||
|
||||
if (errno != 0) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
crash_generator_logic(client_pid);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
16
toolkit/crashreporter/crash_helper/crashhelper_android.cpp
Normal file
16
toolkit/crashreporter/crash_helper/crashhelper_android.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mozilla/crash_helper_ffi_generated.h"
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_mozilla_gecko_crashhelper_CrashHelper_crash_1generator(
|
||||
JNIEnv*, jclass, jint client_pid) {
|
||||
crash_generator_logic(client_pid);
|
||||
}
|
||||
42
toolkit/crashreporter/crash_helper/moz.build
Normal file
42
toolkit/crashreporter/crash_helper/moz.build
Normal file
@@ -0,0 +1,42 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
LOCAL_INCLUDES = [
|
||||
"/toolkit/crashreporter",
|
||||
]
|
||||
|
||||
USE_LIBS += [
|
||||
"breakpad_wrapper",
|
||||
"crash_helper_server",
|
||||
]
|
||||
|
||||
if CONFIG["OS_TARGET"] == "Android":
|
||||
UNIFIED_SOURCES = [
|
||||
"../bionic_missing_funcs.cpp",
|
||||
"crashhelper_android.cpp",
|
||||
]
|
||||
|
||||
GeckoSharedLibrary("crashhelper", linkage=None)
|
||||
else:
|
||||
if CONFIG["OS_ARCH"] == "Darwin":
|
||||
OS_LIBS += [
|
||||
"-framework Foundation",
|
||||
]
|
||||
elif CONFIG["OS_ARCH"] == "WINNT":
|
||||
OS_LIBS += [
|
||||
"advapi32",
|
||||
"bcrypt",
|
||||
"dbghelp",
|
||||
"ntdll",
|
||||
"userenv",
|
||||
"ws2_32",
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
"crashhelper.cpp",
|
||||
]
|
||||
|
||||
GeckoProgram("crashhelper", linkage=None)
|
||||
18
toolkit/crashreporter/crash_helper_client/Cargo.toml
Normal file
18
toolkit/crashreporter/crash_helper_client/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "crash_helper_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
crash_helper_common = { path = "../crash_helper_common" }
|
||||
num-derive = "0.4"
|
||||
num-traits = "0.2"
|
||||
|
||||
[target."cfg(any(target_os = \"android\", target_os = \"linux\", target_os = \"macos\"))".dependencies]
|
||||
nix = { version = "0.29", features = ["process"] }
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies]
|
||||
windows-sys = { version = "0.52" } # All features inherited from crash_helper_common
|
||||
@@ -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"]
|
||||
@@ -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",
|
||||
]
|
||||
223
toolkit/crashreporter/crash_helper_client/src/lib.rs
Normal file
223
toolkit/crashreporter/crash_helper_client/src/lib.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use crash_helper_common::{
|
||||
messages::{self},
|
||||
BreakpadData, BreakpadString, IPCConnector,
|
||||
};
|
||||
use std::{
|
||||
ffi::{c_char, CStr, CString, OsString},
|
||||
ptr::null_mut,
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows_sys::Win32::System::Diagnostics::Debug::{CONTEXT, EXCEPTION_RECORD};
|
||||
|
||||
extern crate num_traits;
|
||||
|
||||
pub use crash_helper_common::{BreakpadChar, BreakpadRawData, Pid};
|
||||
|
||||
mod platform;
|
||||
|
||||
pub struct CrashHelperClient {
|
||||
connector: IPCConnector,
|
||||
pid: Pid,
|
||||
}
|
||||
|
||||
impl CrashHelperClient {
|
||||
fn send_initialize(
|
||||
&self,
|
||||
minidump_path: OsString,
|
||||
breakpad_data: BreakpadData,
|
||||
release_channel: &str,
|
||||
) -> Result<()> {
|
||||
let message = messages::Initialize::new(minidump_path, breakpad_data, release_channel);
|
||||
self.connector.send_message(&message)?;
|
||||
self.connector.recv_reply::<messages::InitializeReply>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer_crash_report(&self, pid: Pid) -> Result<CrashReport> {
|
||||
let message = messages::TransferMinidump::new(pid);
|
||||
self.connector.send_message(&message)?;
|
||||
|
||||
// HACK: Workaround for a macOS-specific bug
|
||||
#[cfg(target_os = "macos")]
|
||||
self.connector.poll(nix::poll::PollFlags::POLLIN)?;
|
||||
|
||||
let reply = self
|
||||
.connector
|
||||
.recv_reply::<messages::TransferMinidumpReply>()?;
|
||||
|
||||
if reply.path.is_empty() {
|
||||
// TODO: We should return Result<Option<CrashReport>> instead of
|
||||
// this. Semantics would be better once we interact with Rust
|
||||
bail!("Minidump for pid {pid:} was not found");
|
||||
}
|
||||
|
||||
Ok(CrashReport {
|
||||
// SAFETY: path is a valid C string we generated ourselves
|
||||
path: unsafe { reply.path.into_raw() },
|
||||
error: reply.error.map_or(null_mut(), |error| error.into_raw()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Launch the crash helper process, initialize it and connect to it. Returns
|
||||
/// a pointer to the client connection or `null` upon failure.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `helper_name` and `minidump_path` arguments must point to byte or wide
|
||||
/// strings where appropriate. The `breakpad_raw_data` argument must either
|
||||
/// point to a string or must contain a valid file descriptor create via
|
||||
/// `CrashGenerationServer::CreateReportChannel()` depending on the platform.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn crash_helper_launch(
|
||||
helper_name: *const BreakpadChar,
|
||||
minidump_path: *const BreakpadChar,
|
||||
breakpad_raw_data: BreakpadRawData,
|
||||
release_channel: *const c_char,
|
||||
) -> *mut CrashHelperClient {
|
||||
if let Ok(crash_helper) = CrashHelperClient::new(helper_name) {
|
||||
let minidump_path = <OsString as BreakpadString>::from_ptr(minidump_path);
|
||||
let breakpad_data = BreakpadData::new(breakpad_raw_data);
|
||||
let release_channel = CStr::from_ptr(release_channel);
|
||||
let res = crash_helper.send_initialize(
|
||||
minidump_path,
|
||||
breakpad_data,
|
||||
&release_channel.to_string_lossy(),
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
let crash_helper_box = Box::new(crash_helper);
|
||||
// The object will be owned by the C++ code from now on, until it is
|
||||
// passed back in `crash_helper_shutdown`.
|
||||
Box::into_raw(crash_helper_box)
|
||||
} else {
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Shutdown the crash helper and dispose of the client object.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `client` parameter must be a valid pointer to the crash helper client
|
||||
/// object returned by the [`crash_helper_launch()`] function.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn crash_helper_shutdown(client: *mut CrashHelperClient) {
|
||||
// The CrashHelperClient object will be automatically destroyed when the
|
||||
// contents of this box are automatically dropped at the end of the function
|
||||
let _crash_helper_box = Box::from_raw(client);
|
||||
}
|
||||
|
||||
/// Return the pid of the crash helper process.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `client` parameter must be a valid pointer to the crash helper client
|
||||
/// object returned by the [`crash_helper_launch()`] function.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn crash_helper_pid(client: *const CrashHelperClient) -> Pid {
|
||||
(*client).pid
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct CrashReport {
|
||||
path: *mut BreakpadChar,
|
||||
error: *mut c_char,
|
||||
}
|
||||
|
||||
/// Request the crash report generated for the process associated with `pid`.
|
||||
/// If the crash report is found an object holding a pointer to the minidump
|
||||
/// and a potential error message will be returned. Otherwise the function will
|
||||
/// return `null`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `client` parameter must be a valid pointer to the crash helper client
|
||||
/// object returned by the [`crash_helper_launch()`] function.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn transfer_crash_report(
|
||||
client: *mut CrashHelperClient,
|
||||
pid: Pid,
|
||||
) -> *mut CrashReport {
|
||||
let client = client.as_ref().unwrap();
|
||||
if let Ok(crash_report) = client.transfer_crash_report(pid) {
|
||||
// The object will be owned by the C++ code from now on, until it is
|
||||
// passed back in `release_crash_report`.
|
||||
Box::into_raw(Box::new(crash_report))
|
||||
} else {
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Release an object obtained via [`transfer_crash_report()`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `crash_report` argument must be a pointer returned by the
|
||||
/// [`transfer_crash_report()`] function.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn release_crash_report(crash_report: *mut CrashReport) {
|
||||
let crash_report = Box::from_raw(crash_report);
|
||||
|
||||
// Release the strings, we just get back ownership of the raw objects and let the drop logic get rid of them
|
||||
let _path = <OsString as BreakpadString>::from_raw(crash_report.path);
|
||||
|
||||
if !crash_report.error.is_null() {
|
||||
let _error = CString::from_raw(crash_report.error);
|
||||
}
|
||||
}
|
||||
|
||||
/// Report an exception to the crash manager that has been captured outside of
|
||||
/// Firefox processes, typically within the Windows Error Reporting runtime
|
||||
/// exception module.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `exception_record_ptr` and `context_ptr` parameters must point to valid
|
||||
/// objects of the corresponding types.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub unsafe fn report_external_exception(
|
||||
main_process_pid: Pid,
|
||||
pid: Pid,
|
||||
thread: Pid, // TODO: This should be a different type, but it's the same on Windows
|
||||
exception_record_ptr: *mut EXCEPTION_RECORD,
|
||||
context_ptr: *mut CONTEXT,
|
||||
) {
|
||||
let exception_records = collect_exception_records(exception_record_ptr);
|
||||
let context = unsafe { context_ptr.read() };
|
||||
let message =
|
||||
messages::WindowsErrorReportingMinidump::new(pid, thread, exception_records, context);
|
||||
|
||||
// In the code below we connect to the crash helper, send our message and wait for a reply before returning, but we ignore errors because we can't do anything about them in the calling code
|
||||
if let Ok(connector) = IPCConnector::connect(main_process_pid) {
|
||||
let _ = connector
|
||||
.send_message(&message)
|
||||
.and_then(|_| connector.recv_reply::<messages::WindowsErrorReportingMinidumpReply>());
|
||||
}
|
||||
}
|
||||
|
||||
// Collect a linked-list of exception records and turn it into a vector
|
||||
#[cfg(target_os = "windows")]
|
||||
fn collect_exception_records(
|
||||
mut exception_record_ptr: *mut EXCEPTION_RECORD,
|
||||
) -> Vec<EXCEPTION_RECORD> {
|
||||
let mut exception_records = Vec::<EXCEPTION_RECORD>::with_capacity(1);
|
||||
loop {
|
||||
if exception_record_ptr.is_null() {
|
||||
return exception_records;
|
||||
}
|
||||
|
||||
let mut exception_record = unsafe { exception_record_ptr.read() };
|
||||
exception_record_ptr = exception_record.ExceptionRecord;
|
||||
exception_record.ExceptionRecord = null_mut();
|
||||
exception_records.push(exception_record);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -0,0 +1,48 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use anyhow::Result;
|
||||
use std::{ffi::c_char, process};
|
||||
|
||||
use crate::CrashHelperClient;
|
||||
use crash_helper_common::{IPCConnector, Pid};
|
||||
|
||||
impl CrashHelperClient {
|
||||
pub(crate) fn new(program: *const c_char) -> Result<CrashHelperClient> {
|
||||
let pid = CrashHelperClient::spawn_crash_helper(program)?;
|
||||
let connector = IPCConnector::connect(process::id() as Pid)?;
|
||||
|
||||
Ok(CrashHelperClient { connector, pid })
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn spawn_crash_helper(program: *const c_char) -> Result<nix::libc::pid_t> {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
use nix::unistd::{execv, fork, getpid, ForkResult};
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
let parent_pid = getpid().to_string();
|
||||
let parent_pid_arg = unsafe { CString::from_vec_unchecked(parent_pid.into_bytes()) };
|
||||
let pid = unsafe { fork() }?;
|
||||
|
||||
// TODO: daemonize the helper by double fork()'ing and waiting on the child
|
||||
match pid {
|
||||
ForkResult::Child => {
|
||||
let program = unsafe { CStr::from_ptr(program) };
|
||||
let _ = execv(program, &[program, &parent_pid_arg]);
|
||||
|
||||
// This point should be unreachable, but let's play it safe
|
||||
unsafe { nix::libc::_exit(1) };
|
||||
}
|
||||
ForkResult::Parent { child } => Ok(child.as_raw()),
|
||||
}
|
||||
}
|
||||
|
||||
// This is a no-op on Android because we spawn the crash helper as an
|
||||
// Android service directly from the Java code, before loading libxul.
|
||||
#[cfg(target_os = "android")]
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
@@ -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<CrashHelperClient> {
|
||||
let pid = CrashHelperClient::spawn_crash_helper(program)?;
|
||||
let connector = IPCConnector::connect(process::id())?;
|
||||
|
||||
Ok(CrashHelperClient { connector, pid })
|
||||
}
|
||||
|
||||
fn spawn_crash_helper(program: *const u16) -> Result<Pid> {
|
||||
let pid = unsafe { GetCurrentProcessId() };
|
||||
let program = <OsString as BreakpadString>::from_ptr(program);
|
||||
let mut cmd_line = OsString::from("\"");
|
||||
cmd_line.push(program);
|
||||
cmd_line.push("\" \"");
|
||||
cmd_line.push(pid.to_string());
|
||||
cmd_line.push("\"\0");
|
||||
let mut cmd_line: Vec<u16> = cmd_line.encode_wide().collect();
|
||||
|
||||
let mut pi = unsafe { zeroed::<PROCESS_INFORMATION>() };
|
||||
let si = STARTUPINFOW {
|
||||
cb: size_of::<STARTUPINFOW>().try_into().unwrap(),
|
||||
..unsafe { zeroed() }
|
||||
};
|
||||
|
||||
let res = unsafe {
|
||||
CreateProcessW(
|
||||
null(),
|
||||
cmd_line.as_mut_ptr(),
|
||||
/* lpProcessAttributes */ null_mut(),
|
||||
/* lpThreadAttributes */ null_mut(),
|
||||
/* bInheritHandles */ FALSE,
|
||||
CREATE_UNICODE_ENVIRONMENT, // 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)
|
||||
}
|
||||
}
|
||||
49
toolkit/crashreporter/crash_helper_server/Cargo.toml
Normal file
49
toolkit/crashreporter/crash_helper_server/Cargo.toml
Normal file
@@ -0,0 +1,49 @@
|
||||
[package]
|
||||
name = "crash_helper_server"
|
||||
version = "0.1.0"
|
||||
authors = ["Gabriele Svelto <gsvelto@mozilla.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
cfg-if = "1"
|
||||
crash_helper_common = { path = "../crash_helper_common" }
|
||||
env_logger = { version = "0.10", default-features = false }
|
||||
log = "0.4"
|
||||
mozannotation_server = { path = "../mozannotation_server" }
|
||||
mozilla-central-workspace-hack = { version = "0.1", features = [
|
||||
"crash_helper_server",
|
||||
], optional = true }
|
||||
num-derive = "0.4"
|
||||
num-traits = "0.2"
|
||||
once_cell = "1"
|
||||
thiserror = "2"
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
|
||||
[target."cfg(any(target_os = \"android\", target_os = \"linux\"))".dependencies]
|
||||
nix = { version = "0.29", features = ["poll", "socket", "uio"] }
|
||||
rust_minidump_writer_linux = { path = "../rust_minidump_writer_linux" }
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies]
|
||||
windows-sys = { version = "0.52", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Kernel",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_SystemServices",
|
||||
] }
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
nix = { version = "0.29", features = ["fs", "poll", "socket", "uio"] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
linked-hash-map = "0.5"
|
||||
mozbuild = "0.1"
|
||||
yaml-rust = "0.4"
|
||||
|
||||
[lib]
|
||||
name = "crash_helper_server"
|
||||
crate-type = ["staticlib"]
|
||||
path = "src/lib.rs"
|
||||
166
toolkit/crashreporter/crash_helper_server/build.rs
Normal file
166
toolkit/crashreporter/crash_helper_server/build.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::env;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Error, Write};
|
||||
use std::path::Path;
|
||||
use std::vec::Vec;
|
||||
|
||||
use yaml_rust::{Yaml, YamlLoader};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
generate_annotations()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Annotation {
|
||||
name: String,
|
||||
type_string: String,
|
||||
altname: Option<String>,
|
||||
skip_if: Option<String>,
|
||||
}
|
||||
|
||||
impl Annotation {
|
||||
fn new(key: &Yaml, value: &Yaml) -> Result<Annotation, Error> {
|
||||
let name = key.as_str().ok_or(Error::other("Not a string"))?;
|
||||
let raw_type = value["type"].as_str().ok_or(Error::other("Missing type"))?;
|
||||
let type_string = Annotation::type_str_to_value(raw_type)?;
|
||||
let altname = value["altname"].as_str().map(str::to_owned);
|
||||
// We don't care about the contents of the `ping` field for the time being
|
||||
let skip_if = value["skip_if"].as_str().map(str::to_owned);
|
||||
|
||||
Ok(Annotation {
|
||||
name: name.to_owned(),
|
||||
type_string,
|
||||
altname,
|
||||
skip_if,
|
||||
})
|
||||
}
|
||||
|
||||
fn type_str_to_value(raw_type: &str) -> Result<String, Error> {
|
||||
match raw_type {
|
||||
"string" => Ok("String".to_owned()),
|
||||
"boolean" => Ok("Boolean".to_owned()),
|
||||
"u32" => Ok("U32".to_owned()),
|
||||
"u64" => Ok("U64".to_owned()),
|
||||
"usize" => Ok("USize".to_owned()),
|
||||
_ => Err(Error::other("Invalid type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_annotations(doc: &Yaml) -> Result<Vec<Annotation>, Error> {
|
||||
let raw_annotations = doc.as_hash().ok_or(Error::other(
|
||||
"Invalid data in YAML file, expected a hash".to_string(),
|
||||
))?;
|
||||
|
||||
let mut annotations = Vec::<Annotation>::new();
|
||||
for annotation in raw_annotations {
|
||||
let annotation = Annotation::new(annotation.0, annotation.1)?;
|
||||
annotations.push(annotation);
|
||||
}
|
||||
|
||||
annotations.sort_by(|a, b| {
|
||||
let a_lower = a.name.to_ascii_lowercase();
|
||||
let b_lower = b.name.to_ascii_lowercase();
|
||||
a_lower.cmp(&b_lower)
|
||||
});
|
||||
|
||||
Ok(annotations)
|
||||
}
|
||||
|
||||
fn generate_annotation_enum(annotations: &[Annotation]) -> Result<String, Error> {
|
||||
let mut annotations_enum = String::new();
|
||||
|
||||
for (index, value) in annotations.iter().enumerate() {
|
||||
let entry = format!(" {} = {index:},\n", value.name);
|
||||
annotations_enum.push_str(&entry);
|
||||
}
|
||||
|
||||
let count = format!(" Count = {},", annotations.len());
|
||||
annotations_enum.push_str(&count);
|
||||
|
||||
Ok(annotations_enum)
|
||||
}
|
||||
|
||||
fn generate_annotation_types(annotations: &[Annotation]) -> Result<String, Error> {
|
||||
let mut types_array = String::new();
|
||||
for value in annotations.iter() {
|
||||
let entry = format!(" CrashAnnotationType::{},\n", value.type_string);
|
||||
types_array.push_str(&entry);
|
||||
}
|
||||
|
||||
// Pop the last newline
|
||||
types_array.pop();
|
||||
|
||||
Ok(types_array)
|
||||
}
|
||||
|
||||
fn generate_annotation_names(annotations: &[Annotation]) -> Result<String, Error> {
|
||||
let mut names_array = String::new();
|
||||
for value in annotations.iter() {
|
||||
let name = value.altname.as_ref().unwrap_or(&value.name);
|
||||
let entry = format!(" \"{}\",\n", name);
|
||||
names_array.push_str(&entry);
|
||||
}
|
||||
|
||||
// Pop the last newline
|
||||
names_array.pop();
|
||||
|
||||
Ok(names_array)
|
||||
}
|
||||
|
||||
fn generate_annotation_skiplist(annotations: &[Annotation]) -> Result<String, Error> {
|
||||
let mut skiplist = String::new();
|
||||
for annotation in annotations.iter() {
|
||||
if let Some(skip_if) = &annotation.skip_if {
|
||||
let entry = format!(
|
||||
" CrashAnnotationSkipValue {{ annotation: CrashAnnotation::{}, value: b\"{}\" }},\n",
|
||||
&annotation.name, skip_if
|
||||
);
|
||||
skiplist.push_str(&entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Pop the last newline
|
||||
skiplist.pop();
|
||||
|
||||
Ok(skiplist)
|
||||
}
|
||||
|
||||
// Generate Rust code to manipulate crash annotations
|
||||
fn generate_annotations() -> Result<(), Error> {
|
||||
const CRASH_ANNOTATIONS_YAML: &str = "../CrashAnnotations.yaml";
|
||||
const CRASH_ANNOTATIONS_TEMPLATE: &str = "../crash_annotations.rs.in";
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let annotations_path = Path::new(&out_dir).join("crash_annotations.rs");
|
||||
let mut annotations_file = File::create(annotations_path)?;
|
||||
|
||||
let template = fs::read_to_string(CRASH_ANNOTATIONS_TEMPLATE)?;
|
||||
let yaml_str = fs::read_to_string(CRASH_ANNOTATIONS_YAML)?;
|
||||
let yaml_doc = YamlLoader::load_from_str(&yaml_str)
|
||||
.map_err(|e| Error::other(format!("Failed to parse YAML file: {}", e)))?;
|
||||
|
||||
let doc = &yaml_doc[0];
|
||||
let annotations = read_annotations(doc)?;
|
||||
|
||||
let annotations_enum = generate_annotation_enum(&annotations)?;
|
||||
let annotations_types = generate_annotation_types(&annotations)?;
|
||||
let annotations_names = generate_annotation_names(&annotations)?;
|
||||
let skiplist = generate_annotation_skiplist(&annotations)?;
|
||||
|
||||
let compiled_template = template
|
||||
.replace("${enum}", &annotations_enum)
|
||||
.replace("${types}", &annotations_types)
|
||||
.replace("${names}", &annotations_names)
|
||||
.replace("${skiplist}", &skiplist);
|
||||
write!(&mut annotations_file, "{}", compiled_template)?;
|
||||
|
||||
println!("cargo:rerun-if-changed={CRASH_ANNOTATIONS_YAML}");
|
||||
println!("cargo:rerun-if-changed={CRASH_ANNOTATIONS_TEMPLATE}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
21
toolkit/crashreporter/crash_helper_server/cbindgen.toml
Normal file
21
toolkit/crashreporter/crash_helper_server/cbindgen.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
header = """/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
|
||||
autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
|
||||
"""
|
||||
include_version = true
|
||||
braces = "SameLine"
|
||||
line_length = 100
|
||||
tab_width = 2
|
||||
language = "C++"
|
||||
include_guard = "crash_helper_ffi_generated_h"
|
||||
|
||||
[defines]
|
||||
"target_os = android" = "MOZ_WIDGET_ANDROID"
|
||||
"target_os = linux" = "XP_LINUX"
|
||||
"target_os = macos" = "XP_MACOSX"
|
||||
"target_os = windows" = "XP_WIN"
|
||||
|
||||
[parse]
|
||||
parse_deps = true
|
||||
include = ["crash_helper_common"]
|
||||
19
toolkit/crashreporter/crash_helper_server/moz.build
Normal file
19
toolkit/crashreporter/crash_helper_server/moz.build
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
RustLibrary("crash_helper_server")
|
||||
|
||||
if CONFIG["COMPILE_ENVIRONMENT"]:
|
||||
# This tells mach to run cbindgen and that this header-file should be created
|
||||
CbindgenHeader(
|
||||
"crash_helper_ffi_generated.h",
|
||||
inputs=["/toolkit/crashreporter/crash_helper_server"],
|
||||
)
|
||||
|
||||
# This tells mach to copy that generated file to obj/dist/mozilla
|
||||
EXPORTS.mozilla += [
|
||||
"!crash_helper_ffi_generated.h",
|
||||
]
|
||||
@@ -0,0 +1,86 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/******************************************************************************
|
||||
* Wrappers used to call into Breakpad code *
|
||||
******************************************************************************/
|
||||
|
||||
use std::{
|
||||
ffi::{c_char, c_void, OsString},
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use crash_helper_common::{BreakpadChar, BreakpadData, BreakpadString};
|
||||
|
||||
use crate::crash_generation::BreakpadProcessId;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
type BreakpadInitType = *const u16;
|
||||
#[cfg(target_os = "macos")]
|
||||
type BreakpadInitType = *const c_char;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
type BreakpadInitType = std::os::fd::RawFd;
|
||||
|
||||
extern "C" {
|
||||
fn CrashGenerationServer_init(
|
||||
breakpad_data: BreakpadInitType,
|
||||
minidump_path: *const BreakpadChar,
|
||||
cb: extern "C" fn(BreakpadProcessId, *const c_char, *const BreakpadChar),
|
||||
) -> *mut c_void;
|
||||
fn CrashGenerationServer_shutdown(server: *mut c_void);
|
||||
}
|
||||
|
||||
pub(crate) struct BreakpadCrashGenerator {
|
||||
ptr: NonNull<c_void>,
|
||||
}
|
||||
|
||||
// Safety: We own the pointer to the Breakpad C++ CrashGeneration server object
|
||||
// so we can safely transfer this object to another thread.
|
||||
unsafe impl Send for BreakpadCrashGenerator {}
|
||||
|
||||
// Safety: All mutations to the pointer to the Breakpad C++ CrashGeneration
|
||||
// server happen within this object meaning it's safe to read it from different
|
||||
// threads.
|
||||
unsafe impl Sync for BreakpadCrashGenerator {}
|
||||
|
||||
impl BreakpadCrashGenerator {
|
||||
pub(crate) fn new(
|
||||
breakpad_data: BreakpadData,
|
||||
path: OsString,
|
||||
finalize_callback: extern "C" fn(BreakpadProcessId, *const c_char, *const BreakpadChar),
|
||||
) -> Result<BreakpadCrashGenerator> {
|
||||
let breakpad_raw_data = breakpad_data.into_raw();
|
||||
// SAFETY: We're converting a valid string for use within C++ code.
|
||||
let path_ptr = unsafe { path.into_raw() };
|
||||
|
||||
// SAFETY: Calling into breakpad code with parameters that have been previously validated.
|
||||
let breakpad_server =
|
||||
unsafe { CrashGenerationServer_init(breakpad_raw_data, path_ptr, finalize_callback) };
|
||||
|
||||
// Retake ownership of the raw data & strings so we don't leak them.
|
||||
let _breakpad_data = BreakpadData::new(breakpad_raw_data);
|
||||
// SAFETY: We've allocated this string within this same block.
|
||||
let _path = unsafe { <OsString as BreakpadString>::from_raw(path_ptr) };
|
||||
|
||||
if breakpad_server.is_null() {
|
||||
bail!("Could not initialize Breakpad crash generator");
|
||||
}
|
||||
|
||||
Ok(BreakpadCrashGenerator {
|
||||
// SAFETY: We already verified that the pointer is non-null.
|
||||
ptr: unsafe { NonNull::new(breakpad_server).unwrap_unchecked() },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BreakpadCrashGenerator {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: The pointer we're passing is guaranteed to be non-null and
|
||||
// valid since we created it ourselves during construction.
|
||||
unsafe {
|
||||
CrashGenerationServer_shutdown(self.ptr.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub mod crash_annotations {
|
||||
include!(concat!(env!("OUT_DIR"), "/crash_annotations.rs"));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use crash_annotations::{
|
||||
should_include_annotation, type_of_annotation, CrashAnnotation, CrashAnnotationType,
|
||||
};
|
||||
use crash_helper_common::{
|
||||
messages::{self, Message},
|
||||
AncillaryData, BreakpadChar, BreakpadData, BreakpadString, Pid,
|
||||
};
|
||||
use mozannotation_server::{AnnotationData, CAnnotation};
|
||||
use num_traits::FromPrimitive;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::TryInto,
|
||||
ffi::{c_char, CStr, CString, OsStr, OsString},
|
||||
fs::File,
|
||||
io::{Seek, SeekFrom, Write},
|
||||
mem::size_of,
|
||||
path::{Path, PathBuf},
|
||||
sync::Mutex,
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
|
||||
use crate::{
|
||||
breakpad_crash_generator::BreakpadCrashGenerator,
|
||||
phc::{self, StackTrace},
|
||||
};
|
||||
|
||||
struct CrashReport {
|
||||
path: OsString,
|
||||
error: Option<CString>,
|
||||
}
|
||||
|
||||
impl CrashReport {
|
||||
fn new(path: &OsStr, error: &Option<CString>) -> CrashReport {
|
||||
CrashReport {
|
||||
path: path.to_owned(),
|
||||
error: error.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Table holding all the crash reports we've generated. It's indexed by PID and
|
||||
// new crash reports are insterted in the corresponding vector in order of
|
||||
// arrival. When crashes are retrieved they're similarly pulled out in the
|
||||
// order they've arrived.
|
||||
static CRASH_REPORTS: Lazy<Mutex<HashMap<Pid, Vec<CrashReport>>>> = Lazy::new(Default::default);
|
||||
|
||||
/******************************************************************************
|
||||
* Crash generator *
|
||||
******************************************************************************/
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum MinidumpOrigin {
|
||||
Breakpad,
|
||||
WindowsErrorReporting,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum CrashGeneratorState {
|
||||
Disconnected,
|
||||
Connected,
|
||||
Initialized,
|
||||
}
|
||||
|
||||
pub(crate) struct CrashGenerator {
|
||||
minidump_path: OsString,
|
||||
breakpad_server: Option<BreakpadCrashGenerator>,
|
||||
release_channel: String,
|
||||
client_pid: Pid,
|
||||
state: CrashGeneratorState,
|
||||
}
|
||||
|
||||
impl CrashGenerator {
|
||||
pub(crate) fn new(client_pid: Pid) -> Result<CrashGenerator> {
|
||||
Ok(CrashGenerator {
|
||||
minidump_path: OsString::default(),
|
||||
breakpad_server: None,
|
||||
release_channel: String::new(),
|
||||
client_pid,
|
||||
state: CrashGeneratorState::Disconnected,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn client_connect(&mut self, pid: Pid) -> bool {
|
||||
match self.state {
|
||||
CrashGeneratorState::Disconnected => {
|
||||
if self.client_pid != pid {
|
||||
// We accept connections only from the main process
|
||||
return false;
|
||||
}
|
||||
|
||||
self.state = CrashGeneratorState::Connected;
|
||||
}
|
||||
CrashGeneratorState::Connected | CrashGeneratorState::Initialized => {
|
||||
// Once we're connected we'll accept further incoming connections but limit what the corresponding clients can do
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Process a message received from the client. Return an optional reply
|
||||
// that will be sent back to the client.
|
||||
pub(crate) fn client_message(
|
||||
&mut self,
|
||||
kind: messages::Kind,
|
||||
data: &[u8],
|
||||
ancillary_data: Option<AncillaryData>,
|
||||
pid: Pid,
|
||||
) -> Result<Option<Box<dyn Message>>> {
|
||||
match kind {
|
||||
messages::Kind::Initialize => {
|
||||
if (self.state != CrashGeneratorState::Connected) || (pid != self.client_pid) {
|
||||
panic!("Already initialized or received a message from the wrong process");
|
||||
}
|
||||
|
||||
let message = messages::Initialize::decode(data, ancillary_data)?;
|
||||
self.initialize(message.breakpad_data, message.path, message.release_channel)?;
|
||||
Ok(Some(Box::new(messages::InitializeReply::new())))
|
||||
}
|
||||
messages::Kind::TransferMinidump => {
|
||||
if (self.state != CrashGeneratorState::Initialized) || (pid != self.client_pid) {
|
||||
panic!(
|
||||
"Not initialized or attempting to request a minidump from a child process"
|
||||
);
|
||||
}
|
||||
|
||||
let message = messages::TransferMinidump::decode(data, ancillary_data)?;
|
||||
Ok(Some(Box::new(self.transfer_minidump(message.pid))))
|
||||
}
|
||||
messages::Kind::GenerateMinidump => {
|
||||
todo!("Implement all messages");
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
messages::Kind::WindowsErrorReporting => {
|
||||
let message =
|
||||
messages::WindowsErrorReportingMinidump::decode(data, ancillary_data)?;
|
||||
let _ = self.generate_wer_minidump(message);
|
||||
Ok(Some(Box::new(
|
||||
messages::WindowsErrorReportingMinidumpReply::new(),
|
||||
)))
|
||||
}
|
||||
kind => {
|
||||
bail!("Unexpected message {:?}", kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(
|
||||
&mut self,
|
||||
breakpad_data: BreakpadData,
|
||||
path: OsString,
|
||||
release_channel: String,
|
||||
) -> Result<()> {
|
||||
self.minidump_path = path.clone();
|
||||
self.release_channel = release_channel;
|
||||
self.breakpad_server = Some(BreakpadCrashGenerator::new(
|
||||
breakpad_data,
|
||||
path,
|
||||
finalize_breakpad_minidump,
|
||||
)?);
|
||||
self.state = CrashGeneratorState::Initialized;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer_minidump(&self, pid: Pid) -> messages::TransferMinidumpReply {
|
||||
let mut map = CRASH_REPORTS.lock().unwrap();
|
||||
if let Some(mut entry) = map.remove(&pid) {
|
||||
let crash_report = entry.remove(0);
|
||||
|
||||
if !entry.is_empty() {
|
||||
map.insert(pid, entry);
|
||||
}
|
||||
|
||||
messages::TransferMinidumpReply::new(crash_report.path, crash_report.error)
|
||||
} else {
|
||||
// Report not found, reply with a zero length path
|
||||
messages::TransferMinidumpReply::new(OsString::new(), None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Crash annotations *
|
||||
******************************************************************************/
|
||||
|
||||
macro_rules! read_numeric_annotation {
|
||||
($t:ty,$d:expr) => {
|
||||
if let AnnotationData::ByteBuffer(buff) = $d {
|
||||
if buff.len() == size_of::<$t>() {
|
||||
let value = buff.get(0..size_of::<$t>()).map(|bytes| {
|
||||
let bytes: [u8; size_of::<$t>()] = bytes.try_into().unwrap();
|
||||
<$t>::from_ne_bytes(bytes)
|
||||
});
|
||||
value.map(|value| value.to_string().into_bytes())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn write_phc_annotations(file: &mut File, buff: &[u8]) -> Result<()> {
|
||||
let addr_info = phc::AddrInfo::from_bytes(buff)?;
|
||||
if addr_info.kind == phc::Kind::Unknown {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
write!(
|
||||
file,
|
||||
"\"PHCKind\":\"{}\",\
|
||||
\"PHCBaseAddress\":\"{}\",\
|
||||
\"PHCUsableSize\":\"{}\",",
|
||||
addr_info.kind_as_str(),
|
||||
addr_info.base_addr as usize,
|
||||
addr_info.usable_size,
|
||||
)?;
|
||||
|
||||
if addr_info.alloc_stack.has_stack != 0 {
|
||||
write!(
|
||||
file,
|
||||
"\"PHCAllocStack\":\"{}\",",
|
||||
serialize_phc_stack(&addr_info.alloc_stack)
|
||||
)?;
|
||||
}
|
||||
|
||||
if addr_info.free_stack.has_stack != 0 {
|
||||
write!(
|
||||
file,
|
||||
"\"PHCFreeStack\":\"{}\",",
|
||||
serialize_phc_stack(&addr_info.free_stack)
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_phc_stack(stack_trace: &StackTrace) -> String {
|
||||
let mut string = String::new();
|
||||
for i in 0..stack_trace.length {
|
||||
string.push_str(&(stack_trace.pcs[i] as usize).to_string());
|
||||
string.push(',');
|
||||
}
|
||||
|
||||
string.pop();
|
||||
string
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct BreakpadProcessId {
|
||||
pub pid: Pid,
|
||||
#[cfg(target_os = "macos")]
|
||||
pub task: u32,
|
||||
#[cfg(target_os = "windows")]
|
||||
pub handle: HANDLE,
|
||||
}
|
||||
|
||||
/// This reads the crash annotations, writes them to the .extra file and
|
||||
/// finally stores the resulting minidump in the global hash table.
|
||||
extern "C" fn finalize_breakpad_minidump(
|
||||
process_id: BreakpadProcessId,
|
||||
error_ptr: *const c_char,
|
||||
minidump_path_ptr: *const BreakpadChar,
|
||||
) {
|
||||
let minidump_path = PathBuf::from(<OsString as BreakpadString>::from_ptr(minidump_path_ptr));
|
||||
let error = if !error_ptr.is_null() {
|
||||
// SAFETY: The string is a valid C string we passed in ourselves.
|
||||
Some(unsafe { CStr::from_ptr(error_ptr) }.to_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
finalize_crash_report(process_id, error, &minidump_path, MinidumpOrigin::Breakpad);
|
||||
}
|
||||
|
||||
fn finalize_crash_report(
|
||||
process_id: BreakpadProcessId,
|
||||
error: Option<CString>,
|
||||
minidump_path: &Path,
|
||||
origin: MinidumpOrigin,
|
||||
) {
|
||||
let mut extra_path = PathBuf::from(minidump_path);
|
||||
extra_path.set_extension("extra");
|
||||
|
||||
let annotations = retrieve_annotations(&process_id, origin);
|
||||
let extra_file_written = annotations
|
||||
.map(|annotations| write_extra_file(&annotations, &extra_path))
|
||||
.is_ok();
|
||||
|
||||
let path = minidump_path.as_os_str();
|
||||
let error = if !extra_file_written {
|
||||
Some(CString::new("MissingAnnotations").unwrap())
|
||||
} else {
|
||||
error
|
||||
};
|
||||
|
||||
let map = &mut CRASH_REPORTS.lock().unwrap();
|
||||
let entry = map.entry(process_id.pid);
|
||||
entry
|
||||
.and_modify(|entry| entry.push(CrashReport::new(path, &error)))
|
||||
.or_insert_with(|| vec![CrashReport::new(path, &error)]);
|
||||
}
|
||||
|
||||
fn retrieve_annotations(
|
||||
process_id: &BreakpadProcessId,
|
||||
origin: MinidumpOrigin,
|
||||
) -> Result<Vec<CAnnotation>> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let res = mozannotation_server::retrieve_annotations(
|
||||
process_id.handle,
|
||||
CrashAnnotation::Count as usize,
|
||||
);
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
let res =
|
||||
mozannotation_server::retrieve_annotations(process_id.pid, CrashAnnotation::Count as usize);
|
||||
#[cfg(target_os = "macos")]
|
||||
let res = mozannotation_server::retrieve_annotations(
|
||||
process_id.task,
|
||||
CrashAnnotation::Count as usize,
|
||||
);
|
||||
|
||||
let mut annotations = res?;
|
||||
if origin == MinidumpOrigin::WindowsErrorReporting {
|
||||
annotations.push(CAnnotation {
|
||||
id: CrashAnnotation::WindowsErrorReporting as u32,
|
||||
data: AnnotationData::ByteBuffer(vec![1]),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(annotations)
|
||||
}
|
||||
|
||||
fn write_extra_file(annotations: &Vec<CAnnotation>, path: &Path) -> Result<()> {
|
||||
let mut annotations_written: usize = 0;
|
||||
let mut file = File::create(path)?;
|
||||
write!(&mut file, "{{")?;
|
||||
|
||||
for annotation in annotations {
|
||||
if let Some(annotation_id) = CrashAnnotation::from_u32(annotation.id) {
|
||||
if annotation_id == CrashAnnotation::PHCBaseAddress {
|
||||
if let AnnotationData::ByteBuffer(buff) = &annotation.data {
|
||||
write_phc_annotations(&mut file, buff)?;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = match type_of_annotation(annotation_id) {
|
||||
CrashAnnotationType::String => match &annotation.data {
|
||||
AnnotationData::String(string) => Some(escape_value(string.as_bytes())),
|
||||
AnnotationData::ByteBuffer(buffer) => Some(escape_value(buffer)),
|
||||
_ => None,
|
||||
},
|
||||
CrashAnnotationType::Boolean => {
|
||||
if let AnnotationData::ByteBuffer(buff) = &annotation.data {
|
||||
if buff.len() == 1 {
|
||||
Some(vec![if buff[0] != 0 { b'1' } else { b'0' }])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
CrashAnnotationType::U32 => {
|
||||
read_numeric_annotation!(u32, &annotation.data)
|
||||
}
|
||||
CrashAnnotationType::U64 => {
|
||||
read_numeric_annotation!(u64, &annotation.data)
|
||||
}
|
||||
CrashAnnotationType::USize => {
|
||||
read_numeric_annotation!(usize, &annotation.data)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(value) = value {
|
||||
if !value.is_empty() && should_include_annotation(annotation_id, &value) {
|
||||
write!(&mut file, "\"{annotation_id:}\":\"")?;
|
||||
file.write_all(&value)?;
|
||||
write!(&mut file, "\",")?;
|
||||
annotations_written += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if annotations_written > 0 {
|
||||
// Drop the last comma
|
||||
file.seek(SeekFrom::Current(-1))?;
|
||||
}
|
||||
writeln!(&mut file, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Escapes the characters of a crash annotation so that they appear correctly
|
||||
// within the JSON output, escaping non-visible characters and the like. This
|
||||
// does not try to make the output valid UTF-8 because the input might be
|
||||
// corrupted so there's no point in that.
|
||||
fn escape_value(input: &[u8]) -> Vec<u8> {
|
||||
let mut escaped = Vec::<u8>::with_capacity(input.len() + 2);
|
||||
for &c in input {
|
||||
if c <= 0x1f || c == b'\\' || c == b'"' {
|
||||
escaped.extend(b"\\u00");
|
||||
escaped.push(hex_digit_as_ascii_char((c & 0x00f0) >> 4));
|
||||
escaped.push(hex_digit_as_ascii_char(c & 0x000f));
|
||||
} else {
|
||||
escaped.push(c)
|
||||
}
|
||||
}
|
||||
|
||||
escaped
|
||||
}
|
||||
|
||||
fn hex_digit_as_ascii_char(value: u8) -> u8 {
|
||||
if value < 10 {
|
||||
b'0' + value
|
||||
} else {
|
||||
b'a' + (value - 10)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use super::{finalize_crash_report, BreakpadProcessId, CrashGenerator};
|
||||
|
||||
use crash_helper_common::{messages, Pid};
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
fs::{create_dir_all, File},
|
||||
mem::{size_of, zeroed},
|
||||
os::windows::io::AsRawHandle,
|
||||
path::PathBuf,
|
||||
ptr::{null, null_mut},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{FALSE, HANDLE},
|
||||
System::{
|
||||
Diagnostics::Debug::{
|
||||
MiniDumpWithFullMemoryInfo, MiniDumpWithIndirectlyReferencedMemory,
|
||||
MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules, MiniDumpWriteDump,
|
||||
EXCEPTION_POINTERS, EXCEPTION_RECORD, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE,
|
||||
},
|
||||
SystemInformation::{
|
||||
VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION,
|
||||
VER_MINORVERSION, VER_SERVICEPACKMAJOR, VER_SERVICEPACKMINOR,
|
||||
},
|
||||
SystemServices::VER_GREATER_EQUAL,
|
||||
Threading::{OpenProcess, PROCESS_ALL_ACCESS},
|
||||
},
|
||||
};
|
||||
|
||||
impl CrashGenerator {
|
||||
pub(super) fn generate_wer_minidump(
|
||||
&self,
|
||||
message: messages::WindowsErrorReportingMinidump,
|
||||
) -> Result<(), ()> {
|
||||
let (minidump_file, path) = self.create_minidump_file()?;
|
||||
|
||||
let minidump_type: MINIDUMP_TYPE = self.get_minidump_type();
|
||||
let mut context = message.context;
|
||||
let mut exception_records = message.exception_records;
|
||||
let exception_records_ptr = link_exception_records(&mut exception_records);
|
||||
|
||||
let handle = open_process(message.pid)?;
|
||||
let mut exception_pointers = EXCEPTION_POINTERS {
|
||||
ExceptionRecord: exception_records_ptr,
|
||||
ContextRecord: &mut context as *mut _,
|
||||
};
|
||||
|
||||
let exception = MINIDUMP_EXCEPTION_INFORMATION {
|
||||
ThreadId: message.tid,
|
||||
ExceptionPointers: &mut exception_pointers,
|
||||
ClientPointers: FALSE,
|
||||
};
|
||||
|
||||
let res = unsafe {
|
||||
MiniDumpWriteDump(
|
||||
handle,
|
||||
message.pid,
|
||||
minidump_file.as_raw_handle() as _,
|
||||
minidump_type,
|
||||
&exception,
|
||||
/* UserStreamParam */ null(),
|
||||
/* CallbackParam */ null(),
|
||||
)
|
||||
};
|
||||
|
||||
if res != FALSE {
|
||||
let process_id = BreakpadProcessId {
|
||||
pid: message.pid,
|
||||
handle,
|
||||
};
|
||||
|
||||
finalize_crash_report(
|
||||
process_id,
|
||||
None,
|
||||
&path,
|
||||
super::MinidumpOrigin::WindowsErrorReporting,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_minidump_type(&self) -> MINIDUMP_TYPE {
|
||||
let mut minidump_type = MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules;
|
||||
if self.release_channel.eq("nightly") {
|
||||
// This is Nightly only because this doubles the size of minidumps based
|
||||
// on the experimental data.
|
||||
minidump_type |= MiniDumpWithProcessThreadData;
|
||||
|
||||
// dbghelp.dll on Win7 can't handle overlapping memory regions so we only
|
||||
// enable this feature on Win8 or later.
|
||||
if is_windows8_or_later() {
|
||||
// This allows us to examine heap objects referenced from stack objects
|
||||
// at the cost of further doubling the size of minidumps.
|
||||
minidump_type |= MiniDumpWithIndirectlyReferencedMemory;
|
||||
}
|
||||
}
|
||||
minidump_type
|
||||
}
|
||||
|
||||
fn create_minidump_file(&self) -> Result<(File, PathBuf), ()> {
|
||||
// Make sure that the target directory is present
|
||||
create_dir_all(&self.minidump_path).map_err(|_| ())?;
|
||||
|
||||
let uuid = Uuid::new_v4()
|
||||
.as_hyphenated()
|
||||
.encode_lower(&mut Uuid::encode_buffer())
|
||||
.to_string();
|
||||
let path = PathBuf::from(self.minidump_path.clone()).join(uuid + ".dmp");
|
||||
let file = File::create(&path).map_err(|_| ())?;
|
||||
Ok((file, path))
|
||||
}
|
||||
}
|
||||
|
||||
fn link_exception_records(exception_records: &mut Vec<EXCEPTION_RECORD>) -> *mut EXCEPTION_RECORD {
|
||||
let mut iter = exception_records.iter_mut().peekable();
|
||||
while let Some(exception_record) = iter.next() {
|
||||
exception_record.ExceptionRecord = null_mut();
|
||||
|
||||
if let Some(next) = iter.peek_mut() {
|
||||
exception_record.ExceptionRecord = *next as *mut _;
|
||||
}
|
||||
}
|
||||
|
||||
if exception_records.is_empty() {
|
||||
null_mut()
|
||||
} else {
|
||||
exception_records.as_mut_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_windows8_or_later() -> bool {
|
||||
let mut info = OSVERSIONINFOEXW {
|
||||
dwOSVersionInfoSize: size_of::<OSVERSIONINFOEXW>().try_into().unwrap(),
|
||||
dwMajorVersion: 6,
|
||||
dwMinorVersion: 2,
|
||||
..unsafe { zeroed() }
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let mut mask: u64 = 0;
|
||||
let ge: u8 = VER_GREATER_EQUAL.try_into().unwrap();
|
||||
mask = VerSetConditionMask(mask, VER_MAJORVERSION, ge);
|
||||
mask = VerSetConditionMask(mask, VER_MINORVERSION, ge);
|
||||
mask = VerSetConditionMask(mask, VER_SERVICEPACKMAJOR, ge);
|
||||
mask = VerSetConditionMask(mask, VER_SERVICEPACKMINOR, ge);
|
||||
|
||||
VerifyVersionInfoW(
|
||||
&mut info,
|
||||
VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR,
|
||||
mask,
|
||||
) != 0
|
||||
}
|
||||
}
|
||||
|
||||
fn open_process(pid: Pid) -> Result<HANDLE, ()> {
|
||||
// SAFETY: No pointers involved, worst case we get an error
|
||||
match unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) } {
|
||||
0 => Err(()),
|
||||
handle => Ok(handle),
|
||||
}
|
||||
}
|
||||
97
toolkit/crashreporter/crash_helper_server/src/ipc_server.rs
Normal file
97
toolkit/crashreporter/crash_helper_server/src/ipc_server.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use anyhow::Result;
|
||||
use crash_helper_common::{
|
||||
errors::IPCError, messages, wait_for_events, IPCConnector, IPCEvent, IPCListener, Pid,
|
||||
};
|
||||
|
||||
use crate::crash_generation::CrashGenerator;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum IPCServerState {
|
||||
Running,
|
||||
ClientDisconnected,
|
||||
}
|
||||
|
||||
pub(crate) struct IPCServer {
|
||||
listener: IPCListener,
|
||||
connectors: Vec<IPCConnector>,
|
||||
client_pid: Pid,
|
||||
}
|
||||
|
||||
impl IPCServer {
|
||||
pub(crate) fn new(client_pid: Pid) -> Result<IPCServer, IPCError> {
|
||||
let mut listener = IPCListener::new(client_pid)?;
|
||||
listener.listen()?;
|
||||
|
||||
Ok(IPCServer {
|
||||
listener,
|
||||
connectors: Vec::with_capacity(1),
|
||||
client_pid,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn run(
|
||||
&mut self,
|
||||
generator: &mut CrashGenerator,
|
||||
) -> Result<IPCServerState, IPCError> {
|
||||
let events = wait_for_events(&mut self.listener, &mut self.connectors)?;
|
||||
|
||||
for event in events {
|
||||
match event {
|
||||
IPCEvent::Connect(connector) => {
|
||||
if generator.client_connect(connector.endpoint_pid()) {
|
||||
self.connectors.push(connector);
|
||||
}
|
||||
}
|
||||
IPCEvent::Header(index, header) => {
|
||||
let connector = self
|
||||
.connectors
|
||||
.get_mut(index)
|
||||
.expect("Invalid connector index");
|
||||
let _res = Self::handle_message(connector, &header, generator);
|
||||
// TODO: Errors at this level are always survivable, but we
|
||||
// should probably log them.
|
||||
}
|
||||
IPCEvent::Disconnect(index) => {
|
||||
let connector = self
|
||||
.connectors
|
||||
.get_mut(index)
|
||||
.expect("Invalid connector index");
|
||||
if connector.endpoint_pid() == self.client_pid {
|
||||
// The main process disconnected, leave
|
||||
return Ok(IPCServerState::ClientDisconnected);
|
||||
} else {
|
||||
// This closes the connection
|
||||
let _ = self.connectors.remove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(IPCServerState::Running)
|
||||
}
|
||||
|
||||
fn handle_message(
|
||||
connector: &mut IPCConnector,
|
||||
header: &messages::Header,
|
||||
generator: &mut CrashGenerator,
|
||||
) -> Result<()> {
|
||||
let (data, ancillary_data) = connector.recv(header.size)?;
|
||||
|
||||
let reply = generator.client_message(
|
||||
header.kind,
|
||||
&data,
|
||||
ancillary_data,
|
||||
connector.endpoint_pid(),
|
||||
)?;
|
||||
|
||||
if let Some(reply) = reply {
|
||||
connector.send_message(reply.as_ref())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
60
toolkit/crashreporter/crash_helper_server/src/lib.rs
Normal file
60
toolkit/crashreporter/crash_helper_server/src/lib.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
extern crate rust_minidump_writer_linux;
|
||||
|
||||
mod breakpad_crash_generator;
|
||||
mod crash_generation;
|
||||
mod ipc_server;
|
||||
mod phc;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use crash_helper_common::Pid;
|
||||
use std::{
|
||||
ffi::{c_char, CStr},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crash_generation::CrashGenerator;
|
||||
use ipc_server::{IPCServer, IPCServerState};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn crash_generator_logic(client_pid: Pid) -> i32 {
|
||||
let crash_generator = CrashGenerator::new(client_pid)
|
||||
.unwrap();
|
||||
|
||||
let ipc_server = IPCServer::new(client_pid)
|
||||
.unwrap();
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(target_os = "android")] {
|
||||
// On Android the main thread is used to respond to the intents so
|
||||
// we can't block it. Run the crash generation loop in a separate
|
||||
// thread.
|
||||
let _ = std::thread::spawn(move || {
|
||||
main_loop(ipc_server, crash_generator)
|
||||
});
|
||||
|
||||
0
|
||||
} else {
|
||||
main_loop(ipc_server, crash_generator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main_loop(mut ipc_server: IPCServer, mut crash_generator: CrashGenerator) -> i32 {
|
||||
loop {
|
||||
match ipc_server.run(&mut crash_generator) {
|
||||
Ok(_result @ IPCServerState::ClientDisconnected) => {
|
||||
return 0;
|
||||
}
|
||||
Err(error) => {
|
||||
return -1;
|
||||
}
|
||||
_ => {} // Go on
|
||||
}
|
||||
}
|
||||
}
|
||||
100
toolkit/crashreporter/crash_helper_server/src/phc.rs
Normal file
100
toolkit/crashreporter/crash_helper_server/src/phc.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// The types here must match the ones in memory/build/PHC.h
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use std::{
|
||||
ffi::{c_char, c_void},
|
||||
mem::{size_of, MaybeUninit},
|
||||
slice,
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum Kind {
|
||||
Unknown = 0,
|
||||
NeverAllocatedPage = 1,
|
||||
InUsePage = 2,
|
||||
FreedPage = 3,
|
||||
GuardPage = 4,
|
||||
}
|
||||
|
||||
const MAX_FRAMES: usize = 16;
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct StackTrace {
|
||||
pub(crate) length: usize,
|
||||
pub(crate) pcs: [*const c_void; MAX_FRAMES],
|
||||
pub(crate) has_stack: c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct AddrInfo {
|
||||
pub(crate) kind: Kind,
|
||||
pub(crate) base_addr: *const c_void,
|
||||
pub(crate) usable_size: usize,
|
||||
pub(crate) alloc_stack: StackTrace,
|
||||
pub(crate) free_stack: StackTrace,
|
||||
pub(crate) phc_was_locked: c_char,
|
||||
}
|
||||
|
||||
impl AddrInfo {
|
||||
pub(crate) fn from_bytes(buff: &[u8]) -> Result<AddrInfo> {
|
||||
if buff.len() != size_of::<AddrInfo>() {
|
||||
bail!(
|
||||
"PHC AddrInfo structure size {} doesn't match expected size {}",
|
||||
buff.len(),
|
||||
size_of::<AddrInfo>()
|
||||
);
|
||||
}
|
||||
|
||||
let mut addr_info = MaybeUninit::<AddrInfo>::uninit();
|
||||
// SAFETY: MaybeUninit<u8> is always valid, even for padding bytes
|
||||
let uninit_addr_info = unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
addr_info.as_mut_ptr() as *mut MaybeUninit<u8>,
|
||||
size_of::<AddrInfo>(),
|
||||
)
|
||||
};
|
||||
|
||||
for (index, &value) in buff.iter().enumerate() {
|
||||
uninit_addr_info[index].write(value);
|
||||
}
|
||||
|
||||
let addr_info = unsafe { addr_info.assume_init() };
|
||||
if !addr_info.check_consistency() {
|
||||
bail!("PHC AddrInfo structure is inconsistent");
|
||||
}
|
||||
|
||||
Ok(addr_info)
|
||||
}
|
||||
|
||||
pub(crate) fn kind_as_str(&self) -> &'static str {
|
||||
match self.kind {
|
||||
Kind::Unknown => "Unknown(?!)",
|
||||
Kind::NeverAllocatedPage => "NeverAllocatedPage",
|
||||
Kind::InUsePage => "InUsePage(?!)",
|
||||
Kind::FreedPage => "FreedPage",
|
||||
Kind::GuardPage => "GuardPage",
|
||||
}
|
||||
}
|
||||
|
||||
fn check_consistency(&self) -> bool {
|
||||
let kind_value = self.kind as u32;
|
||||
|
||||
if (kind_value > Kind::GuardPage as u32)
|
||||
|| (self.alloc_stack.length > MAX_FRAMES)
|
||||
|| (self.free_stack.length > MAX_FRAMES)
|
||||
|| (self.alloc_stack.has_stack > 1)
|
||||
|| (self.free_stack.has_stack > 1)
|
||||
|| (self.phc_was_locked > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -11,7 +11,5 @@ language = "C++"
|
||||
include_guard = "mozannotation_client_ffi_generated_h"
|
||||
includes = ["nsStringFwd.h"]
|
||||
|
||||
[export.rename]
|
||||
"ThinVec" = "nsTArray"
|
||||
[export]
|
||||
exclude = ["nsCString"]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
mod errors;
|
||||
pub mod errors;
|
||||
|
||||
use crate::errors::*;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
@@ -16,20 +16,15 @@ use mozannotation_client::{Annotation, AnnotationContents, AnnotationMutex};
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use mozannotation_client::{MozAnnotationNote, ANNOTATION_NOTE_NAME, ANNOTATION_TYPE};
|
||||
use std::cmp::min;
|
||||
use std::iter::FromIterator;
|
||||
use std::ffi::CString;
|
||||
use std::mem::{size_of, ManuallyDrop};
|
||||
use std::ptr::null_mut;
|
||||
use thin_vec::ThinVec;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub enum AnnotationData {
|
||||
Empty,
|
||||
ByteBuffer(ThinVec<u8>),
|
||||
ByteBuffer(Vec<u8>),
|
||||
String(CString),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct CAnnotation {
|
||||
#[allow(dead_code)] // This is implicitly stored to and used externally
|
||||
pub id: u32,
|
||||
@@ -38,39 +33,10 @@ pub struct CAnnotation {
|
||||
|
||||
pub type ProcessHandle = process_reader::ProcessHandle;
|
||||
|
||||
/// Return the annotations of a given process.
|
||||
///
|
||||
/// This function will be exposed to C++
|
||||
#[no_mangle]
|
||||
pub extern "C" fn mozannotation_retrieve(
|
||||
process: usize,
|
||||
max_annotations: usize,
|
||||
) -> *mut ThinVec<CAnnotation> {
|
||||
let result = retrieve_annotations(process as _, max_annotations);
|
||||
match result {
|
||||
// Leak the object as it will be owned by the C++ code from now on
|
||||
Ok(annotations) => Box::into_raw(annotations) as *mut _,
|
||||
Err(_) => null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Free the annotations returned by `mozannotation_retrieve()`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `ptr` must contain the value returned by a call to
|
||||
/// `mozannotation_retrieve()` and be called only once.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn mozannotation_free(ptr: *mut ThinVec<CAnnotation>) {
|
||||
// The annotation vector will be automatically destroyed when the contents
|
||||
// of this box are automatically dropped at the end of the function.
|
||||
let _box = Box::from_raw(ptr);
|
||||
}
|
||||
|
||||
pub fn retrieve_annotations(
|
||||
process: ProcessHandle,
|
||||
max_annotations: usize,
|
||||
) -> Result<Box<ThinVec<CAnnotation>>, AnnotationsRetrievalError> {
|
||||
) -> Result<Vec<CAnnotation>, AnnotationsRetrievalError> {
|
||||
let reader = ProcessReader::new(process)?;
|
||||
let address = find_annotations(&reader)?;
|
||||
|
||||
@@ -91,7 +57,7 @@ pub fn retrieve_annotations(
|
||||
|
||||
let vec_pointer = annotation_table.get_ptr();
|
||||
let length = annotation_table.len();
|
||||
let mut annotations = ThinVec::<CAnnotation>::with_capacity(min(max_annotations, length));
|
||||
let mut annotations = Vec::<CAnnotation>::with_capacity(min(max_annotations, length));
|
||||
|
||||
for i in 0..length {
|
||||
let annotation_address = unsafe { vec_pointer.add(i) };
|
||||
@@ -100,7 +66,7 @@ pub fn retrieve_annotations(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Box::new(annotations))
|
||||
Ok(annotations)
|
||||
}
|
||||
|
||||
fn find_annotations(reader: &ProcessReader) -> Result<usize, AnnotationsRetrievalError> {
|
||||
@@ -159,19 +125,19 @@ fn read_annotation(
|
||||
AnnotationContents::Empty => {}
|
||||
AnnotationContents::NSCStringPointer => {
|
||||
let string = copy_nscstring(reader, raw_annotation.address)?;
|
||||
annotation.data = AnnotationData::ByteBuffer(string);
|
||||
annotation.data = AnnotationData::String(string);
|
||||
}
|
||||
AnnotationContents::CStringPointer => {
|
||||
let string = copy_null_terminated_string_pointer(reader, raw_annotation.address)?;
|
||||
annotation.data = AnnotationData::ByteBuffer(string);
|
||||
annotation.data = AnnotationData::String(string);
|
||||
}
|
||||
AnnotationContents::CString => {
|
||||
let string = copy_null_terminated_string(reader, raw_annotation.address)?;
|
||||
annotation.data = AnnotationData::ByteBuffer(string);
|
||||
annotation.data =
|
||||
AnnotationData::String(reader.copy_null_terminated_string(raw_annotation.address)?);
|
||||
}
|
||||
AnnotationContents::ByteBuffer(size) | AnnotationContents::OwnedByteBuffer(size) => {
|
||||
let string = copy_bytebuffer(reader, raw_annotation.address, size)?;
|
||||
annotation.data = AnnotationData::ByteBuffer(string);
|
||||
let buffer = copy_bytebuffer(reader, raw_annotation.address, size)?;
|
||||
annotation.data = AnnotationData::ByteBuffer(buffer);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -181,23 +147,15 @@ fn read_annotation(
|
||||
fn copy_null_terminated_string_pointer(
|
||||
reader: &ProcessReader,
|
||||
address: usize,
|
||||
) -> Result<ThinVec<u8>, process_reader::error::ReadError> {
|
||||
) -> Result<CString, process_reader::error::ReadError> {
|
||||
let buffer_address = reader.copy_object::<usize>(address)?;
|
||||
copy_null_terminated_string(reader, buffer_address)
|
||||
}
|
||||
|
||||
fn copy_null_terminated_string(
|
||||
reader: &ProcessReader,
|
||||
address: usize,
|
||||
) -> Result<ThinVec<u8>, process_reader::error::ReadError> {
|
||||
let string = reader.copy_null_terminated_string(address)?;
|
||||
Ok(ThinVec::<u8>::from(string.as_bytes()))
|
||||
reader.copy_null_terminated_string(buffer_address)
|
||||
}
|
||||
|
||||
fn copy_nscstring(
|
||||
reader: &ProcessReader,
|
||||
address: usize,
|
||||
) -> Result<ThinVec<u8>, process_reader::error::ReadError> {
|
||||
) -> Result<CString, process_reader::error::ReadError> {
|
||||
// HACK: This assumes the layout of the nsCString object
|
||||
let length_address = address + size_of::<usize>();
|
||||
let length = reader.copy_object::<u32>(length_address)?;
|
||||
@@ -214,9 +172,11 @@ fn copy_nscstring(
|
||||
vec.truncate(nul_byte_pos);
|
||||
}
|
||||
|
||||
Ok(ThinVec::from(vec))
|
||||
// SAFETY: This is safe because we verified that there are no nul
|
||||
// characters inside the string.
|
||||
Ok(unsafe { CString::from_vec_unchecked(vec) })
|
||||
} else {
|
||||
Ok(ThinVec::<u8>::new())
|
||||
Ok(CString::default())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +184,6 @@ fn copy_bytebuffer(
|
||||
reader: &ProcessReader,
|
||||
address: usize,
|
||||
size: u32,
|
||||
) -> Result<ThinVec<u8>, process_reader::error::ReadError> {
|
||||
let value = reader.copy_array::<u8>(address, size as _)?;
|
||||
Ok(ThinVec::<u8>::from_iter(value.into_iter()))
|
||||
) -> Result<Vec<u8>, process_reader::error::ReadError> {
|
||||
reader.copy_array::<u8>(address, size as _)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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(|_| ())?;
|
||||
if process_type == MAIN_PROCESS_TYPE {
|
||||
match is_sandboxed_process(process) {
|
||||
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)?;
|
||||
if process_type == MAIN_PROCESS_TYPE {
|
||||
match is_sandboxed_process(process) {
|
||||
Ok(false) => handle_main_process_crash(crash_report, &application_info),
|
||||
|
||||
handle_main_process_crash(crash_report, &application_info)
|
||||
}
|
||||
_ => {
|
||||
// The parent process should never be sandboxed, bail out so the
|
||||
// process which is impersonating it gets killed right away. Also
|
||||
@@ -190,7 +180,7 @@ fn out_of_process_exception_event_callback(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handle_child_process_crash(crash_report, process)
|
||||
handle_child_process_crash(exception_information)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,87 +235,24 @@ fn handle_main_process_crash(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_child_process_crash(crash_report: CrashReport, child_process: HANDLE) -> Result<()> {
|
||||
let parent_process = get_parent_process(child_process)?;
|
||||
let process_reader = ProcessReader::new(parent_process).map_err(|_e| ())?;
|
||||
let libxul_address = process_reader.find_module("xul.dll").map_err(|_e| ())?;
|
||||
let wer_notify_proc = process_reader
|
||||
.find_section(libxul_address, b"mozwerpt")
|
||||
.map_err(|_e| ())?;
|
||||
let wer_notify_proc = unsafe { transmute::<_, LPTHREAD_START_ROUTINE>(wer_notify_proc) };
|
||||
|
||||
let wer_data = WindowsErrorReportingData {
|
||||
child_pid: get_process_id(child_process)?,
|
||||
minidump_name: crash_report.get_minidump_name(),
|
||||
};
|
||||
let address = copy_object_into_process(parent_process, wer_data)?;
|
||||
notify_main_process(parent_process, wer_notify_proc, address)
|
||||
}
|
||||
|
||||
fn copy_object_into_process<T>(process: HANDLE, data: T) -> Result<*mut T> {
|
||||
let address = unsafe {
|
||||
VirtualAllocEx(
|
||||
process,
|
||||
null(),
|
||||
size_of::<T>(),
|
||||
MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_READWRITE,
|
||||
)
|
||||
};
|
||||
|
||||
if address.is_null() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let res = unsafe {
|
||||
WriteProcessMemory(
|
||||
process,
|
||||
address,
|
||||
addr_of!(data) as *const _,
|
||||
size_of::<T>(),
|
||||
null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if res == 0 {
|
||||
unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) };
|
||||
Err(())
|
||||
} else {
|
||||
Ok(address as *mut T)
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_main_process(
|
||||
process: HANDLE,
|
||||
wer_notify_proc: LPTHREAD_START_ROUTINE,
|
||||
address: *mut WindowsErrorReportingData,
|
||||
fn handle_child_process_crash(
|
||||
exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
|
||||
) -> Result<()> {
|
||||
let thread = unsafe {
|
||||
CreateRemoteThread(
|
||||
process,
|
||||
null_mut(),
|
||||
0,
|
||||
wer_notify_proc,
|
||||
address as LPVOID,
|
||||
0,
|
||||
null_mut(),
|
||||
)
|
||||
};
|
||||
let process = unsafe { (*exception_information).hProcess };
|
||||
let process_id = get_process_id(process)?;
|
||||
let thread = unsafe { (*exception_information).hThread };
|
||||
let thread_id = get_thread_id(thread)?;
|
||||
let parent_process = get_parent_process(process)?;
|
||||
let parent_pid = get_process_id(parent_process)?;
|
||||
|
||||
if thread == 0 {
|
||||
unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) };
|
||||
return Err(());
|
||||
}
|
||||
|
||||
// From this point on the memory pointed to by address is owned by the
|
||||
// thread we've created in the main process, so we don't free it.
|
||||
|
||||
let thread = unsafe { OwnedHandle::from_raw_handle(thread as RawHandle) };
|
||||
|
||||
// Don't wait forever as we want the process to get killed eventually
|
||||
let res = unsafe { WaitForSingleObject(thread.as_raw_handle() as HANDLE, 5000) };
|
||||
if res != WAIT_OBJECT_0 {
|
||||
return Err(());
|
||||
unsafe {
|
||||
report_external_exception(
|
||||
parent_pid,
|
||||
process_id,
|
||||
thread_id,
|
||||
&raw mut (*exception_information).exceptionRecord,
|
||||
&raw mut (*exception_information).context,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -366,6 +293,13 @@ fn get_process_id(process: HANDLE) -> Result<DWORD> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_thread_id(thread: HANDLE) -> Result<DWORD> {
|
||||
match unsafe { GetThreadId(thread) } {
|
||||
0 => Err(()),
|
||||
tid => Ok(tid),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_process_handle(pid: DWORD) -> Result<HANDLE> {
|
||||
let handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) };
|
||||
if handle != 0 {
|
||||
@@ -693,11 +627,6 @@ impl CrashReport {
|
||||
self.get_pending_path().join(self.uuid.to_string() + ".dmp")
|
||||
}
|
||||
|
||||
fn get_minidump_name(&self) -> [u8; 40] {
|
||||
let bytes = (self.uuid.to_string() + ".dmp").into_bytes();
|
||||
bytes[0..40].try_into().unwrap()
|
||||
}
|
||||
|
||||
fn get_extra_file_path(&self) -> PathBuf {
|
||||
self.get_pending_path()
|
||||
.join(self.uuid.to_string() + ".extra")
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
RustLibrary("mozwer_s")
|
||||
|
||||
OS_LIBS += [
|
||||
"advapi32",
|
||||
"dbghelp",
|
||||
"kernel32",
|
||||
"ntdll",
|
||||
"ole32",
|
||||
"psapi",
|
||||
"shell32",
|
||||
"user32",
|
||||
"userenv",
|
||||
|
||||
@@ -13,20 +13,18 @@
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsDirectoryService.h"
|
||||
#include "nsIFileStreams.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTHashMap.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/EnumeratedRange.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Printf.h"
|
||||
#include "mozilla/RuntimeExceptionModule.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
#include "mozilla/StaticMutex.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Unused.h"
|
||||
@@ -35,12 +33,11 @@
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsThread.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "private/pprio.h"
|
||||
#include "base/process_util.h"
|
||||
#include "common/basictypes.h"
|
||||
|
||||
#include "mozilla/toolkit/crashreporter/mozannotation_client_ffi_generated.h"
|
||||
#include "mozilla/toolkit/crashreporter/mozannotation_server_ffi_generated.h"
|
||||
#include "mozilla/crash_helper_client_ffi_generated.h"
|
||||
|
||||
#ifdef MOZ_BACKGROUNDTASKS
|
||||
# include "mozilla/BackgroundTasks.h"
|
||||
@@ -82,12 +79,16 @@
|
||||
# include "mac_utils.h"
|
||||
#elif defined(XP_LINUX)
|
||||
# include "nsIINIParser.h"
|
||||
# if defined(MOZ_WIDGET_ANDROID)
|
||||
# include "common/linux/eintr_wrapper.h"
|
||||
# else
|
||||
# include <sys/prctl.h> // For prctl() and PR_SET_PTRACER
|
||||
# endif // defined(MOZ_WIDGET_ANDROID)
|
||||
# include "common/linux/linux_libc_support.h"
|
||||
# include "third_party/lss/linux_syscall_support.h"
|
||||
# include "breakpad-client/linux/crash_generation/client_info.h"
|
||||
# include "breakpad-client/linux/crash_generation/crash_generation_server.h"
|
||||
# include "breakpad-client/linux/handler/exception_handler.h"
|
||||
# include "common/linux/eintr_wrapper.h"
|
||||
# include <fcntl.h>
|
||||
# include <sys/types.h>
|
||||
# include "sys/sysinfo.h"
|
||||
@@ -109,7 +110,6 @@
|
||||
#include <prio.h>
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsDebug.h"
|
||||
#include "nsCRT.h"
|
||||
#include "nsIFile.h"
|
||||
|
||||
#include "mozilla/IOInterposer.h"
|
||||
@@ -137,6 +137,8 @@ using google_breakpad::PageAllocator;
|
||||
#endif
|
||||
using namespace mozilla;
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
|
||||
namespace mozilla::phc {
|
||||
|
||||
// Global instance that is retrieved by the process generating the crash report
|
||||
@@ -144,6 +146,8 @@ MOZ_GLOBINIT mozilla::phc::AddrInfo gAddrInfo;
|
||||
|
||||
} // namespace mozilla::phc
|
||||
|
||||
#endif // defined(MOZ_PHC)
|
||||
|
||||
namespace CrashReporter {
|
||||
|
||||
#ifdef XP_WIN
|
||||
@@ -154,6 +158,7 @@ typedef std::wstring xpstring;
|
||||
# define XP_STRLEN(x) wcslen(x)
|
||||
# define my_strlen strlen
|
||||
# define my_memchr memchr
|
||||
# define CRASH_HELPER_FILENAME u"crashhelper.exe"_ns
|
||||
# define CRASH_REPORTER_FILENAME u"crashreporter.exe"_ns
|
||||
# define XP_PATH_SEPARATOR L"\\"
|
||||
# define XP_PATH_SEPARATOR_CHAR L'\\'
|
||||
@@ -167,6 +172,7 @@ typedef char XP_CHAR;
|
||||
typedef std::string xpstring;
|
||||
# define XP_TEXT(x) x
|
||||
# define CONVERT_XP_CHAR_TO_UTF16(x) NS_ConvertUTF8toUTF16(x)
|
||||
# define CRASH_HELPER_FILENAME u"crashhelper"_ns
|
||||
# define CRASH_REPORTER_FILENAME u"crashreporter"_ns
|
||||
# define XP_PATH_SEPARATOR "/"
|
||||
# define XP_PATH_SEPARATOR_CHAR '/'
|
||||
@@ -207,12 +213,14 @@ MOZ_RUNINIT static std::optional<xpstring> defaultMemoryReportPath = {};
|
||||
|
||||
static const char kCrashMainID[] = "crash.main.3\n";
|
||||
|
||||
static Maybe<CrashHelperClient*> gCrashHelperClient;
|
||||
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
|
||||
static mozilla::Atomic<bool> gEncounteredChildException(false);
|
||||
MOZ_CONSTINIT static nsCString gServerURL;
|
||||
|
||||
MOZ_RUNINIT static xpstring pendingDirectory;
|
||||
MOZ_RUNINIT static xpstring crashReporterPath;
|
||||
MOZ_RUNINIT static xpstring crashHelperPath;
|
||||
MOZ_RUNINIT static xpstring memoryReportPath;
|
||||
|
||||
// Where crash events should go.
|
||||
@@ -240,6 +248,8 @@ static char* androidUserSerial = nullptr;
|
||||
static const char* androidStartServiceCommand = nullptr;
|
||||
#endif
|
||||
|
||||
static ProcessId gCrashHelperPid = 0;
|
||||
|
||||
// this holds additional data sent via the API
|
||||
static Mutex* notesFieldLock;
|
||||
static nsCString* notesField = nullptr;
|
||||
@@ -268,7 +278,7 @@ static CrashGenerationServer* crashServer; // chrome process has this
|
||||
static std::terminate_handler oldTerminateHandler = nullptr;
|
||||
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||||
static char* childCrashNotifyPipe;
|
||||
MOZ_RUNINIT static nsCString childCrashNotifyPipe;
|
||||
|
||||
#elif defined(XP_LINUX)
|
||||
static int serverSocketFd = -1;
|
||||
@@ -276,18 +286,6 @@ static int clientSocketFd = -1;
|
||||
|
||||
#endif
|
||||
|
||||
// |dumpMapLock| must protect all access to |pidToMinidump|.
|
||||
static Mutex* dumpMapLock;
|
||||
struct ChildProcessData : public nsUint32HashKey {
|
||||
explicit ChildProcessData(KeyTypePointer aKey)
|
||||
: nsUint32HashKey(aKey), annotations(nullptr) {}
|
||||
|
||||
nsCOMPtr<nsIFile> minidump;
|
||||
UniquePtr<AnnotationTable> annotations;
|
||||
};
|
||||
|
||||
typedef nsTHashtable<ChildProcessData> ChildMinidumpMap;
|
||||
static ChildMinidumpMap* pidToMinidump;
|
||||
static bool OOPInitialized();
|
||||
|
||||
void RecordMainThreadId() {
|
||||
@@ -728,7 +726,7 @@ static void PHCStackTraceToString(char* aBuffer, size_t aBufferLen,
|
||||
strcat(aBuffer, ",");
|
||||
}
|
||||
XP_STOA(uintptr_t(aStack.mPcs[i]), addrString);
|
||||
strncat(aBuffer, addrString, aBufferLen);
|
||||
strncat(aBuffer, addrString, aBufferLen - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,55 +783,6 @@ static void WritePHCAddrInfo(AnnotationWriter& writer,
|
||||
}
|
||||
}
|
||||
|
||||
static void PopulatePHCStackTraceAnnotation(
|
||||
AnnotationTable& aAnnotations, const Annotation aName,
|
||||
const Maybe<phc::StackTrace>& aStack) {
|
||||
if (aStack.isNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
char addrsString[phcStringifiedAnnotationSize];
|
||||
PHCStackTraceToString(addrsString, sizeof(addrsString), *aStack);
|
||||
aAnnotations[aName] = addrsString;
|
||||
}
|
||||
|
||||
static void PopulatePHCAnnotations(AnnotationTable& aAnnotations,
|
||||
const phc::AddrInfo* aAddrInfo) {
|
||||
// Is this a PHC allocation needing special treatment?
|
||||
if (aAddrInfo && aAddrInfo->mKind != phc::AddrInfo::Kind::Unknown) {
|
||||
const char* kindString;
|
||||
switch (aAddrInfo->mKind) {
|
||||
case phc::AddrInfo::Kind::Unknown:
|
||||
kindString = "Unknown(?!)";
|
||||
break;
|
||||
case phc::AddrInfo::Kind::NeverAllocatedPage:
|
||||
kindString = "NeverAllocatedPage";
|
||||
break;
|
||||
case phc::AddrInfo::Kind::InUsePage:
|
||||
kindString = "InUsePage(?!)";
|
||||
break;
|
||||
case phc::AddrInfo::Kind::FreedPage:
|
||||
kindString = "FreedPage";
|
||||
break;
|
||||
case phc::AddrInfo::Kind::GuardPage:
|
||||
kindString = "GuardPage";
|
||||
break;
|
||||
default:
|
||||
kindString = "Unmatched(?!)";
|
||||
break;
|
||||
}
|
||||
|
||||
aAnnotations[Annotation::PHCKind] = kindString;
|
||||
aAnnotations[Annotation::PHCBaseAddress] =
|
||||
nsPrintfCString("%zu", uintptr_t(aAddrInfo->mBaseAddr));
|
||||
aAnnotations[Annotation::PHCUsableSize] =
|
||||
nsPrintfCString("%zu", aAddrInfo->mUsableSize);
|
||||
PopulatePHCStackTraceAnnotation(aAnnotations, Annotation::PHCAllocStack,
|
||||
aAddrInfo->mAllocStack);
|
||||
PopulatePHCStackTraceAnnotation(aAnnotations, Annotation::PHCFreeStack,
|
||||
aAddrInfo->mFreeStack);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -1739,7 +1688,13 @@ static void PrepareForMinidump() {
|
||||
# if defined(DEBUG) && defined(HAS_DLL_BLOCKLIST)
|
||||
DllBlocklist_Shutdown();
|
||||
# endif
|
||||
#endif // XP_WIN
|
||||
#elif defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
|
||||
if (gCrashHelperPid) {
|
||||
// Ignore the return value because we're in the exception handler, so
|
||||
// there's not much we can do safely, not even log the error.
|
||||
Unused << prctl(PR_SET_PTRACER, gCrashHelperPid);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
@@ -1815,22 +1770,6 @@ static bool ChildFilter(void* context) {
|
||||
|
||||
#endif // !defined(XP_WIN)
|
||||
|
||||
static bool ChildMinidumpCallback(
|
||||
#if defined(XP_WIN)
|
||||
const wchar_t* dump_path, const wchar_t* minidump_id,
|
||||
#elif defined(XP_LINUX)
|
||||
const MinidumpDescriptor& descriptor,
|
||||
#else // defined(XP_MACOSX)
|
||||
const char* dump_dir, const char* minidump_id,
|
||||
#endif
|
||||
void* context,
|
||||
#if defined(XP_WIN)
|
||||
EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion,
|
||||
#endif // defined(XP_WIN)
|
||||
const mozilla::phc::AddrInfo* addr_info, bool succeeded) {
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
static bool ShouldReport() {
|
||||
// this environment variable prevents us from launching
|
||||
// the crash reporter client
|
||||
@@ -1860,10 +1799,12 @@ static nsresult LocateExecutable(nsIFile* aXREDirectory, const nsAString& aName,
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
# ifdef XP_MACOSX
|
||||
if (aName.Equals(CRASH_REPORTER_FILENAME)) {
|
||||
exePath->SetNativeLeafName("MacOS"_ns);
|
||||
exePath->Append(u"crashreporter.app"_ns);
|
||||
exePath->Append(u"Contents"_ns);
|
||||
exePath->Append(u"MacOS"_ns);
|
||||
}
|
||||
# endif
|
||||
|
||||
exePath->Append(aName);
|
||||
@@ -1955,8 +1896,16 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) {
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
// Locate the crash helper executable
|
||||
PathString crashHelperPath_temp;
|
||||
rv = LocateExecutable(aXREDirectory, CRASH_HELPER_FILENAME,
|
||||
crashHelperPath_temp);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
crashReporterPath = crashReporterPath_temp.get();
|
||||
crashHelperPath = crashHelperPath_temp.get();
|
||||
#else
|
||||
// On Android, we launch a service defined via MOZ_ANDROID_CRASH_HANDLER
|
||||
const char* androidCrashHandler = PR_GetEnv("MOZ_ANDROID_CRASH_HANDLER");
|
||||
@@ -1976,6 +1925,10 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) {
|
||||
androidStartServiceCommand = (char*)"startservice";
|
||||
}
|
||||
}
|
||||
|
||||
const char* crashHelperPathEnv = PR_GetEnv("MOZ_ANDROID_PACKAGE_NAME");
|
||||
MOZ_ASSERT(crashHelperPathEnv, "The application package name is required");
|
||||
crashHelperPath = crashHelperPathEnv;
|
||||
#endif // !defined(MOZ_WIDGET_ANDROID)
|
||||
|
||||
// get temp path to use for minidump path
|
||||
@@ -3093,6 +3046,34 @@ bool GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static nsresult ReadExtraFile(nsCOMPtr<nsIFile>& aFile,
|
||||
AnnotationTable& aAnnotations) {
|
||||
const int64_t kExtraFileMaxSize = 1024 * 1024 * 1024;
|
||||
int64_t fileSize;
|
||||
|
||||
nsresult rv = aFile->GetFileSize(&fileSize);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// Reject humongous extra files, Socorro will discard them anyway
|
||||
NS_ENSURE_TRUE((fileSize > 0) && (fileSize < kExtraFileMaxSize),
|
||||
NS_ERROR_OUT_OF_MEMORY);
|
||||
nsTArray<uint8_t> buffer((size_t)rv);
|
||||
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFile);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCString json;
|
||||
rv = NS_ReadInputStreamToString(stream, json, fileSize);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
auto annotations = ExtraFileParser::Parse(json);
|
||||
|
||||
if (!annotations) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aAnnotations = *annotations;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static bool WriteExtraFile(PlatformWriter& pw,
|
||||
const AnnotationTable& aAnnotations) {
|
||||
if (!pw.Valid()) {
|
||||
@@ -3210,79 +3191,6 @@ static void AddSharedAnnotations(AnnotationTable& aAnnotations) {
|
||||
AddCommonAnnotations(aAnnotations);
|
||||
}
|
||||
|
||||
static void AddChildProcessAnnotations(
|
||||
AnnotationTable& aAnnotations, nsTArray<CAnnotation>* aChildAnnotations) {
|
||||
if (!aChildAnnotations) {
|
||||
// TODO: We should probably make a list of errors that occurred when
|
||||
// generating a crash report as more than one can occurr.
|
||||
aAnnotations[Annotation::DumperError] = "MissingAnnotations";
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& annotation : *aChildAnnotations) {
|
||||
Annotation id = static_cast<Annotation>(annotation.id);
|
||||
const AnnotationData& data = annotation.data;
|
||||
|
||||
if ((id == Annotation::PHCBaseAddress) &&
|
||||
(data.tag == AnnotationData::Tag::ByteBuffer)) {
|
||||
// PHC is special for now, let's deal with it here
|
||||
#ifdef MOZ_PHC
|
||||
const auto& buffer = data.byte_buffer._0;
|
||||
alignas(mozilla::phc::AddrInfo) char mem[sizeof(mozilla::phc::AddrInfo)];
|
||||
memcpy(mem, buffer.Elements(), sizeof(mozilla::phc::AddrInfo));
|
||||
const auto* addr_info =
|
||||
reinterpret_cast<const mozilla::phc::AddrInfo*>(mem);
|
||||
PopulatePHCAnnotations(aAnnotations, addr_info);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data.tag == AnnotationData::Tag::Empty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nsAutoCString value;
|
||||
const uint8_t* buffer = data.byte_buffer._0.Elements();
|
||||
const size_t length = data.byte_buffer._0.Length();
|
||||
|
||||
switch (TypeOfAnnotation(id)) {
|
||||
case AnnotationType::String:
|
||||
value.Assign(reinterpret_cast<const char*>(buffer), length);
|
||||
break;
|
||||
case AnnotationType::Boolean:
|
||||
if (length == sizeof(bool)) {
|
||||
value.Assign(*reinterpret_cast<const bool*>(buffer) ? "1" : "0");
|
||||
}
|
||||
break;
|
||||
case AnnotationType::U32:
|
||||
if (length == sizeof(uint32_t)) {
|
||||
value.AppendInt(*reinterpret_cast<const uint32_t*>(buffer));
|
||||
}
|
||||
break;
|
||||
case AnnotationType::U64:
|
||||
if (length == sizeof(uint64_t)) {
|
||||
value.AppendInt(*reinterpret_cast<const uint64_t*>(buffer));
|
||||
}
|
||||
break;
|
||||
case AnnotationType::USize:
|
||||
if (length == sizeof(size_t)) {
|
||||
#ifdef XP_MACOSX
|
||||
// macOS defines size_t as unsigned long, which causes ambiguity
|
||||
// when it comes to function overload, use a 64-bit integer instead
|
||||
value.AppendInt(*reinterpret_cast<const uint64_t*>(buffer));
|
||||
#else
|
||||
value.AppendInt(*reinterpret_cast<const size_t*>(buffer));
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!value.IsEmpty() && ShouldIncludeAnnotation(id, value.get())) {
|
||||
aAnnotations[id] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It really only makes sense to call this function when
|
||||
// ShouldReport() is true.
|
||||
// Uses dumpFile's filename to generate memoryReport's filename (same name
|
||||
@@ -3318,74 +3226,7 @@ static bool MoveToPending(nsIFile* dumpFile, nsIFile* extraFile,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void MaybeAnnotateDumperError(const ClientInfo& aClientInfo,
|
||||
AnnotationTable& aAnnotations) {
|
||||
#if defined(MOZ_OXIDIZED_BREAKPAD)
|
||||
if (aClientInfo.had_error()) {
|
||||
aAnnotations[Annotation::DumperError] =
|
||||
nsDependentCString(aClientInfo.error_msg());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void OnChildProcessDumpRequested(
|
||||
void* aContext, const ClientInfo& aClientInfo,
|
||||
const xpstring& aFilePath) MOZ_NO_THREAD_SAFETY_ANALYSIS {
|
||||
nsCOMPtr<nsIFile> minidump;
|
||||
|
||||
// Hold the mutex until the current dump request is complete, to
|
||||
// prevent UnsetExceptionHandler() from pulling the rug out from
|
||||
// under us.
|
||||
MutexAutoLock lock(*dumpSafetyLock);
|
||||
if (!isSafeToDump) return;
|
||||
|
||||
CreateFileFromPath(aFilePath, getter_AddRefs(minidump));
|
||||
MOZ_ASSERT(minidump);
|
||||
|
||||
ProcessId pid = aClientInfo.pid();
|
||||
if (ShouldReport()) {
|
||||
nsCOMPtr<nsIFile> memoryReport;
|
||||
if (!memoryReportPath.empty()) {
|
||||
CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport));
|
||||
MOZ_ASSERT(memoryReport);
|
||||
}
|
||||
MoveToPending(minidump, nullptr, memoryReport);
|
||||
}
|
||||
|
||||
#if XP_WIN
|
||||
nsTArray<CAnnotation>* child_annotations = mozannotation_retrieve(
|
||||
reinterpret_cast<uintptr_t>(aClientInfo.process_handle()),
|
||||
static_cast<size_t>(Annotation::Count));
|
||||
#elif defined(XP_MACOSX)
|
||||
nsTArray<CAnnotation>* child_annotations = mozannotation_retrieve(
|
||||
aClientInfo.task(), static_cast<size_t>(Annotation::Count));
|
||||
#else
|
||||
nsTArray<CAnnotation>* child_annotations =
|
||||
mozannotation_retrieve(pid, static_cast<size_t>(Annotation::Count));
|
||||
#endif
|
||||
|
||||
// TODO: Write a minimal set of annotations if we fail to read them, and
|
||||
// add an error to the minidump to highlight this fact.
|
||||
|
||||
{
|
||||
MutexAutoLock lock(*dumpMapLock);
|
||||
ChildProcessData* pd = pidToMinidump->PutEntry(pid);
|
||||
MOZ_ASSERT(!pd->minidump);
|
||||
pd->minidump = minidump;
|
||||
pd->annotations = MakeUnique<AnnotationTable>();
|
||||
AnnotationTable& annotations = *(pd->annotations);
|
||||
AddSharedAnnotations(annotations);
|
||||
AddChildProcessAnnotations(annotations, child_annotations);
|
||||
|
||||
MaybeAnnotateDumperError(aClientInfo, annotations);
|
||||
}
|
||||
|
||||
if (child_annotations) {
|
||||
mozannotation_free(child_annotations);
|
||||
}
|
||||
}
|
||||
|
||||
static bool OOPInitialized() { return pidToMinidump != nullptr; }
|
||||
static bool OOPInitialized() { return gCrashHelperClient.isSome(); }
|
||||
|
||||
void OOPInit() {
|
||||
class ProxyToMainThread : public Runnable {
|
||||
@@ -3412,56 +3253,48 @@ void OOPInit() {
|
||||
"attempt to initialize OOP crash reporter before in-process "
|
||||
"crashreporter!");
|
||||
|
||||
CrashHelperClient* crashHelperClient;
|
||||
|
||||
#if defined(XP_WIN)
|
||||
childCrashNotifyPipe =
|
||||
mozilla::Smprintf("\\\\.\\pipe\\gecko-crash-server-pipe.%i",
|
||||
static_cast<int>(::GetCurrentProcessId()))
|
||||
.release();
|
||||
|
||||
const std::wstring dumpPath = gExceptionHandler->dump_path();
|
||||
crashServer = new CrashGenerationServer(
|
||||
std::wstring(NS_ConvertASCIItoUTF16(childCrashNotifyPipe).get()),
|
||||
nullptr, // default security attributes
|
||||
nullptr, nullptr, // we don't care about process connect here
|
||||
OnChildProcessDumpRequested, nullptr, nullptr, nullptr,
|
||||
nullptr, // we don't care about process exit here
|
||||
nullptr, nullptr, // we don't care about upload request here
|
||||
true, // automatically generate dumps
|
||||
&dumpPath);
|
||||
|
||||
if (sIncludeContextHeap) {
|
||||
crashServer->set_include_context_heap(sIncludeContextHeap);
|
||||
}
|
||||
childCrashNotifyPipe = nsCString("\\\\.\\pipe\\gecko-crash-server-pipe.");
|
||||
childCrashNotifyPipe.AppendInt(static_cast<int>(::GetCurrentProcessId()));
|
||||
|
||||
// TODO: Create the crash server and set include_context_heap based on the
|
||||
// value of sIncludeContextHeap. Also pass the release channel so we can set
|
||||
// the appropriate type of minidump in the crash helper.
|
||||
crashHelperClient = crash_helper_launch(
|
||||
(const BreakpadChar*)crashHelperPath.c_str(),
|
||||
(const BreakpadChar*)gExceptionHandler->dump_path().c_str(),
|
||||
(const BreakpadChar*)NS_ConvertUTF8toUTF16(childCrashNotifyPipe)
|
||||
.BeginReading(),
|
||||
MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL));
|
||||
#elif defined(XP_LINUX)
|
||||
if (!CrashGenerationServer::CreateReportChannel(&serverSocketFd,
|
||||
&clientSocketFd))
|
||||
&clientSocketFd)) {
|
||||
MOZ_CRASH("can't create crash reporter socketpair()");
|
||||
}
|
||||
|
||||
const std::string dumpPath =
|
||||
gExceptionHandler->minidump_descriptor().directory();
|
||||
crashServer =
|
||||
new CrashGenerationServer(serverSocketFd, OnChildProcessDumpRequested,
|
||||
nullptr, nullptr, nullptr, true, &dumpPath);
|
||||
|
||||
crashHelperClient =
|
||||
crash_helper_launch(crashHelperPath.c_str(), dumpPath.c_str(),
|
||||
serverSocketFd, MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL));
|
||||
close(serverSocketFd);
|
||||
#elif defined(XP_MACOSX)
|
||||
childCrashNotifyPipe = mozilla::Smprintf("gecko-crash-server-pipe.%i",
|
||||
static_cast<int>(getpid()))
|
||||
.release();
|
||||
const std::string dumpPath = gExceptionHandler->dump_path();
|
||||
childCrashNotifyPipe = nsCString("gecko-crash-server-pipe.");
|
||||
childCrashNotifyPipe.AppendInt(static_cast<int>(getpid()));
|
||||
|
||||
crashServer = new CrashGenerationServer(childCrashNotifyPipe, nullptr,
|
||||
nullptr, OnChildProcessDumpRequested,
|
||||
nullptr, nullptr, nullptr,
|
||||
true, // automatically generate dumps
|
||||
dumpPath);
|
||||
crashHelperClient = crash_helper_launch(
|
||||
crashHelperPath.c_str(), gExceptionHandler->dump_path().c_str(),
|
||||
(BreakpadRawData)childCrashNotifyPipe.get(),
|
||||
MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL));
|
||||
#endif
|
||||
|
||||
if (!crashServer->Start()) MOZ_CRASH("can't start crash reporter server()");
|
||||
|
||||
pidToMinidump = new ChildMinidumpMap();
|
||||
|
||||
dumpMapLock = new Mutex("CrashReporter::dumpMapLock");
|
||||
// After this point we'll have a value for the crash helper client, but this
|
||||
// value may be null in case we failed to start and initialize the external
|
||||
// process. This is fine as we only need to know if we've already tried
|
||||
// starting it or not.
|
||||
gCrashHelperClient = Some(crashHelperClient);
|
||||
|
||||
FindPendingDir();
|
||||
UpdateCrashEventsDir();
|
||||
@@ -3476,16 +3309,9 @@ static void OOPDeinit() {
|
||||
delete crashServer;
|
||||
crashServer = nullptr;
|
||||
|
||||
delete dumpMapLock;
|
||||
dumpMapLock = nullptr;
|
||||
|
||||
delete pidToMinidump;
|
||||
pidToMinidump = nullptr;
|
||||
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||||
free(childCrashNotifyPipe);
|
||||
childCrashNotifyPipe = nullptr;
|
||||
#endif
|
||||
childCrashNotifyPipe = ""_ns;
|
||||
#endif // defined(XP_WIN) || defined(XP_MACOSX)
|
||||
}
|
||||
|
||||
// Parent-side API for children
|
||||
@@ -3497,14 +3323,28 @@ CrashPipeType GetChildNotificationPipe() {
|
||||
MOZ_ASSERT(OOPInitialized());
|
||||
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||||
return childCrashNotifyPipe;
|
||||
return childCrashNotifyPipe.get();
|
||||
#elif defined(XP_LINUX)
|
||||
return DuplicateFileHandle(clientSocketFd);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) {
|
||||
#if defined(XP_LINUX)
|
||||
|
||||
ProcessId GetCrashHelperPid() {
|
||||
if (gCrashHelperClient.isSome() && *gCrashHelperClient) {
|
||||
return crash_helper_pid(*gCrashHelperClient);
|
||||
}
|
||||
|
||||
return base::kInvalidProcessId;
|
||||
}
|
||||
|
||||
#endif // defined(XP_LINUX)
|
||||
|
||||
bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe,
|
||||
ProcessId aCrashHelperPid) {
|
||||
MOZ_ASSERT(!gExceptionHandler, "crash client already init'd");
|
||||
gCrashHelperPid = aCrashHelperPid;
|
||||
RegisterRuntimeExceptionModule();
|
||||
InitializeAppNotes();
|
||||
RegisterAnnotations();
|
||||
@@ -3519,13 +3359,15 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) {
|
||||
static_cast<uint32_t>(Annotation::PHCBaseAddress),
|
||||
&mozilla::phc::gAddrInfo, sizeof(mozilla::phc::gAddrInfo));
|
||||
#endif
|
||||
|
||||
#if defined(XP_WIN)
|
||||
gExceptionHandler = new google_breakpad::ExceptionHandler(
|
||||
L"", ChildFilter, ChildMinidumpCallback,
|
||||
L"", ChildFilter,
|
||||
nullptr, // no callback
|
||||
nullptr, // no callback context
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL, GetMinidumpType(),
|
||||
NS_ConvertASCIItoUTF16(aCrashPipe).get(), nullptr);
|
||||
(const wchar_t*)NS_ConvertUTF8toUTF16(aCrashPipe).BeginReading(),
|
||||
nullptr // no custom info
|
||||
);
|
||||
gExceptionHandler->set_handle_debug_exceptions(true);
|
||||
|
||||
# if defined(HAVE_64BIT_BUILD)
|
||||
@@ -3535,14 +3377,16 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) {
|
||||
// MinidumpDescriptor requires a non-empty path.
|
||||
google_breakpad::MinidumpDescriptor path(".");
|
||||
|
||||
gExceptionHandler = new google_breakpad::ExceptionHandler(
|
||||
path, ChildFilter, ChildMinidumpCallback,
|
||||
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,
|
||||
gExceptionHandler =
|
||||
new google_breakpad::ExceptionHandler("", ChildFilter,
|
||||
nullptr, // no callback
|
||||
nullptr, // no callback context
|
||||
true, // install signal handlers
|
||||
aCrashPipe);
|
||||
@@ -3556,37 +3400,59 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) {
|
||||
return gExceptionHandler->IsOutOfProcess();
|
||||
}
|
||||
|
||||
void GetAnnotation(ProcessId childPid, Annotation annotation,
|
||||
nsACString& outStr) {
|
||||
if (!GetEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(*dumpMapLock);
|
||||
|
||||
ChildProcessData* pd = pidToMinidump->GetEntry(childPid);
|
||||
if (!pd) {
|
||||
return;
|
||||
}
|
||||
|
||||
outStr = (*pd->annotations)[annotation];
|
||||
}
|
||||
|
||||
bool TakeMinidumpForChild(ProcessId childPid, nsIFile** dump,
|
||||
AnnotationTable& aAnnotations) {
|
||||
if (!GetEnabled()) return false;
|
||||
if (!GetEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(*dumpMapLock);
|
||||
CrashReport* crash_report = nullptr;
|
||||
|
||||
ChildProcessData* pd = pidToMinidump->GetEntry(childPid);
|
||||
if (!pd) return false;
|
||||
if (gCrashHelperClient.isSome() && *gCrashHelperClient) {
|
||||
crash_report = transfer_crash_report(*gCrashHelperClient, childPid);
|
||||
}
|
||||
|
||||
NS_IF_ADDREF(*dump = pd->minidump);
|
||||
aAnnotations = *(pd->annotations);
|
||||
if (!crash_report) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pidToMinidump->RemoveEntry(pd);
|
||||
CreateFileFromPath(xpstring((XP_CHAR*)crash_report->path), dump);
|
||||
nsCString error =
|
||||
crash_report->error ? nsCString(crash_report->error) : ""_ns;
|
||||
release_crash_report(crash_report);
|
||||
|
||||
return !!*dump;
|
||||
nsCOMPtr<nsIFile> extra = nullptr;
|
||||
NS_ENSURE_TRUE(GetExtraFileForMinidump(*dump, getter_AddRefs(extra)), false);
|
||||
|
||||
if (ShouldReport()) {
|
||||
nsCOMPtr<nsIFile> memoryReport;
|
||||
if (!memoryReportPath.empty()) {
|
||||
CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport));
|
||||
MOZ_ASSERT(memoryReport);
|
||||
}
|
||||
|
||||
MoveToPending(*dump, extra, memoryReport);
|
||||
}
|
||||
|
||||
nsresult rv = ReadExtraFile(extra, aAnnotations);
|
||||
|
||||
// Unconditionally remove the temporary .extra file, it will be regenarated
|
||||
// later when we finalize the crash report.
|
||||
extra->Remove(false);
|
||||
|
||||
if (rv != NS_OK) {
|
||||
// TODO: We failed to read the annotations, this will leave an orphaned
|
||||
// crash that we won't be able to submit. Clean everything up instead?
|
||||
return false;
|
||||
}
|
||||
|
||||
AddSharedAnnotations(aAnnotations);
|
||||
|
||||
if (error.Length() > 0) {
|
||||
aAnnotations[Annotation::DumperError] = error;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FinalizeOrphanedMinidump(ProcessId aChildPid, GeckoProcessType aType,
|
||||
@@ -3613,58 +3479,6 @@ bool FinalizeOrphanedMinidump(ProcessId aChildPid, GeckoProcessType aType,
|
||||
return WriteExtraFile(id, annotations);
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
|
||||
// Function invoked by the WER runtime exception handler running in an
|
||||
// external process. This function isn't used anywhere inside Gecko directly
|
||||
// but rather invoked via CreateRemoteThread() in the main process.
|
||||
|
||||
// Store this global in a section called mozwerpt where we can find it by just
|
||||
// looking at the program headers.
|
||||
# pragma section("mozwerpt", read, executable, shared)
|
||||
|
||||
__declspec(allocate("mozwerpt")) MOZ_EXPORT DWORD WINAPI
|
||||
WerNotifyProc(LPVOID aParameter) {
|
||||
const WindowsErrorReportingData* werData =
|
||||
static_cast<const WindowsErrorReportingData*>(aParameter);
|
||||
|
||||
auto freeParameterOnExit = MakeScopeExit([&aParameter] {
|
||||
VirtualFree(aParameter, sizeof(WindowsErrorReportingData), MEM_RELEASE);
|
||||
});
|
||||
|
||||
// Hold the mutex until the current dump request is complete, to
|
||||
// prevent UnsetExceptionHandler() from pulling the rug out from
|
||||
// under us.
|
||||
MutexAutoLock safetyLock(*dumpSafetyLock);
|
||||
if (!isSafeToDump || !ShouldReport()) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ProcessId pid = werData->mChildPid;
|
||||
nsCOMPtr<nsIFile> minidump;
|
||||
if (!GetPendingDir(getter_AddRefs(minidump))) {
|
||||
return S_OK;
|
||||
}
|
||||
xpstring minidump_native_name(werData->mMinidumpFile,
|
||||
werData->mMinidumpFile + 40);
|
||||
nsString minidump_name(minidump_native_name.c_str());
|
||||
minidump->Append(minidump_name);
|
||||
|
||||
{
|
||||
MutexAutoLock lock(*dumpMapLock);
|
||||
ChildProcessData* pd = pidToMinidump->PutEntry(pid);
|
||||
MOZ_ASSERT(!pd->minidump);
|
||||
pd->minidump = minidump;
|
||||
pd->annotations = MakeUnique<AnnotationTable>();
|
||||
(*pd->annotations)[Annotation::WindowsErrorReporting] = "1"_ns;
|
||||
AddSharedAnnotations(*(pd->annotations));
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
#endif // XP_WIN
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// CreateMinidumpsAndPair() and helpers
|
||||
//
|
||||
@@ -3838,18 +3652,7 @@ bool CreateMinidumpsAndPair(ProcessHandle aTargetHandle,
|
||||
#endif
|
||||
|
||||
AddSharedAnnotations(aTargetAnnotations);
|
||||
#if XP_WIN
|
||||
nsTArray<CAnnotation>* child_annotations =
|
||||
mozannotation_retrieve(reinterpret_cast<uintptr_t>(aTargetHandle),
|
||||
static_cast<size_t>(Annotation::Count));
|
||||
#else
|
||||
nsTArray<CAnnotation>* child_annotations = mozannotation_retrieve(
|
||||
aTargetHandle, static_cast<size_t>(Annotation::Count));
|
||||
#endif
|
||||
AddChildProcessAnnotations(aTargetAnnotations, child_annotations);
|
||||
if (child_annotations) {
|
||||
mozannotation_free(child_annotations);
|
||||
}
|
||||
// TODO: Retrieve annotations from child process
|
||||
|
||||
targetMinidump.forget(aMainDumpOut);
|
||||
|
||||
|
||||
@@ -165,9 +165,6 @@ nsresult UnregisterAppMemory(void* ptr);
|
||||
// Include heap regions of the crash context.
|
||||
void SetIncludeContextHeap(bool aValue);
|
||||
|
||||
void GetAnnotation(ProcessId childPid, Annotation annotation,
|
||||
nsACString& outStr);
|
||||
|
||||
// Functions for working with minidumps and .extras
|
||||
typedef mozilla::EnumeratedArray<Annotation, nsCString,
|
||||
size_t(Annotation::Count)>
|
||||
@@ -200,18 +197,6 @@ nsresult AppendObjCExceptionInfoToAppNotes(void* inException);
|
||||
nsresult GetSubmitReports(bool* aSubmitReport);
|
||||
nsresult SetSubmitReports(bool aSubmitReport);
|
||||
|
||||
#ifdef XP_WIN
|
||||
// This data is stored in the parent process, there is one copy for each child
|
||||
// process. The mChildPid and mMinidumpFile fields are filled by the WER runtime
|
||||
// exception module when the associated child process crashes.
|
||||
struct WindowsErrorReportingData {
|
||||
// PID of the child process that crashed.
|
||||
DWORD mChildPid;
|
||||
// Filename of the generated minidump; this is not a 0-terminated string
|
||||
char mMinidumpFile[40];
|
||||
};
|
||||
#endif // XP_WIN
|
||||
|
||||
// Out-of-process crash reporter API.
|
||||
|
||||
// Initializes out-of-process crash reporting. This method must be called
|
||||
@@ -272,7 +257,7 @@ bool CreateMinidumpsAndPair(ProcessHandle aTargetPid,
|
||||
AnnotationTable& aTargetAnnotations,
|
||||
nsIFile** aTargetDumpOut);
|
||||
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_IOS)
|
||||
using CrashPipeType = const char*;
|
||||
#else
|
||||
using CrashPipeType = mozilla::UniqueFileHandle;
|
||||
@@ -281,8 +266,18 @@ using CrashPipeType = mozilla::UniqueFileHandle;
|
||||
// Parent-side API for children
|
||||
CrashPipeType GetChildNotificationPipe();
|
||||
|
||||
#if defined(XP_LINUX)
|
||||
|
||||
// Return the pid of the crash helper process. This only works in Linux, not
|
||||
// Android where the crash helper is an Android service and is not under the
|
||||
// main process' control.
|
||||
MOZ_EXPORT ProcessId GetCrashHelperPid();
|
||||
|
||||
#endif // XP_LINUX
|
||||
|
||||
// Child-side API
|
||||
bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe);
|
||||
MOZ_EXPORT bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe,
|
||||
ProcessId aCrashHelperPid = 0);
|
||||
bool UnsetRemoteExceptionHandler(bool wasSet = true);
|
||||
|
||||
} // namespace CrashReporter
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -209,13 +209,14 @@ static CommandLineArg<bool> sNotForBrowser{"-notForBrowser", "notforbrowser"};
|
||||
static CommandLineArg<const char*> sPluginPath{"-pluginPath", "pluginpath"};
|
||||
static CommandLineArg<bool> sPluginNativeEvent{"-pluginNativeEvent",
|
||||
"pluginnativeevent"};
|
||||
|
||||
#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
|
||||
static CommandLineArg<const char*> sCrashReporter{"-crashReporter",
|
||||
"crashreporter"};
|
||||
#elif defined(XP_UNIX)
|
||||
#if defined(XP_LINUX)
|
||||
static CommandLineArg<UniqueFileHandle> sCrashReporter{"-crashReporter",
|
||||
"crashreporter"};
|
||||
static CommandLineArg<uint64_t> sCrashHelperPid{"-crashHelperPid",
|
||||
"crashhelperpid"};
|
||||
#else
|
||||
static CommandLineArg<const char*> sCrashReporter{"-crashReporter",
|
||||
"crashreporter"};
|
||||
#endif
|
||||
|
||||
#if defined(XP_WIN)
|
||||
|
||||
@@ -352,8 +352,16 @@ nsresult XRE_InitChildProcess(int aArgc, char* aArgv[],
|
||||
if (!CrashReporter::IsDummy()) {
|
||||
auto crashReporterArg = geckoargs::sCrashReporter.Get(aArgc, aArgv);
|
||||
if (crashReporterArg) {
|
||||
CrashReporter::ProcessId crashHelperPid = base::kInvalidProcessId;
|
||||
#if defined(XP_LINUX)
|
||||
auto crashHelperPidArg = geckoargs::sCrashHelperPid.Get(aArgc, aArgv);
|
||||
MOZ_ASSERT(crashHelperPidArg);
|
||||
crashHelperPid =
|
||||
static_cast<CrashReporter::ProcessId>(*crashHelperPidArg);
|
||||
#endif
|
||||
|
||||
exceptionHandlerIsSet = CrashReporter::SetRemoteExceptionHandler(
|
||||
std::move(*crashReporterArg));
|
||||
std::move(*crashReporterArg), crashHelperPid);
|
||||
MOZ_ASSERT(exceptionHandlerIsSet,
|
||||
"Should have been able to set remote exception handler");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user