diff --git a/Cargo.lock b/Cargo.lock index 6a982af05ba8..3ebb72c7d3f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 50c9db1fdff6..a10ecb2377c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,10 @@ members = [ "testing/geckodriver", "toolkit/components/uniffi-bindgen-gecko-js", "toolkit/crashreporter/client/app", + "toolkit/crashreporter/crash_helper_server", + "toolkit/crashreporter/crash_helper_client", "toolkit/crashreporter/minidump-analyzer/android/export", "toolkit/crashreporter/mozwer-rust", - "toolkit/crashreporter/rust_minidump_writer_linux", "toolkit/library/gtest/rust", "toolkit/library/rust/", ] diff --git a/browser/app/macbuild/Contents/MacOS-files.in b/browser/app/macbuild/Contents/MacOS-files.in index 7517c5b42ac7..2f420f773610 100644 --- a/browser/app/macbuild/Contents/MacOS-files.in +++ b/browser/app/macbuild/Contents/MacOS-files.in @@ -13,6 +13,9 @@ #if defined(MOZ_ASAN) || defined(MOZ_TSAN) || defined(FUZZING) /llvm-symbolizer #endif +#if defined(MOZ_CRASHREPORTER) +/crashhelper +#endif /nmhproxy /pingsender /pk12util diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 8ce07e228f5e..d06952934172 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -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@ diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh index bc8f5775785e..17dfb2aece85 100755 --- a/browser/installer/windows/nsis/shared.nsh +++ b/browser/installer/windows/nsis/shared.nsh @@ -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" diff --git a/build/rust/mozbuild/generate_buildconfig.py b/build/rust/mozbuild/generate_buildconfig.py index 2a3e6437154a..42a784bbd485 100644 --- a/build/rust/mozbuild/generate_buildconfig.py +++ b/build/rust/mozbuild/generate_buildconfig.py @@ -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")) diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index c508af8cef15..34485a73c378 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -843,20 +843,8 @@ void BrowserParent::ActorDestroy(ActorDestroyReason why) { nsCOMPtr principal = GetContentPrincipal(); if (principal) { - nsAutoCString crash_reason; - CrashReporter::GetAnnotation(OtherPid(), - CrashReporter::Annotation::MozCrashReason, - crash_reason); - // FIXME(arenevier): Find a less fragile way to identify that a crash - // was caused by OOM - bool is_oom = false; - if (crash_reason == "OOM" || crash_reason == "OOM!" || - StringBeginsWith(crash_reason, "[unhandlable oom]"_ns) || - StringBeginsWith(crash_reason, "Unhandlable OOM"_ns)) { - is_oom = true; - } - - CrashReport::Deliver(principal, is_oom); + // TODO: Flag out-of-memory crashes appropriately. + CrashReport::Deliver(principal, /* aIsOOM */ false); } } } diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index 19ad76f88544..9bb8314bd98c 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -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 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(); diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp index 24b89ce1aa9a..46fcabc79530 100644 --- a/js/xpconnect/src/XPCShellImpl.cpp +++ b/js/xpconnect/src/XPCShellImpl.cpp @@ -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 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; diff --git a/mobile/android/geckoview/src/main/AndroidManifest.xml b/mobile/android/geckoview/src/main/AndroidManifest.xml index 72f3d2726f5c..ac6e718f3cde 100644 --- a/mobile/android/geckoview/src/main/AndroidManifest.xml +++ b/mobile/android/geckoview/src/main/AndroidManifest.xml @@ -87,6 +87,14 @@ android:isolatedProcess="false" android:process=":ipdlunittest"> + + diff --git a/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/crashhelper/ICrashHelper.aidl b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/crashhelper/ICrashHelper.aidl new file mode 100644 index 000000000000..f1f150b97e07 --- /dev/null +++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/crashhelper/ICrashHelper.aidl @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.crashhelper; + +interface ICrashHelper { + boolean start(in int mainProcessPid, in ParcelFileDescriptor breakpadFd, in String minidumpPath, in ParcelFileDescriptor listenFd, in ParcelFileDescriptor serverFd); +} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java index 51c034688d80..8ce3e64c8a8d 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java @@ -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); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/crashhelper/CrashHelper.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/crashhelper/CrashHelper.java new file mode 100644 index 000000000000..f1927201ef2f --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/crashhelper/CrashHelper.java @@ -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); +} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java index 03ead94beace..af279ef5bb4d 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java @@ -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(); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java index 31bd7657e901..31cbe1aa39df 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java @@ -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 { diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java index 1aa2daf5666c..da94e4608953 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java @@ -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(); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java index 5965107eefcb..f6aaeeebfaa7 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java @@ -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 cls = + (Class) 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 diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 0f4327c41acc..2c91d92d60ef 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -76,6 +76,7 @@ #endif #ifdef MOZ_CRASHREPORTER +@BINPATH@/@DLL_PREFIX@crashhelper@DLL_SUFFIX@ @BINPATH@/@DLL_PREFIX@minidump_analyzer@DLL_SUFFIX@ #endif diff --git a/mozglue/android/APKOpen.cpp b/mozglue/android/APKOpen.cpp index 08e70a090354..0e361fb6b159 100644 --- a/mozglue/android/APKOpen.cpp +++ b/mozglue/android/APKOpen.cpp @@ -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) { diff --git a/python/mozbuild/mozbuild/artifacts.py b/python/mozbuild/mozbuild/artifacts.py index c2961140b437..0c2a5c983878 100644 --- a/python/mozbuild/mozbuild/artifacts.py +++ b/python/mozbuild/mozbuild/artifacts.py @@ -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", diff --git a/security/mac/hardenedruntime/v2/developer/utility.xml b/security/mac/hardenedruntime/v2/developer/utility.xml index 305323e42a12..63802f0cbf7d 100644 --- a/security/mac/hardenedruntime/v2/developer/utility.xml +++ b/security/mac/hardenedruntime/v2/developer/utility.xml @@ -2,7 +2,8 @@ diff --git a/taskcluster/config.yml b/taskcluster/config.yml index 107d6c7a2062..37d477696f79 100644 --- a/taskcluster/config.yml +++ b/taskcluster/config.yml @@ -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" diff --git a/testing/performance/android-resource/parse_resource_usage.py b/testing/performance/android-resource/parse_resource_usage.py index b86fc6e4af4e..5811fdf4dd8e 100644 --- a/testing/performance/android-resource/parse_resource_usage.py +++ b/testing/performance/android-resource/parse_resource_usage.py @@ -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. diff --git a/toolkit/crashreporter/bionic_missing_funcs.cpp b/toolkit/crashreporter/bionic_missing_funcs.cpp index 81e8bef303b7..2c9daea50f38 100644 --- a/toolkit/crashreporter/bionic_missing_funcs.cpp +++ b/toolkit/crashreporter/bionic_missing_funcs.cpp @@ -12,14 +12,29 @@ extern "C" { +#if defined(__ANDROID_API__) && (__ANDROID_API__ < 28) + +// Bionic introduced support for syncfs only in version 28 (that is +// Android Pie / 9). Since GeckoView is built with version 21, those functions +// aren't defined, but nix needs them and the crash helper relies on nix. These +// functions should never be called in practice hence we implement them only to +// satisfy nix linking requirements but we crash if we accidentally enter them. + +int syncfs(int fd) { + MOZ_CRASH("syncfs() is not available"); + return EPERM; +} + +#endif // __ANDROID_API__ && (__ANDROID_API__ < 28) + #if defined(__ANDROID_API__) && (__ANDROID_API__ < 24) // Bionic introduced support for getgrgid_r() and getgrnam_r() only in version // 24 (that is Android Nougat / 7.0). Since GeckoView is built with version 21, -// those functions aren't defined, but nix needs them and minidump-writer -// relies on nix. These functions should never be called in practice hence we -// implement them only to satisfy nix linking requirements but we crash if we -// accidentally enter them. +// those functions aren't defined, but the nix crate needs them and +// minidump-writer relies on nix. These functions should never be called in +// practice hence we implement them only to satisfy nix linking requirements +// but we crash if we accidentally enter them. int getgrgid_r(gid_t gid, struct group* grp, char* buf, size_t buflen, struct group** result) { diff --git a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc index 911a7e6ac212..936f0b73e8da 100644 --- a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc +++ b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc @@ -62,15 +62,18 @@ namespace google_breakpad { CrashGenerationServer::CrashGenerationServer( const int listen_fd, #if defined(MOZ_OXIDIZED_BREAKPAD) - std::function get_auxv_info, + std::function get_auxv_info, #endif // defined(MOZ_OXIDIZED_BREAKPAD) std::function 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; diff --git a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h index 04abdeec58c0..9a9086c246e8 100644 --- a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h +++ b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h @@ -51,24 +51,31 @@ 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 get_auxv_info, + std::function get_auxv_info, #endif // defined(MOZ_OXIDIZED_BREAKPAD) - std::function dump_callback, - const string* dump_path); + std::function 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 get_auxv_info_; + std::function get_auxv_info_; #endif // defined(MOZ_OXIDIZED_BREAKPAD) std::function dump_callback_; + void* dump_context_; + pthread_mutex_t dump_dir_mutex_; string dump_dir_; bool started_; diff --git a/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.cc index 8cbc012caa12..c2b7481524c6 100644 --- a/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.cc +++ b/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.cc @@ -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, diff --git a/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.h b/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.h index d0b39f3acf8e..339ef21bdab6 100644 --- a/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.h +++ b/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.h @@ -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_; diff --git a/toolkit/crashreporter/breakpad-client/moz.build b/toolkit/crashreporter/breakpad-client/moz.build index 9ee42e41364d..89a0a9d98bc4 100644 --- a/toolkit/crashreporter/breakpad-client/moz.build +++ b/toolkit/crashreporter/breakpad-client/moz.build @@ -8,8 +8,6 @@ SOURCES += [ 'minidump_file_writer.cc', ] -Library('breakpad_client') - USE_LIBS += [ 'breakpad_common_s', ] @@ -19,15 +17,19 @@ if CONFIG['OS_ARCH'] == 'Darwin': 'breakpad_mac_common_s', ] elif CONFIG['OS_ARCH'] == 'Linux': + UNIFIED_SOURCES += [ + '../linux_utils.cc', + ] + USE_LIBS += [ 'breakpad_linux_common_s', ] -FINAL_LIBRARY = 'xul' - LOCAL_INCLUDES += [ '/toolkit/crashreporter/google-breakpad/src', ] if CONFIG['CC_TYPE'] in ('clang', 'gcc'): CXXFLAGS += ['-Wno-error=stack-protector'] + +Library('breakpad_client') diff --git a/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.cc index 964a42f9db13..c178f7869b45 100644 --- a/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.cc +++ b/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.cc @@ -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, diff --git a/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.h b/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.h index cc1912cf3c1a..f1da86389acc 100644 --- a/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.h +++ b/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.h @@ -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 diff --git a/toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp b/toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp new file mode 100644 index 000000000000..7c51756b7a35 --- /dev/null +++ b/toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp @@ -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 + +#if defined(XP_LINUX) +# include +# include +# include "linux/crash_generation/client_info.h" +# include "linux/crash_generation/crash_generation_server.h" +using breakpad_char = char; +using breakpad_string = std::string; +using breakpad_init_type = int; +using breakpad_pid = pid_t; +#elif defined(XP_WIN) +# include "windows/crash_generation/client_info.h" +# include "windows/crash_generation/crash_generation_server.h" +using breakpad_char = wchar_t; +using breakpad_string = std::wstring; +using breakpad_init_type = wchar_t*; +using breakpad_pid = DWORD; +#elif defined(XP_MACOSX) +# include +# include +# include "mac/crash_generation/client_info.h" +# include "mac/crash_generation/crash_generation_server.h" +using breakpad_char = char; +using breakpad_string = std::string; +using breakpad_init_type = const char*; +using breakpad_pid = pid_t; +#else +# error "Unsupported platform" +#endif + +#ifdef MOZ_PHC + +# include "PHC.h" + +namespace mozilla::phc { + +// HACK: The breakpad code expects this global variable even though we don't +// use it in the wrapper. +MOZ_RUNINIT mozilla::phc::AddrInfo gAddrInfo; + +} // namespace mozilla::phc + +#endif // defined(MOZ_PHC) + +using google_breakpad::ClientInfo; +using google_breakpad::CrashGenerationServer; + +// This struct and the callback that uses it need to be kept in sync with the +// corresponding Rust code in src/crash_generation.rs. +struct BreakpadProcessId { + breakpad_pid pid; +#if defined(XP_MACOSX) + task_t task; +#elif defined(XP_WIN) + HANDLE handle; +#endif +}; + +using 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(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(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(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(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(aServer); + delete server; +} + +extern "C" void CrashGenerationServer_set_path( + void* aServer, const breakpad_char* aMinidumpPath) { + CrashGenerationServer* server = static_cast(aServer); + server->SetPath(aMinidumpPath); +} diff --git a/toolkit/crashreporter/breakpad_wrapper/moz.build b/toolkit/crashreporter/breakpad_wrapper/moz.build new file mode 100644 index 000000000000..372c5b4cbba7 --- /dev/null +++ b/toolkit/crashreporter/breakpad_wrapper/moz.build @@ -0,0 +1,37 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Library("breakpad_wrapper") + +if CONFIG["MOZ_PHC"]: + DEFINES["MOZ_PHC"] = True + +SOURCES = [ + "breakpad_wrapper.cpp", +] + +LOCAL_INCLUDES += [ + "../breakpad-client/", + "../google-breakpad/src/", +] + +if CONFIG["OS_ARCH"] == "WINNT": + USE_LIBS += [ + "google_breakpad_libxul_s", + ] + + OS_LIBS += [ + "advapi32", + "bcrypt", + "dbghelp", + "ntdll", + "userenv", + "ws2_32", + ] +else: + USE_LIBS += [ + "breakpad_client", + ] diff --git a/toolkit/crashreporter/crash_annotations.rs.in b/toolkit/crashreporter/crash_annotations.rs.in new file mode 100644 index 000000000000..caac4650a57d --- /dev/null +++ b/toolkit/crashreporter/crash_annotations.rs.in @@ -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 + } +} diff --git a/toolkit/crashreporter/crash_helper/crashhelper.cpp b/toolkit/crashreporter/crash_helper/crashhelper.cpp new file mode 100644 index 000000000000..88f635a9b65f --- /dev/null +++ b/toolkit/crashreporter/crash_helper/crashhelper.cpp @@ -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 + +#if defined(XP_WIN) +# include // 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(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(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); +} diff --git a/toolkit/crashreporter/crash_helper/crashhelper_android.cpp b/toolkit/crashreporter/crash_helper/crashhelper_android.cpp new file mode 100644 index 000000000000..e0e79f60c2c6 --- /dev/null +++ b/toolkit/crashreporter/crash_helper/crashhelper_android.cpp @@ -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 + +#include +#include +#include + +// 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); +} diff --git a/toolkit/crashreporter/crash_helper/moz.build b/toolkit/crashreporter/crash_helper/moz.build new file mode 100644 index 000000000000..d5af66b076f9 --- /dev/null +++ b/toolkit/crashreporter/crash_helper/moz.build @@ -0,0 +1,42 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +LOCAL_INCLUDES = [ + "/toolkit/crashreporter", +] + +USE_LIBS += [ + "breakpad_wrapper", + "crash_helper_server", +] + +if CONFIG["OS_TARGET"] == "Android": + UNIFIED_SOURCES = [ + "../bionic_missing_funcs.cpp", + "crashhelper_android.cpp", + ] + + GeckoSharedLibrary("crashhelper", linkage=None) +else: + if CONFIG["OS_ARCH"] == "Darwin": + OS_LIBS += [ + "-framework Foundation", + ] + elif CONFIG["OS_ARCH"] == "WINNT": + OS_LIBS += [ + "advapi32", + "bcrypt", + "dbghelp", + "ntdll", + "userenv", + "ws2_32", + ] + + UNIFIED_SOURCES += [ + "crashhelper.cpp", + ] + + GeckoProgram("crashhelper", linkage=None) diff --git a/toolkit/crashreporter/crash_helper_client/Cargo.toml b/toolkit/crashreporter/crash_helper_client/Cargo.toml new file mode 100644 index 000000000000..71d46cc6115c --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/Cargo.toml @@ -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 diff --git a/toolkit/crashreporter/mozannotation_server/cbindgen.toml b/toolkit/crashreporter/crash_helper_client/cbindgen.toml similarity index 61% rename from toolkit/crashreporter/mozannotation_server/cbindgen.toml rename to toolkit/crashreporter/crash_helper_client/cbindgen.toml index 532919626274..49839c0813d4 100644 --- a/toolkit/crashreporter/mozannotation_server/cbindgen.toml +++ b/toolkit/crashreporter/crash_helper_client/cbindgen.toml @@ -8,8 +8,14 @@ braces = "SameLine" line_length = 100 tab_width = 2 language = "C++" -include_guard = "mozannotation_server_ffi_generated_h" -includes = ["nsString.h", "nsTArrayForwardDeclare.h"] +include_guard = "crash_helper_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"] diff --git a/toolkit/crashreporter/mozannotation_server/moz.build b/toolkit/crashreporter/crash_helper_client/moz.build similarity index 58% rename from toolkit/crashreporter/mozannotation_server/moz.build rename to toolkit/crashreporter/crash_helper_client/moz.build index 21762b95b197..4eb427592e77 100644 --- a/toolkit/crashreporter/mozannotation_server/moz.build +++ b/toolkit/crashreporter/crash_helper_client/moz.build @@ -7,11 +7,11 @@ if CONFIG["COMPILE_ENVIRONMENT"]: # This tells mach to run cbindgen and that this header-file should be created CbindgenHeader( - "mozannotation_server_ffi_generated.h", - inputs=["/toolkit/crashreporter/mozannotation_server"], + "crash_helper_client_ffi_generated.h", + inputs=["/toolkit/crashreporter/crash_helper_client"], ) - # This tells mach to copy that generated file to obj/dist/include/mozilla/toolkit/crashreporter - EXPORTS.mozilla.toolkit.crashreporter += [ - "!mozannotation_server_ffi_generated.h", + # This tells mach to copy that generated file to obj/dist/mozilla + EXPORTS.mozilla += [ + "!crash_helper_client_ffi_generated.h", ] diff --git a/toolkit/crashreporter/crash_helper_client/src/lib.rs b/toolkit/crashreporter/crash_helper_client/src/lib.rs new file mode 100644 index 000000000000..c8cb63461d73 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/lib.rs @@ -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 { + let message = messages::TransferMinidump::new(pid); + self.connector.send_message(&message)?; + + // HACK: Workaround for a macOS-specific bug + #[cfg(target_os = "macos")] + self.connector.poll(nix::poll::PollFlags::POLLIN)?; + + let reply = self + .connector + .recv_reply::()?; + + if reply.path.is_empty() { + // TODO: We should return Result> instead of + // this. Semantics would be better once we interact with Rust + bail!("Minidump for pid {pid:} was not found"); + } + + Ok(CrashReport { + 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 = ::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 = ::from_raw(crash_report.path); + + if !crash_report.error.is_null() { + let _error = CString::from_raw(crash_report.error); + } +} + +/// Report an exception to the crash manager that has been captured outside of +/// Firefox processes, typically within the Windows Error Reporting runtime +/// exception module. +/// +/// # Safety +/// +/// The `exception_record_ptr` and `context_ptr` parameters must point to valid +/// objects of the corresponding types. +#[cfg(target_os = "windows")] +pub unsafe fn report_external_exception( + main_process_pid: Pid, + pid: Pid, + thread: Pid, // TODO: This should be a different type, but it's the same on Windows + exception_record_ptr: *mut EXCEPTION_RECORD, + context_ptr: *mut CONTEXT, +) { + let exception_records = collect_exception_records(exception_record_ptr); + let context = unsafe { context_ptr.read() }; + let message = + messages::WindowsErrorReportingMinidump::new(pid, thread, exception_records, context); + + // In the code below we connect to the crash helper, send our message and wait for a reply before returning, but we ignore errors because we can't do anything about them in the calling code + if let Ok(connector) = IPCConnector::connect(main_process_pid) { + let _ = connector + .send_message(&message) + .and_then(|_| connector.recv_reply::()); + } +} + +// Collect a linked-list of exception records and turn it into a vector +#[cfg(target_os = "windows")] +fn collect_exception_records( + mut exception_record_ptr: *mut EXCEPTION_RECORD, +) -> Vec { + let mut exception_records = Vec::::with_capacity(1); + loop { + if exception_record_ptr.is_null() { + return exception_records; + } + + let mut exception_record = unsafe { exception_record_ptr.read() }; + exception_record_ptr = exception_record.ExceptionRecord; + exception_record.ExceptionRecord = null_mut(); + exception_records.push(exception_record); + } +} + +/// 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() +} diff --git a/toolkit/crashreporter/crash_helper_client/src/platform.rs b/toolkit/crashreporter/crash_helper_client/src/platform.rs new file mode 100644 index 000000000000..a5f387457e30 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform.rs @@ -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; diff --git a/toolkit/crashreporter/crash_helper_client/src/platform/android.rs b/toolkit/crashreporter/crash_helper_client/src/platform/android.rs new file mode 100644 index 000000000000..65f003f5704f --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform/android.rs @@ -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 { + // 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 }) + } +} diff --git a/toolkit/crashreporter/crash_helper_client/src/platform/unix.rs b/toolkit/crashreporter/crash_helper_client/src/platform/unix.rs new file mode 100644 index 000000000000..d40723fa890e --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform/unix.rs @@ -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 { + 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 { + 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()), + } + } +} diff --git a/toolkit/crashreporter/crash_helper_client/src/platform/windows.rs b/toolkit/crashreporter/crash_helper_client/src/platform/windows.rs new file mode 100644 index 000000000000..62a0616f5426 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform/windows.rs @@ -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 { + // SAFETY: `program` points to a valid string passed in by Firefox + let program = unsafe { ::from_ptr(program) }; + // SAFETY: `minidump_path` points to a valid string passed in by Firefox + let minidump_path = unsafe { ::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 = cmd_line.encode_wide().collect(); + + let mut pi = unsafe { zeroed::() }; + let si = STARTUPINFOW { + cb: size_of::().try_into().unwrap(), + ..unsafe { zeroed() } + }; + + let res = unsafe { + CreateProcessW( + /* 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 = arg.encode_wide().collect(); + let mut escaped_arg = Vec::::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) +} diff --git a/toolkit/crashreporter/crash_helper_common/Cargo.toml b/toolkit/crashreporter/crash_helper_common/Cargo.toml index aaaa632579e4..47220478660c 100644 --- a/toolkit/crashreporter/crash_helper_common/Cargo.toml +++ b/toolkit/crashreporter/crash_helper_common/Cargo.toml @@ -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", diff --git a/toolkit/crashreporter/crash_helper_common/src/messages.rs b/toolkit/crashreporter/crash_helper_common/src/messages.rs index f4bb25a67ac2..935a251b546c 100644 --- a/toolkit/crashreporter/crash_helper_common/src/messages.rs +++ b/toolkit/crashreporter/crash_helper_common/src/messages.rs @@ -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::() + (size_of::() * 4) + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +impl Message for RegisterAuxvInfo { + fn kind() -> Kind { + Kind::RegisterAuxvInfo + } + + fn header(&self) -> Vec { + Header { + kind: Self::kind(), + size: self.payload_size(), + } + .encode() + } + + fn payload(&self) -> Vec { + 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 { + None + } + + fn decode( + data: &[u8], + ancillary_data: Option, + ) -> Result { + debug_assert!( + ancillary_data.is_none(), + "RegisterAuxvInfo messages cannot carry ancillary data" + ); + + let bytes: [u8; size_of::()] = data[0..size_of::()].try_into()?; + let pid = Pid::from_ne_bytes(bytes); + let offset = size_of::(); + + let bytes: [u8; size_of::()] = + data[offset..(offset + size_of::())].try_into()?; + let program_header_count = AuxvType::from_ne_bytes(bytes); + let offset = offset + size_of::(); + + let bytes: [u8; size_of::()] = + data[offset..(offset + size_of::())].try_into()?; + let program_header_address = AuxvType::from_ne_bytes(bytes); + let offset = offset + size_of::(); + + let bytes: [u8; size_of::()] = + data[offset..(offset + size_of::())].try_into()?; + let linux_gate_address = AuxvType::from_ne_bytes(bytes); + let offset = offset + size_of::(); + + let bytes: [u8; size_of::()] = + data[offset..(offset + size_of::())].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::() + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +impl Message for UnregisterAuxvInfo { + fn kind() -> Kind { + Kind::UnregisterAuxvInfo + } + + fn header(&self) -> Vec { + Header { + kind: Self::kind(), + size: self.payload_size(), + } + .encode() + } + + fn payload(&self) -> Vec { + 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 { + None + } + + fn decode( + data: &[u8], + ancillary_data: Option, + ) -> Result { + debug_assert!( + ancillary_data.is_none(), + "UnregisterAuxvInfo messages cannot carry ancillary data" + ); + + let bytes: [u8; size_of::()] = data[0..size_of::()].try_into()?; + let pid = Pid::from_ne_bytes(bytes); + + Ok(UnregisterAuxvInfo { pid }) + } +} diff --git a/toolkit/crashreporter/crash_helper_server/Cargo.toml b/toolkit/crashreporter/crash_helper_server/Cargo.toml new file mode 100644 index 000000000000..3bb95cc9f7d3 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "crash_helper_server" +version = "0.1.0" +authors = ["Gabriele Svelto "] +edition = "2018" + +[dependencies] +anyhow = "1" +cfg-if = "1" +crash_helper_common = { path = "../crash_helper_common" } +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" diff --git a/toolkit/crashreporter/crash_helper_server/build.rs b/toolkit/crashreporter/crash_helper_server/build.rs new file mode 100644 index 000000000000..82957d588db0 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/build.rs @@ -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, + skip_if: Option, +} + +impl Annotation { + fn new(key: &Yaml, value: &Yaml) -> Result { + let name = key.as_str().ok_or(Error::other("Not a string"))?; + let raw_type = value["type"].as_str().ok_or(Error::other("Missing type"))?; + let type_string = Annotation::type_str_to_value(raw_type)?; + let altname = value["altname"].as_str().map(str::to_owned); + // We don't care about the contents of the `ping` field for the time being + let skip_if = value["skip_if"].as_str().map(str::to_owned); + + Ok(Annotation { + name: name.to_owned(), + type_string, + altname, + skip_if, + }) + } + + fn type_str_to_value(raw_type: &str) -> Result { + match raw_type { + "string" => Ok("String"), + "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, Error> { + let raw_annotations = doc.as_hash().ok_or(Error::other( + "Invalid data in YAML file, expected a hash".to_string(), + ))?; + + let mut annotations = Vec::::new(); + for annotation in raw_annotations { + let annotation = Annotation::new(annotation.0, annotation.1)?; + annotations.push(annotation); + } + + annotations.sort_by(|a, b| { + let a_lower = a.name.to_ascii_lowercase(); + let b_lower = b.name.to_ascii_lowercase(); + a_lower.cmp(&b_lower) + }); + + Ok(annotations) +} + +fn generate_annotation_enum(annotations: &[Annotation]) -> Result { + let mut annotations_enum = String::new(); + + for (index, value) in annotations.iter().enumerate() { + let entry = format!(" {} = {index:},\n", value.name); + annotations_enum.push_str(&entry); + } + + let count = format!(" Count = {},", annotations.len()); + annotations_enum.push_str(&count); + + Ok(annotations_enum) +} + +fn generate_annotation_types(annotations: &[Annotation]) -> Result { + let mut types_array = String::new(); + for value in annotations.iter() { + let entry = format!(" CrashAnnotationType::{},\n", value.type_string); + types_array.push_str(&entry); + } + + // Pop the last newline + types_array.pop(); + + Ok(types_array) +} + +fn generate_annotation_names(annotations: &[Annotation]) -> Result { + let mut names_array = String::new(); + for value in annotations.iter() { + let name = value.altname.as_ref().unwrap_or(&value.name); + let entry = format!(" \"{}\",\n", name); + names_array.push_str(&entry); + } + + // Pop the last newline + names_array.pop(); + + Ok(names_array) +} + +fn generate_annotation_skiplist(annotations: &[Annotation]) -> Result { + let mut skiplist = String::new(); + for annotation in annotations.iter() { + if let Some(skip_if) = &annotation.skip_if { + let entry = format!( + " CrashAnnotationSkipValue {{ annotation: CrashAnnotation::{}, value: b\"{}\" }},\n", + &annotation.name, skip_if + ); + skiplist.push_str(&entry); + } + } + + // Pop the last newline + skiplist.pop(); + + Ok(skiplist) +} + +// Generate Rust code to manipulate crash annotations +fn generate_annotations() -> Result<(), Error> { + const CRASH_ANNOTATIONS_YAML: &str = "../CrashAnnotations.yaml"; + const CRASH_ANNOTATIONS_TEMPLATE: &str = "../crash_annotations.rs.in"; + + let out_dir = env::var("OUT_DIR").unwrap(); + let annotations_path = Path::new(&out_dir).join("crash_annotations.rs"); + let mut annotations_file = File::create(annotations_path)?; + + let template = fs::read_to_string(CRASH_ANNOTATIONS_TEMPLATE)?; + let yaml_str = fs::read_to_string(CRASH_ANNOTATIONS_YAML)?; + let yaml_doc = YamlLoader::load_from_str(&yaml_str) + .map_err(|e| Error::other(format!("Failed to parse YAML file: {}", e)))?; + + let doc = &yaml_doc[0]; + let annotations = read_annotations(doc)?; + + let annotations_enum = generate_annotation_enum(&annotations)?; + let annotations_types = generate_annotation_types(&annotations)?; + let annotations_names = generate_annotation_names(&annotations)?; + let skiplist = generate_annotation_skiplist(&annotations)?; + + let compiled_template = template + .replace("${enum}", &annotations_enum) + .replace("${types}", &annotations_types) + .replace("${names}", &annotations_names) + .replace("${skiplist}", &skiplist); + write!(&mut annotations_file, "{}", compiled_template)?; + + println!("cargo:rerun-if-changed={CRASH_ANNOTATIONS_YAML}"); + println!("cargo:rerun-if-changed={CRASH_ANNOTATIONS_TEMPLATE}"); + + Ok(()) +} diff --git a/toolkit/crashreporter/crash_helper_server/cbindgen.toml b/toolkit/crashreporter/crash_helper_server/cbindgen.toml new file mode 100644 index 000000000000..5fd914767a04 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/cbindgen.toml @@ -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"] diff --git a/toolkit/crashreporter/crash_helper_server/moz.build b/toolkit/crashreporter/crash_helper_server/moz.build new file mode 100644 index 000000000000..798a6ed812c1 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/moz.build @@ -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", + ] diff --git a/toolkit/crashreporter/crash_helper_server/src/breakpad_crash_generator.rs b/toolkit/crashreporter/crash_helper_server/src/breakpad_crash_generator.rs new file mode 100644 index 000000000000..793c40e8f85b --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/breakpad_crash_generator.rs @@ -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, + path: NonNull, + #[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 { + 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 = ::from_raw(self.path.as_ptr()); + } + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/crash_generation.rs b/toolkit/crashreporter/crash_helper_server/src/crash_generation.rs new file mode 100644 index 000000000000..6a3d7770fa54 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/crash_generation.rs @@ -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, +} + +impl CrashReport { + fn new(path: &OsStr, error: &Option) -> CrashReport { + CrashReport { + path: path.to_owned(), + error: error.to_owned(), + } + } +} + +// Table holding all the crash reports we've generated. It's indexed by PID and +// new crash reports are insterted in the corresponding vector in order of +// arrival. When crashes are retrieved they're similarly pulled out in the +// order they've arrived. +static CRASH_REPORTS: Lazy>>> = Lazy::new(Default::default); + +// 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>> = 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 { + 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, + pid: Pid, + ) -> Result>> { + 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 { ::from_ptr(minidump_path_ptr) }); + let error = if !error_ptr.is_null() { + // SAFETY: The string is a valid C string we passed in ourselves. + Some(unsafe { CStr::from_ptr(error_ptr) }.to_owned()) + } else { + None + }; + + finalize_crash_report(process_id, error, &minidump_path, MinidumpOrigin::Breakpad); +} + +fn finalize_crash_report( + process_id: BreakpadProcessId, + error: Option, + minidump_path: &Path, + origin: MinidumpOrigin, +) { + let mut extra_path = PathBuf::from(minidump_path); + extra_path.set_extension("extra"); + + let annotations = retrieve_annotations(&process_id, origin); + let extra_file_written = annotations + .map(|annotations| write_extra_file(&annotations, &extra_path)) + .is_ok(); + + let path = minidump_path.as_os_str(); + let error = if !extra_file_written { + Some(CString::new("MissingAnnotations").unwrap()) + } else { + error + }; + + let map = &mut CRASH_REPORTS.lock().unwrap(); + let entry = map.entry(process_id.pid); + entry + .and_modify(|entry| entry.push(CrashReport::new(path, &error))) + .or_insert_with(|| vec![CrashReport::new(path, &error)]); +} + +#[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> { + #[cfg(target_os = "windows")] + let res = mozannotation_server::retrieve_annotations( + process_id.handle, + CrashAnnotation::Count as usize, + ); + #[cfg(any(target_os = "linux", target_os = "android"))] + let res = + mozannotation_server::retrieve_annotations(process_id.pid, CrashAnnotation::Count as usize); + #[cfg(target_os = "macos")] + let res = mozannotation_server::retrieve_annotations( + process_id.task, + CrashAnnotation::Count as usize, + ); + + let mut annotations = res?; + if origin == MinidumpOrigin::WindowsErrorReporting { + annotations.push(CAnnotation { + id: CrashAnnotation::WindowsErrorReporting as u32, + data: AnnotationData::ByteBuffer(vec![1]), + }); + } + + Ok(annotations) +} + +fn write_extra_file(annotations: &Vec, path: &Path) -> Result<()> { + let mut annotations_written: usize = 0; + let mut file = File::create(path)?; + write!(&mut file, "{{")?; + + for annotation in annotations { + if let Some(annotation_id) = CrashAnnotation::from_u32(annotation.id) { + if annotation_id == CrashAnnotation::PHCBaseAddress { + if let AnnotationData::ByteBuffer(buff) = &annotation.data { + write_phc_annotations(&mut file, buff)?; + } + + continue; + } + + let value = match type_of_annotation(annotation_id) { + CrashAnnotationType::String => match &annotation.data { + AnnotationData::String(string) => Some(escape_value(string.as_bytes())), + AnnotationData::ByteBuffer(buffer) => Some(escape_value(buffer)), + _ => None, + }, + CrashAnnotationType::Boolean => { + if let AnnotationData::ByteBuffer(buff) = &annotation.data { + if buff.len() == 1 { + Some(vec![if buff[0] != 0 { b'1' } else { b'0' }]) + } else { + None + } + } else { + None + } + } + CrashAnnotationType::U32 => { + read_numeric_annotation!(u32, &annotation.data) + } + CrashAnnotationType::U64 => { + read_numeric_annotation!(u64, &annotation.data) + } + CrashAnnotationType::USize => { + read_numeric_annotation!(usize, &annotation.data) + } + 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 { + let mut escaped = Vec::::with_capacity(input.len() + 2); + for &c in input { + if c <= 0x1f || c == b'\\' || c == b'"' { + escaped.extend(b"\\u00"); + escaped.push(hex_digit_as_ascii_char((c & 0x00f0) >> 4)); + escaped.push(hex_digit_as_ascii_char(c & 0x000f)); + } else { + escaped.push(c) + } + } + + escaped +} + +fn hex_digit_as_ascii_char(value: u8) -> u8 { + if value < 10 { + b'0' + value + } else { + b'a' + (value - 10) + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/crash_generation/windows.rs b/toolkit/crashreporter/crash_helper_server/src/crash_generation/windows.rs new file mode 100644 index 000000000000..391412551f05 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/crash_generation/windows.rs @@ -0,0 +1,166 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::{finalize_crash_report, BreakpadProcessId, CrashGenerator}; + +use crash_helper_common::{messages, Pid}; +use std::{ + convert::TryInto, + fs::{create_dir_all, File}, + mem::{size_of, zeroed}, + os::windows::io::AsRawHandle, + path::PathBuf, + ptr::{null, null_mut}, +}; +use uuid::Uuid; +use windows_sys::Win32::{ + Foundation::{FALSE, HANDLE}, + System::{ + Diagnostics::Debug::{ + MiniDumpWithFullMemoryInfo, MiniDumpWithIndirectlyReferencedMemory, + MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules, MiniDumpWriteDump, + EXCEPTION_POINTERS, EXCEPTION_RECORD, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE, + }, + SystemInformation::{ + VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION, + VER_MINORVERSION, VER_SERVICEPACKMAJOR, VER_SERVICEPACKMINOR, + }, + SystemServices::VER_GREATER_EQUAL, + Threading::{OpenProcess, PROCESS_ALL_ACCESS}, + }, +}; + +impl CrashGenerator { + pub(super) fn generate_wer_minidump( + &self, + message: messages::WindowsErrorReportingMinidump, + ) -> Result<(), ()> { + let (minidump_file, path) = self.create_minidump_file()?; + + let minidump_type: MINIDUMP_TYPE = self.get_minidump_type(); + let mut context = message.context; + let mut exception_records = message.exception_records; + let exception_records_ptr = link_exception_records(&mut exception_records); + + let handle = open_process(message.pid)?; + let mut exception_pointers = EXCEPTION_POINTERS { + ExceptionRecord: exception_records_ptr, + ContextRecord: &mut context as *mut _, + }; + + let exception = MINIDUMP_EXCEPTION_INFORMATION { + ThreadId: message.tid, + ExceptionPointers: &mut exception_pointers, + ClientPointers: FALSE, + }; + + let res = unsafe { + MiniDumpWriteDump( + handle, + message.pid, + minidump_file.as_raw_handle() as _, + minidump_type, + &exception, + /* UserStreamParam */ null(), + /* CallbackParam */ null(), + ) + }; + + if res != FALSE { + let process_id = BreakpadProcessId { + pid: message.pid, + handle, + }; + + finalize_crash_report( + process_id, + None, + &path, + super::MinidumpOrigin::WindowsErrorReporting, + ); + } + + Ok(()) + } + + fn get_minidump_type(&self) -> MINIDUMP_TYPE { + let mut minidump_type = MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules; + if 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) -> *mut EXCEPTION_RECORD { + let mut iter = exception_records.iter_mut().peekable(); + while let Some(exception_record) = iter.next() { + exception_record.ExceptionRecord = null_mut(); + + if let Some(next) = iter.peek_mut() { + exception_record.ExceptionRecord = *next as *mut _; + } + } + + if exception_records.is_empty() { + null_mut() + } else { + exception_records.as_mut_ptr() + } +} + +fn is_windows8_or_later() -> bool { + let mut info = OSVERSIONINFOEXW { + dwOSVersionInfoSize: size_of::().try_into().unwrap(), + dwMajorVersion: 6, + dwMinorVersion: 2, + ..unsafe { zeroed() } + }; + + unsafe { + let mut mask: u64 = 0; + let ge: u8 = VER_GREATER_EQUAL.try_into().unwrap(); + mask = VerSetConditionMask(mask, VER_MAJORVERSION, ge); + mask = VerSetConditionMask(mask, VER_MINORVERSION, ge); + mask = VerSetConditionMask(mask, VER_SERVICEPACKMAJOR, ge); + mask = VerSetConditionMask(mask, VER_SERVICEPACKMINOR, ge); + + VerifyVersionInfoW( + &mut info, + VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, + mask, + ) != 0 + } +} + +fn open_process(pid: Pid) -> Result { + // SAFETY: No pointers involved, worst case we get an error + match unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) } { + 0 => Err(()), + handle => Ok(handle), + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/ipc_server.rs b/toolkit/crashreporter/crash_helper_server/src/ipc_server.rs new file mode 100644 index 000000000000..bc58b75e3706 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/ipc_server.rs @@ -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, + 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 { + 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(()) + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/lib.rs b/toolkit/crashreporter/crash_helper_server/src/lib.rs new file mode 100644 index 000000000000..b51a152c02a5 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/lib.rs @@ -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 + } + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/phc.rs b/toolkit/crashreporter/crash_helper_server/src/phc.rs new file mode 100644 index 000000000000..c5da6a204724 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/phc.rs @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// The types here must match the ones in memory/build/PHC.h + +use anyhow::{bail, Result}; +use std::{ + ffi::{c_char, c_void}, + mem::{size_of, MaybeUninit}, + slice, +}; + +#[repr(C)] +#[derive(Clone, Copy, PartialEq)] +#[allow(dead_code)] +pub(crate) enum Kind { + Unknown = 0, + NeverAllocatedPage = 1, + InUsePage = 2, + FreedPage = 3, + GuardPage = 4, +} + +const MAX_FRAMES: usize = 16; + +#[repr(C)] +pub(crate) struct StackTrace { + pub(crate) length: usize, + pub(crate) pcs: [*const c_void; MAX_FRAMES], + pub(crate) has_stack: c_char, +} + +#[repr(C)] +pub(crate) struct AddrInfo { + pub(crate) kind: Kind, + pub(crate) base_addr: *const c_void, + pub(crate) usable_size: usize, + pub(crate) alloc_stack: StackTrace, + pub(crate) free_stack: StackTrace, + pub(crate) phc_was_locked: c_char, +} + +impl AddrInfo { + pub(crate) fn from_bytes(buff: &[u8]) -> Result { + if buff.len() != size_of::() { + bail!( + "PHC AddrInfo structure size {} doesn't match expected size {}", + buff.len(), + size_of::() + ); + } + + let mut addr_info = MaybeUninit::::uninit(); + // SAFETY: MaybeUninit is always valid, even for padding bytes + let uninit_addr_info = unsafe { + slice::from_raw_parts_mut( + addr_info.as_mut_ptr() as *mut MaybeUninit, + size_of::(), + ) + }; + + for (index, &value) in buff.iter().enumerate() { + uninit_addr_info[index].write(value); + } + + let addr_info = unsafe { addr_info.assume_init() }; + if !addr_info.check_consistency() { + bail!("PHC AddrInfo structure is inconsistent"); + } + + Ok(addr_info) + } + + pub(crate) fn kind_as_str(&self) -> &'static str { + match self.kind { + Kind::Unknown => "Unknown(?!)", + Kind::NeverAllocatedPage => "NeverAllocatedPage", + Kind::InUsePage => "InUsePage(?!)", + Kind::FreedPage => "FreedPage", + Kind::GuardPage => "GuardPage", + } + } + + fn check_consistency(&self) -> bool { + let kind_value = self.kind as u32; + + if (kind_value > Kind::GuardPage as u32) + || (self.alloc_stack.length > MAX_FRAMES) + || (self.free_stack.length > MAX_FRAMES) + || (self.alloc_stack.has_stack > 1) + || (self.free_stack.has_stack > 1) + || (self.phc_was_locked > 1) + { + return false; + } + + true + } +} diff --git a/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc b/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc index 701a4f070f28..aaf5feb4aa07 100644 --- a/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc +++ b/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc @@ -46,6 +46,7 @@ #include "common/linux/linux_libc_support.h" #include "common/linux/memory_mapped_file.h" #include "common/using_std_string.h" +#include "google_breakpad/common/minidump_format.h" #include "third_party/lss/linux_syscall_support.h" namespace google_breakpad { diff --git a/toolkit/crashreporter/moz.build b/toolkit/crashreporter/moz.build index cc42e69fe542..66a13bece4dc 100644 --- a/toolkit/crashreporter/moz.build +++ b/toolkit/crashreporter/moz.build @@ -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", diff --git a/toolkit/crashreporter/mozannotation_client/cbindgen.toml b/toolkit/crashreporter/mozannotation_client/cbindgen.toml index 63f85bd4f4c5..6802953fc5f7 100644 --- a/toolkit/crashreporter/mozannotation_client/cbindgen.toml +++ b/toolkit/crashreporter/mozannotation_client/cbindgen.toml @@ -11,7 +11,5 @@ language = "C++" include_guard = "mozannotation_client_ffi_generated_h" includes = ["nsStringFwd.h"] -[export.rename] -"ThinVec" = "nsTArray" [export] exclude = ["nsCString"] diff --git a/toolkit/crashreporter/mozannotation_server/Cargo.toml b/toolkit/crashreporter/mozannotation_server/Cargo.toml index bd3fb283bf17..11bc257189e0 100644 --- a/toolkit/crashreporter/mozannotation_server/Cargo.toml +++ b/toolkit/crashreporter/mozannotation_server/Cargo.toml @@ -10,8 +10,7 @@ license = "MPL-2.0" [dependencies] mozannotation_client = { path = "../mozannotation_client/" } process_reader = { path = "../process_reader/" } -thin-vec = { version = "0.2.7", features = ["gecko-ffi"] } -thiserror = "1.0.38" +thiserror = "2" [target."cfg(any(target_os = \"linux\", target_os = \"android\"))".dependencies] -memoffset = "0.8" +memoffset = "0.9" diff --git a/toolkit/crashreporter/mozannotation_server/src/lib.rs b/toolkit/crashreporter/mozannotation_server/src/lib.rs index b814ad0e506e..3fef0dcf5e13 100644 --- a/toolkit/crashreporter/mozannotation_server/src/lib.rs +++ b/toolkit/crashreporter/mozannotation_server/src/lib.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -mod errors; +pub mod errors; use crate::errors::*; #[cfg(any(target_os = "linux", target_os = "android"))] @@ -16,20 +16,15 @@ use mozannotation_client::{Annotation, AnnotationContents, AnnotationMutex}; #[cfg(any(target_os = "linux", target_os = "android"))] use mozannotation_client::{MozAnnotationNote, ANNOTATION_NOTE_NAME, ANNOTATION_TYPE}; use std::cmp::min; -use std::iter::FromIterator; +use std::ffi::CString; use std::mem::{size_of, ManuallyDrop}; -use std::ptr::null_mut; -use thin_vec::ThinVec; -#[repr(C)] -#[derive(Debug)] pub enum AnnotationData { Empty, - ByteBuffer(ThinVec), + ByteBuffer(Vec), + String(CString), } -#[repr(C)] -#[derive(Debug)] pub struct CAnnotation { #[allow(dead_code)] // This is implicitly stored to and used externally pub id: u32, @@ -38,39 +33,10 @@ pub struct CAnnotation { pub type ProcessHandle = process_reader::ProcessHandle; -/// Return the annotations of a given process. -/// -/// This function will be exposed to C++ -#[no_mangle] -pub extern "C" fn mozannotation_retrieve( - process: usize, - max_annotations: usize, -) -> *mut ThinVec { - let result = retrieve_annotations(process as _, max_annotations); - match result { - // Leak the object as it will be owned by the C++ code from now on - Ok(annotations) => Box::into_raw(annotations) as *mut _, - Err(_) => null_mut(), - } -} - -/// Free the annotations returned by `mozannotation_retrieve()`. -/// -/// # Safety -/// -/// `ptr` must contain the value returned by a call to -/// `mozannotation_retrieve()` and be called only once. -#[no_mangle] -pub unsafe extern "C" fn mozannotation_free(ptr: *mut ThinVec) { - // The annotation vector will be automatically destroyed when the contents - // of this box are automatically dropped at the end of the function. - let _box = Box::from_raw(ptr); -} - pub fn retrieve_annotations( process: ProcessHandle, max_annotations: usize, -) -> Result>, AnnotationsRetrievalError> { +) -> Result, AnnotationsRetrievalError> { let reader = ProcessReader::new(process)?; let address = find_annotations(&reader)?; @@ -91,7 +57,7 @@ pub fn retrieve_annotations( let vec_pointer = annotation_table.get_ptr(); let length = annotation_table.len(); - let mut annotations = ThinVec::::with_capacity(min(max_annotations, length)); + let mut annotations = Vec::::with_capacity(min(max_annotations, length)); for i in 0..length { let annotation_address = unsafe { vec_pointer.add(i) }; @@ -100,7 +66,7 @@ pub fn retrieve_annotations( } } - Ok(Box::new(annotations)) + Ok(annotations) } fn find_annotations(reader: &ProcessReader) -> Result { @@ -159,19 +125,19 @@ fn read_annotation( AnnotationContents::Empty => {} AnnotationContents::NSCStringPointer => { let string = copy_nscstring(reader, raw_annotation.address)?; - annotation.data = AnnotationData::ByteBuffer(string); + annotation.data = AnnotationData::String(string); } AnnotationContents::CStringPointer => { let string = copy_null_terminated_string_pointer(reader, raw_annotation.address)?; - annotation.data = AnnotationData::ByteBuffer(string); + annotation.data = AnnotationData::String(string); } AnnotationContents::CString => { - let string = copy_null_terminated_string(reader, raw_annotation.address)?; - annotation.data = AnnotationData::ByteBuffer(string); + annotation.data = + AnnotationData::String(reader.copy_null_terminated_string(raw_annotation.address)?); } AnnotationContents::ByteBuffer(size) | AnnotationContents::OwnedByteBuffer(size) => { - let string = copy_bytebuffer(reader, raw_annotation.address, size)?; - annotation.data = AnnotationData::ByteBuffer(string); + let buffer = copy_bytebuffer(reader, raw_annotation.address, size)?; + annotation.data = AnnotationData::ByteBuffer(buffer); } }; @@ -181,23 +147,15 @@ fn read_annotation( fn copy_null_terminated_string_pointer( reader: &ProcessReader, address: usize, -) -> Result, process_reader::error::ReadError> { +) -> Result { let buffer_address = reader.copy_object::(address)?; - copy_null_terminated_string(reader, buffer_address) -} - -fn copy_null_terminated_string( - reader: &ProcessReader, - address: usize, -) -> Result, process_reader::error::ReadError> { - let string = reader.copy_null_terminated_string(address)?; - Ok(ThinVec::::from(string.as_bytes())) + reader.copy_null_terminated_string(buffer_address) } fn copy_nscstring( reader: &ProcessReader, address: usize, -) -> Result, process_reader::error::ReadError> { +) -> Result { // HACK: This assumes the layout of the nsCString object let length_address = address + size_of::(); let length = reader.copy_object::(length_address)?; @@ -214,9 +172,11 @@ fn copy_nscstring( vec.truncate(nul_byte_pos); } - Ok(ThinVec::from(vec)) + // SAFETY: This is safe because we verified that there are no nul + // characters inside the string. + Ok(unsafe { CString::from_vec_unchecked(vec) }) } else { - Ok(ThinVec::::new()) + Ok(CString::default()) } } @@ -224,7 +184,6 @@ fn copy_bytebuffer( reader: &ProcessReader, address: usize, size: u32, -) -> Result, process_reader::error::ReadError> { - let value = reader.copy_array::(address, size as _)?; - Ok(ThinVec::::from_iter(value.into_iter())) +) -> Result, process_reader::error::ReadError> { + reader.copy_array::(address, size as _) } diff --git a/toolkit/crashreporter/mozwer-rust/Cargo.toml b/toolkit/crashreporter/mozwer-rust/Cargo.toml index 89cf3d43561b..fe86aea692c7 100644 --- a/toolkit/crashreporter/mozwer-rust/Cargo.toml +++ b/toolkit/crashreporter/mozwer-rust/Cargo.toml @@ -6,11 +6,11 @@ edition = "2018" license = "MPL-2.0" [dependencies] +crash_helper_client = { path = "../crash_helper_client" } libc = "0.2.0" mozilla-central-workspace-hack = { version = "0.1", features = [ "mozwer_s", ], optional = true } -process_reader = { path = "../process_reader/" } rust-ini = "0.10" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } @@ -22,6 +22,7 @@ features = [ "Wdk_System_Threading", "Win32_Foundation", "Win32_Security", + "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_ErrorReporting", @@ -29,7 +30,6 @@ features = [ "Win32_System_ProcessStatus", "Win32_System_SystemInformation", "Win32_System_SystemServices", - "Win32_System_Threading", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", ] diff --git a/toolkit/crashreporter/mozwer-rust/lib.rs b/toolkit/crashreporter/mozwer-rust/lib.rs index 61fc89446229..4579350a35f8 100644 --- a/toolkit/crashreporter/mozwer-rust/lib.rs +++ b/toolkit/crashreporter/mozwer-rust/lib.rs @@ -2,20 +2,20 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crash_helper_client::report_external_exception; use ini::Ini; use libc::time; -use process_reader::ProcessReader; use serde::Serialize; use serde_json::ser::to_writer; use std::convert::TryInto; use std::ffi::{c_void, OsString}; use std::fs::{read_to_string, DirBuilder, File, OpenOptions}; use std::io::{BufRead, BufReader, Write}; -use std::mem::{size_of, transmute, zeroed}; +use std::mem::{size_of, zeroed}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; -use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; +use std::os::windows::io::AsRawHandle; use std::path::{Path, PathBuf}; -use std::ptr::{addr_of, null, null_mut}; +use std::ptr::{null, null_mut}; use std::slice::from_raw_parts; use uuid::Uuid; use windows_sys::core::{HRESULT, PWSTR}; @@ -24,7 +24,7 @@ use windows_sys::Win32::{ Foundation::{ CloseHandle, GetLastError, SetLastError, BOOL, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS, EXCEPTION_BREAKPOINT, E_UNEXPECTED, FALSE, FILETIME, HANDLE, HWND, LPARAM, MAX_PATH, - STATUS_SUCCESS, S_OK, TRUE, WAIT_OBJECT_0, + STATUS_SUCCESS, S_OK, TRUE, }, Security::{ GetSidSubAuthority, GetSidSubAuthorityCount, GetTokenInformation, IsTokenRestricted, @@ -34,12 +34,9 @@ use windows_sys::Win32::{ System::Diagnostics::Debug::{ GetThreadContext, MiniDumpWithFullMemoryInfo, MiniDumpWithIndirectlyReferencedMemory, MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules, MiniDumpWriteDump, - WriteProcessMemory, EXCEPTION_POINTERS, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE, + EXCEPTION_POINTERS, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE, }, System::ErrorReporting::WER_RUNTIME_EXCEPTION_INFORMATION, - System::Memory::{ - VirtualAllocEx, VirtualFreeEx, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, - }, System::ProcessStatus::K32GetModuleFileNameExW, System::SystemInformation::{ VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION, @@ -47,9 +44,8 @@ use windows_sys::Win32::{ }, System::SystemServices::{SECURITY_MANDATORY_MEDIUM_RID, VER_GREATER_EQUAL}, System::Threading::{ - CreateProcessW, CreateRemoteThread, GetProcessId, GetProcessTimes, GetThreadId, - OpenProcess, OpenProcessToken, OpenThread, TerminateProcess, WaitForSingleObject, - CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, LPTHREAD_START_ROUTINE, + CreateProcessW, GetProcessId, GetProcessTimes, GetThreadId, OpenProcess, OpenProcessToken, + OpenThread, TerminateProcess, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, NORMAL_PRIORITY_CLASS, PROCESS_ALL_ACCESS, PROCESS_BASIC_INFORMATION, PROCESS_INFORMATION, STARTUPINFOW, THREAD_GET_CONTEXT, }, @@ -67,15 +63,6 @@ type PDWORD = *mut DWORD; #[allow(non_camel_case_types)] type PWER_RUNTIME_EXCEPTION_INFORMATION = *mut WER_RUNTIME_EXCEPTION_INFORMATION; -/* The following struct must be kept in sync with the identically named one in - * nsExceptionHandler.h. WER will use it to communicate with the main process - * when a child process is encountered. */ -#[repr(C)] -struct WindowsErrorReportingData { - child_pid: DWORD, - minidump_name: [u8; 40], -} - // This value comes from GeckoProcessTypes.h static MAIN_PROCESS_TYPE: u32 = 0; @@ -174,14 +161,17 @@ fn out_of_process_exception_event_callback( } let process = exception_information.hProcess; - let application_info = ApplicationInformation::from_process(process)?; let process_type: u32 = (context as usize).try_into().map_err(|_| ())?; - let startup_time = get_startup_time(process)?; - let crash_report = CrashReport::new(&application_info, startup_time, is_ui_hang); - crash_report.write_minidump(exception_information)?; if process_type == MAIN_PROCESS_TYPE { match is_sandboxed_process(process) { - Ok(false) => handle_main_process_crash(crash_report, &application_info), + Ok(false) => { + let application_info = ApplicationInformation::from_process(process)?; + let startup_time = get_startup_time(process)?; + let crash_report = CrashReport::new(&application_info, startup_time, is_ui_hang); + crash_report.write_minidump(exception_information)?; + + handle_main_process_crash(crash_report, &application_info) + } _ => { // The parent process should never be sandboxed, bail out so the // process which is impersonating it gets killed right away. Also @@ -190,7 +180,7 @@ fn out_of_process_exception_event_callback( } } } else { - handle_child_process_crash(crash_report, process) + handle_child_process_crash(exception_information) } } @@ -245,87 +235,24 @@ fn handle_main_process_crash( Ok(()) } -fn handle_child_process_crash(crash_report: CrashReport, child_process: HANDLE) -> Result<()> { - let parent_process = get_parent_process(child_process)?; - let process_reader = ProcessReader::new(parent_process).map_err(|_e| ())?; - let libxul_address = process_reader.find_module("xul.dll").map_err(|_e| ())?; - let wer_notify_proc = process_reader - .find_section(libxul_address, b"mozwerpt") - .map_err(|_e| ())?; - let wer_notify_proc = unsafe { transmute::<_, LPTHREAD_START_ROUTINE>(wer_notify_proc) }; - - let wer_data = WindowsErrorReportingData { - child_pid: get_process_id(child_process)?, - minidump_name: crash_report.get_minidump_name(), - }; - let address = copy_object_into_process(parent_process, wer_data)?; - notify_main_process(parent_process, wer_notify_proc, address) -} - -fn copy_object_into_process(process: HANDLE, data: T) -> Result<*mut T> { - let address = unsafe { - VirtualAllocEx( - process, - null(), - size_of::(), - MEM_RESERVE | MEM_COMMIT, - PAGE_READWRITE, - ) - }; - - if address.is_null() { - return Err(()); - } - - let res = unsafe { - WriteProcessMemory( - process, - address, - addr_of!(data) as *const _, - size_of::(), - null_mut(), - ) - }; - - if res == 0 { - unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) }; - Err(()) - } else { - Ok(address as *mut T) - } -} - -fn notify_main_process( - process: HANDLE, - wer_notify_proc: LPTHREAD_START_ROUTINE, - address: *mut WindowsErrorReportingData, +fn handle_child_process_crash( + exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, ) -> Result<()> { - let thread = unsafe { - CreateRemoteThread( - process, - null_mut(), - 0, - wer_notify_proc, - address as LPVOID, - 0, - null_mut(), - ) - }; + let process = unsafe { (*exception_information).hProcess }; + let process_id = get_process_id(process)?; + let thread = unsafe { (*exception_information).hThread }; + let thread_id = get_thread_id(thread)?; + let parent_process = get_parent_process(process)?; + let parent_pid = get_process_id(parent_process)?; - if thread == 0 { - unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) }; - return Err(()); - } - - // From this point on the memory pointed to by address is owned by the - // thread we've created in the main process, so we don't free it. - - let thread = unsafe { OwnedHandle::from_raw_handle(thread as RawHandle) }; - - // Don't wait forever as we want the process to get killed eventually - let res = unsafe { WaitForSingleObject(thread.as_raw_handle() as HANDLE, 5000) }; - if res != WAIT_OBJECT_0 { - return Err(()); + unsafe { + report_external_exception( + parent_pid, + process_id, + thread_id, + &raw mut (*exception_information).exceptionRecord, + &raw mut (*exception_information).context, + ); } Ok(()) @@ -366,6 +293,13 @@ fn get_process_id(process: HANDLE) -> Result { } } +fn get_thread_id(thread: HANDLE) -> Result { + match unsafe { GetThreadId(thread) } { + 0 => Err(()), + tid => Ok(tid), + } +} + fn get_process_handle(pid: DWORD) -> Result { let handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) }; if handle != 0 { @@ -693,11 +627,6 @@ impl CrashReport { self.get_pending_path().join(self.uuid.to_string() + ".dmp") } - fn get_minidump_name(&self) -> [u8; 40] { - let bytes = (self.uuid.to_string() + ".dmp").into_bytes(); - bytes[0..40].try_into().unwrap() - } - fn get_extra_file_path(&self) -> PathBuf { self.get_pending_path() .join(self.uuid.to_string() + ".extra") diff --git a/toolkit/crashreporter/mozwer-rust/moz.build b/toolkit/crashreporter/mozwer-rust/moz.build index 18c8039e6896..098a191914c3 100644 --- a/toolkit/crashreporter/mozwer-rust/moz.build +++ b/toolkit/crashreporter/mozwer-rust/moz.build @@ -1,11 +1,11 @@ RustLibrary("mozwer_s") OS_LIBS += [ + "advapi32", "dbghelp", "kernel32", "ntdll", "ole32", - "psapi", "shell32", "user32", "userenv", diff --git a/toolkit/crashreporter/nsDummyExceptionHandler.cpp b/toolkit/crashreporter/nsDummyExceptionHandler.cpp index 73afbc5b6d0a..06f14b1ee8f7 100644 --- a/toolkit/crashreporter/nsDummyExceptionHandler.cpp +++ b/toolkit/crashreporter/nsDummyExceptionHandler.cpp @@ -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) { diff --git a/toolkit/crashreporter/nsExceptionHandler.cpp b/toolkit/crashreporter/nsExceptionHandler.cpp index b575d1f70cae..1add5be9eb94 100644 --- a/toolkit/crashreporter/nsExceptionHandler.cpp +++ b/toolkit/crashreporter/nsExceptionHandler.cpp @@ -13,21 +13,19 @@ #include "nsComponentManagerUtils.h" #include "nsDirectoryServiceDefs.h" #include "nsDirectoryService.h" +#include "nsIFileStreams.h" +#include "nsNetUtil.h" #include "nsString.h" -#include "nsTHashMap.h" #include "mozilla/ArrayUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/EnumeratedRange.h" #include "mozilla/Services.h" #include "nsIObserverService.h" #include "mozilla/Unused.h" -#include "mozilla/UniquePtr.h" -#include "mozilla/Printf.h" #include "mozilla/RuntimeExceptionModule.h" #include "mozilla/ScopeExit.h" #include "mozilla/Sprintf.h" #include "mozilla/StaticPrefs_browser.h" -#include "mozilla/StaticMutex.h" #include "mozilla/SyncRunnable.h" #include "mozilla/ToString.h" #include "mozilla/TimeStamp.h" @@ -37,12 +35,11 @@ #include "nsThreadUtils.h" #include "nsThread.h" #include "jsfriendapi.h" -#include "private/pprio.h" #include "base/process_util.h" #include "common/basictypes.h" #include "mozilla/toolkit/crashreporter/mozannotation_client_ffi_generated.h" -#include "mozilla/toolkit/crashreporter/mozannotation_server_ffi_generated.h" +#include "mozilla/crash_helper_client_ffi_generated.h" #ifdef MOZ_BACKGROUNDTASKS # include "mozilla/BackgroundTasks.h" @@ -84,12 +81,16 @@ # include "mac_utils.h" #elif defined(XP_LINUX) # include "nsIINIParser.h" +# if defined(MOZ_WIDGET_ANDROID) +# include "common/linux/eintr_wrapper.h" +# else +# include // For prctl() and PR_SET_PTRACER +# endif // defined(MOZ_WIDGET_ANDROID) # include "common/linux/linux_libc_support.h" # include "third_party/lss/linux_syscall_support.h" # include "breakpad-client/linux/crash_generation/client_info.h" # include "breakpad-client/linux/crash_generation/crash_generation_server.h" # include "breakpad-client/linux/handler/exception_handler.h" -# include "common/linux/eintr_wrapper.h" # include # include # include "sys/sysinfo.h" @@ -119,7 +120,6 @@ #include #include "mozilla/Mutex.h" #include "nsDebug.h" -#include "nsCRT.h" #include "nsIFile.h" #include "mozilla/IOInterposer.h" @@ -147,6 +147,8 @@ using google_breakpad::PageAllocator; #endif using namespace mozilla; +#ifdef MOZ_PHC + namespace mozilla::phc { // Global instance that is retrieved by the process generating the crash report @@ -154,6 +156,8 @@ MOZ_GLOBINIT mozilla::phc::AddrInfo gAddrInfo; } // namespace mozilla::phc +#endif // defined(MOZ_PHC) + namespace CrashReporter { #ifdef XP_WIN @@ -164,6 +168,7 @@ typedef std::wstring xpstring; # define XP_STRLEN(x) wcslen(x) # define my_strlen strlen # define my_memchr memchr +# define CRASH_HELPER_FILENAME u"crashhelper.exe"_ns # define CRASH_REPORTER_FILENAME u"crashreporter.exe"_ns # define XP_PATH_SEPARATOR L"\\" # define XP_PATH_SEPARATOR_CHAR L'\\' @@ -177,6 +182,7 @@ typedef char XP_CHAR; typedef std::string xpstring; # define XP_TEXT(x) x # define CONVERT_XP_CHAR_TO_UTF16(x) NS_ConvertUTF8toUTF16(x) +# define CRASH_HELPER_FILENAME u"crashhelper"_ns # define CRASH_REPORTER_FILENAME u"crashreporter"_ns # define XP_PATH_SEPARATOR "/" # define XP_PATH_SEPARATOR_CHAR '/' @@ -207,51 +213,6 @@ typedef std::string xpstring; # define MAYBE_UNUSED #endif // defined(__GNUC__) -#if defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) -class ChildProcessAuxvStore { - public: - static ChildProcessAuxvStore& global() { - static ChildProcessAuxvStore instance; - return instance; - } - void Add(pid_t aChildPid, const DirectAuxvDumpInfo& aAuxvInfo) { - std::lock_guard lock(mMutex); - mMap.emplace(aChildPid, aAuxvInfo); - } - void Remove(pid_t aChildPid) { - std::lock_guard lock(mMutex); - mMap.erase(aChildPid); - } - bool Get(pid_t aChildPid, DirectAuxvDumpInfo* aAuxvInfo) { - std::lock_guard lock(mMutex); - auto entry = mMap.find(aChildPid); - if (entry == mMap.end()) { - return false; - } - *aAuxvInfo = entry->second; - return true; - } - - private: - std::mutex mMutex; - std::unordered_map mMap; -}; - -void GetCurrentProcessAuxvInfo(DirectAuxvDumpInfo* aAuxvInfo) { - aAuxvInfo->program_header_count = getauxval(AT_PHNUM); - aAuxvInfo->program_header_address = getauxval(AT_PHDR); - aAuxvInfo->linux_gate_address = getauxval(AT_SYSINFO_EHDR); - aAuxvInfo->entry_address = getauxval(AT_ENTRY); -} -void RegisterChildAuxvInfo(pid_t aChildPid, - const DirectAuxvDumpInfo& aAuxvInfo) { - ChildProcessAuxvStore::global().Add(aChildPid, aAuxvInfo); -} -void UnregisterChildAuxvInfo(pid_t aChildPid) { - ChildProcessAuxvStore::global().Remove(aChildPid); -} -#endif // defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) - #ifndef XP_LINUX static const XP_CHAR dumpFileExtension[] = XP_TEXT(".dmp"); #endif @@ -262,12 +223,14 @@ MOZ_RUNINIT static std::optional defaultMemoryReportPath = {}; static const char kCrashMainID[] = "crash.main.3\n"; +static CrashHelperClient* gCrashHelperClient = nullptr; static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr; static mozilla::Atomic gEncounteredChildException(false); MOZ_CONSTINIT static nsCString gServerURL; MOZ_RUNINIT static xpstring pendingDirectory; MOZ_RUNINIT static xpstring crashReporterPath; +MOZ_RUNINIT static xpstring crashHelperPath; MOZ_RUNINIT static xpstring memoryReportPath; // Where crash events should go. @@ -295,6 +258,8 @@ static char* androidUserSerial = nullptr; static const char* androidStartServiceCommand = nullptr; #endif +static ProcessId gCrashHelperPid = 0; + // this holds additional data sent via the API static Mutex* notesFieldLock; static nsCString* notesField = nullptr; @@ -317,33 +282,20 @@ static bool isSafeToDump = false; // Whether to include heap regions of the crash context. static bool sIncludeContextHeap = false; -// OOP crash reporting -static CrashGenerationServer* crashServer; // chrome process has this - static std::terminate_handler oldTerminateHandler = nullptr; #if defined(XP_WIN) || defined(XP_MACOSX) -static char* childCrashNotifyPipe; +MOZ_RUNINIT static nsCString childCrashNotifyPipe; #elif defined(XP_LINUX) static int serverSocketFd = -1; static int clientSocketFd = -1; - +# if defined(MOZ_WIDGET_ANDROID) +static int crashHelperClientFd = -1; +# endif // defined(MOZ_WIDGET_ANDROID) #endif -// |dumpMapLock| must protect all access to |pidToMinidump|. -static Mutex* dumpMapLock; -struct ChildProcessData : public nsUint32HashKey { - explicit ChildProcessData(KeyTypePointer aKey) - : nsUint32HashKey(aKey), annotations(nullptr) {} - - nsCOMPtr minidump; - UniquePtr annotations; -}; - -typedef nsTHashtable ChildMinidumpMap; -static ChildMinidumpMap* pidToMinidump; -static bool OOPInitialized(); +static void OOPInit(); void RecordMainThreadId() { gMainThreadId = @@ -782,7 +734,7 @@ static void PHCStackTraceToString(char* aBuffer, size_t aBufferLen, strcat(aBuffer, ","); } XP_STOA(uintptr_t(aStack.mPcs[i]), addrString); - strncat(aBuffer, addrString, aBufferLen); + strncat(aBuffer, addrString, aBufferLen - 1); } } @@ -839,55 +791,6 @@ static void WritePHCAddrInfo(AnnotationWriter& writer, } } -static void PopulatePHCStackTraceAnnotation( - AnnotationTable& aAnnotations, const Annotation aName, - const Maybe& aStack) { - if (aStack.isNothing()) { - return; - } - - char addrsString[phcStringifiedAnnotationSize]; - PHCStackTraceToString(addrsString, sizeof(addrsString), *aStack); - aAnnotations[aName] = addrsString; -} - -static void PopulatePHCAnnotations(AnnotationTable& aAnnotations, - const phc::AddrInfo* aAddrInfo) { - // Is this a PHC allocation needing special treatment? - if (aAddrInfo && aAddrInfo->mKind != phc::AddrInfo::Kind::Unknown) { - const char* kindString; - switch (aAddrInfo->mKind) { - case phc::AddrInfo::Kind::Unknown: - kindString = "Unknown(?!)"; - break; - case phc::AddrInfo::Kind::NeverAllocatedPage: - kindString = "NeverAllocatedPage"; - break; - case phc::AddrInfo::Kind::InUsePage: - kindString = "InUsePage(?!)"; - break; - case phc::AddrInfo::Kind::FreedPage: - kindString = "FreedPage"; - break; - case phc::AddrInfo::Kind::GuardPage: - kindString = "GuardPage"; - break; - default: - kindString = "Unmatched(?!)"; - break; - } - - aAnnotations[Annotation::PHCKind] = kindString; - aAnnotations[Annotation::PHCBaseAddress] = - nsPrintfCString("%zu", uintptr_t(aAddrInfo->mBaseAddr)); - aAnnotations[Annotation::PHCUsableSize] = - nsPrintfCString("%zu", aAddrInfo->mUsableSize); - PopulatePHCStackTraceAnnotation(aAnnotations, Annotation::PHCAllocStack, - aAddrInfo->mAllocStack); - PopulatePHCStackTraceAnnotation(aAnnotations, Annotation::PHCFreeStack, - aAddrInfo->mFreeStack); - } -} #endif /** @@ -1813,7 +1716,13 @@ static void PrepareForMinidump() { # if defined(DEBUG) && defined(HAS_DLL_BLOCKLIST) DllBlocklist_Shutdown(); # endif -#endif // XP_WIN +#elif defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) + if (gCrashHelperPid) { + // Ignore the return value because we're in the exception handler, so + // there's not much we can do safely, not even log the error. + Unused << prctl(PR_SET_PTRACER, gCrashHelperPid); + } +#endif } #ifdef XP_WIN @@ -1889,22 +1798,6 @@ static bool ChildFilter(void* context) { #endif // !defined(XP_WIN) -static bool ChildMinidumpCallback( -#if defined(XP_WIN) - const wchar_t* dump_path, const wchar_t* minidump_id, -#elif defined(XP_LINUX) - const MinidumpDescriptor& descriptor, -#else // defined(XP_MACOSX) - const char* dump_dir, const char* minidump_id, -#endif - void* context, -#if defined(XP_WIN) - EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, -#endif // defined(XP_WIN) - const mozilla::phc::AddrInfo* addr_info, bool succeeded) { - return succeeded; -} - static bool ShouldReport() { // this environment variable prevents us from launching // the crash reporter client @@ -1934,10 +1827,12 @@ static nsresult LocateExecutable(nsIFile* aXREDirectory, const nsAString& aName, NS_ENSURE_SUCCESS(rv, rv); # ifdef XP_MACOSX - exePath->SetNativeLeafName("MacOS"_ns); - exePath->Append(u"crashreporter.app"_ns); - exePath->Append(u"Contents"_ns); - exePath->Append(u"MacOS"_ns); + if (aName.Equals(CRASH_REPORTER_FILENAME)) { + exePath->SetNativeLeafName("MacOS"_ns); + exePath->Append(u"crashreporter.app"_ns); + exePath->Append(u"Contents"_ns); + exePath->Append(u"MacOS"_ns); + } # endif exePath->Append(aName); @@ -2029,8 +1924,16 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) { if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + // Locate the crash helper executable + PathString crashHelperPath_temp; + rv = LocateExecutable(aXREDirectory, CRASH_HELPER_FILENAME, + crashHelperPath_temp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } crashReporterPath = crashReporterPath_temp.get(); + crashHelperPath = crashHelperPath_temp.get(); #else // On Android, we launch a service defined via MOZ_ANDROID_CRASH_HANDLER const char* androidCrashHandler = PR_GetEnv("MOZ_ANDROID_CRASH_HANDLER"); @@ -2050,6 +1953,10 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) { androidStartServiceCommand = (char*)"startservice"; } } + + const char* crashHelperPathEnv = PR_GetEnv("MOZ_ANDROID_PACKAGE_NAME"); + MOZ_ASSERT(crashHelperPathEnv, "The application package name is required"); + crashHelperPath = crashHelperPathEnv; #endif // !defined(MOZ_WIDGET_ANDROID) // get temp path to use for minidump path @@ -2161,6 +2068,8 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) { oldTerminateHandler = std::set_terminate(&TerminateHandler); + OOPInit(); + return NS_OK; } @@ -2179,17 +2088,33 @@ bool GetMinidumpPath(nsAString& aPath) { } nsresult SetMinidumpPath(const nsAString& aPath) { - if (!gExceptionHandler) return NS_ERROR_NOT_INITIALIZED; + if (!gExceptionHandler) { + return NS_ERROR_NOT_INITIALIZED; + } + AutoPathString path; #ifdef XP_WIN - gExceptionHandler->set_dump_path( - std::wstring(char16ptr_t(aPath.BeginReading()))); + path = aPath; +#else + path = NS_ConvertUTF16toUTF8(aPath); +#endif + + // Set the path for the in-process exception handler +#ifdef XP_WIN + gExceptionHandler->set_dump_path(std::wstring(path.get())); #elif defined(XP_LINUX) gExceptionHandler->set_minidump_descriptor( - MinidumpDescriptor(NS_ConvertUTF16toUTF8(aPath).BeginReading())); + MinidumpDescriptor(path.BeginReading())); #else - gExceptionHandler->set_dump_path(NS_ConvertUTF16toUTF8(aPath).BeginReading()); + gExceptionHandler->set_dump_path(path.BeginReading()); #endif + + // Set the path used by the crash helper for out-of-process crash generation + if (gCrashHelperClient) { + set_crash_report_path(gCrashHelperClient, + (const BreakpadChar*)path.BeginReading()); + } + return NS_OK; } @@ -2943,15 +2868,36 @@ void SetProfileDirectory(nsIFile* aDir) { SetCrashEventsDir(dir); } -void SetUserAppDataDirectory(nsIFile* aDir) { - nsCOMPtr dir; - aDir->Clone(getter_AddRefs(dir)); +static void PopulatePendingDir(nsIFile* aUserAppDataDir) { + if (!pendingDirectory.empty()) { + return; + } - dir->Append(u"Crash Reports"_ns); - EnsureDirectoryExists(dir); - dir->Append(u"events"_ns); - EnsureDirectoryExists(dir); - SetCrashEventsDir(dir); + nsCOMPtr pendingDir; + aUserAppDataDir->Clone(getter_AddRefs(pendingDir)); + pendingDir->Append(u"Crash Reports"_ns); + pendingDir->Append(u"pending"_ns); + + PathString path; +#ifdef XP_WIN + pendingDir->GetPath(path); +#else + pendingDir->GetNativePath(path); +#endif + pendingDirectory = xpstring(path.get()); +} + +void SetUserAppDataDirectory(nsIFile* aDir) { + nsCOMPtr eventsDir; + aDir->Clone(getter_AddRefs(eventsDir)); + + eventsDir->Append(u"Crash Reports"_ns); + EnsureDirectoryExists(eventsDir); + eventsDir->Append(u"events"_ns); + EnsureDirectoryExists(eventsDir); + SetCrashEventsDir(eventsDir); + + PopulatePendingDir(aDir); } void UpdateCrashEventsDir() { @@ -3024,36 +2970,10 @@ nsresult GetDefaultMemoryReportFile(nsIFile** aFile) { return NS_OK; } -static void FindPendingDir() { - if (!pendingDirectory.empty()) { - return; - } - nsCOMPtr pendingDir; - nsresult rv = - NS_GetSpecialDirectory(XRE_USER_APP_DATA_DIR, getter_AddRefs(pendingDir)); - if (NS_FAILED(rv)) { - NS_WARNING( - "Couldn't get the user appdata directory, crash dumps will go in an " - "unusual location"); - } else { - pendingDir->Append(u"Crash Reports"_ns); - pendingDir->Append(u"pending"_ns); - - PathString path; -#ifdef XP_WIN - pendingDir->GetPath(path); -#else - pendingDir->GetNativePath(path); -#endif - pendingDirectory = xpstring(path.get()); - } -} - // The "pending" dir is Crash Reports/pending, from which minidumps // can be submitted. Because this method may be called off the main thread, // we store the pending directory as a path. static bool GetPendingDir(nsIFile** dir) { - // MOZ_ASSERT(OOPInitialized()); if (pendingDirectory.empty()) { return false; } @@ -3167,6 +3087,34 @@ bool GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile) { return true; } +static nsresult ReadExtraFile(nsCOMPtr& aFile, + AnnotationTable& aAnnotations) { + const int64_t kExtraFileMaxSize = 1024 * 1024 * 1024; + int64_t fileSize; + + nsresult rv = aFile->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + // Reject humongous extra files, Socorro will discard them anyway + NS_ENSURE_TRUE((fileSize > 0) && (fileSize < kExtraFileMaxSize), + NS_ERROR_OUT_OF_MEMORY); + nsTArray buffer((size_t)rv); + + nsCOMPtr stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFile); + NS_ENSURE_SUCCESS(rv, rv); + nsCString json; + rv = NS_ReadInputStreamToString(stream, json, fileSize); + NS_ENSURE_SUCCESS(rv, rv); + auto annotations = ExtraFileParser::Parse(json); + + if (!annotations) { + return NS_ERROR_FAILURE; + } + + aAnnotations = *annotations; + return NS_OK; +} + static bool WriteExtraFile(PlatformWriter& pw, const AnnotationTable& aAnnotations) { if (!pw.Valid()) { @@ -3287,82 +3235,6 @@ static void AddSharedAnnotations(AnnotationTable& aAnnotations) { AddCommonAnnotations(aAnnotations); } -static void AddChildProcessAnnotations( - AnnotationTable& aAnnotations, nsTArray* aChildAnnotations) { - if (!aChildAnnotations) { - // TODO: We should probably make a list of errors that occurred when - // generating a crash report as more than one can occurr. - aAnnotations[Annotation::DumperError] = "MissingAnnotations"; - return; - } - - for (const auto& annotation : *aChildAnnotations) { - Annotation id = static_cast(annotation.id); - const AnnotationData& data = annotation.data; - - if ((id == Annotation::PHCBaseAddress) && - (data.tag == AnnotationData::Tag::ByteBuffer)) { - // PHC is special for now, let's deal with it here -#ifdef MOZ_PHC - const auto& buffer = data.byte_buffer._0; - alignas(mozilla::phc::AddrInfo) char mem[sizeof(mozilla::phc::AddrInfo)]; - memcpy(mem, buffer.Elements(), sizeof(mozilla::phc::AddrInfo)); - const auto* addr_info = - reinterpret_cast(mem); - PopulatePHCAnnotations(aAnnotations, addr_info); -#endif - continue; - } - - if (data.tag == AnnotationData::Tag::Empty) { - continue; - } - - nsAutoCString value; - const uint8_t* buffer = data.byte_buffer._0.Elements(); - const size_t length = data.byte_buffer._0.Length(); - - switch (TypeOfAnnotation(id)) { - case AnnotationType::String: - value.Assign(reinterpret_cast(buffer), length); - break; - case AnnotationType::Boolean: - if (length == sizeof(bool)) { - value.Assign(*reinterpret_cast(buffer) ? "1" : "0"); - } - break; - case AnnotationType::U32: - if (length == sizeof(uint32_t)) { - value.AppendInt(*reinterpret_cast(buffer)); - } - break; - case AnnotationType::U64: - if (length == sizeof(uint64_t)) { - value.AppendInt(*reinterpret_cast(buffer)); - } - break; - case AnnotationType::USize: - if (length == sizeof(size_t)) { -#ifdef XP_MACOSX - // macOS defines size_t as unsigned long, which causes ambiguity - // when it comes to function overload, use a 64-bit integer instead - value.AppendInt(*reinterpret_cast(buffer)); -#else - value.AppendInt(*reinterpret_cast(buffer)); -#endif - } - break; - case AnnotationType::Object: - // Object annotations are only produced later by minidump-analyzer. - break; - } - - if (!value.IsEmpty() && ShouldIncludeAnnotation(id, value.get())) { - aAnnotations[id] = value; - } - } -} - // It really only makes sense to call this function when // ShouldReport() is true. // Uses dumpFile's filename to generate memoryReport's filename (same name @@ -3370,7 +3242,9 @@ static void AddChildProcessAnnotations( static bool MoveToPending(nsIFile* dumpFile, nsIFile* extraFile, nsIFile* memoryReport) { nsCOMPtr pendingDir; - if (!GetPendingDir(getter_AddRefs(pendingDir))) return false; + if (!GetPendingDir(getter_AddRefs(pendingDir))) { + return false; + } if (NS_FAILED(dumpFile->MoveTo(pendingDir, u""_ns))) { return false; @@ -3398,201 +3272,91 @@ static bool MoveToPending(nsIFile* dumpFile, nsIFile* extraFile, return true; } -static void MaybeAnnotateDumperError(const ClientInfo& aClientInfo, - AnnotationTable& aAnnotations) { -#if defined(MOZ_OXIDIZED_BREAKPAD) - if (aClientInfo.had_error()) { - aAnnotations[Annotation::DumperError] = - nsDependentCString(aClientInfo.error_msg()); - } -#endif -} - -static void OnChildProcessDumpRequested( - void* aContext, const ClientInfo& aClientInfo, - const xpstring& aFilePath) MOZ_NO_THREAD_SAFETY_ANALYSIS { - nsCOMPtr minidump; - - // Hold the mutex until the current dump request is complete, to - // prevent UnsetExceptionHandler() from pulling the rug out from - // under us. - MutexAutoLock lock(*dumpSafetyLock); - if (!isSafeToDump) return; - - CreateFileFromPath(aFilePath, getter_AddRefs(minidump)); - MOZ_ASSERT(minidump); - - ProcessId pid = aClientInfo.pid(); - if (ShouldReport()) { - nsCOMPtr memoryReport; - if (!memoryReportPath.empty()) { - CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport)); - MOZ_ASSERT(memoryReport); - } - MoveToPending(minidump, nullptr, memoryReport); - } - -#if XP_WIN - nsTArray* child_annotations = mozannotation_retrieve( - reinterpret_cast(aClientInfo.process_handle()), - static_cast(Annotation::Count)); -#elif defined(XP_MACOSX) - nsTArray* child_annotations = mozannotation_retrieve( - aClientInfo.task(), static_cast(Annotation::Count)); -#else - nsTArray* child_annotations = - mozannotation_retrieve(pid, static_cast(Annotation::Count)); -#endif - - // TODO: Write a minimal set of annotations if we fail to read them, and - // add an error to the minidump to highlight this fact. - - { - MutexAutoLock lock(*dumpMapLock); - ChildProcessData* pd = pidToMinidump->PutEntry(pid); - MOZ_ASSERT(!pd->minidump); - pd->minidump = minidump; - pd->annotations = MakeUnique(); - AnnotationTable& annotations = *(pd->annotations); - AddSharedAnnotations(annotations); - AddChildProcessAnnotations(annotations, child_annotations); - - MaybeAnnotateDumperError(aClientInfo, annotations); - } - - if (child_annotations) { - mozannotation_free(child_annotations); - } -} - -static bool OOPInitialized() { return pidToMinidump != nullptr; } - -void OOPInit() { - class ProxyToMainThread : public Runnable { - public: - ProxyToMainThread() : Runnable("nsExceptionHandler::ProxyToMainThread") {} - NS_IMETHOD Run() override { - OOPInit(); - return NS_OK; - } - }; - if (!NS_IsMainThread()) { - // This logic needs to run on the main thread - nsCOMPtr mainThread = do_GetMainThread(); - mozilla::SyncRunnable::DispatchToThread(mainThread, - new ProxyToMainThread()); - return; - } - - if (OOPInitialized()) return; - - MOZ_ASSERT(NS_IsMainThread()); - - MOZ_ASSERT(gExceptionHandler != nullptr, - "attempt to initialize OOP crash reporter before in-process " - "crashreporter!"); +static void OOPInit() { + CrashHelperClient* crashHelperClient; #if defined(XP_WIN) - childCrashNotifyPipe = - mozilla::Smprintf("\\\\.\\pipe\\gecko-crash-server-pipe.%i", - static_cast(::GetCurrentProcessId())) - .release(); - - const std::wstring dumpPath = gExceptionHandler->dump_path(); - crashServer = new CrashGenerationServer( - std::wstring(NS_ConvertASCIItoUTF16(childCrashNotifyPipe).get()), - nullptr, // default security attributes - nullptr, nullptr, // we don't care about process connect here - OnChildProcessDumpRequested, nullptr, nullptr, nullptr, - nullptr, // we don't care about process exit here - nullptr, nullptr, // we don't care about upload request here - true, // automatically generate dumps - &dumpPath); - - if (sIncludeContextHeap) { - crashServer->set_include_context_heap(sIncludeContextHeap); - } + childCrashNotifyPipe = nsCString("\\\\.\\pipe\\gecko-crash-server-pipe."); + childCrashNotifyPipe.AppendInt(static_cast(::GetCurrentProcessId())); + // TODO: Create the crash server and set include_context_heap based on the + // value of sIncludeContextHeap. Also pass the release channel so we can set + // the appropriate type of minidump in the crash helper. + crashHelperClient = crash_helper_launch( + (const BreakpadChar*)crashHelperPath.c_str(), + (const BreakpadChar*)NS_ConvertUTF8toUTF16(childCrashNotifyPipe) + .BeginReading(), + (const BreakpadChar*)gExceptionHandler->dump_path().c_str()); #elif defined(XP_LINUX) - if (!CrashGenerationServer::CreateReportChannel(&serverSocketFd, - &clientSocketFd)) - MOZ_CRASH("can't create crash reporter socketpair()"); - const std::string dumpPath = gExceptionHandler->minidump_descriptor().directory(); - crashServer = new CrashGenerationServer( - serverSocketFd, -# if defined(MOZ_OXIDIZED_BREAKPAD) - [](pid_t aPid, DirectAuxvDumpInfo* aAuxvInfo) { - return ChildProcessAuxvStore::global().Get(aPid, aAuxvInfo); - }, -# endif // defined(MOZ_OXIDIZED_BREAKPAD) - [](const ClientInfo& aClientInfo, const xpstring& aFilePath) { - OnChildProcessDumpRequested(nullptr, aClientInfo, aFilePath); - }, - &dumpPath); +# if !defined(MOZ_WIDGET_ANDROID) + if (!CrashGenerationServer::CreateReportChannel(&serverSocketFd, + &clientSocketFd)) { + MOZ_CRASH("can't create crash reporter socketpair()"); + } + crashHelperClient = crash_helper_launch(crashHelperPath.c_str(), + serverSocketFd, dumpPath.c_str()); + close(serverSocketFd); +# else + crashHelperClient = crash_helper_connect(crashHelperClientFd); + set_crash_report_path(crashHelperClient, dumpPath.c_str()); +# endif // !defined(MOZ_WIDGET_ANDROID) #elif defined(XP_MACOSX) - childCrashNotifyPipe = mozilla::Smprintf("gecko-crash-server-pipe.%i", - static_cast(getpid())) - .release(); - const std::string dumpPath = gExceptionHandler->dump_path(); + childCrashNotifyPipe = nsCString("gecko-crash-server-pipe."); + childCrashNotifyPipe.AppendInt(static_cast(getpid())); - crashServer = new CrashGenerationServer(childCrashNotifyPipe, nullptr, - nullptr, OnChildProcessDumpRequested, - nullptr, nullptr, nullptr, - true, // automatically generate dumps - dumpPath); + crashHelperClient = crash_helper_launch( + crashHelperPath.c_str(), (BreakpadRawData)childCrashNotifyPipe.get(), + gExceptionHandler->dump_path().c_str()); #endif - if (!crashServer->Start()) MOZ_CRASH("can't start crash reporter server()"); - - pidToMinidump = new ChildMinidumpMap(); - - dumpMapLock = new Mutex("CrashReporter::dumpMapLock"); - - FindPendingDir(); - UpdateCrashEventsDir(); + gCrashHelperClient = crashHelperClient; } static void OOPDeinit() { - if (!OOPInitialized()) { - NS_WARNING("OOPDeinit() without successful OOPInit()"); - return; - } - - delete crashServer; - crashServer = nullptr; - - delete dumpMapLock; - dumpMapLock = nullptr; - - delete pidToMinidump; - pidToMinidump = nullptr; - #if defined(XP_WIN) || defined(XP_MACOSX) - free(childCrashNotifyPipe); - childCrashNotifyPipe = nullptr; -#endif + childCrashNotifyPipe = ""_ns; +#endif // defined(XP_WIN) || defined(XP_MACOSX) } // Parent-side API for children +#if defined(MOZ_WIDGET_ANDROID) +void SetCrashHelperPipes(FileHandle breakpadFd, FileHandle crashHelperFd) { + clientSocketFd = breakpadFd; + crashHelperClientFd = crashHelperFd; +} +#endif // defined(MOZ_WIDGET_ANDROID) + CrashPipeType GetChildNotificationPipe() { if (!GetEnabled()) { return nullptr; } - MOZ_ASSERT(OOPInitialized()); - #if defined(XP_WIN) || defined(XP_MACOSX) - return childCrashNotifyPipe; + return childCrashNotifyPipe.get(); #elif defined(XP_LINUX) return DuplicateFileHandle(clientSocketFd); #endif } -bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) { +#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) + +ProcessId GetCrashHelperPid() { + if (gCrashHelperClient) { + return crash_helper_pid(gCrashHelperClient); + } + + return base::kInvalidProcessId; +} + +#endif // defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) + +bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe, + ProcessId aCrashHelperPid) { MOZ_ASSERT(!gExceptionHandler, "crash client already init'd"); + gCrashHelperPid = aCrashHelperPid; RegisterRuntimeExceptionModule(); InitializeAppNotes(); RegisterAnnotations(); @@ -3607,13 +3371,15 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) { static_cast(Annotation::PHCBaseAddress), &mozilla::phc::gAddrInfo, sizeof(mozilla::phc::gAddrInfo)); #endif - #if defined(XP_WIN) gExceptionHandler = new google_breakpad::ExceptionHandler( - L"", ChildFilter, ChildMinidumpCallback, + L"", ChildFilter, + nullptr, // no callback nullptr, // no callback context google_breakpad::ExceptionHandler::HANDLER_ALL, GetMinidumpType(), - NS_ConvertASCIItoUTF16(aCrashPipe).get(), nullptr); + (const wchar_t*)NS_ConvertUTF8toUTF16(aCrashPipe).BeginReading(), + nullptr // no custom info + ); gExceptionHandler->set_handle_debug_exceptions(true); # if defined(HAVE_64BIT_BUILD) @@ -3623,17 +3389,19 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) { // MinidumpDescriptor requires a non-empty path. google_breakpad::MinidumpDescriptor path("."); - gExceptionHandler = new google_breakpad::ExceptionHandler( - path, ChildFilter, ChildMinidumpCallback, - nullptr, // no callback context - true, // install signal handlers - aCrashPipe.release()); + gExceptionHandler = + new google_breakpad::ExceptionHandler(path, ChildFilter, + nullptr, // no callback + nullptr, // no callback context + true, // install signal handlers + aCrashPipe.release()); #elif defined(XP_MACOSX) - gExceptionHandler = new google_breakpad::ExceptionHandler( - "", ChildFilter, ChildMinidumpCallback, - nullptr, // no callback context - true, // install signal handlers - aCrashPipe); + gExceptionHandler = + new google_breakpad::ExceptionHandler("", ChildFilter, + nullptr, // no callback + nullptr, // no callback context + true, // install signal handlers + aCrashPipe); #endif RecordMainThreadId(); @@ -3644,37 +3412,59 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) { return gExceptionHandler->IsOutOfProcess(); } -void GetAnnotation(ProcessId childPid, Annotation annotation, - nsACString& outStr) { - if (!GetEnabled()) { - return; - } - - MutexAutoLock lock(*dumpMapLock); - - ChildProcessData* pd = pidToMinidump->GetEntry(childPid); - if (!pd) { - return; - } - - outStr = (*pd->annotations)[annotation]; -} - bool TakeMinidumpForChild(ProcessId childPid, nsIFile** dump, AnnotationTable& aAnnotations) { - if (!GetEnabled()) return false; + if (!GetEnabled()) { + return false; + } - MutexAutoLock lock(*dumpMapLock); + CrashReport* crash_report = nullptr; - ChildProcessData* pd = pidToMinidump->GetEntry(childPid); - if (!pd) return false; + if (gCrashHelperClient) { + crash_report = transfer_crash_report(gCrashHelperClient, childPid); + } - NS_IF_ADDREF(*dump = pd->minidump); - aAnnotations = *(pd->annotations); + if (!crash_report) { + return false; + } - pidToMinidump->RemoveEntry(pd); + CreateFileFromPath(xpstring((XP_CHAR*)crash_report->path), dump); + nsCString error = + crash_report->error ? nsCString(crash_report->error) : ""_ns; + release_crash_report(crash_report); - return !!*dump; + nsCOMPtr extra = nullptr; + NS_ENSURE_TRUE(GetExtraFileForMinidump(*dump, getter_AddRefs(extra)), false); + + if (ShouldReport()) { + nsCOMPtr memoryReport; + if (!memoryReportPath.empty()) { + CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport)); + MOZ_ASSERT(memoryReport); + } + + MoveToPending(*dump, extra, memoryReport); + } + + nsresult rv = ReadExtraFile(extra, aAnnotations); + + // Unconditionally remove the temporary .extra file, it will be regenarated + // later when we finalize the crash report. + extra->Remove(false); + + if (rv != NS_OK) { + // TODO: We failed to read the annotations, this will leave an orphaned + // crash that we won't be able to submit. Clean everything up instead? + return false; + } + + AddSharedAnnotations(aAnnotations); + + if (error.Length() > 0) { + aAnnotations[Annotation::DumperError] = error; + } + + return true; } bool FinalizeOrphanedMinidump(ProcessId aChildPid, GeckoProcessType aType, @@ -3701,58 +3491,6 @@ bool FinalizeOrphanedMinidump(ProcessId aChildPid, GeckoProcessType aType, return WriteExtraFile(id, annotations); } -#ifdef XP_WIN - -// Function invoked by the WER runtime exception handler running in an -// external process. This function isn't used anywhere inside Gecko directly -// but rather invoked via CreateRemoteThread() in the main process. - -// Store this global in a section called mozwerpt where we can find it by just -// looking at the program headers. -# pragma section("mozwerpt", read, executable, shared) - -__declspec(allocate("mozwerpt")) MOZ_EXPORT DWORD WINAPI -WerNotifyProc(LPVOID aParameter) { - const WindowsErrorReportingData* werData = - static_cast(aParameter); - - auto freeParameterOnExit = MakeScopeExit([&aParameter] { - VirtualFree(aParameter, sizeof(WindowsErrorReportingData), MEM_RELEASE); - }); - - // Hold the mutex until the current dump request is complete, to - // prevent UnsetExceptionHandler() from pulling the rug out from - // under us. - MutexAutoLock safetyLock(*dumpSafetyLock); - if (!isSafeToDump || !ShouldReport()) { - return S_OK; - } - - ProcessId pid = werData->mChildPid; - nsCOMPtr minidump; - if (!GetPendingDir(getter_AddRefs(minidump))) { - return S_OK; - } - xpstring minidump_native_name(werData->mMinidumpFile, - werData->mMinidumpFile + 40); - nsString minidump_name(minidump_native_name.c_str()); - minidump->Append(minidump_name); - - { - MutexAutoLock lock(*dumpMapLock); - ChildProcessData* pd = pidToMinidump->PutEntry(pid); - MOZ_ASSERT(!pd->minidump); - pd->minidump = minidump; - pd->annotations = MakeUnique(); - (*pd->annotations)[Annotation::WindowsErrorReporting] = "1"_ns; - AddSharedAnnotations(*(pd->annotations)); - } - - return S_OK; -} - -#endif // XP_WIN - //----------------------------------------------------------------------------- // CreateMinidumpsAndPair() and helpers // @@ -3879,17 +3617,11 @@ bool CreateMinidumpsAndPair(ProcessHandle aTargetHandle, // callback when generating a dump of the calling process. XP_CHAR minidumpPath[XP_PATH_MAX] = {}; -#if defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) - DirectAuxvDumpInfo auxvInfo = {}; - bool auxvInfoValid = - ChildProcessAuxvStore::global().Get(aTargetHandle, &auxvInfo); -#endif // defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) - // dump the target if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild( aTargetHandle, targetThread, #if defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) - auxvInfoValid ? &auxvInfo : nullptr, + /* auxvInfo */ nullptr, #endif // defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) dump_path, PairedDumpCallback, static_cast(minidumpPath) #ifdef XP_WIN @@ -3935,18 +3667,7 @@ bool CreateMinidumpsAndPair(ProcessHandle aTargetHandle, #endif AddSharedAnnotations(aTargetAnnotations); -#if XP_WIN - nsTArray* child_annotations = - mozannotation_retrieve(reinterpret_cast(aTargetHandle), - static_cast(Annotation::Count)); -#else - nsTArray* child_annotations = mozannotation_retrieve( - aTargetHandle, static_cast(Annotation::Count)); -#endif - AddChildProcessAnnotations(aTargetAnnotations, child_annotations); - if (child_annotations) { - mozannotation_free(child_annotations); - } + // TODO: Retrieve annotations from child process targetMinidump.forget(aMainDumpOut); @@ -3970,4 +3691,28 @@ bool UnsetRemoteExceptionHandler(bool wasSet) { return true; } +#if defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) + +void GetCurrentProcessAuxvInfo(DirectAuxvDumpInfo* aAuxvInfo) { + aAuxvInfo->program_header_count = getauxval(AT_PHNUM); + aAuxvInfo->program_header_address = getauxval(AT_PHDR); + aAuxvInfo->linux_gate_address = getauxval(AT_SYSINFO_EHDR); + aAuxvInfo->entry_address = getauxval(AT_ENTRY); +} + +void RegisterChildAuxvInfo(pid_t aChildPid, + const DirectAuxvDumpInfo& aAuxvInfo) { + if (gCrashHelperClient) { + register_child_auxv_info(gCrashHelperClient, aChildPid, &aAuxvInfo); + } +} + +void UnregisterChildAuxvInfo(pid_t aChildPid) { + if (gCrashHelperClient) { + unregister_child_auxv_info(gCrashHelperClient, aChildPid); + } +} + +#endif // defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) + } // namespace CrashReporter diff --git a/toolkit/crashreporter/nsExceptionHandler.h b/toolkit/crashreporter/nsExceptionHandler.h index 61711bcf174e..78274200fbe6 100644 --- a/toolkit/crashreporter/nsExceptionHandler.h +++ b/toolkit/crashreporter/nsExceptionHandler.h @@ -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 @@ -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 diff --git a/toolkit/crashreporter/process_reader/Cargo.toml b/toolkit/crashreporter/process_reader/Cargo.toml index f866ae31692f..b5e2c470690b 100644 --- a/toolkit/crashreporter/process_reader/Cargo.toml +++ b/toolkit/crashreporter/process_reader/Cargo.toml @@ -10,7 +10,7 @@ license = "MPL-2.0" [dependencies] goblin = { version = "0.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" diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index f0d190ecf6dc..fb81bf1d2f35 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -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"] diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index e1d3d46874b5..0034fc82e01b 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -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; diff --git a/toolkit/xre/Bootstrap.h b/toolkit/xre/Bootstrap.h index 9f0fcd4d0cbd..4adcfa3a5bd2 100644 --- a/toolkit/xre/Bootstrap.h +++ b/toolkit/xre/Bootstrap.h @@ -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 }; /** diff --git a/toolkit/xre/GeckoArgs.h b/toolkit/xre/GeckoArgs.h index abadf8bfcec4..457a301376cc 100644 --- a/toolkit/xre/GeckoArgs.h +++ b/toolkit/xre/GeckoArgs.h @@ -228,13 +228,16 @@ static CommandLineArg sNotForBrowser{"-notForBrowser", "notforbrowser"}; static CommandLineArg sPluginPath{"-pluginPath", "pluginpath"}; static CommandLineArg sPluginNativeEvent{"-pluginNativeEvent", "pluginnativeevent"}; - -#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) -static CommandLineArg sCrashReporter{"-crashReporter", - "crashreporter"}; -#elif defined(XP_UNIX) +#if defined(XP_LINUX) static CommandLineArg sCrashReporter{"-crashReporter", "crashreporter"}; +# if !defined(MOZ_WIDGET_ANDROID) +static CommandLineArg sCrashHelperPid{"-crashHelperPid", + "crashhelperpid"}; +# endif // !defined(MOZ_WIDGET_ANDROID) +#else +static CommandLineArg sCrashReporter{"-crashReporter", + "crashreporter"}; #endif #if defined(XP_WIN) diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 9d5e267cc4fe..3a69d17eba62 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -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(); diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp index eeab0cf10455..9ce6af94637c 100644 --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -352,8 +352,16 @@ nsresult XRE_InitChildProcess(int aArgc, char* aArgv[], if (!CrashReporter::IsDummy()) { auto crashReporterArg = geckoargs::sCrashReporter.Get(aArgc, aArgv); if (crashReporterArg) { + CrashReporter::ProcessId crashHelperPid = base::kInvalidProcessId; +#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) + auto crashHelperPidArg = geckoargs::sCrashHelperPid.Get(aArgc, aArgv); + MOZ_ASSERT(crashHelperPidArg); + crashHelperPid = + static_cast(*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"); diff --git a/xpcom/build/XREShellData.h b/xpcom/build/XREShellData.h index 8fa47ab459dc..57b7396a8b67 100644 --- a/xpcom/build/XREShellData.h +++ b/xpcom/build/XREShellData.h @@ -33,6 +33,8 @@ struct XREShellData { #if defined(ANDROID) FILE* outFile; FILE* errFile; + int crashChildNotificationSocket; + int crashHelperSocket; #endif #if defined(LIBFUZZER) LibFuzzerDriver fuzzerDriver;