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.

The crash helper process is started when the first exception handler is set
on desktop platforms and before loading libxul on Android. In both cases
starting this process happens asynchronously so that neither the main process
nor child processes have to wait for it to come up. If a crash happens before
the crash helper has started, the crashed process will wait for it to fully
come up and then proceed with regular crash generation.

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

Differential Revision: https://phabricator.services.mozilla.com/D231083
This commit is contained in:
Gabriele Svelto
2025-04-18 16:37:06 +00:00
parent 316cf25cda
commit 08a8136801
76 changed files with 3505 additions and 844 deletions

62
Cargo.lock generated
View File

@@ -1089,6 +1089,57 @@ dependencies = [
"mach2",
]
[[package]]
name = "crash_helper_client"
version = "0.1.0"
dependencies = [
"anyhow",
"crash_helper_common",
"minidump-writer",
"nix 0.29.0",
"num-derive",
"num-traits",
"rust_minidump_writer_linux",
"windows-sys",
]
[[package]]
name = "crash_helper_common"
version = "0.1.0"
dependencies = [
"minidump-writer",
"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",
"dirs",
"linked-hash-map",
"minidump-writer",
"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"
@@ -2444,6 +2495,7 @@ dependencies = [
"cert_storage",
"chardetng_c",
"cose-c",
"crash_helper_client",
"crypto_hash",
"cubeb-coreaudio",
"cubeb-pulse",
@@ -4297,11 +4349,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]]
@@ -4463,9 +4514,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",
@@ -4713,6 +4764,7 @@ dependencies = [
"cfg-if",
"cfg_aliases",
"libc",
"memoffset 0.9.0",
]
[[package]]
@@ -5214,7 +5266,7 @@ dependencies = [
"memoffset 0.9.0",
"mozilla-central-workspace-hack",
"scroll",
"thiserror 1.999.999",
"thiserror 2.0.9",
"windows-sys",
]

View File

@@ -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/",
]

View File

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

View File

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

View File

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

View File

@@ -107,7 +107,9 @@ def generate(output):
# Write out some useful strings from the buildconfig.
output.write(generate_string("MOZ_MACBUNDLE_ID"))
output.write(generate_string("MOZ_APP_BASENAME"))
output.write(generate_string("MOZ_APP_NAME"))
output.write(generate_string("MOZ_APP_VENDOR"))
# Write out some useful booleans from the buildconfig.
output.write(generate_bool("MOZ_FOLD_LIBS"))

View File

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

View File

@@ -611,10 +611,6 @@ void GeckoChildProcessHost::SetEnv(const char* aKey, const char* aValue) {
bool GeckoChildProcessHost::PrepareLaunch(
geckoargs::ChildProcessArgs& aExtraOpts) {
if (CrashReporter::GetEnabled()) {
CrashReporter::OOPInit();
}
#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
if (!SandboxLaunch::Configure(mProcessType, mSandbox, aExtraOpts,
mLaunchOptions.get())) {
@@ -1125,13 +1121,20 @@ 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
# if !defined(MOZ_WIDGET_ANDROID)
CrashReporter::ProcessId pid = CrashReporter::GetCrashHelperPid();
if (pid != base::kInvalidProcessId) {
geckoargs::sCrashHelperPid.Put(pid, mChildArgs);
}
# endif // !defined(MOZ_WIDGET_ANDROID)
#endif // XP_UNIX && !XP_IOS
}
return Ok();

View File

@@ -1200,7 +1200,22 @@ int XRE_XPCShellMain(int argc, char** argv, char** envp,
const char* val = getenv("MOZ_CRASHREPORTER");
if (val && *val && !CrashReporter::IsDummy()) {
rv = CrashReporter::SetExceptionHandler(greDir, true);
nsCOMPtr<nsIFile> greBinDir;
bool persistent = false;
rv = dirprovider.GetFile(NS_GRE_BIN_DIR, &persistent,
getter_AddRefs(greBinDir));
if (NS_FAILED(rv)) {
printf("Could not get the GreBinD directory\n");
return 1;
}
#if defined(MOZ_WIDGET_ANDROID)
CrashReporter::SetCrashHelperPipes(
aShellData->crashChildNotificationSocket,
aShellData->crashHelperSocket);
#endif // defined(MOZ_WIDGET_ANDROID)
rv = CrashReporter::SetExceptionHandler(greBinDir, true);
if (NS_FAILED(rv)) {
printf("CrashReporter::SetExceptionHandler failed!\n");
return 1;

View File

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

View File

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

View File

@@ -147,6 +147,7 @@ public class GeckoThread extends Thread {
1 << 2; // Enable native crash reporting.
public static final int FLAG_DISABLE_LOW_MEMORY_DETECTION =
1 << 3; // Disable low-memory detection and notifications.
public static final int FLAG_CHILD = 1 << 4; // This is a child process.
/* package */ static final String EXTRA_ARGS = "args";
@@ -267,7 +268,7 @@ public class GeckoThread extends Thread {
@WrapForJNI
private static boolean isChildProcess() {
final InitInfo info = INSTANCE.mInitInfo;
return info != null && info.fds != null;
return info != null && ((info.flags & FLAG_CHILD) != 0);
}
public static boolean init(final InitInfo info) {
@@ -408,6 +409,17 @@ public class GeckoThread extends Thread {
return result;
}
// See GeckoLoader.java and APKOpen.cpp
private int processType() {
if (mInitInfo.xpcshell) {
return GeckoLoader.PROCESS_TYPE_XPCSHELL;
} else if ((mInitInfo.flags & FLAG_CHILD) != 0) {
return GeckoLoader.PROCESS_TYPE_CHILD;
} else {
return GeckoLoader.PROCESS_TYPE_MAIN;
}
}
@Override
public void run() {
Log.i(LOGTAG, "preparing to run Gecko");
@@ -502,10 +514,7 @@ public class GeckoThread extends Thread {
// And go.
GeckoLoader.nativeRun(
args,
mInitInfo.fds,
!isChildProcess && mInitInfo.xpcshell,
isChildProcess ? null : mInitInfo.outFilePath);
args, mInitInfo.fds, processType(), isChildProcess ? null : mInitInfo.outFilePath);
// And... we're done.
final boolean restarting = isState(State.RESTARTING);

View File

@@ -0,0 +1,185 @@
/* 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.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.IOException;
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 static boolean sNativeLibLoaded;
private final Binder mBinder = new CrashHelperBinder();
@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,
final ParcelFileDescriptor breakpadFd,
final String minidumpPath,
final ParcelFileDescriptor listenFd,
final ParcelFileDescriptor serverFd) {
// 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, breakpadFd.detachFd(), minidumpPath, listenFd.detachFd(), serverFd.detachFd());
return false;
}
}
@Override
public IBinder onBind(final Intent intent) {
return mBinder;
}
public static ServiceConnection createConnection(
final ParcelFileDescriptor breakpadFd,
final String minidumpPath,
final ParcelFileDescriptor listenFd,
final ParcelFileDescriptor serverFd) {
class CrashHelperConnection implements ServiceConnection {
public CrashHelperConnection(
final ParcelFileDescriptor breakpadFd,
final String minidumpPath,
final ParcelFileDescriptor listenFd,
final ParcelFileDescriptor serverFd) {
mBreakpadFd = breakpadFd;
mMinidumpPath = minidumpPath;
mListenFd = listenFd;
mServerFd = serverFd;
}
@Override
public void onServiceConnected(final ComponentName name, final IBinder service) {
final ICrashHelper helper = ICrashHelper.Stub.asInterface(service);
try {
helper.start(Process.myPid(), mBreakpadFd, mMinidumpPath, mListenFd, mServerFd);
} catch (final RemoteException e) {
throw new RuntimeException(e);
}
}
@Override
public void onServiceDisconnected(final ComponentName name) {
// Nothing to do here
}
ParcelFileDescriptor mBreakpadFd;
String mMinidumpPath;
ParcelFileDescriptor mListenFd;
ParcelFileDescriptor mServerFd;
}
return new CrashHelperConnection(breakpadFd, minidumpPath, listenFd, serverFd);
}
public static final class Pipes {
public final ParcelFileDescriptor mBreakpadClient;
public final ParcelFileDescriptor mBreakpadServer;
public final ParcelFileDescriptor mListener;
public final ParcelFileDescriptor mClient;
public final ParcelFileDescriptor mServer;
public Pipes(
final FileDescriptor breakpadClientFd,
final FileDescriptor breakpadServerFd,
final FileDescriptor listenerFd,
final FileDescriptor clientFd,
final FileDescriptor serverFd)
throws IOException {
mBreakpadClient = ParcelFileDescriptor.dup(breakpadClientFd);
mBreakpadServer = ParcelFileDescriptor.dup(breakpadServerFd);
mListener = ParcelFileDescriptor.dup(listenerFd);
mClient = ParcelFileDescriptor.dup(clientFd);
mServer = ParcelFileDescriptor.dup(serverFd);
}
}
// This builds five sockets used for communication between Gecko and the
// crash helper process. The first two are a socket pair identical to the one
// created via a call to
// google_breakpad::CrashGenerationServer::CreateReportChannel(), so we can
// use them Breakpad's crash generation server & clients. The rest are
// specific to the crash helper process.
public static Pipes createCrashHelperPipes() {
// Ideally we'd set all the required options for the server socket, by using
// Os.setsockoptInt() to enable the SO_PASSCRED socket option and then a
// couple of calls to Os.fcntlInt() to read the socket file flags and then
// set it in non-blocking mode by setting O_NONBLOCK. Unfortunately both
// calls require Android API version more recent than we support, so this
// job is delegated to crashhelper_android.cpp after receiving the socket.
try {
final FileDescriptor breakpad_client_fd = new FileDescriptor();
final FileDescriptor breakpad_server_fd = new FileDescriptor();
Os.socketpair(
OsConstants.AF_UNIX,
OsConstants.SOCK_SEQPACKET,
0,
breakpad_client_fd,
breakpad_server_fd);
final FileDescriptor listener_fd =
Os.socket(OsConstants.AF_UNIX, OsConstants.SOCK_SEQPACKET, 0);
final FileDescriptor client_fd = new FileDescriptor();
final FileDescriptor server_fd = new FileDescriptor();
Os.socketpair(OsConstants.AF_UNIX, OsConstants.SOCK_SEQPACKET, 0, client_fd, server_fd);
final Pipes pipes =
new Pipes(breakpad_client_fd, breakpad_server_fd, listener_fd, client_fd, server_fd);
// Manually close all the file descriptors we created.
// ParcelFileDescriptor instances in the Pipes object will close their
// underlying fds when garbage-collected but FileDescriptor instances do
// not, leaving us the job to clean them up.
Os.close(breakpad_client_fd);
Os.close(breakpad_server_fd);
Os.close(listener_fd);
Os.close(client_fd);
Os.close(server_fd);
return pipes;
} catch (final ErrnoException | IOException e) {
Log.e(LOGTAG, "Could not create the crash helper pipes: " + e.toString());
return null;
}
}
// 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, int breakpadFd, String minidumpPath, int listenFd, int serverFd);
}

View File

@@ -412,9 +412,14 @@ public final class GeckoLoader {
// These methods are implemented in mozglue/android/nsGeckoUtils.cpp
private static native void putenv(String map);
// These are mirrored in mozglue/android/APKOpen.cpp
public static final int PROCESS_TYPE_MAIN = 0;
public static final int PROCESS_TYPE_CHILD = 1;
public static final int PROCESS_TYPE_XPCSHELL = 2;
// These methods are implemented in mozglue/android/APKOpen.cpp
public static native void nativeRun(
String[] args, int[] fds, boolean xpcshell, String outFilePath);
String[] args, int[] fds, int processType, String outFilePath);
private static native void loadGeckoLibsNative();

View File

@@ -763,7 +763,9 @@ public final class GeckoProcessManager extends IProcessManager.Stub {
.args(args)
.userSerialNumber(System.getenv("MOZ_ANDROID_USER_SERIAL_NUMBER"))
.extras(GeckoThread.getActiveExtras())
.flags(filterFlagsForChild(GeckoThread.getActiveFlags()))
.flags(
filterFlagsForChild(
GeckoThread.getActiveFlags(), type != GeckoProcessType.PARENT))
.fds(fds)
.build());
@@ -778,8 +780,9 @@ public final class GeckoProcessManager extends IProcessManager.Stub {
return result;
}
private static int filterFlagsForChild(final int flags) {
return flags & GeckoThread.FLAG_ENABLE_NATIVE_CRASHREPORTER;
private static int filterFlagsForChild(final int flags, final boolean child) {
return (flags & GeckoThread.FLAG_ENABLE_NATIVE_CRASHREPORTER)
| (child ? GeckoThread.FLAG_CHILD : 0);
}
private static class StartInfo {

View File

@@ -134,7 +134,7 @@ public class GeckoServiceChildProcess extends Service {
GeckoThread.InitInfo.builder()
.args(args)
.extras(extras)
.flags(flags)
.flags(flags | GeckoThread.FLAG_CHILD)
.userSerialNumber(userSerialNumber)
.fds(fds)
.build();

View File

@@ -9,6 +9,7 @@ package org.mozilla.geckoview;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -50,6 +51,7 @@ import org.mozilla.gecko.GeckoScreenOrientation.ScreenOrientation;
import org.mozilla.gecko.GeckoSystemStateListener;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.crashhelper.CrashHelper;
import org.mozilla.gecko.process.MemoryController;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.DebugConfig;
@@ -396,6 +398,33 @@ public final class GeckoRuntime implements Parcelable {
return null;
}
private int[] startCrashHelper() {
final CrashHelper.Pipes pipes = CrashHelper.createCrashHelperPipes();
if (pipes == null) {
Log.e(LOGTAG, "Could not create the crash reporter IPC pipes");
return new int[] {-1, -1};
}
final Context context = GeckoAppShell.getApplicationContext();
try {
@SuppressWarnings("unchecked")
final Class<? extends Service> cls =
(Class<? extends Service>) Class.forName("org.mozilla.gecko.crashhelper.CrashHelper");
final Intent i = new Intent(context, cls);
final File minidumps = new File(context.getFilesDir(), "minidumps");
context.bindService(
i,
CrashHelper.createConnection(
pipes.mBreakpadServer, minidumps.getPath(), pipes.mListener, pipes.mServer),
Context.BIND_AUTO_CREATE);
} catch (final ClassNotFoundException e) {
Log.w(LOGTAG, "Couldn't find the crash helper class");
}
return new int[] {pipes.mBreakpadClient.detachFd(), pipes.mClient.detachFd()};
}
/* package */ boolean init(
final @NonNull Context context, final @NonNull GeckoRuntimeSettings settings) {
if (DEBUG) {
@@ -466,6 +495,8 @@ public final class GeckoRuntime implements Parcelable {
}
}
final int[] fds = startCrashHelper();
final GeckoThread.InitInfo info =
GeckoThread.InitInfo.builder()
.args(args)
@@ -473,6 +504,7 @@ public final class GeckoRuntime implements Parcelable {
.flags(flags)
.prefs(prefs)
.outFilePath(extras != null ? extras.getString("out_file") : null)
.fds(fds)
.build();
if (info.xpcshell

View File

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

View File

@@ -362,18 +362,23 @@ static void FreeArgv(char** argv, int argc) {
delete[] (argv);
}
// These are mirrored in GeckoLoader.java
#define PROCESS_TYPE_MAIN 0
#define PROCESS_TYPE_CHILD 1
#define PROCESS_TYPE_XPCSHELL 2
extern "C" APKOPEN_EXPORT void MOZ_JNICALL
Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv* jenv, jclass jc,
jobjectArray jargs,
jintArray jfds,
bool xpcshell,
jint processType,
jstring outFilePath) {
EnsureBaseProfilerInitialized();
int argc = 0;
char** argv = CreateArgvFromObjectArray(jenv, jargs, &argc);
if (!jfds) {
if (processType != PROCESS_TYPE_CHILD) {
if (gBootstrap == nullptr) {
FreeArgv(argv, argc);
return;
@@ -384,12 +389,26 @@ Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv* jenv, jclass jc,
#endif
gBootstrap->XRE_SetGeckoThreadEnv(jenv);
if (!argv) {
__android_log_print(ANDROID_LOG_FATAL, "mozglue",
"Failed to get arguments for %s",
xpcshell ? "XRE_XPCShellMain" : "XRE_main");
__android_log_print(
ANDROID_LOG_FATAL, "mozglue", "Failed to get arguments for %s",
processType == PROCESS_TYPE_XPCSHELL ? "XRE_XPCShellMain"
: "XRE_main");
return;
}
if (xpcshell) {
jsize size = jenv->GetArrayLength(jfds);
if (size != 2) {
__android_log_print(ANDROID_LOG_FATAL, "mozglue",
"Wrong number of file descriptors passed to a "
"browser/xpcshell process");
return;
}
jint* fds = jenv->GetIntArrayElements(jfds, nullptr);
int crashChildNotificationSocket = fds[0];
int crashHelperSocket = fds[1];
jenv->ReleaseIntArrayElements(jfds, fds, JNI_ABORT);
if (processType == PROCESS_TYPE_XPCSHELL) {
MOZ_ASSERT(outFilePath);
const char* outFilePathRaw =
jenv->GetStringUTFChars(outFilePath, nullptr);
@@ -400,6 +419,8 @@ Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv* jenv, jclass jc,
// what runxpcshell.py does on Desktop.
shellData.outFile = outFile;
shellData.errFile = outFile;
shellData.crashChildNotificationSocket = crashChildNotificationSocket;
shellData.crashHelperSocket = crashHelperSocket;
int result =
gBootstrap->XRE_XPCShellMain(argc, argv, nullptr, &shellData);
fclose(shellData.outFile);
@@ -418,6 +439,8 @@ Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv* jenv, jclass jc,
BootstrapConfig config;
config.appData = &sAppData;
config.appDataPath = nullptr;
config.crashChildNotificationSocket = crashChildNotificationSocket;
config.crashHelperSocket = crashHelperSocket;
int result = gBootstrap->XRE_main(argc, argv, config);
if (result) {

View File

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

View File

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

View File

@@ -1018,6 +1018,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"
@@ -1062,6 +1063,7 @@ mac-signing:
force: true
entitlements: security/mac/hardenedruntime/v2/developer/utility.xml
globs:
- "/Contents/MacOS/crashhelper"
- "/Contents/MacOS/crashreporter.app"
- "/Contents/MacOS/updater.app/Contents/Frameworks/UpdateSettings.framework"
- "/Contents/MacOS/updater.app"

View File

@@ -137,8 +137,8 @@ def parse_memory_usage(mem_file, binary):
final_mems[curr_mem][name] = round(float(mem_usage.replace(",", "")), 2)
measurements = {
"rss": {"tab": 0, "gpu": 0, "main": 0},
"pss": {"tab": 0, "gpu": 0, "main": 0},
"rss": {"tab": 0, "gpu": 0, "main": 0, "crashhelper": 0},
"pss": {"tab": 0, "gpu": 0, "main": 0, "crashhelper": 0},
}
for mem_type, mem_info in final_mems.items():
for name, mem_usage in mem_info.items():
@@ -180,7 +180,7 @@ def parse_cpu_usage(cpu_file, binary):
final_times[name] = vals[-2]
# Convert the final times to milliseconds
cpu_times = {"tab": 0, "gpu": 0, "main": 0}
cpu_times = {"tab": 0, "gpu": 0, "main": 0, "crashhelper": 0}
for name, time in final_times.items():
# adb shell ps -o time+= gives us MIN:SEC.HUNDREDTHS.
# That's why we divide dt.microseconds by 1000 for measuring in milliseconds.

View File

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

View File

@@ -62,15 +62,18 @@ namespace google_breakpad {
CrashGenerationServer::CrashGenerationServer(
const int listen_fd,
#if defined(MOZ_OXIDIZED_BREAKPAD)
std::function<GetAuxvInfo> get_auxv_info,
std::function<GetAuxvInfoCallback> get_auxv_info,
#endif // defined(MOZ_OXIDIZED_BREAKPAD)
std::function<OnClientDumpRequestCallback> dump_callback,
void* dump_context,
const string* dump_path) :
server_fd_(listen_fd),
#if defined(MOZ_OXIDIZED_BREAKPAD)
get_auxv_info_(std::move(get_auxv_info)),
#endif // defined(MOZ_OXIDIZED_BREAKPAD)
dump_callback_(std::move(dump_callback)),
dump_context_(dump_context),
dump_dir_mutex_(PTHREAD_MUTEX_INITIALIZER),
started_(false),
reserved_fds_{-1, -1}
{
@@ -137,6 +140,14 @@ CrashGenerationServer::Stop()
started_ = false;
}
void
CrashGenerationServer::SetPath(const char* dump_path)
{
pthread_mutex_lock(&dump_dir_mutex_);
this->dump_dir_ = string(dump_path);
pthread_mutex_unlock(&dump_dir_mutex_);
}
//static
bool
CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd)
@@ -153,8 +164,6 @@ CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd)
if (fcntl(fds[1], F_SETFL, O_NONBLOCK))
return false;
if (fcntl(fds[1], F_SETFD, FD_CLOEXEC))
return false;
*client_fd = fds[0];
*server_fd = fds[1];
@@ -338,7 +347,7 @@ CrashGenerationServer::ClientEvent(short revents)
}
#endif
if (dump_callback_) {
dump_callback_(info, minidump_filename);
dump_callback_(dump_context_, info, minidump_filename);
}
// Send the done signal to the process: it can exit now.
@@ -385,7 +394,9 @@ CrashGenerationServer::MakeMinidumpFilename(string& outFilename)
return false;
char path[PATH_MAX];
pthread_mutex_lock(&dump_dir_mutex_);
snprintf(path, sizeof(path), "%s/%s.dmp", dump_dir_.c_str(), guidString);
pthread_mutex_unlock(&dump_dir_mutex_);
outFilename = path;
return true;

View File

@@ -51,23 +51,30 @@ public:
// WARNING: callbacks may be invoked on a different thread
// than that which creates the CrashGenerationServer. They must
// be thread safe.
using OnClientDumpRequestCallback = void (const ClientInfo& client_info,
using OnClientDumpRequestCallback = void (void* dump_context,
const ClientInfo& client_info,
const string& file_path);
#if defined(MOZ_OXIDIZED_BREAKPAD)
using GetAuxvInfo = bool (pid_t pid, DirectAuxvDumpInfo*);
using GetAuxvInfoCallback = bool (pid_t pid, DirectAuxvDumpInfo*);
#endif // defined(MOZ_OXIDIZED_BREAKPAD)
// listen_fd: The server fd created by CreateReportChannel()
// get_auxv_info: Callback to retrieve the stored auxiliary vector for the given PID
// dump_callback: Callback for a client crash dump request.
// dump_path: Path for generating dumps
CrashGenerationServer(
const int listen_fd,
// Create an instance with the given parameters.
//
// Parameter listen_fd: The server fd created by CreateReportChannel().
// Parameter: get_auxv_info: Callback to retrieve the stored auxiliary vector for the given PID.
// Parameter dump_callback: Callback for a client crash dump request.
// Parameter dump_context: Context for client crash dump request callback.
// Client code of this class might want to generate dumps explicitly
// in the crash dump request callback. In that case, false can be
// passed for this parameter.
// Parameter dump_path: Path for generating dumps; required only if true is
// passed for generateDumps parameter; NULL can be passed otherwise.
CrashGenerationServer(const int listen_fd,
#if defined(MOZ_OXIDIZED_BREAKPAD)
std::function<GetAuxvInfo> get_auxv_info,
std::function<GetAuxvInfoCallback> get_auxv_info,
#endif // defined(MOZ_OXIDIZED_BREAKPAD)
std::function<OnClientDumpRequestCallback> dump_callback,
void* dump_context,
const string* dump_path);
~CrashGenerationServer();
@@ -80,6 +87,9 @@ public:
// Stop the server.
void Stop();
// Adjust the path where minidumps are placed, this is thread-safe
void SetPath(const char* dump_path);
// Create a "channel" that can be used by clients to report crashes
// to a CrashGenerationServer. |*server_fd| should be passed to
// this class's constructor, and |*client_fd| should be passed to
@@ -115,11 +125,13 @@ private:
int server_fd_;
#if defined(MOZ_OXIDIZED_BREAKPAD)
std::function<GetAuxvInfo> get_auxv_info_;
std::function<GetAuxvInfoCallback> get_auxv_info_;
#endif // defined(MOZ_OXIDIZED_BREAKPAD)
std::function<OnClientDumpRequestCallback> dump_callback_;
void* dump_context_;
pthread_mutex_t dump_dir_mutex_;
string dump_dir_;
bool started_;

View File

@@ -54,6 +54,7 @@ CrashGenerationServer::CrashGenerationServer(
exit_callback_(exit_callback),
exit_context_(exit_context),
generate_dumps_(generate_dumps),
dump_dir_mutex_(PTHREAD_MUTEX_INITIALIZER),
dump_dir_(dump_path.empty() ? "/tmp" : dump_path),
started_(false),
receive_port_(mach_port_name),
@@ -89,6 +90,14 @@ bool CrashGenerationServer::Stop() {
return !started_;
}
void
CrashGenerationServer::SetPath(const char* dump_path)
{
pthread_mutex_lock(&dump_dir_mutex_);
this->dump_dir_ = string(dump_path);
pthread_mutex_unlock(&dump_dir_mutex_);
}
// static
void *CrashGenerationServer::WaitForMessages(void *server) {
pthread_setname_np("Breakpad CrashGenerationServer");
@@ -120,7 +129,9 @@ bool CrashGenerationServer::WaitForOneMessage() {
ScopedTaskSuspend suspend(remote_task);
MinidumpGenerator generator(remote_task, handler_thread);
pthread_mutex_lock(&dump_dir_mutex_);
dump_path = generator.UniqueNameInDirectory(dump_dir_, NULL);
pthread_mutex_unlock(&dump_dir_mutex_);
if (info.exception_type && info.exception_code) {
generator.SetExceptionInformation(info.exception_type,

View File

@@ -104,6 +104,9 @@ class CrashGenerationServer {
// Stop the server.
bool Stop();
// Adjust the path where minidumps are placed, this is thread-safe
void SetPath(const char* dump_path);
private:
// Return a unique filename at which a minidump can be written.
bool MakeMinidumpFilename(std::string &outFilename);
@@ -127,6 +130,7 @@ class CrashGenerationServer {
bool generate_dumps_;
pthread_mutex_t dump_dir_mutex_;
std::string dump_dir_;
bool started_;

View File

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

View File

@@ -265,6 +265,17 @@ bool CrashGenerationServer::Start() {
return true;
}
void CrashGenerationServer::SetPath(const wchar_t* dump_path) {
AutoCriticalSection lock(&sync_);
std::wstring local_path(dump_path);
dump_path_ = local_path;
}
std::wstring CrashGenerationServer::GetPath() {
AutoCriticalSection lock(&sync_);
return dump_path_;
}
// If the server thread serving clients ever gets into the
// ERROR state, reset the event, close the pipe and remain
// in the error state forever. Error state means something
@@ -953,7 +964,7 @@ bool CrashGenerationServer::GenerateDump(const ClientInfo& client,
!include_context_heap_);
}
MinidumpGenerator dump_generator(dump_path_,
MinidumpGenerator dump_generator(GetPath(),
client.process_handle(),
client.pid(),
client_thread_id,

View File

@@ -108,6 +108,12 @@ class CrashGenerationServer {
// Returns true if initialization is successful; false otherwise.
bool Start();
// Adjust the path where minidumps are placed, this is thread-safe.
void SetPath(const wchar_t* dump_path);
// Read the current dump path, this is thread-safe.
std::wstring GetPath();
void pre_fetch_custom_info(bool do_pre_fetch) {
pre_fetch_custom_info_ = do_pre_fetch;
}
@@ -228,7 +234,7 @@ class CrashGenerationServer {
// asynchronous IO operation.
void EnterStateWhenSignaled(IPCServerState state);
// Sync object for thread-safe access to the shared list of clients.
// Sync object for thread-safe access to the shared list of clients and path.
CRITICAL_SECTION sync_;
// List of clients.
@@ -283,7 +289,7 @@ class CrashGenerationServer {
bool pre_fetch_custom_info_;
// The dump path for the server.
const std::wstring dump_path_;
std::wstring dump_path_;
// State of the server in performing the IPC with the client.
// Note that since we restrict the pipe to one instance, we

View File

@@ -0,0 +1,193 @@
/* -*- 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 RustDumpCallback = void (*)(BreakpadProcessId, const char*,
const breakpad_char*);
#if defined(XP_LINUX)
using RustAuxvCallback = bool (*)(breakpad_pid, DirectAuxvDumpInfo*);
#endif // defined(XP_LINUX)
void onClientDumpRequestCallback(void* context, const ClientInfo& client_info,
const breakpad_string& file_path) {
RustDumpCallback callback = reinterpret_cast<RustDumpCallback>(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());
}
#if defined(XP_LINUX)
bool getAuxvDumpInfo(RustAuxvCallback callback, breakpad_pid aPid,
DirectAuxvDumpInfo* aAuxvInfo) {
return callback(aPid, aAuxvInfo);
}
#endif // defined(XP_LINUX)
#ifdef XP_WIN
extern "C" void* CrashGenerationServer_init(breakpad_init_type aBreakpadData,
const breakpad_char* aMinidumpPath,
RustDumpCallback 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,
RustDumpCallback 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,
RustDumpCallback aDumpCallback,
RustAuxvCallback aAuxvCallback) {
breakpad_string minidumpPath(aMinidumpPath);
breakpad_init_type breakpadData = aBreakpadData;
CrashGenerationServer* server = new CrashGenerationServer(
breakpadData,
[aAuxvCallback](pid_t aPid, DirectAuxvDumpInfo* aAuxvInfo) {
return getAuxvDumpInfo(aAuxvCallback, aPid, aAuxvInfo);
},
[aDumpCallback](void* dump_context, const ClientInfo& aClientInfo,
const breakpad_string& aFilePath) {
onClientDumpRequestCallback(reinterpret_cast<void*>(aDumpCallback),
aClientInfo, aFilePath);
},
/* dump_context */ nullptr, &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;
}
extern "C" void CrashGenerationServer_set_path(
void* aServer, const breakpad_char* aMinidumpPath) {
CrashGenerationServer* server = static_cast<CrashGenerationServer*>(aServer);
server->SetPath(aMinidumpPath);
}

View File

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

View File

@@ -0,0 +1,81 @@
/* 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
Object = 5, // Not usable via the Rust API
}
// Type of each annotation
static CRASH_ANNOTATION_TYPES: &[CrashAnnotationType] = &[
${types}
];
// Stringified representation of each annotation. Most of these will just match
// the corresponding enum values, but for historical reasons some of them are
// different in string form when stored in the .extra file.
static CRASH_ANNOTATION_STRINGS: &[&str] = &[
${names}
];
// Annotations which should be skipped when they have specific values
struct CrashAnnotationSkipValue {
annotation: CrashAnnotation,
value: &'static [u8],
}
static CRASH_ANNOTATIONS_SKIP_VALUES: &[CrashAnnotationSkipValue] = &[
${skiplist}
];
/// Returns the type of a crash annotation.
///
/// # Arguments
///
/// * `annotation` - a crash annotation
pub(crate) fn type_of_annotation(annotation: CrashAnnotation) -> CrashAnnotationType {
CRASH_ANNOTATION_TYPES[annotation as usize]
}
/// Checks if the annotation should be included. Some annotations are skipped
/// if their value matches a specific one (like the value 0).
///
/// # Arguments
///
/// * `annotation` - the crash annotation to be checked
/// * `value` - the contents of the annotation as a string
pub(crate) fn should_include_annotation(annotation: CrashAnnotation, value: &[u8]) -> bool {
if let Some(skip_value) = CRASH_ANNOTATIONS_SKIP_VALUES
.iter()
.find(|&a| a.annotation == annotation)
{
skip_value.value != value
} else {
true
}
}

View File

@@ -0,0 +1,67 @@
/* 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> // for HANDLE
#endif // defined(XP_WIN)
#if defined(XP_LINUX)
// For DirectAuxvDumpInfo
# include "mozilla/toolkit/crashreporter/rust_minidump_writer_linux_ffi_generated.h"
#endif // defined(XP_LINUX)
#include "mozilla/crash_helper_ffi_generated.h"
static int parse_int_or_exit(const char* aArg) {
errno = 0;
long value = strtol(aArg, nullptr, 10);
if ((errno != 0) || (value < 0) || (value > INT_MAX)) {
exit(EXIT_FAILURE);
}
return static_cast<int>(value);
}
static BreakpadRawData parse_breakpad_data(const char* aArg) {
#if defined(XP_MACOSX)
return aArg;
#elif defined(XP_WIN)
// This is always an ASCII string so we don't need a proper conversion.
size_t len = strlen(aArg);
uint16_t* data = new uint16_t[len + 1];
for (size_t i = 0; i < len; i++) {
data[i] = aArg[i];
}
data[len] = 0;
return data;
#else // Linux and friends
return parse_int_or_exit(aArg);
#endif
}
static void free_breakpad_data(BreakpadRawData aData) {
#if defined(XP_WIN)
delete aData;
#endif
}
int main(int argc, char* argv[]) {
if (argc < 6) {
exit(EXIT_FAILURE);
}
Pid client_pid = static_cast<Pid>(parse_int_or_exit(argv[1]));
BreakpadRawData breakpad_data = parse_breakpad_data(argv[2]);
char* minidump_path = argv[3];
char* listener = argv[4];
char* connector = argv[5];
int res = crash_generator_logic_desktop(client_pid, breakpad_data,
minidump_path, listener, connector);
free_breakpad_data(breakpad_data);
exit(res);
}

View File

@@ -0,0 +1,51 @@
/* 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 <android/log.h>
#include <fcntl.h>
#include <sys/socket.h>
// For DirectAuxvDumpInfo
#include "mozilla/toolkit/crashreporter/rust_minidump_writer_linux_ffi_generated.h"
#include "mozilla/crash_helper_ffi_generated.h"
#define CRASH_HELPER_LOGTAG "GeckoCrashHelper"
extern "C" JNIEXPORT void JNICALL
Java_org_mozilla_gecko_crashhelper_CrashHelper_crash_1generator(
JNIEnv* jenv, jclass, jint client_pid, jint breakpad_fd,
jstring minidump_path, jint listen_fd, jint server_fd) {
// Enable passing credentials on the server socket and set it in non-blocking
// mode. We'd love to do it inside CrashHelper.java but the Java methods
// require an Android API version that's too recent for us.
const int val = 1;
int res = setsockopt(breakpad_fd, SOL_SOCKET, SO_PASSCRED, &val, sizeof(val));
if (res < 0) {
__android_log_print(ANDROID_LOG_FATAL, CRASH_HELPER_LOGTAG,
"Unable to set the Breakpad pipe socket options");
return;
}
int flags = fcntl(breakpad_fd, F_GETFL);
if (flags == -1) {
__android_log_print(ANDROID_LOG_FATAL, CRASH_HELPER_LOGTAG,
"Unable to get the Breakpad pipe file options");
return;
}
res = fcntl(breakpad_fd, F_SETFL, flags | O_NONBLOCK);
if (res == -1) {
__android_log_print(ANDROID_LOG_FATAL, CRASH_HELPER_LOGTAG,
"Unable to set the Breakpad pipe in non-blocking mode");
return;
}
const char* minidump_path_str =
jenv->GetStringUTFChars(minidump_path, nullptr);
crash_generator_logic_android(client_pid, breakpad_fd, minidump_path_str,
listen_fd, server_fd);
jenv->ReleaseStringUTFChars(minidump_path, minidump_path_str);
}

View File

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

View File

@@ -0,0 +1,22 @@
[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(any(target_os = \"android\", target_os = \"linux\"))".dependencies]
minidump-writer = "0.10"
rust_minidump_writer_linux = { path = "../rust_minidump_writer_linux" }
[target."cfg(target_os = \"windows\")".dependencies]
windows-sys = { version = "0.52" } # All features inherited from crash_helper_common

View File

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

View File

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

View File

@@ -0,0 +1,317 @@
/* 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},
BreakpadString, IPCConnector,
};
#[cfg(any(target_os = "android", target_os = "linux"))]
use minidump_writer::minidump_writer::{AuxvType, DirectAuxvDumpInfo};
#[cfg(target_os = "android")]
use std::os::fd::RawFd;
use std::{
ffi::{c_char, 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,
#[cfg(target_os = "linux")]
pid: Pid,
}
impl CrashHelperClient {
fn set_crash_report_path(&mut self, path: OsString) -> Result<()> {
let message = messages::SetCrashReportPath::new(path);
self.connector.send_message(&message)?;
Ok(())
}
#[cfg(any(target_os = "android", target_os = "linux"))]
fn register_auxv_info(&mut self, pid: Pid, auxv_info: DirectAuxvDumpInfo) -> Result<()> {
let message = messages::RegisterAuxvInfo::new(pid, auxv_info);
self.connector.send_message(&message)?;
Ok(())
}
#[cfg(any(target_os = "android", target_os = "linux"))]
fn unregister_auxv_info(&mut self, pid: Pid) -> Result<()> {
let message = messages::UnregisterAuxvInfo::new(pid);
self.connector.send_message(&message)?;
Ok(())
}
fn transfer_crash_report(&mut 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 {
path: 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.
#[cfg(not(target_os = "android"))]
#[no_mangle]
pub unsafe extern "C" fn crash_helper_launch(
helper_name: *const BreakpadChar,
breakpad_raw_data: BreakpadRawData,
minidump_path: *const BreakpadChar,
) -> *mut CrashHelperClient {
use crash_helper_common::BreakpadData;
let breakpad_data = BreakpadData::new(breakpad_raw_data);
if let Ok(crash_helper) = CrashHelperClient::new(helper_name, breakpad_data, minidump_path) {
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()
}
}
/// Connect to an already launching crash helper process. This is only available
/// on Android where the crash helper is a service. Returns a pointer to the
/// client connection or `null` upon failure.
///
/// # Safety
///
/// The `minidump_path` argument must point to a valid nul-terminated C string.
/// The `breakpad_raw_data` and `client_socket` arguments must be valid file
/// descriptors used to connect with Breakpad's crash generator and the crash
/// helper respectively..
#[cfg(target_os = "android")]
#[no_mangle]
pub unsafe extern "C" fn crash_helper_connect(client_socket: RawFd) -> *mut CrashHelperClient {
if let Ok(crash_helper) = CrashHelperClient::new(client_socket) {
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()`] or
/// [`crash_helper_connect()`] functions.
#[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()`] or
/// [`crash_helper_connect()`] functions.
#[cfg(target_os = "linux")]
#[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,
}
/// Changes the path where crash reports are generated.
///
/// # Safety
///
/// The `client` parameter must be a valid pointer to the crash helper client
/// object returned by the [`crash_helper_launch()`] or
/// [`crash_helper_connect()`] functions.
#[no_mangle]
pub unsafe extern "C" fn set_crash_report_path(
client: *mut CrashHelperClient,
path: *const BreakpadChar,
) -> bool {
let client = client.as_mut().unwrap();
let path = <OsString as BreakpadString>::from_ptr(path);
client.set_crash_report_path(path).is_ok()
}
/// 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()`] or
/// [`crash_helper_connect()`] functions.
#[no_mangle]
pub unsafe extern "C" fn transfer_crash_report(
client: *mut CrashHelperClient,
pid: Pid,
) -> *mut CrashReport {
let client = client.as_mut().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()`] or [`crash_helper_connect()`] functions.
#[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);
}
}
/// Send the auxiliary vector information for the process identified by `pid`
/// to the crash helper.
///
/// # Safety
///
/// The `client` parameter must be a valid pointer to the crash helper client
/// object returned by the [`crash_helper_launch()`] or
/// [`crash_helper_connect()`] functions. The `auxv_info` pointer must be
/// non-null and point to a properly populated `DirectAuxvDumpInfo` structure.
#[cfg(any(target_os = "android", target_os = "linux"))]
#[no_mangle]
pub unsafe extern "C" fn register_child_auxv_info(
client: *mut CrashHelperClient,
pid: Pid,
auxv_info_ptr: *const rust_minidump_writer_linux::DirectAuxvDumpInfo,
) -> bool {
let client = client.as_mut().unwrap();
let auxv_info = DirectAuxvDumpInfo {
program_header_count: (*auxv_info_ptr).program_header_count as AuxvType,
program_header_address: (*auxv_info_ptr).program_header_address as AuxvType,
linux_gate_address: (*auxv_info_ptr).linux_gate_address as AuxvType,
entry_address: (*auxv_info_ptr).entry_address as AuxvType,
};
client.register_auxv_info(pid, auxv_info).is_ok()
}
/// Deregister previously sent auxiliary vector information for the process
/// identified by `pid`.
///
/// # Safety
///
/// The `client` parameter must be a valid pointer to the crash helper client
/// object returned by the [`crash_helper_launch()`] or
/// [`crash_helper_connect()`] functions.
#[cfg(any(target_os = "android", target_os = "linux"))]
#[no_mangle]
pub unsafe extern "C" fn unregister_child_auxv_info(
client: *mut CrashHelperClient,
pid: Pid,
) -> bool {
let client = client.as_mut().unwrap();
client.unregister_auxv_info(pid).is_ok()
}

View File

@@ -0,0 +1,12 @@
/* 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 = "linux", target_os = "macos"))]
pub(crate) mod unix;
#[cfg(any(target_os = "android"))]
pub(crate) mod android;

View File

@@ -0,0 +1,19 @@
/* 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::IPCConnector;
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
use crate::CrashHelperClient;
impl CrashHelperClient {
pub(crate) fn new(server_socket: RawFd) -> Result<CrashHelperClient> {
// SAFETY: The `server_socket` passed in from the application is valid
let server_socket = unsafe { OwnedFd::from_raw_fd(server_socket) };
let connector = IPCConnector::from_fd(server_socket)?;
Ok(CrashHelperClient { connector })
}
}

View File

@@ -0,0 +1,74 @@
/* 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::{BreakpadChar, BreakpadData, IPCChannel, IPCConnector, IPCListener};
use nix::unistd::{execv, fork, getpid, ForkResult};
use std::ffi::{CStr, CString};
use crate::CrashHelperClient;
impl CrashHelperClient {
pub(crate) fn new(
program: *const BreakpadChar,
breakpad_data: BreakpadData,
minidump_path: *const BreakpadChar,
) -> Result<CrashHelperClient> {
let channel = IPCChannel::new()?;
let (listener, server_endpoint, client_endpoint) = channel.deconstruct();
let _pid = CrashHelperClient::spawn_crash_helper(
program,
breakpad_data,
minidump_path,
listener,
server_endpoint,
)?;
Ok(CrashHelperClient {
connector: client_endpoint,
#[cfg(target_os = "linux")]
pid: _pid,
})
}
fn spawn_crash_helper(
program: *const BreakpadChar,
breakpad_data: BreakpadData,
minidump_path: *const BreakpadChar,
listener: IPCListener,
endpoint: IPCConnector,
) -> Result<nix::libc::pid_t> {
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 breakpad_data_arg =
unsafe { CString::from_vec_unchecked(breakpad_data.to_string().into_bytes()) };
let minidump_path = unsafe { CStr::from_ptr(minidump_path) };
let listener_arg = listener.serialize();
let endpoint_arg = endpoint.serialize();
let _ = execv(
program,
&[
program,
&parent_pid_arg,
&breakpad_data_arg,
minidump_path,
&listener_arg,
&endpoint_arg,
],
);
// This point should be unreachable, but let's play it safe
unsafe { nix::libc::_exit(1) };
}
ForkResult::Parent { child } => Ok(child.as_raw()),
}
}
}

View File

@@ -0,0 +1,160 @@
/* 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, BreakpadData, BreakpadString, IPCChannel, IPCConnector, IPCListener,
};
use std::{
ffi::{OsStr, OsString},
mem::{size_of, zeroed},
os::windows::ffi::{OsStrExt, OsStringExt},
ptr::{null, null_mut},
};
use windows_sys::Win32::{
Foundation::{CloseHandle, FALSE, TRUE},
System::Threading::{
CreateProcessW, GetCurrentProcessId, CREATE_UNICODE_ENVIRONMENT, DETACHED_PROCESS,
PROCESS_INFORMATION, STARTUPINFOW,
},
};
use crate::CrashHelperClient;
impl CrashHelperClient {
pub(crate) fn new(
program: *const BreakpadChar,
breakpad_data: BreakpadData,
minidump_path: *const BreakpadChar,
) -> Result<CrashHelperClient> {
// SAFETY: `program` points to a valid string passed in by Firefox
let program = unsafe { <OsString as BreakpadString>::from_ptr(program) };
// SAFETY: `minidump_path` points to a valid string passed in by Firefox
let minidump_path = unsafe { <OsString as BreakpadString>::from_ptr(minidump_path) };
let channel = IPCChannel::new()?;
let (listener, server_endpoint, client_endpoint) = channel.deconstruct();
let _ = std::thread::spawn(move || {
// If this fails we have no way to tell, but the IPC won't work so
// it's fine to ignore the return value.
let _ = CrashHelperClient::spawn_crash_helper(
program,
breakpad_data,
minidump_path,
listener,
server_endpoint,
);
});
Ok(CrashHelperClient {
connector: client_endpoint,
})
}
fn spawn_crash_helper(
program: OsString,
breakpad_data: BreakpadData,
minidump_path: OsString,
listener: IPCListener,
endpoint: IPCConnector,
) -> Result<()> {
// SAFETY: `GetCurrentProcessId()` takes no arguments and should always work
let pid = OsString::from(unsafe { GetCurrentProcessId() }.to_string());
let mut cmd_line = escape_cmd_line_arg(&program);
cmd_line.push(" ");
cmd_line.push(escape_cmd_line_arg(&pid));
cmd_line.push(" ");
cmd_line.push(escape_cmd_line_arg(breakpad_data.as_ref()));
cmd_line.push(" ");
cmd_line.push(escape_cmd_line_arg(&minidump_path));
cmd_line.push(" ");
cmd_line.push(escape_cmd_line_arg(&listener.serialize()));
cmd_line.push(" ");
cmd_line.push(escape_cmd_line_arg(&endpoint.serialize()));
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(
/* lpApplicationName */ null(),
cmd_line.as_mut_ptr(),
/* lpProcessAttributes */ null_mut(),
/* lpThreadAttributes */ null_mut(),
/* bInheritHandles */ TRUE,
CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS,
/* lpEnvironment */ null_mut(),
/* lpCurrentDirectory */ null_mut(),
&si,
&mut pi,
)
};
if res == FALSE {
bail!("Could not create the crash helper process");
}
// SAFETY: We just successfully populated the `PROCESS_INFORMATION`
// structure and the `hProcess` field contains a valid handle.
unsafe { CloseHandle(pi.hProcess) };
Ok(())
}
}
/// Escape an argument so that it is suitable for use in the command line
/// parameter of a CreateProcess() call. This involves escaping all inner
/// double-quote characters as well as trailing backslashes. The algorithm
/// used is described in this article:
/// https://learn.microsoft.com/it-it/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
fn escape_cmd_line_arg(arg: &OsStr) -> OsString {
const DOUBLE_QUOTES: u16 = '"' as u16;
const BACKSLASH: u16 = '\\' as u16;
let encoded_arg: Vec<u16> = arg.encode_wide().collect();
let mut escaped_arg = Vec::<u16>::new();
escaped_arg.push(DOUBLE_QUOTES as u16);
let mut it = encoded_arg.iter().peekable();
loop {
let mut backslash_num = 0;
while let Some(&&_c @ BACKSLASH) = it.peek() {
it.next();
backslash_num += 1;
}
match it.peek() {
None => {
for _ in 0..backslash_num {
escaped_arg.extend([BACKSLASH, BACKSLASH]);
}
break;
}
Some(&&_c @ DOUBLE_QUOTES) => {
for _ in 0..backslash_num {
escaped_arg.extend([BACKSLASH, BACKSLASH]);
}
escaped_arg.extend([BACKSLASH, DOUBLE_QUOTES]);
}
Some(&&c) => {
for _ in 0..backslash_num {
escaped_arg.push(BACKSLASH);
}
escaped_arg.push(c)
}
}
it.next();
}
escaped_arg.push(DOUBLE_QUOTES);
OsString::from_wide(&escaped_arg)
}

View File

@@ -13,6 +13,9 @@ thiserror = "2"
[target."cfg(any(target_os = \"android\", target_os = \"linux\", target_os = \"macos\"))".dependencies]
nix = { version = "0.29", features = ["fs", "poll", "socket", "uio"] }
[target."cfg(any(target_os = \"android\", target_os = \"linux\"))".dependencies]
minidump-writer = "0.10"
[target."cfg(target_os = \"windows\")".dependencies]
windows-sys = { version = "0.52", features = [
"Win32_Foundation",

View File

@@ -2,6 +2,8 @@
* 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 = "android", target_os = "linux"))]
use minidump_writer::minidump_writer::{AuxvType, DirectAuxvDumpInfo};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use std::{
@@ -38,6 +40,12 @@ pub enum Kind {
WindowsErrorReporting = 6,
#[cfg(target_os = "windows")]
WindowsErrorReportingReply = 7,
/// Register and unregister additional information for the auxiliary
/// vector of a process.
#[cfg(any(target_os = "android", target_os = "linux"))]
RegisterAuxvInfo = 8,
#[cfg(any(target_os = "android", target_os = "linux"))]
UnregisterAuxvInfo = 9,
}
pub trait Message {
@@ -459,3 +467,156 @@ impl Message for WindowsErrorReportingMinidumpReply {
Ok(WindowsErrorReportingMinidumpReply::new())
}
}
/* Message used to send information about a process' auxiliary vector. */
#[cfg(any(target_os = "android", target_os = "linux"))]
pub struct RegisterAuxvInfo {
pub pid: Pid,
pub auxv_info: DirectAuxvDumpInfo,
}
#[cfg(any(target_os = "android", target_os = "linux"))]
impl RegisterAuxvInfo {
pub fn new(pid: Pid, auxv_info: DirectAuxvDumpInfo) -> RegisterAuxvInfo {
RegisterAuxvInfo { pid, auxv_info }
}
fn payload_size(&self) -> usize {
// A bit hacky but we'll change this when we make
// serialization/deserialization later.
size_of::<Pid>() + (size_of::<AuxvType>() * 4)
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
impl Message for RegisterAuxvInfo {
fn kind() -> Kind {
Kind::RegisterAuxvInfo
}
fn header(&self) -> Vec<u8> {
Header {
kind: Self::kind(),
size: self.payload_size(),
}
.encode()
}
fn payload(&self) -> Vec<u8> {
let mut payload = Vec::with_capacity(self.payload_size());
payload.extend(self.pid.to_ne_bytes());
payload.extend(self.auxv_info.program_header_count.to_ne_bytes());
payload.extend(self.auxv_info.program_header_address.to_ne_bytes());
payload.extend(self.auxv_info.linux_gate_address.to_ne_bytes());
payload.extend(self.auxv_info.entry_address.to_ne_bytes());
debug_assert!(self.payload_size() == payload.len());
payload
}
fn ancillary_payload(&self) -> Option<AncillaryData> {
None
}
fn decode(
data: &[u8],
ancillary_data: Option<AncillaryData>,
) -> Result<RegisterAuxvInfo, MessageError> {
debug_assert!(
ancillary_data.is_none(),
"RegisterAuxvInfo messages cannot carry ancillary data"
);
let bytes: [u8; size_of::<Pid>()] = data[0..size_of::<Pid>()].try_into()?;
let pid = Pid::from_ne_bytes(bytes);
let offset = size_of::<Pid>();
let bytes: [u8; size_of::<AuxvType>()] =
data[offset..(offset + size_of::<AuxvType>())].try_into()?;
let program_header_count = AuxvType::from_ne_bytes(bytes);
let offset = offset + size_of::<AuxvType>();
let bytes: [u8; size_of::<AuxvType>()] =
data[offset..(offset + size_of::<AuxvType>())].try_into()?;
let program_header_address = AuxvType::from_ne_bytes(bytes);
let offset = offset + size_of::<AuxvType>();
let bytes: [u8; size_of::<AuxvType>()] =
data[offset..(offset + size_of::<AuxvType>())].try_into()?;
let linux_gate_address = AuxvType::from_ne_bytes(bytes);
let offset = offset + size_of::<AuxvType>();
let bytes: [u8; size_of::<AuxvType>()] =
data[offset..(offset + size_of::<AuxvType>())].try_into()?;
let entry_address = AuxvType::from_ne_bytes(bytes);
let auxv_info = DirectAuxvDumpInfo {
program_header_count,
program_header_address,
entry_address,
linux_gate_address,
};
Ok(RegisterAuxvInfo { pid, auxv_info })
}
}
/* Message used to inform the crash helper that a process' auxiliary vector
* information is not needed anymore. */
#[cfg(any(target_os = "android", target_os = "linux"))]
pub struct UnregisterAuxvInfo {
pub pid: Pid,
}
#[cfg(any(target_os = "android", target_os = "linux"))]
impl UnregisterAuxvInfo {
pub fn new(pid: Pid) -> UnregisterAuxvInfo {
UnregisterAuxvInfo { pid }
}
fn payload_size(&self) -> usize {
size_of::<Pid>()
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
impl Message for UnregisterAuxvInfo {
fn kind() -> Kind {
Kind::UnregisterAuxvInfo
}
fn header(&self) -> Vec<u8> {
Header {
kind: Self::kind(),
size: self.payload_size(),
}
.encode()
}
fn payload(&self) -> Vec<u8> {
let mut payload = Vec::with_capacity(self.payload_size());
payload.extend(self.pid.to_ne_bytes());
debug_assert!(self.payload_size() == payload.len());
payload
}
fn ancillary_payload(&self) -> Option<AncillaryData> {
None
}
fn decode(
data: &[u8],
ancillary_data: Option<AncillaryData>,
) -> Result<UnregisterAuxvInfo, MessageError> {
debug_assert!(
ancillary_data.is_none(),
"UnregisterAuxvInfo messages cannot carry ancillary data"
);
let bytes: [u8; size_of::<Pid>()] = data[0..size_of::<Pid>()].try_into()?;
let pid = Pid::from_ne_bytes(bytes);
Ok(UnregisterAuxvInfo { pid })
}
}

View File

@@ -0,0 +1,49 @@
[package]
name = "crash_helper_server"
version = "0.1.0"
authors = ["Gabriele Svelto <gsvelto@mozilla.com>"]
edition = "2018"
[dependencies]
anyhow = "1"
cfg-if = "1"
crash_helper_common = { path = "../crash_helper_common" }
dirs = "4"
mozannotation_server = { path = "../mozannotation_server" }
mozbuild = "0.1"
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"] }
minidump-writer = { version = "0.10" }
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"
yaml-rust = "0.4"
[lib]
name = "crash_helper_server"
crate-type = ["staticlib"]
path = "src/lib.rs"

View File

@@ -0,0 +1,168 @@
/* 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"),
"boolean" => Ok("Boolean"),
"u32" => Ok("U32"),
"u64" => Ok("U64"),
"usize" => Ok("USize"),
"object" => Ok("Object"),
_ => Err(Error::other("Invalid type")),
}
.map(str::to_owned)
}
}
fn read_annotations(doc: &Yaml) -> Result<Vec<Annotation>, Error> {
let raw_annotations = doc.as_hash().ok_or(Error::other(
"Invalid data in YAML file, expected a hash".to_string(),
))?;
let mut annotations = Vec::<Annotation>::new();
for annotation in raw_annotations {
let annotation = Annotation::new(annotation.0, annotation.1)?;
annotations.push(annotation);
}
annotations.sort_by(|a, b| {
let a_lower = a.name.to_ascii_lowercase();
let b_lower = b.name.to_ascii_lowercase();
a_lower.cmp(&b_lower)
});
Ok(annotations)
}
fn generate_annotation_enum(annotations: &[Annotation]) -> Result<String, Error> {
let mut annotations_enum = String::new();
for (index, value) in annotations.iter().enumerate() {
let entry = format!(" {} = {index:},\n", value.name);
annotations_enum.push_str(&entry);
}
let count = format!(" Count = {},", annotations.len());
annotations_enum.push_str(&count);
Ok(annotations_enum)
}
fn generate_annotation_types(annotations: &[Annotation]) -> Result<String, Error> {
let mut types_array = String::new();
for value in annotations.iter() {
let entry = format!(" CrashAnnotationType::{},\n", value.type_string);
types_array.push_str(&entry);
}
// Pop the last newline
types_array.pop();
Ok(types_array)
}
fn generate_annotation_names(annotations: &[Annotation]) -> Result<String, Error> {
let mut names_array = String::new();
for value in annotations.iter() {
let name = value.altname.as_ref().unwrap_or(&value.name);
let entry = format!(" \"{}\",\n", name);
names_array.push_str(&entry);
}
// Pop the last newline
names_array.pop();
Ok(names_array)
}
fn generate_annotation_skiplist(annotations: &[Annotation]) -> Result<String, Error> {
let mut skiplist = String::new();
for annotation in annotations.iter() {
if let Some(skip_if) = &annotation.skip_if {
let entry = format!(
" CrashAnnotationSkipValue {{ annotation: CrashAnnotation::{}, value: b\"{}\" }},\n",
&annotation.name, skip_if
);
skiplist.push_str(&entry);
}
}
// Pop the last newline
skiplist.pop();
Ok(skiplist)
}
// Generate Rust code to manipulate crash annotations
fn generate_annotations() -> Result<(), Error> {
const CRASH_ANNOTATIONS_YAML: &str = "../CrashAnnotations.yaml";
const CRASH_ANNOTATIONS_TEMPLATE: &str = "../crash_annotations.rs.in";
let out_dir = env::var("OUT_DIR").unwrap();
let annotations_path = Path::new(&out_dir).join("crash_annotations.rs");
let mut annotations_file = File::create(annotations_path)?;
let template = fs::read_to_string(CRASH_ANNOTATIONS_TEMPLATE)?;
let yaml_str = fs::read_to_string(CRASH_ANNOTATIONS_YAML)?;
let yaml_doc = YamlLoader::load_from_str(&yaml_str)
.map_err(|e| Error::other(format!("Failed to parse YAML file: {}", e)))?;
let doc = &yaml_doc[0];
let annotations = read_annotations(doc)?;
let annotations_enum = generate_annotation_enum(&annotations)?;
let annotations_types = generate_annotation_types(&annotations)?;
let annotations_names = generate_annotation_names(&annotations)?;
let skiplist = generate_annotation_skiplist(&annotations)?;
let compiled_template = template
.replace("${enum}", &annotations_enum)
.replace("${types}", &annotations_types)
.replace("${names}", &annotations_names)
.replace("${skiplist}", &skiplist);
write!(&mut annotations_file, "{}", compiled_template)?;
println!("cargo:rerun-if-changed={CRASH_ANNOTATIONS_YAML}");
println!("cargo:rerun-if-changed={CRASH_ANNOTATIONS_TEMPLATE}");
Ok(())
}

View File

@@ -0,0 +1,28 @@
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"
[export]
exclude = [
"CrashGenerationServer_init",
"CrashGenerationServer_shutdown",
"CrashGenerationServer_set_path",
]
[parse]
parse_deps = true
include = ["crash_helper_common"]

View File

@@ -0,0 +1,25 @@
# -*- 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["OS_TARGET"] == "WINNT":
OS_LIBS += [
"ole32",
"shell32",
]
if CONFIG["COMPILE_ENVIRONMENT"]:
# This tells mach to run cbindgen and that this header-file should be created
CbindgenHeader(
"crash_helper_ffi_generated.h",
inputs=["/toolkit/crashreporter/crash_helper_server"],
)
# This tells mach to copy that generated file to obj/dist/mozilla
EXPORTS.mozilla += [
"!crash_helper_ffi_generated.h",
]

View File

@@ -0,0 +1,135 @@
/* 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 cfg_if::cfg_if;
use crash_helper_common::{BreakpadChar, BreakpadData, BreakpadString};
#[cfg(any(target_os = "android", target_os = "linux"))]
use minidump_writer::minidump_writer::DirectAuxvDumpInfo;
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;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::fd::{FromRawFd, OwnedFd};
extern "C" {
fn CrashGenerationServer_init(
breakpad_data: BreakpadInitType,
minidump_path: *const BreakpadChar,
cb: extern "C" fn(BreakpadProcessId, *const c_char, *const BreakpadChar),
#[cfg(any(target_os = "android", target_os = "linux"))] auxv_cb: extern "C" fn(
crash_helper_common::Pid,
*mut DirectAuxvDumpInfo,
)
-> bool,
) -> *mut c_void;
fn CrashGenerationServer_shutdown(server: *mut c_void);
fn CrashGenerationServer_set_path(server: *mut c_void, path: *const BreakpadChar);
}
pub(crate) struct BreakpadCrashGenerator {
ptr: NonNull<c_void>,
path: NonNull<BreakpadChar>,
#[allow(
dead_code,
reason = "This socket is used by Breakpad so it must be closed on Drop() as we own it"
)]
#[cfg(any(target_os = "linux", target_os = "android"))]
breakpad_socket: OwnedFd,
}
// 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),
#[cfg(any(target_os = "android", target_os = "linux"))]
auxv_callback: extern "C" fn(
crash_helper_common::Pid,
*mut DirectAuxvDumpInfo,
) -> bool,
) -> Result<BreakpadCrashGenerator> {
let breakpad_raw_data = breakpad_data.into_raw();
let path_ptr = 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,
#[cfg(any(target_os = "android", target_os = "linux"))]
auxv_callback,
)
};
// Retake ownership of the raw data & strings so we don't leak them.
cfg_if! {
if #[cfg(any(target_os = "macos", target_os = "windows"))] {
// SAFETY: We've allocated this object within this same block.
let _breakpad_data = unsafe { BreakpadData::new(breakpad_raw_data) };
}
}
if breakpad_server.is_null() {
bail!("Could not initialize Breakpad crash generator");
}
// SAFETY: We already verified that the pointers are non-null. On Linux
// and Android the breakpad socket is also a valid file descriptor. We
// store it in an owned file descriptor because we're taking ownership
// of it and we want it closed when we shut down the crash generation
// server.
Ok(unsafe {
BreakpadCrashGenerator {
ptr: NonNull::new(breakpad_server).unwrap_unchecked(),
path: NonNull::new(path_ptr).unwrap_unchecked(),
#[cfg(any(target_os = "linux", target_os = "android"))]
breakpad_socket: OwnedFd::from_raw_fd(breakpad_raw_data),
}
})
}
pub(crate) fn set_path(&self, path: OsString) {
unsafe {
let path = path.into_raw();
CrashGenerationServer_set_path(self.ptr.as_ptr(), path);
};
}
}
impl Drop for BreakpadCrashGenerator {
fn drop(&mut self) {
// SAFETY: The pointers we're passing are guaranteed to be non-null and
// valid since we created them ourselves during construction.
unsafe {
CrashGenerationServer_shutdown(self.ptr.as_ptr());
let _path = <OsString as BreakpadString>::from_raw(self.path.as_ptr());
}
}
}

View File

@@ -0,0 +1,454 @@
/* 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,
};
#[cfg(any(target_os = "android", target_os = "linux"))]
use minidump_writer::minidump_writer::DirectAuxvDumpInfo;
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);
// Table holding the information about the auxiliary vector of potentially
// every process registered with the crash helper.
#[cfg(any(target_os = "android", target_os = "linux"))]
static AUXV_INFO_MAP: Lazy<Mutex<HashMap<Pid, DirectAuxvDumpInfo>>> = Lazy::new(Default::default);
/******************************************************************************
* Crash generator *
******************************************************************************/
#[derive(PartialEq)]
enum MinidumpOrigin {
Breakpad,
WindowsErrorReporting,
}
pub(crate) struct CrashGenerator {
// This will be used for generating hangs
_minidump_path: OsString,
breakpad_server: BreakpadCrashGenerator,
client_pid: Pid,
}
impl CrashGenerator {
pub(crate) fn new(
client_pid: Pid,
breakpad_data: BreakpadData,
minidump_path: OsString,
) -> Result<CrashGenerator> {
let breakpad_server = BreakpadCrashGenerator::new(
breakpad_data,
minidump_path.clone(),
finalize_breakpad_minidump,
#[cfg(any(target_os = "android", target_os = "linux"))]
get_auxv_info,
)?;
Ok(CrashGenerator {
_minidump_path: minidump_path,
breakpad_server,
client_pid,
})
}
// 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::SetCrashReportPath => {
if pid != self.client_pid {
panic!("Not connected or attempting to set the path from the wrong process");
}
let message = messages::SetCrashReportPath::decode(data, ancillary_data)?;
self.set_path(message.path);
Ok(None)
}
messages::Kind::TransferMinidump => {
if pid != self.client_pid {
panic!(
"Not connected 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(),
)))
}
#[cfg(any(target_os = "android", target_os = "linux"))]
messages::Kind::RegisterAuxvInfo => {
if pid != self.client_pid {
panic!(
"Attempting to register some auxiliary information from the wrong process"
);
}
let message = messages::RegisterAuxvInfo::decode(data, ancillary_data)?;
let map = &mut AUXV_INFO_MAP.lock().unwrap();
map.insert(message.pid, message.auxv_info);
Ok(None)
}
#[cfg(any(target_os = "android", target_os = "linux"))]
messages::Kind::UnregisterAuxvInfo => {
if pid != self.client_pid {
panic!("Attempting to unregister auxiliary information from the wrong process");
}
let message = messages::UnregisterAuxvInfo::decode(data, ancillary_data)?;
let map = &mut AUXV_INFO_MAP.lock().unwrap();
map.remove(&message.pid);
Ok(None)
}
kind => {
bail!("Unexpected message {:?}", kind);
}
}
}
fn set_path(&mut self, path: OsString) {
self.breakpad_server.set_path(path);
}
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(unsafe { <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)]);
}
#[cfg(any(target_os = "android", target_os = "linux"))]
extern "C" fn get_auxv_info(pid: Pid, auxv_info_ptr: *mut DirectAuxvDumpInfo) -> bool {
let map = &mut AUXV_INFO_MAP.lock().unwrap();
if let Some(auxv_info) = map.get(&pid) {
// SAFETY: The auxv_info_ptr is guaranteed to be valid by the caller.
unsafe { auxv_info_ptr.write(auxv_info.to_owned()) };
true
} else {
false
}
}
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)
}
CrashAnnotationType::Object => None, // This cannot be found in memory
};
if let Some(value) = value {
if !value.is_empty() && should_include_annotation(annotation_id, &value) {
write!(&mut file, "\"{annotation_id:}\":\"")?;
file.write_all(&value)?;
write!(&mut file, "\",")?;
annotations_written += 1;
}
}
}
}
if annotations_written > 0 {
// Drop the last comma
file.seek(SeekFrom::Current(-1))?;
}
writeln!(&mut file, "}}")?;
Ok(())
}
// Escapes the characters of a crash annotation so that they appear correctly
// within the JSON output, escaping non-visible characters and the like. This
// does not try to make the output valid UTF-8 because the input might be
// corrupted so there's no point in that.
fn escape_value(input: &[u8]) -> Vec<u8> {
let mut escaped = Vec::<u8>::with_capacity(input.len() + 2);
for &c in input {
if c <= 0x1f || c == b'\\' || c == b'"' {
escaped.extend(b"\\u00");
escaped.push(hex_digit_as_ascii_char((c & 0x00f0) >> 4));
escaped.push(hex_digit_as_ascii_char(c & 0x000f));
} else {
escaped.push(c)
}
}
escaped
}
fn hex_digit_as_ascii_char(value: u8) -> u8 {
if value < 10 {
b'0' + value
} else {
b'a' + (value - 10)
}
}

View File

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

View File

@@ -0,0 +1,96 @@
/* 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,
listener: IPCListener,
connector: IPCConnector,
) -> IPCServer {
IPCServer {
listener,
connectors: vec![connector],
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) => {
self.connectors.push(connector);
}
IPCEvent::Header(index, header) => {
let connector = self
.connectors
.get_mut(index)
.expect("Invalid connector index");
let _res = Self::handle_message(connector, &header, generator);
// TODO: Errors at this level are always survivable, but we
// should probably log them.
}
IPCEvent::Disconnect(index) => {
let connector = self
.connectors
.get_mut(index)
.expect("Invalid connector index");
if connector.endpoint_pid() == self.client_pid {
// The main process disconnected, leave
return Ok(IPCServerState::ClientDisconnected);
} else {
// This closes the connection
let _ = self.connectors.remove(index);
}
}
}
}
Ok(IPCServerState::Running)
}
fn handle_message(
connector: &mut IPCConnector,
header: &messages::Header,
generator: &mut CrashGenerator,
) -> Result<()> {
let (data, ancillary_data) = connector.recv(header.size)?;
let reply = generator.client_message(
header.kind,
&data,
ancillary_data,
connector.endpoint_pid(),
)?;
if let Some(reply) = reply {
connector.send_message(reply.as_ref())?;
}
Ok(())
}
}

View File

@@ -0,0 +1,111 @@
/* 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 crash_helper_common::{BreakpadData, BreakpadRawData, IPCConnector, IPCListener, Pid};
use std::ffi::{c_char, CStr, OsString};
use crash_generation::CrashGenerator;
use ipc_server::{IPCServer, IPCServerState};
#[cfg(target_os = "android")]
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
/// Runs the crash generator process logic, this includes the IPC used by
/// processes to signal that they crashed, the IPC used to retrieve crash
/// reports from the crash helper process and the logic used to generate the
/// actual minidumps. This function will return when the main process has
/// disconnected from the crash helper.
///
/// # Safety
///
/// `minidump_data`, `listener` and `pipe` must point to valid,
/// nul-terminated C strings. `breakpad_data` must be a valid file descriptor
/// (Linux) or point to a nul-terminated C string using either byte (macOS)
/// or wide characters (Windows).
#[cfg(not(target_os = "android"))]
#[no_mangle]
pub unsafe extern "C" fn crash_generator_logic_desktop(
client_pid: Pid,
breakpad_data: BreakpadRawData,
minidump_path: *const c_char,
listener: *const c_char,
pipe: *const c_char,
) -> i32 {
let breakpad_data = BreakpadData::new(breakpad_data);
let minidump_path = unsafe { CStr::from_ptr(minidump_path) }
.to_owned()
.into_string()
.unwrap();
let minidump_path = OsString::from(minidump_path);
let listener = unsafe { CStr::from_ptr(listener) };
let listener = IPCListener::deserialize(listener, client_pid).unwrap();
let pipe = unsafe { CStr::from_ptr(pipe) };
let connector = IPCConnector::deserialize(pipe).unwrap();
let crash_generator = CrashGenerator::new(client_pid, breakpad_data, minidump_path).unwrap();
let ipc_server = IPCServer::new(client_pid, listener, connector);
main_loop(ipc_server, crash_generator)
}
/// Runs the crash generator process logic, this includes the IPC used by
/// processes to signal that they crashed, the IPC used to retrieve crash
/// reports from the crash helper process and the logic used to generate the
/// actual minidumps. The logic will run in a separate thread and this
/// function will return immediately after launching it.
///
/// # Safety
///
/// `minidump_data` must point to valid, nul-terminated C strings. `listener`
/// and `server_pipe` must be valid file descriptors and `breakpad_data` must
/// also be a valid file descriptor compatible with Breakpad's crash generation
/// server.
#[cfg(target_os = "android")]
#[no_mangle]
pub unsafe extern "C" fn crash_generator_logic_android(
client_pid: Pid,
breakpad_data: BreakpadRawData,
minidump_path: *const c_char,
listener: RawFd,
pipe: RawFd,
) {
let breakpad_data = BreakpadData::new(breakpad_data);
let minidump_path = unsafe { CStr::from_ptr(minidump_path) }
.to_owned()
.into_string()
.unwrap();
let minidump_path = OsString::from(minidump_path);
let crash_generator = CrashGenerator::new(client_pid, breakpad_data, minidump_path).unwrap();
let listener = unsafe { OwnedFd::from_raw_fd(listener) };
let listener = IPCListener::from_fd(client_pid, listener).unwrap();
let pipe = unsafe { OwnedFd::from_raw_fd(pipe) };
let connector = IPCConnector::from_fd(pipe).unwrap();
let ipc_server = IPCServer::new(client_pid, listener, connector);
// 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));
}
fn main_loop(mut ipc_server: IPCServer, mut crash_generator: CrashGenerator) -> i32 {
loop {
match ipc_server.run(&mut crash_generator) {
Ok(_result @ IPCServerState::ClientDisconnected) => {
return 0;
}
Err(_error) => {
return -1;
}
_ => {} // Go on
}
}
}

View File

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

View File

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

View File

@@ -31,6 +31,11 @@ include("/ipc/chromium/chromium-config.mozbuild")
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",
@@ -59,10 +64,6 @@ if CONFIG["MOZ_CRASHREPORTER"]:
"google-breakpad/src/processor",
]
UNIFIED_SOURCES += [
"linux_utils.cc",
]
EXPORTS += [
"linux_utils.h",
]
@@ -72,13 +73,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"]
@@ -119,7 +122,9 @@ if CONFIG["MOZ_CRASHREPORTER"]:
"google-breakpad/src",
]
USE_LIBS += ["jsoncpp"]
USE_LIBS += [
"jsoncpp",
]
PYTHON_UNITTEST_MANIFESTS += [
"tools/python.toml",

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,20 +2,20 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crash_helper_client::report_external_exception;
use ini::Ini;
use libc::time;
use process_reader::ProcessReader;
use serde::Serialize;
use serde_json::ser::to_writer;
use std::convert::TryInto;
use std::ffi::{c_void, OsString};
use std::fs::{read_to_string, DirBuilder, File, OpenOptions};
use std::io::{BufRead, BufReader, Write};
use std::mem::{size_of, transmute, zeroed};
use std::mem::{size_of, zeroed};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle};
use std::os::windows::io::AsRawHandle;
use std::path::{Path, PathBuf};
use std::ptr::{addr_of, null, null_mut};
use std::ptr::{null, null_mut};
use std::slice::from_raw_parts;
use uuid::Uuid;
use windows_sys::core::{HRESULT, PWSTR};
@@ -24,7 +24,7 @@ use windows_sys::Win32::{
Foundation::{
CloseHandle, GetLastError, SetLastError, BOOL, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS,
EXCEPTION_BREAKPOINT, E_UNEXPECTED, FALSE, FILETIME, HANDLE, HWND, LPARAM, MAX_PATH,
STATUS_SUCCESS, S_OK, TRUE, WAIT_OBJECT_0,
STATUS_SUCCESS, S_OK, TRUE,
},
Security::{
GetSidSubAuthority, GetSidSubAuthorityCount, GetTokenInformation, IsTokenRestricted,
@@ -34,12 +34,9 @@ use windows_sys::Win32::{
System::Diagnostics::Debug::{
GetThreadContext, MiniDumpWithFullMemoryInfo, MiniDumpWithIndirectlyReferencedMemory,
MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules, MiniDumpWriteDump,
WriteProcessMemory, EXCEPTION_POINTERS, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE,
EXCEPTION_POINTERS, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE,
},
System::ErrorReporting::WER_RUNTIME_EXCEPTION_INFORMATION,
System::Memory::{
VirtualAllocEx, VirtualFreeEx, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE,
},
System::ProcessStatus::K32GetModuleFileNameExW,
System::SystemInformation::{
VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION,
@@ -47,9 +44,8 @@ use windows_sys::Win32::{
},
System::SystemServices::{SECURITY_MANDATORY_MEDIUM_RID, VER_GREATER_EQUAL},
System::Threading::{
CreateProcessW, CreateRemoteThread, GetProcessId, GetProcessTimes, GetThreadId,
OpenProcess, OpenProcessToken, OpenThread, TerminateProcess, WaitForSingleObject,
CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, LPTHREAD_START_ROUTINE,
CreateProcessW, GetProcessId, GetProcessTimes, GetThreadId, OpenProcess, OpenProcessToken,
OpenThread, TerminateProcess, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT,
NORMAL_PRIORITY_CLASS, PROCESS_ALL_ACCESS, PROCESS_BASIC_INFORMATION, PROCESS_INFORMATION,
STARTUPINFOW, THREAD_GET_CONTEXT,
},
@@ -67,15 +63,6 @@ type PDWORD = *mut DWORD;
#[allow(non_camel_case_types)]
type PWER_RUNTIME_EXCEPTION_INFORMATION = *mut WER_RUNTIME_EXCEPTION_INFORMATION;
/* The following struct must be kept in sync with the identically named one in
* nsExceptionHandler.h. WER will use it to communicate with the main process
* when a child process is encountered. */
#[repr(C)]
struct WindowsErrorReportingData {
child_pid: DWORD,
minidump_name: [u8; 40],
}
// This value comes from GeckoProcessTypes.h
static MAIN_PROCESS_TYPE: u32 = 0;
@@ -174,14 +161,17 @@ fn out_of_process_exception_event_callback(
}
let process = exception_information.hProcess;
let application_info = ApplicationInformation::from_process(process)?;
let process_type: u32 = (context as usize).try_into().map_err(|_| ())?;
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")

View File

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

View File

@@ -118,11 +118,6 @@ nsresult AppendAppNotesToCrashReport(const nsACString& data) {
bool GetAnnotation(const nsACString& key, nsACString& data) { return false; }
void GetAnnotation(ProcessId childPid, Annotation annotation,
nsACString& outStr) {
return;
}
nsresult RegisterAppMemory(void* ptr, size_t length) {
return NS_ERROR_NOT_IMPLEMENTED;
}
@@ -203,13 +198,27 @@ bool WriteExtraFile(const nsAString& id, const AnnotationTable& annotations) {
return false;
}
void OOPInit() {}
#if defined(MOZ_WIDGET_ANDROID)
void SetNotificationPipeForChild(FileHandle breakpadFd,
FileHandle crashHelperFd) {}
#endif // defined(MOZ_WIDGET_ANDROID)
CrashPipeType GetChildNotificationPipe() { return nullptr; }
#if defined(MOZ_WIDGET_ANDROID)
void SetCrashHelperPipes(FileHandle breakpadFd, FileHandle crashHelperFd) {}
#endif // defined(MOZ_WIDGET_ANDROID)
#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
MOZ_EXPORT ProcessId GetCrashHelperPid() { return -1; };
#endif // XP_LINUX && !defined(MOZ_WIDGET_ANDROID)
bool GetLastRunCrashID(nsAString& id) { return false; }
bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) { return false; }
bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe,
ProcessId aCrashHelperPid) {
return false;
}
bool TakeMinidumpForChild(ProcessId childPid, nsIFile** dump,
AnnotationTable& aAnnotations) {

File diff suppressed because it is too large Load Diff

View File

@@ -175,9 +175,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)>
@@ -210,26 +207,8 @@ 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
// before the platform-specific notification pipe APIs are called. If called
// from off the main thread, this method will synchronously proxy to the main
// thread.
void OOPInit();
// Return true if a dump was found for |childPid|, and return the
// path in |dump|. The caller owns the last reference to |dump| if it
// is non-nullptr. The annotations for the crash will be stored in
@@ -282,17 +261,28 @@ 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;
#endif
// Parent-side API for children
#if defined(MOZ_WIDGET_ANDROID)
void SetCrashHelperPipes(FileHandle breakpadFd, FileHandle crashHelperFd);
#endif
CrashPipeType GetChildNotificationPipe();
#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
// Return the pid of the crash helper process.
MOZ_EXPORT ProcessId GetCrashHelperPid();
#endif // XP_LINUX && !defined(MOZ_WIDGET_ANDROID)
// Child-side API
bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe);
MOZ_EXPORT bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe,
ProcessId aCrashHelperPid = 0);
bool UnsetRemoteExceptionHandler(bool wasSet = true);
} // namespace CrashReporter

View File

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

View File

@@ -71,6 +71,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"]

View File

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

View File

@@ -46,6 +46,12 @@ struct BootstrapConfig {
* When the pointer above is non-null, may indicate the directory where
* application files are, relative to the XRE. */
const char* appDataPath;
#if defined(MOZ_WIDGET_ANDROID)
/* Crash notification socket used by Breakpad. */
int crashChildNotificationSocket;
/* Crash socket used to communicate with the crash helper. */
int crashHelperSocket;
#endif
};
/**

View File

@@ -228,13 +228,16 @@ 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"};
# if !defined(MOZ_WIDGET_ANDROID)
static CommandLineArg<uint64_t> sCrashHelperPid{"-crashHelperPid",
"crashhelperpid"};
# endif // !defined(MOZ_WIDGET_ANDROID)
#else
static CommandLineArg<const char*> sCrashReporter{"-crashReporter",
"crashreporter"};
#endif
#if defined(XP_WIN)

View File

@@ -6104,6 +6104,11 @@ int XREMain::XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig) {
return NS_OK;
});
#if defined(MOZ_WIDGET_ANDROID)
CrashReporter::SetCrashHelperPipes(aConfig.crashChildNotificationSocket,
aConfig.crashHelperSocket);
#endif // defined(MOZ_WIDGET_ANDROID)
mozilla::AutoIOInterposer ioInterposerGuard;
ioInterposerGuard.Init();

View File

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

View File

@@ -33,6 +33,8 @@ struct XREShellData {
#if defined(ANDROID)
FILE* outFile;
FILE* errFile;
int crashChildNotificationSocket;
int crashHelperSocket;
#endif
#if defined(LIBFUZZER)
LibFuzzerDriver fuzzerDriver;