From 06bf0d08dc3c941ccce0dfb0f9e0e146d9232605 Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Thu, 7 May 2020 13:31:26 -0700 Subject: [PATCH] fix: crashReporter incompatible with sandbox on Linux (#23265) --- BUILD.gn | 37 +- chromium_src/BUILD.gn | 4 + docs/api/app.md | 1 + docs/api/crash-reporter.md | 150 ++-- docs/breaking-changes.md | 65 ++ filenames.auto.gni | 13 +- filenames.gni | 24 +- lib/browser/api/crash-reporter.js | 12 - .../api/crash-reporter.ts} | 59 +- lib/browser/crash-reporter-init.js | 25 - lib/browser/rpc-server.js | 25 +- lib/renderer/api/crash-reporter.js | 12 - lib/renderer/api/crash-reporter.ts | 50 ++ patches/chromium/.patches | 5 + ...getting_string_values_for_crash_keys.patch | 29 + .../breakpad_disable_upload_compression.patch | 49 ++ ..._node_processes_as_browser_processes.patch | 45 ++ .../crash_allow_setting_more_options.patch | 201 +++++ .../upload_list_add_loadsync_method.patch | 38 + patches/node/.patches | 1 - ..._crashpad_pipe_name_in_child_process.patch | 22 - .../zip_manifests/dist_zip.mac.x64.manifest | 4 +- .../dist_zip.mac_mas.x64.manifest | 1 + shell/app/electron_content_client.cc | 2 +- shell/app/electron_crash_reporter_client.cc | 209 +++++ shell/app/electron_crash_reporter_client.h | 96 +++ shell/app/electron_main.cc | 62 +- shell/app/electron_main_delegate.cc | 117 ++- shell/app/electron_main_delegate.h | 3 + shell/app/node_main.cc | 93 ++- shell/browser/api/electron_api_app.cc | 4 +- shell/browser/api/electron_api_app_mac.mm | 2 +- .../browser/api/electron_api_auto_updater.cc | 18 +- .../api/electron_api_crash_reporter.cc | 216 +++++ .../browser/api/electron_api_crash_reporter.h | 40 + shell/browser/browser.cc | 2 +- shell/browser/electron_browser_client.cc | 102 ++- shell/browser/electron_browser_client.h | 6 + shell/browser/electron_browser_context.cc | 2 +- shell/browser/electron_browser_main_parts.cc | 2 +- .../electron_browser_main_parts_mac.mm | 2 +- shell/browser/ui/devtools_manager_delegate.cc | 2 +- shell/common/api/api.mojom | 2 - .../common/api/electron_api_crash_reporter.cc | 67 -- shell/common/crash_keys.cc | 144 ++++ shell/common/crash_keys.h | 30 + shell/common/crash_reporter/crash_reporter.cc | 159 ---- shell/common/crash_reporter/crash_reporter.h | 79 -- .../crash_reporter/crash_reporter_crashpad.cc | 118 --- .../crash_reporter/crash_reporter_crashpad.h | 49 -- .../crash_reporter/crash_reporter_linux.cc | 150 ---- .../crash_reporter/crash_reporter_linux.h | 68 -- .../crash_reporter/crash_reporter_mac.h | 43 - .../crash_reporter/crash_reporter_mac.mm | 85 -- .../crash_reporter/crash_reporter_win.cc | 151 ---- .../crash_reporter/crash_reporter_win.h | 52 -- .../linux/crash_dump_handler.cc | 753 ------------------ .../crash_reporter/linux/crash_dump_handler.h | 46 -- .../crash_reporter/win/crash_service_main.cc | 80 -- .../crash_reporter/win/crash_service_main.h | 17 - shell/common/electron_constants.cc | 4 - shell/common/electron_constants.h | 5 - shell/{browser => common}/electron_paths.h | 8 +- shell/common/gin_converters/time_converter.cc | 21 + shell/common/gin_converters/time_converter.h | 25 + shell/common/node_bindings.cc | 5 +- shell/common/options_switches.cc | 2 + shell/common/options_switches.h | 2 + .../electron_api_crash_reporter_renderer.cc | 42 + shell/renderer/electron_api_service_impl.cc | 8 - shell/renderer/electron_api_service_impl.h | 1 - spec-main/api-crash-reporter-spec.ts | 488 ++++++++---- spec-main/fixtures/apps/crash/main.js | 23 +- spec-main/fixtures/apps/crash/node-crash.js | 26 +- .../fixtures/apps/crash/sandbox-preload.js | 18 +- .../fixtures/apps/remote-control/main.js | 14 +- .../fixtures/module/print-crash-parameters.js | 2 + 77 files changed, 2235 insertions(+), 2404 deletions(-) delete mode 100644 lib/browser/api/crash-reporter.js rename lib/{common/crash-reporter.js => browser/api/crash-reporter.ts} (51%) delete mode 100644 lib/browser/crash-reporter-init.js delete mode 100644 lib/renderer/api/crash-reporter.js create mode 100644 lib/renderer/api/crash-reporter.ts create mode 100644 patches/chromium/breakpad_allow_getting_string_values_for_crash_keys.patch create mode 100644 patches/chromium/breakpad_disable_upload_compression.patch create mode 100644 patches/chromium/breakpad_treat_node_processes_as_browser_processes.patch create mode 100644 patches/chromium/crash_allow_setting_more_options.patch create mode 100644 patches/chromium/upload_list_add_loadsync_method.patch delete mode 100644 patches/node/inherit_electron_crashpad_pipe_name_in_child_process.patch create mode 100644 shell/app/electron_crash_reporter_client.cc create mode 100644 shell/app/electron_crash_reporter_client.h create mode 100644 shell/browser/api/electron_api_crash_reporter.cc create mode 100644 shell/browser/api/electron_api_crash_reporter.h delete mode 100644 shell/common/api/electron_api_crash_reporter.cc create mode 100644 shell/common/crash_keys.cc create mode 100644 shell/common/crash_keys.h delete mode 100644 shell/common/crash_reporter/crash_reporter.cc delete mode 100644 shell/common/crash_reporter/crash_reporter.h delete mode 100644 shell/common/crash_reporter/crash_reporter_crashpad.cc delete mode 100644 shell/common/crash_reporter/crash_reporter_crashpad.h delete mode 100644 shell/common/crash_reporter/crash_reporter_linux.cc delete mode 100644 shell/common/crash_reporter/crash_reporter_linux.h delete mode 100644 shell/common/crash_reporter/crash_reporter_mac.h delete mode 100644 shell/common/crash_reporter/crash_reporter_mac.mm delete mode 100644 shell/common/crash_reporter/crash_reporter_win.cc delete mode 100644 shell/common/crash_reporter/crash_reporter_win.h delete mode 100644 shell/common/crash_reporter/linux/crash_dump_handler.cc delete mode 100644 shell/common/crash_reporter/linux/crash_dump_handler.h delete mode 100644 shell/common/crash_reporter/win/crash_service_main.cc delete mode 100644 shell/common/crash_reporter/win/crash_service_main.h rename shell/{browser => common}/electron_paths.h (86%) create mode 100644 shell/common/gin_converters/time_converter.cc create mode 100644 shell/common/gin_converters/time_converter.h create mode 100644 shell/renderer/api/electron_api_crash_reporter_renderer.cc create mode 100644 spec-main/fixtures/module/print-crash-parameters.js diff --git a/BUILD.gn b/BUILD.gn index 09cbd511731..c1ee685edee 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -325,6 +325,7 @@ source_set("electron_lib") { "//chrome/app/resources:platform_locale_settings", "//chrome/services/printing/public/mojom", "//components/certificate_transparency", + "//components/crash/core/app", "//components/language/core/browser", "//components/net_log", "//components/network_hints/browser", @@ -333,6 +334,7 @@ source_set("electron_lib") { "//components/network_session_configurator/common", "//components/pref_registry", "//components/prefs", + "//components/upload_list", "//components/user_prefs", "//components/viz/host", "//components/viz/service", @@ -446,10 +448,15 @@ source_set("electron_lib") { ] } + if (is_linux) { + deps += [ "//components/crash/content/browser" ] + } + if (is_mac) { deps += [ "//components/remote_cocoa/app_shim", "//content/common:mac_helpers", + "//third_party/crashpad/crashpad/client", "//ui/accelerated_widget_mac", ] @@ -471,11 +478,7 @@ source_set("electron_lib") { ] if (is_mas_build) { sources += [ "shell/browser/api/electron_api_app_mas.mm" ] - sources -= [ - "shell/browser/auto_updater_mac.mm", - "shell/common/crash_reporter/crash_reporter_mac.h", - "shell/common/crash_reporter/crash_reporter_mac.mm", - ] + sources -= [ "shell/browser/auto_updater_mac.mm" ] defines += [ "MAS_BUILD" ] } else { libs += [ @@ -498,7 +501,6 @@ source_set("electron_lib") { "//build/config/linux/gtk", "//dbus", "//device/bluetooth", - "//third_party/breakpad:client", "//ui/events/devices/x11", "//ui/events/platform/x11", "//ui/gtk", @@ -512,7 +514,6 @@ source_set("electron_lib") { ] } configs += [ ":gio_unix" ] - include_dirs += [ "//third_party/breakpad" ] configs += [ "//build/config/linux:x11" ] defines += [ # Disable warnings for g_settings_list_schemas. @@ -528,6 +529,7 @@ source_set("electron_lib") { if (is_win) { libs += [ "dwmapi.lib" ] deps += [ + "//components/crash/core/app:crash_export_thunks", "//ui/native_theme:native_theme_browser", "//ui/views/controls/webview", "//ui/wm", @@ -539,14 +541,6 @@ source_set("electron_lib") { ] } - if ((is_mac && !is_mas_build) || is_win) { - sources += [ - "shell/common/crash_reporter/crash_reporter_crashpad.cc", - "shell/common/crash_reporter/crash_reporter_crashpad.h", - ] - deps += [ "//third_party/crashpad/crashpad/client" ] - } - if (enable_plugins) { deps += [ "chromium_src:plugins" ] sources += [ @@ -751,11 +745,11 @@ if (is_mac) { } bundle_data("electron_crashpad_helper") { - sources = [ "$root_out_dir/crashpad_handler" ] + sources = [ "$root_out_dir/chrome_crashpad_handler" ] - outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ] + outputs = [ "{{bundle_contents_dir}}/Helpers/{{source_file_part}}" ] - public_deps = [ "//third_party/crashpad/crashpad/handler:crashpad_handler" ] + public_deps = [ "//components/crash/core/app:chrome_crashpad_handler" ] if (is_asan) { # crashpad_handler requires the ASan runtime at its @executable_path. @@ -770,6 +764,7 @@ if (is_mac) { framework_contents = [ "Resources", "Libraries", + "Helpers", ] public_deps = [ ":electron_framework_libraries", @@ -1037,6 +1032,7 @@ if (is_mac) { ":electron_app_manifest", ":electron_lib", ":packed_resources", + "//components/crash/core/app", "//content:sandbox_helper_win", "//electron/buildflags", "//ui/strings", @@ -1066,6 +1062,11 @@ if (is_mac) { "shell/browser/resources/win/resource.h", ] + deps += [ + "//components/browser_watcher:browser_watcher_client", + "//components/crash/core/app:run_as_crashpad_handler", + ] + libs = [ "comctl32.lib", "uiautomationcore.lib", diff --git a/chromium_src/BUILD.gn b/chromium_src/BUILD.gn index 13b65275ace..ab6b3fc561d 100644 --- a/chromium_src/BUILD.gn +++ b/chromium_src/BUILD.gn @@ -14,6 +14,8 @@ static_library("chrome") { sources = [ "//chrome/browser/browser_process.cc", "//chrome/browser/browser_process.h", + "//chrome/browser/crash_upload_list/crash_upload_list_crashpad.cc", + "//chrome/browser/crash_upload_list/crash_upload_list_crashpad.h", "//chrome/browser/devtools/devtools_contents_resizing_strategy.cc", "//chrome/browser/devtools/devtools_contents_resizing_strategy.h", "//chrome/browser/devtools/devtools_embedder_message_dispatcher.cc", @@ -55,6 +57,8 @@ static_library("chrome") { "//chrome/browser/ui/views/autofill/autofill_popup_view_utils.h", "//chrome/browser/win/chrome_process_finder.cc", "//chrome/browser/win/chrome_process_finder.h", + "//chrome/child/v8_crashpad_support_win.cc", + "//chrome/child/v8_crashpad_support_win.h", "//extensions/browser/app_window/size_constraints.cc", "//extensions/browser/app_window/size_constraints.h", ] diff --git a/docs/api/app.md b/docs/api/app.md index 83510f318ef..94450792eab 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -610,6 +610,7 @@ Returns `String` - The current application directory. * `videos` Directory for a user's videos. * `logs` Directory for your app's log folder. * `pepperFlashSystemPlugin` Full path to the system version of the Pepper Flash plugin. + * `crashDumps` Directory where crash dumps are stored. Returns `String` - A path to a special directory or file associated with `name`. On failure, an `Error` is thrown. diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 6ac538dfaca..73b38782329 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -4,18 +4,13 @@ Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) -The following is an example of automatically submitting a crash report to a -remote server: +The following is an example of setting up Electron to automatically submit +crash reports to a remote server: ```javascript const { crashReporter } = require('electron') -crashReporter.start({ - productName: 'YourName', - companyName: 'YourCompany', - submitURL: 'https://your-domain.com/url-to-submit', - uploadToServer: true -}) +crashReporter.start({ submitURL: 'https://your-domain.com/url-to-submit' }) ``` For setting up a server to accept and process crash reports, you can use @@ -30,11 +25,19 @@ Or use a 3rd party hosted solution: * [Sentry](https://docs.sentry.io/clients/electron) * [BugSplat](https://www.bugsplat.com/docs/platforms/electron) -Crash reports are saved locally in an application-specific temp directory folder. -For a `productName` of `YourName`, crash reports will be stored in a folder -named `YourName Crashes` inside the temp directory. You can customize this temp -directory location for your app by calling the `app.setPath('temp', '/my/custom/temp')` -API before starting the crash reporter. +Crash reports are stored temporarily before being uploaded in a directory +underneath the app's user data directory (called 'Crashpad' on Windows and Mac, +or 'Crash Reports' on Linux). You can override this directory by calling +`app.setPath('crashDumps', '/path/to/crashes')` before starting the crash +reporter. + +On Windows and macOS, Electron uses +[crashpad](https://chromium.googlesource.com/crashpad/crashpad/+/master/README.md) +to monitor and report crashes. On Linux, Electron uses +[breakpad](https://chromium.googlesource.com/breakpad/breakpad/+/master/). This +is an implementation detail driven by Chromium, and it may change in future. In +particular, crashpad is newer and will likely eventually replace breakpad on +all platforms. ## Methods @@ -43,45 +46,68 @@ The `crashReporter` module has the following methods: ### `crashReporter.start(options)` * `options` Object - * `companyName` String * `submitURL` String - URL that crash reports will be sent to as POST. * `productName` String (optional) - Defaults to `app.name`. - * `uploadToServer` Boolean (optional) - Whether crash reports should be sent to the server. Default is `true`. - * `ignoreSystemCrashHandler` Boolean (optional) - Default is `false`. + * `companyName` String (optional) _Deprecated_ - Deprecated alias for + `{ globalExtra: { _companyName: ... } }`. + * `uploadToServer` Boolean (optional) - Whether crash reports should be sent + to the server. If false, crash reports will be collected and stored in the + crashes directory, but not uploaded. Default is `true`. + * `ignoreSystemCrashHandler` Boolean (optional) - If true, crashes generated + in the main process will not be forwarded to the system crash handler. + Default is `false`. * `rateLimit` Boolean (optional) _macOS_ _Windows_ - If true, limit the number of crashes uploaded to 1/hour. Default is `false`. * `compress` Boolean (optional) _macOS_ _Windows_ - If true, crash reports will be compressed and uploaded with `Content-Encoding: gzip`. Not all collection servers support compressed payloads. Default is `false`. - * `extra` Record (optional) - An object you can define that will be sent along with the - report. Only string properties are sent correctly. Nested objects are not - supported. When using Windows, the property names and values must be fewer than 64 characters. - * `crashesDirectory` String (optional) - Directory to store the crash reports temporarily (only used when the crash reporter is started via `process.crashReporter.start`). + * `extra` Record (optional) - Extra string key/value + annotations that will be sent along with crash reports that are generated + in the main process. Only string values are supported. Crashes generated in + child processes will not contain these extra + parameters to crash reports generated from child processes, call + [`addExtraParameter`](#crashreporteraddextraparameterkey-value) from the + child process. + * `globalExtra` Record (optional) - Extra string key/value + annotations that will be sent along with any crash reports generated in any + process. These annotations cannot be changed once the crash reporter has + been started. If a key is present in both the global extra parameters and + the process-specific extra parameters, then the global one will take + precedence. By default, `productName` and the app version are included, as + well as the Electron version. -You are required to call this method before using any other `crashReporter` APIs -and in each process (main/renderer) from which you want to collect crash reports. -You can pass different options to `crashReporter.start` when calling from different processes. +This method must be called before using any other `crashReporter` APIs. Once +initialized this way, the crashpad handler collects crashes from all +subsequently created processes. The crash reporter cannot be disabled once +started. -**Note** Child processes created via the `child_process` module will not have access to the Electron modules. -Therefore, to collect crash reports from them, use `process.crashReporter.start` instead. Pass the same options as above -along with an additional one called `crashesDirectory` that should point to a directory to store the crash -reports temporarily. You can test this out by calling `process.crash()` to crash the child process. +This method should be called as early as possible in app startup, preferably +before `app.on('ready')`. If the crash reporter is not initialized at the time +a renderer process is created, then that renderer process will not be monitored +by the crash reporter. -**Note:** If you need send additional/updated `extra` parameters after your -first call `start` you can call `addExtraParameter` on macOS or call `start` -again with the new/updated `extra` parameters on Linux and Windows. +**Note:** You can test out the crash reporter by generating a crash using +`process.crash()`. -**Note:** On macOS and windows, Electron uses a new `crashpad` client for crash collection and reporting. -If you want to enable crash reporting, initializing `crashpad` from the main process using `crashReporter.start` is required -regardless of which process you want to collect crashes from. Once initialized this way, the crashpad handler collects -crashes from all processes. You still have to call `crashReporter.start` from the renderer or child process, otherwise crashes from -them will get reported without `companyName`, `productName` or any of the `extra` information. +**Note:** If you need to send additional/updated `extra` parameters after your +first call `start` you can call `addExtraParameter`. + +**Note:** Parameters passed in `extra`, `globalExtra` or set with +`addExtraParameter` have limits on the length of the keys and values. Key names +must be at most 39 bytes long, and values must be no longer than 127 bytes. +Keys with names longer than the maximum will be silently ignored. Key values +longer than the maximum length will be truncated. + +**Note:** Calling this method from the renderer process is deprecated. ### `crashReporter.getLastCrashReport()` -Returns [`CrashReport`](structures/crash-report.md): +Returns [`CrashReport`](structures/crash-report.md) - The date and ID of the +last crash report. Only crash reports that have been uploaded will be returned; +even if a crash report is present on disk it will not be returned until it is +uploaded. In the case that there are no uploaded reports, `null` is returned. -Returns the date and ID of the last crash report. Only crash reports that have been uploaded will be returned; even if a crash report is present on disk it will not be returned until it is uploaded. In the case that there are no uploaded reports, `null` is returned. +**Note:** Calling this method from the renderer process is deprecated. ### `crashReporter.getUploadedReports()` @@ -90,43 +116,61 @@ Returns [`CrashReport[]`](structures/crash-report.md): Returns all uploaded crash reports. Each report contains the date and uploaded ID. +**Note:** Calling this method from the renderer process is deprecated. + ### `crashReporter.getUploadToServer()` Returns `Boolean` - Whether reports should be submitted to the server. Set through the `start` method or `setUploadToServer`. -**Note:** This API can only be called from the main process. +**Note:** Calling this method from the renderer process is deprecated. ### `crashReporter.setUploadToServer(uploadToServer)` -* `uploadToServer` Boolean _macOS_ - Whether reports should be submitted to the server. +* `uploadToServer` Boolean - Whether reports should be submitted to the server. This would normally be controlled by user preferences. This has no effect if called before `start` is called. -**Note:** This API can only be called from the main process. +**Note:** Calling this method from the renderer process is deprecated. -### `crashReporter.addExtraParameter(key, value)` _macOS_ _Windows_ +### `crashReporter.getCrashesDirectory()` _Deprecated_ -* `key` String - Parameter key, must be less than 64 characters long. -* `value` String - Parameter value, must be less than 64 characters long. +Returns `String` - The directory where crashes are temporarily stored before being uploaded. -Set an extra parameter to be sent with the crash report. The values -specified here will be sent in addition to any values set via the `extra` option when `start` was called. This API is only available on macOS and windows, if you need to add/update extra parameters on Linux after your first call to `start` you can call `start` again with the updated `extra` options. +**Note:** This method is deprecated, use `app.getPath('crashDumps')` instead. -### `crashReporter.removeExtraParameter(key)` _macOS_ _Windows_ +### `crashReporter.addExtraParameter(key, value)` -* `key` String - Parameter key, must be less than 64 characters long. +* `key` String - Parameter key, must be no longer than 39 bytes. +* `value` String - Parameter value, must be no longer than 127 bytes. -Remove a extra parameter from the current set of parameters so that it will not be sent with the crash report. +Set an extra parameter to be sent with the crash report. The values specified +here will be sent in addition to any values set via the `extra` option when +`start` was called. + +Parameters added in this fashion (or via the `extra` parameter to +`crashReporter.start`) are specific to the calling process. Adding extra +parameters in the main process will not cause those parameters to be sent along +with crashes from renderer or other child processes. Similarly, adding extra +parameters in a renderer process will not result in those parameters being sent +with crashes that occur in other renderer processes or in the main process. + +**Note:** Parameters have limits on the length of the keys and values. Key +names must be no longer than 39 bytes, and values must be no longer than 127 +bytes. Keys with names longer than the maximum will be silently ignored. Key +values longer than the maximum length will be truncated. + +### `crashReporter.removeExtraParameter(key)` + +* `key` String - Parameter key, must be no longer than 39 bytes. + +Remove a extra parameter from the current set of parameters. Future crashes +will not include this parameter. ### `crashReporter.getParameters()` -See all of the current parameters being passed to the crash reporter. - -### `crashReporter.getCrashesDirectory()` - -Returns `String` - The directory where crashes are temporarily stored before being uploaded. +Returns `Record` - The current 'extra' parameters of the crash reporter. ## Crash Report Payload diff --git a/docs/breaking-changes.md b/docs/breaking-changes.md index 151edd9b3b0..6f36ce64f83 100644 --- a/docs/breaking-changes.md +++ b/docs/breaking-changes.md @@ -12,8 +12,73 @@ This document uses the following convention to categorize breaking changes: - **Deprecated:** An API was marked as deprecated. The API will continue to function, but will emit a deprecation warning, and will be removed in a future release. - **Removed:** An API or feature was removed, and is no longer supported by Electron. +## Planned Breaking API Changes (12.0) + +### Removed: `crashReporter` methods in the renderer process + +The following `crashReporter` methods are no longer available in the renderer +process: + +- `crashReporter.start` +- `crashReporter.getLastCrashReport` +- `crashReporter.getUploadedReports` +- `crashReporter.getUploadToServer` +- `crashReporter.setUploadToServer` +- `crashReporter.getCrashesDirectory` + +They should be called only from the main process. + +See [#23265](https://github.com/electron/electron/pull/23265) for more details. + +## Planned Breaking API Changes (11.0) + ## Planned Breaking API Changes (10.0) +### Deprecated: `companyName` argument to `crashReporter.start()` + +The `companyName` argument to `crashReporter.start()`, which was previously +required, is now optional, and further, is deprecated. To get the same +behavior in a non-deprecated way, you can pass a `companyName` value in +`globalExtra`. + +```js +// Deprecated in Electron 10 +crashReporter.start({ companyName: 'Umbrella Corporation' }) +// Replace with +crashReporter.start({ globalExtra: { _companyName: 'Umbrella Corporation' } }) +``` + +### Deprecated: `crashReporter.getCrashesDirectory()` + +The `crashReporter.getCrashesDirectory` method has been deprecated. Usage +should be replaced by `app.getPath('crashDumps')`. + +```js +// Deprecated in Electron 10 +crashReporter.getCrashesDirectory() +// Replace with +app.getPath('crashDumps') +``` + +### Deprecated: `crashReporter` methods in the renderer process + +Calling the following `crashReporter` methods from the renderer process is +deprecated: + +- `crashReporter.start` +- `crashReporter.getLastCrashReport` +- `crashReporter.getUploadedReports` +- `crashReporter.getUploadToServer` +- `crashReporter.setUploadToServer` +- `crashReporter.getCrashesDirectory` + +The only non-deprecated methods remaining in the `crashReporter` module in the +renderer are `addExtraParameter`, `removeExtraParameter` and `getParameters`. + +All above methods remain non-deprecated when called from the main process. + +See [#23265](https://github.com/electron/electron/pull/23265) for more details. + ### Removed: Browser Window Affinity The `affinity` option when constructing a new `BrowserWindow` will be removed diff --git a/filenames.auto.gni b/filenames.auto.gni index 6c7f830fc33..d2a62606619 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -141,14 +141,13 @@ auto_filenames = { "lib/common/api/module-list.ts", "lib/common/api/native-image.js", "lib/common/api/shell.js", - "lib/common/crash-reporter.js", "lib/common/define-properties.ts", "lib/common/electron-binding-setup.ts", "lib/common/type-utils.ts", "lib/common/web-view-methods.ts", "lib/common/webpack-globals-provider.ts", "lib/renderer/api/context-bridge.ts", - "lib/renderer/api/crash-reporter.js", + "lib/renderer/api/crash-reporter.ts", "lib/renderer/api/desktop-capturer.ts", "lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/remote.js", @@ -193,7 +192,7 @@ auto_filenames = { "lib/browser/api/browser-view.js", "lib/browser/api/browser-window.js", "lib/browser/api/content-tracing.js", - "lib/browser/api/crash-reporter.js", + "lib/browser/api/crash-reporter.ts", "lib/browser/api/dialog.js", "lib/browser/api/exports/electron.ts", "lib/browser/api/global-shortcut.js", @@ -223,7 +222,6 @@ auto_filenames = { "lib/browser/api/web-contents-view.js", "lib/browser/api/web-contents.js", "lib/browser/chrome-extension-shim.js", - "lib/browser/crash-reporter-init.js", "lib/browser/default-menu.ts", "lib/browser/desktop-capturer.ts", "lib/browser/devtools.ts", @@ -244,7 +242,6 @@ auto_filenames = { "lib/common/api/module-list.ts", "lib/common/api/native-image.js", "lib/common/api/shell.js", - "lib/common/crash-reporter.js", "lib/common/define-properties.ts", "lib/common/electron-binding-setup.ts", "lib/common/init.ts", @@ -267,7 +264,6 @@ auto_filenames = { "lib/common/api/module-list.ts", "lib/common/api/native-image.js", "lib/common/api/shell.js", - "lib/common/crash-reporter.js", "lib/common/define-properties.ts", "lib/common/electron-binding-setup.ts", "lib/common/init.ts", @@ -276,7 +272,7 @@ auto_filenames = { "lib/common/web-view-methods.ts", "lib/common/webpack-globals-provider.ts", "lib/renderer/api/context-bridge.ts", - "lib/renderer/api/crash-reporter.js", + "lib/renderer/api/crash-reporter.ts", "lib/renderer/api/desktop-capturer.ts", "lib/renderer/api/exports/electron.ts", "lib/renderer/api/ipc-renderer.ts", @@ -310,7 +306,6 @@ auto_filenames = { "lib/common/api/module-list.ts", "lib/common/api/native-image.js", "lib/common/api/shell.js", - "lib/common/crash-reporter.js", "lib/common/define-properties.ts", "lib/common/electron-binding-setup.ts", "lib/common/init.ts", @@ -318,7 +313,7 @@ auto_filenames = { "lib/common/type-utils.ts", "lib/common/webpack-globals-provider.ts", "lib/renderer/api/context-bridge.ts", - "lib/renderer/api/crash-reporter.js", + "lib/renderer/api/crash-reporter.ts", "lib/renderer/api/desktop-capturer.ts", "lib/renderer/api/exports/electron.ts", "lib/renderer/api/ipc-renderer.ts", diff --git a/filenames.gni b/filenames.gni index ab2bb6af674..a57cd1dc140 100644 --- a/filenames.gni +++ b/filenames.gni @@ -31,6 +31,8 @@ filenames = { "shell/app/command_line_args.h", "shell/app/electron_content_client.cc", "shell/app/electron_content_client.h", + "shell/app/electron_crash_reporter_client.cc", + "shell/app/electron_crash_reporter_client.h", "shell/app/electron_main_delegate.cc", "shell/app/electron_main_delegate.h", "shell/app/electron_main_delegate_mac.h", @@ -51,6 +53,8 @@ filenames = { "shell/browser/api/electron_api_content_tracing.cc", "shell/browser/api/electron_api_cookies.cc", "shell/browser/api/electron_api_cookies.h", + "shell/browser/api/electron_api_crash_reporter.cc", + "shell/browser/api/electron_api_crash_reporter.h", "shell/browser/api/electron_api_data_pipe_holder.cc", "shell/browser/api/electron_api_data_pipe_holder.h", "shell/browser/api/electron_api_debugger.cc", @@ -170,7 +174,6 @@ filenames = { "shell/browser/electron_javascript_dialog_manager.h", "shell/browser/electron_navigation_throttle.cc", "shell/browser/electron_navigation_throttle.h", - "shell/browser/electron_paths.h", "shell/browser/electron_permission_manager.cc", "shell/browser/electron_permission_manager.h", "shell/browser/electron_quota_permission_context.cc", @@ -442,7 +445,6 @@ filenames = { "shell/common/api/electron_api_clipboard.h", "shell/common/api/electron_api_clipboard_mac.mm", "shell/common/api/electron_api_command_line.cc", - "shell/common/api/electron_api_crash_reporter.cc", "shell/common/api/electron_api_key_weak_map.h", "shell/common/api/electron_api_native_image.cc", "shell/common/api/electron_api_native_image.h", @@ -465,22 +467,13 @@ filenames = { "shell/common/asar/scoped_temporary_file.h", "shell/common/color_util.cc", "shell/common/color_util.h", - "shell/common/crash_reporter/crash_reporter.cc", - "shell/common/crash_reporter/crash_reporter.h", - "shell/common/crash_reporter/crash_reporter_linux.cc", - "shell/common/crash_reporter/crash_reporter_linux.h", - "shell/common/crash_reporter/crash_reporter_mac.h", - "shell/common/crash_reporter/crash_reporter_mac.mm", - "shell/common/crash_reporter/crash_reporter_win.cc", - "shell/common/crash_reporter/crash_reporter_win.h", - "shell/common/crash_reporter/linux/crash_dump_handler.cc", - "shell/common/crash_reporter/linux/crash_dump_handler.h", - "shell/common/crash_reporter/win/crash_service_main.cc", - "shell/common/crash_reporter/win/crash_service_main.h", + "shell/common/crash_keys.cc", + "shell/common/crash_keys.h", "shell/common/electron_command_line.cc", "shell/common/electron_command_line.h", "shell/common/electron_constants.cc", "shell/common/electron_constants.h", + "shell/common/electron_paths.h", "shell/common/gin_converters/accelerator_converter.cc", "shell/common/gin_converters/accelerator_converter.h", "shell/common/gin_converters/blink_converter.cc", @@ -502,6 +495,8 @@ filenames = { "shell/common/gin_converters/net_converter.cc", "shell/common/gin_converters/net_converter.h", "shell/common/gin_converters/std_converter.h", + "shell/common/gin_converters/time_converter.cc", + "shell/common/gin_converters/time_converter.h", "shell/common/gin_converters/value_converter.cc", "shell/common/gin_converters/value_converter.h", "shell/common/gin_helper/arguments.cc", @@ -583,6 +578,7 @@ filenames = { "shell/renderer/api/context_bridge/render_frame_function_store.h", "shell/renderer/api/electron_api_context_bridge.cc", "shell/renderer/api/electron_api_context_bridge.h", + "shell/renderer/api/electron_api_crash_reporter_renderer.cc", "shell/renderer/api/electron_api_ipc_renderer.cc", "shell/renderer/api/electron_api_spell_check_client.cc", "shell/renderer/api/electron_api_spell_check_client.h", diff --git a/lib/browser/api/crash-reporter.js b/lib/browser/api/crash-reporter.js deleted file mode 100644 index cb279176616..00000000000 --- a/lib/browser/api/crash-reporter.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -const CrashReporter = require('@electron/internal/common/crash-reporter'); -const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init'); - -class CrashReporterMain extends CrashReporter { - init (options) { - return crashReporterInit(options); - } -} - -module.exports = new CrashReporterMain(); diff --git a/lib/common/crash-reporter.js b/lib/browser/api/crash-reporter.ts similarity index 51% rename from lib/common/crash-reporter.js rename to lib/browser/api/crash-reporter.ts index 3ef27736e5b..2d116414018 100644 --- a/lib/common/crash-reporter.js +++ b/lib/browser/api/crash-reporter.ts @@ -1,47 +1,35 @@ -'use strict'; +import { app } from 'electron'; const binding = process.electronBinding('crash_reporter'); class CrashReporter { - constructor () { - this.productName = null; - this.crashesDirectory = null; - } - - init (options) { - throw new Error('Not implemented'); - } - - start (options) { - if (options == null) options = {}; - + start (options: Electron.CrashReporterStartOptions) { const { - productName, + productName = app.name, companyName, extra = {}, + globalExtra = {}, ignoreSystemCrashHandler = false, submitURL, uploadToServer = true, rateLimit = false, compress = false - } = options; + } = options || {}; - if (companyName == null) throw new Error('companyName is a required option to crashReporter.start'); if (submitURL == null) throw new Error('submitURL is a required option to crashReporter.start'); - const ret = this.init({ - submitURL, - productName - }); + const appVersion = app.getVersion(); - this.productName = ret.productName; - this.crashesDirectory = ret.crashesDirectory; + if (companyName && globalExtra._companyName == null) globalExtra._companyName = companyName; - if (extra._productName == null) extra._productName = ret.productName; - if (extra._companyName == null) extra._companyName = companyName; - if (extra._version == null) extra._version = ret.appVersion; + const globalExtraAmended = { + _productName: productName, + _version: appVersion, + ...globalExtra + }; - binding.start(submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, rateLimit, compress, extra); + binding.start(submitURL, uploadToServer, + ignoreSystemCrashHandler, rateLimit, compress, globalExtraAmended, extra, false); } getLastCrashReport () { @@ -55,17 +43,12 @@ class CrashReporter { return (reports.length > 0) ? reports[0] : null; } - getUploadedReports () { - const crashDir = this.getCrashesDirectory(); - if (!crashDir) { - throw new Error('crashReporter has not been started'); - } - - return binding.getUploadedReports(crashDir); + getUploadedReports (): Electron.CrashReport[] { + return binding.getUploadedReports(); } getCrashesDirectory () { - return this.crashesDirectory; + return app.getPath('crashDumps'); } getUploadToServer () { @@ -76,7 +59,7 @@ class CrashReporter { } } - setUploadToServer (uploadToServer) { + setUploadToServer (uploadToServer: boolean) { if (process.type === 'browser') { return binding.setUploadToServer(uploadToServer); } else { @@ -84,11 +67,11 @@ class CrashReporter { } } - addExtraParameter (key, value) { + addExtraParameter (key: string, value: string) { binding.addExtraParameter(key, value); } - removeExtraParameter (key) { + removeExtraParameter (key: string) { binding.removeExtraParameter(key); } @@ -97,4 +80,4 @@ class CrashReporter { } } -module.exports = CrashReporter; +export default new CrashReporter(); diff --git a/lib/browser/crash-reporter-init.js b/lib/browser/crash-reporter-init.js deleted file mode 100644 index 566e320cf59..00000000000 --- a/lib/browser/crash-reporter-init.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const { app } = require('electron'); -const path = require('path'); - -const getTempDirectory = function () { - try { - return app.getPath('temp'); - } catch { - // Delibrately laze-load the os module, this file is on the hot - // path when booting Electron and os takes between 5 - 8ms to load and we do not need it yet - return require('os').tmpdir(); - } -}; - -exports.crashReporterInit = function (options) { - const productName = options.productName || app.name; - const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`); - - return { - productName, - crashesDirectory, - appVersion: app.getVersion() - }; -}; diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index 689b1e71b01..009c2739ace 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -7,7 +7,6 @@ const eventBinding = process.electronBinding('event'); const clipboard = process.electronBinding('clipboard'); const features = process.electronBinding('features'); -const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init'); const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); const guestViewManager = require('@electron/internal/browser/guest-view-manager'); @@ -37,10 +36,6 @@ ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) { event.returnValue = null; }); -ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_INIT', function (event, options) { - return crashReporterInit(options); -}); - ipcMainInternal.handle('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) { return event.sender.getLastWebPreferences(); }); @@ -120,3 +115,23 @@ ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) ipcMainInternal.on('ELECTRON_BROWSER_PRELOAD_ERROR', function (event, preloadPath, error) { event.sender.emit('preload-error', event, preloadPath, error); }); + +ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_GET_LAST_CRASH_REPORT', () => { + return electron.crashReporter.getLastCrashReport(); +}); + +ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_GET_UPLOADED_REPORTS', () => { + return electron.crashReporter.getUploadedReports(); +}); + +ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_GET_UPLOAD_TO_SERVER', () => { + return electron.crashReporter.getUploadToServer(); +}); + +ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_SET_UPLOAD_TO_SERVER', (event, uploadToServer) => { + return electron.crashReporter.setUploadToServer(uploadToServer); +}); + +ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_GET_CRASHES_DIRECTORY', () => { + return electron.crashReporter.getCrashesDirectory(); +}); diff --git a/lib/renderer/api/crash-reporter.js b/lib/renderer/api/crash-reporter.js deleted file mode 100644 index 48212ab170f..00000000000 --- a/lib/renderer/api/crash-reporter.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -const CrashReporter = require('@electron/internal/common/crash-reporter'); -const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils'); - -class CrashReporterRenderer extends CrashReporter { - init (options) { - return ipcRendererUtils.invokeSync('ELECTRON_CRASH_REPORTER_INIT', options); - } -} - -module.exports = new CrashReporterRenderer(); diff --git a/lib/renderer/api/crash-reporter.ts b/lib/renderer/api/crash-reporter.ts new file mode 100644 index 00000000000..3a9571af251 --- /dev/null +++ b/lib/renderer/api/crash-reporter.ts @@ -0,0 +1,50 @@ +import { invokeSync } from '../ipc-renderer-internal-utils'; +import { deprecate } from 'electron'; + +const binding = process.electronBinding('crash_reporter'); + +export default { + start (options: Electron.CrashReporterStartOptions) { + deprecate.log('crashReporter.start is deprecated in the renderer process. Call it from the main process instead.'); + for (const [k, v] of Object.entries(options.extra || {})) { + binding.addExtraParameter(k, String(v)); + } + }, + + getLastCrashReport (): Electron.CrashReport | null { + deprecate.log('crashReporter.getLastCrashReport is deprecated in the renderer process. Call it from the main process instead.'); + return invokeSync('ELECTRON_CRASH_REPORTER_GET_LAST_CRASH_REPORT'); + }, + + getUploadedReports () { + deprecate.log('crashReporter.getUploadedReports is deprecated in the renderer process. Call it from the main process instead.'); + return invokeSync('ELECTRON_CRASH_REPORTER_GET_UPLOADED_REPORTS'); + }, + + getUploadToServer () { + deprecate.log('crashReporter.getUploadToServer is deprecated in the renderer process. Call it from the main process instead.'); + return invokeSync('ELECTRON_CRASH_REPORTER_GET_UPLOAD_TO_SERVER'); + }, + + setUploadToServer (uploadToServer: boolean) { + deprecate.log('crashReporter.setUploadToServer is deprecated in the renderer process. Call it from the main process instead.'); + return invokeSync('ELECTRON_CRASH_REPORTER_SET_UPLOAD_TO_SERVER', uploadToServer); + }, + + getCrashesDirectory () { + deprecate.log('crashReporter.getCrashesDirectory is deprecated in the renderer process. Call it from the main process instead.'); + return invokeSync('ELECTRON_CRASH_REPORTER_GET_CRASHES_DIRECTORY'); + }, + + addExtraParameter (key: string, value: string) { + binding.addExtraParameter(key, value); + }, + + removeExtraParameter (key: string) { + binding.removeExtraParameter(key); + }, + + getParameters () { + return binding.getParameters(); + } +}; diff --git a/patches/chromium/.patches b/patches/chromium/.patches index ec5784bccda..03f1b803e1c 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -89,3 +89,8 @@ feat_add_onclose_to_messageport.patch fix_account_for_print_preview_disabled_when_printing_to_pdf.patch web_contents.patch ui_gtk_public_header.patch +crash_allow_setting_more_options.patch +breakpad_disable_upload_compression.patch +breakpad_treat_node_processes_as_browser_processes.patch +upload_list_add_loadsync_method.patch +breakpad_allow_getting_string_values_for_crash_keys.patch diff --git a/patches/chromium/breakpad_allow_getting_string_values_for_crash_keys.patch b/patches/chromium/breakpad_allow_getting_string_values_for_crash_keys.patch new file mode 100644 index 00000000000..894595345ba --- /dev/null +++ b/patches/chromium/breakpad_allow_getting_string_values_for_crash_keys.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jeremy Apthorp +Date: Tue, 5 May 2020 12:36:39 -0700 +Subject: breakpad: allow getting string values for crash keys + +Linux is currently recording both crashpad and breakpad keys on linux +(because upstream is experimenting with crashpad-on-linux). We can fetch +the string values for crashpad keys on win/mac, and they're easily +available on linux too, this just exposes them. + +Should be upstreamed, or failing that, deleted once crashpad is enabled +on linux. If removing this patch doesn't cause a compile failure, it's +fine to delete! + +diff --git a/components/crash/core/common/crash_key.h b/components/crash/core/common/crash_key.h +index 9d193ea962e919d4509eef296c900e47059761f4..225b8ce822a42d31d59bc89aab473710384c3010 100644 +--- a/components/crash/core/common/crash_key.h ++++ b/components/crash/core/common/crash_key.h +@@ -212,6 +212,10 @@ class CrashKeyStringCombined : public internal::CrashKeyStringCombinedImpl { + crashpad_key_.Set(value); + } + ++ const base::StringPiece value() const { ++ return crashpad_key_.value(); ++ } ++ + private: + CrashKeyStringBreakpad breakpad_key_; + crashpad::StringAnnotation crashpad_key_; diff --git a/patches/chromium/breakpad_disable_upload_compression.patch b/patches/chromium/breakpad_disable_upload_compression.patch new file mode 100644 index 00000000000..ac66e9faaa0 --- /dev/null +++ b/patches/chromium/breakpad_disable_upload_compression.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jeremy Apthorp +Date: Wed, 29 Apr 2020 16:28:35 -0700 +Subject: breakpad: disable upload compression + +Our prior implementation of breakpad uploading did not compress files on +upload. In order to maintain that behavior, this disables compression in +//components/crash. + +Ideally, this would be made configurable. + +diff --git a/components/crash/core/app/breakpad_linux.cc b/components/crash/core/app/breakpad_linux.cc +index 364af690879d79d25d118d5b2fdbaabe21a1c52d..390cca9edc26d3153c8dbc86024cb50097d7ac78 100644 +--- a/components/crash/core/app/breakpad_linux.cc ++++ b/components/crash/core/app/breakpad_linux.cc +@@ -1334,6 +1334,7 @@ void ExecUploadProcessOrTerminate(const BreakpadInfo& info, + + #else // defined(OS_CHROMEOS) + ++ /* + // Compress |dumpfile| with gzip. + const pid_t gzip_child = sys_fork(); + if (gzip_child < 0) { +@@ -1377,13 +1378,16 @@ void ExecUploadProcessOrTerminate(const BreakpadInfo& info, + WriteLog(msg, sizeof(msg) - 1); + sys__exit(1); + } ++ */ + + // The --header argument to wget looks like: + // --header=Content-Encoding: gzip + // --header=Content-Type: multipart/form-data; boundary=XYZ + // where the boundary has two fewer leading '-' chars ++ /* + static const char header_content_encoding[] = + "--header=Content-Encoding: gzip"; ++ */ + static const char header_msg[] = + "--header=Content-Type: multipart/form-data; boundary="; + const size_t header_content_type_size = +@@ -1412,7 +1416,7 @@ void ExecUploadProcessOrTerminate(const BreakpadInfo& info, + static const char kWgetBinary[] = "/usr/bin/wget"; + const char* args[] = { + kWgetBinary, +- header_content_encoding, ++ //header_content_encoding, + header_content_type, + post_file, + upload_url, diff --git a/patches/chromium/breakpad_treat_node_processes_as_browser_processes.patch b/patches/chromium/breakpad_treat_node_processes_as_browser_processes.patch new file mode 100644 index 00000000000..b4e2e7fc755 --- /dev/null +++ b/patches/chromium/breakpad_treat_node_processes_as_browser_processes.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jeremy Apthorp +Date: Thu, 30 Apr 2020 17:04:13 -0700 +Subject: breakpad: treat node processes as browser processes + +On Linux, to avoid the need to pass breakpad FDs to child node processes +spawned by child_process.fork(), each child process must re-initialize +breakpad independently, as a "browser" process. This patches +//components/crash so that it will correctly report 'ptype=node' as a +crash annotation. + +diff --git a/components/crash/core/app/breakpad_linux.cc b/components/crash/core/app/breakpad_linux.cc +index 390cca9edc26d3153c8dbc86024cb50097d7ac78..2ae2429e6054f2d54e785c49a49fc243469bb72b 100644 +--- a/components/crash/core/app/breakpad_linux.cc ++++ b/components/crash/core/app/breakpad_linux.cc +@@ -722,8 +722,13 @@ bool CrashDone(const MinidumpDescriptor& minidump, + log_path[log_path_len] = '\0'; + info.log_filename = log_path; + #endif +- info.process_type = "browser"; +- info.process_type_length = 7; ++ if (g_is_node) { ++ info.process_type = "node"; ++ info.process_type_length = 4; ++ } else { ++ info.process_type = "browser"; ++ info.process_type_length = 7; ++ } + info.distro = base::g_linux_distro; + info.distro_length = my_strlen(base::g_linux_distro); + info.upload = upload; +@@ -2050,8 +2055,13 @@ void InitCrashReporter(const std::string& process_type) { + process_type == kWebViewSingleProcessType || + process_type == kBrowserProcessType || + #endif ++ process_type == "node" || + process_type.empty(); + ++ if (process_type == "node") { ++ g_is_node = true; ++ } ++ + std::string upload_url; + if (GetCrashReporterClient()->GetUploadUrl(&upload_url)) + SetUploadURL(upload_url); diff --git a/patches/chromium/crash_allow_setting_more_options.patch b/patches/chromium/crash_allow_setting_more_options.patch new file mode 100644 index 00000000000..d503dfbc12f --- /dev/null +++ b/patches/chromium/crash_allow_setting_more_options.patch @@ -0,0 +1,201 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jeremy Apthorp +Date: Thu, 30 Apr 2020 10:08:06 -0700 +Subject: crash: allow setting more options + +This allows the client of //components/crash to set upload url, +rate-limiting, compression and global annotations. + +This should be upstreamed. + +diff --git a/components/crash/core/app/breakpad_linux.cc b/components/crash/core/app/breakpad_linux.cc +index 192b0a7f137f311abb6a991f997e11f5eba52256..364af690879d79d25d118d5b2fdbaabe21a1c52d 100644 +--- a/components/crash/core/app/breakpad_linux.cc ++++ b/components/crash/core/app/breakpad_linux.cc +@@ -103,9 +103,18 @@ namespace { + // while we do have functions to deal with uint64_t's. + uint64_t g_crash_loop_before_time = 0; + #else +-const char kUploadURL[] = "https://clients2.google.com/cr/report"; ++const char kDefaultUploadURL[] = "https://clients2.google.com/cr/report"; ++char* g_upload_url = nullptr; + #endif + ++void SetUploadURL(const std::string& url) { ++ const size_t url_len = url.size() + 1; ++ DCHECK(!g_upload_url); ++ g_upload_url = new char[url_len]; ++ strncpy(g_upload_url, url.c_str(), url_len); ++} ++ ++bool g_is_node = false; + bool g_is_crash_reporter_enabled = false; + uint64_t g_process_start_time = 0; + pid_t g_pid = 0; +@@ -1398,13 +1407,15 @@ void ExecUploadProcessOrTerminate(const BreakpadInfo& info, + char* status_fd_path = + StringFromPrefixAndUint("/dev/fd/", upload_status_fd, allocator); + ++ const char* upload_url = g_upload_url ? g_upload_url : kDefaultUploadURL; ++ + static const char kWgetBinary[] = "/usr/bin/wget"; + const char* args[] = { + kWgetBinary, + header_content_encoding, + header_content_type, + post_file, +- kUploadURL, ++ upload_url, + "--timeout=10", // Set a timeout so we don't hang forever. + "--tries=1", // Don't retry if the upload fails. + "-O", // Output reply to the file descriptor path. +@@ -2037,6 +2048,10 @@ void InitCrashReporter(const std::string& process_type) { + #endif + process_type.empty(); + ++ std::string upload_url; ++ if (GetCrashReporterClient()->GetUploadUrl(&upload_url)) ++ SetUploadURL(upload_url); ++ + if (is_browser_process) { + bool enable_breakpad = GetCrashReporterClient()->GetCollectStatsConsent() || + GetCrashReporterClient()->IsRunningUnattended(); +diff --git a/components/crash/core/app/crash_reporter_client.cc b/components/crash/core/app/crash_reporter_client.cc +index e778f68af30f8c071d1360d06b553cc957160c5b..b7f6ca60ef9c2367989d39e1268bf9f9a517951c 100644 +--- a/components/crash/core/app/crash_reporter_client.cc ++++ b/components/crash/core/app/crash_reporter_client.cc +@@ -148,6 +148,17 @@ bool CrashReporterClient::ReportingIsEnforcedByPolicy(bool* breakpad_enabled) { + return false; + } + ++bool CrashReporterClient::GetShouldRateLimit() { ++ return true; ++} ++ ++bool CrashReporterClient::GetShouldCompressUploads() { ++ return true; ++} ++ ++void CrashReporterClient::GetProcessSimpleAnnotations(std::map* annotations) { ++} ++ + #if defined(OS_ANDROID) + unsigned int CrashReporterClient::GetCrashDumpPercentage() { + return 100; +@@ -196,6 +207,10 @@ void CrashReporterClient::GetSanitizationInformation( + } + #endif + ++bool CrashReporterClient::GetUploadUrl(std::string* url) { ++ return false; ++} ++ + bool CrashReporterClient::ShouldMonitorCrashHandlerExpensively() { + return false; + } +diff --git a/components/crash/core/app/crash_reporter_client.h b/components/crash/core/app/crash_reporter_client.h +index 9cc78fc2584061d26fd1e83b3ebf5ada0a12c138..aa63d7b95c37e4a143283450798b8bd500dc17a1 100644 +--- a/components/crash/core/app/crash_reporter_client.h ++++ b/components/crash/core/app/crash_reporter_client.h +@@ -5,6 +5,7 @@ + #ifndef COMPONENTS_CRASH_CORE_APP_CRASH_REPORTER_CLIENT_H_ + #define COMPONENTS_CRASH_CORE_APP_CRASH_REPORTER_CLIENT_H_ + ++#include + #include + + #include "base/strings/string16.h" +@@ -153,6 +154,19 @@ class CrashReporterClient { + // that case, |breakpad_enabled| is set to the value enforced by policies. + virtual bool ReportingIsEnforcedByPolicy(bool* breakpad_enabled); + ++ // Returns true if crash uploads should be rate limited. If false, no ++ // throttling will be applied for uploads. ++ virtual bool GetShouldRateLimit(); ++ ++ // Returns true if crash uploads should be compressed with gzip. If false, ++ // reports will be uploaded uncompressed. ++ virtual bool GetShouldCompressUploads(); ++ ++ // Allows the client to add or edit global annotations passed to the crashpad ++ // handler. ++ virtual void GetProcessSimpleAnnotations( ++ std::map* annotations); ++ + #if defined(OS_ANDROID) + // Used by WebView to sample crashes without generating the unwanted dumps. If + // the returned value is less than 100, crash dumping will be sampled to that +@@ -194,6 +208,9 @@ class CrashReporterClient { + bool* sanitize_stacks); + #endif + ++ // Override the default upload url. Returns true if overridden. ++ virtual bool GetUploadUrl(std::string* url); ++ + // This method should return true to configure a crash reporter capable of + // monitoring itself for its own crashes to do so, even if self-monitoring + // would be expensive. "Expensive" self-monitoring dedicates an additional +diff --git a/components/crash/core/app/crashpad_mac.mm b/components/crash/core/app/crashpad_mac.mm +index b579521d55860823722df2ee849f6b1628b3c950..f4f71e5174cf8fb706a2f8385252ba877d1a03a7 100644 +--- a/components/crash/core/app/crashpad_mac.mm ++++ b/components/crash/core/app/crashpad_mac.mm +@@ -67,6 +67,8 @@ std::map GetProcessSimpleAnnotations() { + } // @autoreleasepool + return process_annotations; + }(); ++ CrashReporterClient* crash_reporter_client = GetCrashReporterClient(); ++ crash_reporter_client->GetProcessSimpleAnnotations(&annotations); + return annotations; + } + +@@ -140,9 +142,17 @@ base::FilePath PlatformCrashpadInitialization( + #else + std::string url; + #endif ++ crash_reporter_client->GetUploadUrl(&url); + + std::vector arguments; + ++ if (!crash_reporter_client->GetShouldRateLimit()) { ++ arguments.push_back("--no-rate-limit"); ++ } ++ if (!crash_reporter_client->GetShouldCompressUploads()) { ++ arguments.push_back("--no-upload-gzip"); ++ } ++ + if (crash_reporter_client->ShouldMonitorCrashHandlerExpensively()) { + arguments.push_back("--monitor-self"); + } +diff --git a/components/crash/core/app/crashpad_win.cc b/components/crash/core/app/crashpad_win.cc +index 669f5bea844d75f0e5c34b58994f4cfb8e856af0..8c1fb8fb8f2e02466b51ef08de25b056f8b2052d 100644 +--- a/components/crash/core/app/crashpad_win.cc ++++ b/components/crash/core/app/crashpad_win.cc +@@ -84,12 +84,14 @@ base::FilePath PlatformCrashpadInitialization( + + std::map process_annotations; + GetPlatformCrashpadAnnotations(&process_annotations); ++ crash_reporter_client->GetProcessSimpleAnnotations(&process_annotations); + + #if BUILDFLAG(GOOGLE_CHROME_BRANDING) + std::string url = "https://clients2.google.com/cr/report"; + #else + std::string url; + #endif ++ crash_reporter_client->GetUploadUrl(&url); + + // Allow the crash server to be overridden for testing. If the variable + // isn't present in the environment then the default URL will remain. +@@ -126,6 +128,13 @@ base::FilePath PlatformCrashpadInitialization( + + std::vector arguments(start_arguments); + ++ if (!crash_reporter_client->GetShouldRateLimit()) { ++ arguments.push_back("--no-rate-limit"); ++ } ++ if (!crash_reporter_client->GetShouldCompressUploads()) { ++ arguments.push_back("--no-upload-gzip"); ++ } ++ + if (crash_reporter_client->ShouldMonitorCrashHandlerExpensively()) { + arguments.push_back("--monitor-self"); + for (const std::string& start_argument : start_arguments) { diff --git a/patches/chromium/upload_list_add_loadsync_method.patch b/patches/chromium/upload_list_add_loadsync_method.patch new file mode 100644 index 00000000000..45d14269381 --- /dev/null +++ b/patches/chromium/upload_list_add_loadsync_method.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jeremy Apthorp +Date: Fri, 1 May 2020 18:25:59 -0700 +Subject: upload_list: add LoadSync method + +This allows synchronous loading of the upload list, which is required by +the crashReporter.getUploadedReports() API. The synchronous version is +deprecated, and this API should be removed once the deprecated behavior +is no longer supported. + +diff --git a/components/upload_list/upload_list.cc b/components/upload_list/upload_list.cc +index e39549c9bb552dc19cad3685629406fdbe44c2c7..428b0f486767b630fc42e0e19c8d63bdb2d04cbc 100644 +--- a/components/upload_list/upload_list.cc ++++ b/components/upload_list/upload_list.cc +@@ -71,6 +71,10 @@ void UploadList::Load(base::OnceClosure callback) { + base::BindOnce(&UploadList::OnLoadComplete, this)); + } + ++void UploadList::LoadSync() { ++ uploads_ = LoadUploadList(); ++} ++ + void UploadList::Clear(const base::Time& begin, + const base::Time& end, + base::OnceClosure callback) { +diff --git a/components/upload_list/upload_list.h b/components/upload_list/upload_list.h +index 20358339df63ae2bb8b955870c5daf51b65f19f7..7cf89626bea8ee9436f15366446f053a479ac438 100644 +--- a/components/upload_list/upload_list.h ++++ b/components/upload_list/upload_list.h +@@ -73,6 +73,8 @@ class UploadList : public base::RefCountedThreadSafe { + // overwrite the previously supplied one, and the first will not be called. + void Load(base::OnceClosure callback); + ++ void LoadSync(); ++ + // Clears any data associated with the upload list, where the upload time or + // capture time falls within the given range. + void Clear(const base::Time& begin, diff --git a/patches/node/.patches b/patches/node/.patches index 2bb0178f0b6..e34ab7472d2 100644 --- a/patches/node/.patches +++ b/patches/node/.patches @@ -20,7 +20,6 @@ fix_key_gen_apis_are_not_available_in_boringssl.patch build_modify_js2c_py_to_allow_injection_of_original-fs_and_custom_embedder_js.patch refactor_allow_embedder_overriding_of_internal_fs_calls.patch chore_prevent_warn_non_context-aware_native_modules_being_loaded.patch -inherit_electron_crashpad_pipe_name_in_child_process.patch chore_read_nobrowserglobals_from_global_not_process.patch build_bring_back_node_with_ltcg_configuration.patch revert_crypto_add_oaeplabel_option.patch diff --git a/patches/node/inherit_electron_crashpad_pipe_name_in_child_process.patch b/patches/node/inherit_electron_crashpad_pipe_name_in_child_process.patch deleted file mode 100644 index 5c401bfc4f3..00000000000 --- a/patches/node/inherit_electron_crashpad_pipe_name_in_child_process.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Cheng Zhao -Date: Tue, 4 Jun 2019 17:42:11 +0900 -Subject: Inherit ELECTRON_CRASHPAD_PIPE_NAME in child process - -This is required for crashReporter to work correctly in node process. - -diff --git a/lib/child_process.js b/lib/child_process.js -index 5ed166e1ed76b830c2d97f8170a4a72841201537..c919527a7c06f87fc5220cb7234368c4686563a3 100644 ---- a/lib/child_process.js -+++ b/lib/child_process.js -@@ -115,6 +115,10 @@ function fork(modulePath /* , args, options */) { - options.env = Object.create(options.env || process.env) - options.env.ELECTRON_RUN_AS_NODE = 1; - -+ if (process.platform === 'win32') { -+ options.env.ELECTRON_CRASHPAD_PIPE_NAME = process.env.ELECTRON_CRASHPAD_PIPE_NAME; -+ } -+ - if (!options.execPath && process.type && process.platform == 'darwin') { - options.execPath = process.helperExecPath; - } diff --git a/script/zip_manifests/dist_zip.mac.x64.manifest b/script/zip_manifests/dist_zip.mac.x64.manifest index bb357ceb94b..28834fb1852 100644 --- a/script/zip_manifests/dist_zip.mac.x64.manifest +++ b/script/zip_manifests/dist_zip.mac.x64.manifest @@ -3,11 +3,14 @@ Electron.app/Contents/ Electron.app/Contents/Frameworks/ Electron.app/Contents/Frameworks/Electron Framework.framework/ Electron.app/Contents/Frameworks/Electron Framework.framework/Electron Framework +Electron.app/Contents/Frameworks/Electron Framework.framework/Helpers Electron.app/Contents/Frameworks/Electron Framework.framework/Libraries Electron.app/Contents/Frameworks/Electron Framework.framework/Resources Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/ Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/ Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework +Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/ +Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/chrome_crashpad_handler Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/ Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libGLESv2.dylib @@ -31,7 +34,6 @@ Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resourc Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/ca.lproj/locale.pak Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/chrome_100_percent.pak Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/chrome_200_percent.pak -Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/crashpad_handler Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/cs.lproj/ Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/cs.lproj/locale.pak Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/da.lproj/ diff --git a/script/zip_manifests/dist_zip.mac_mas.x64.manifest b/script/zip_manifests/dist_zip.mac_mas.x64.manifest index df05fa1caee..19fbaebd855 100644 --- a/script/zip_manifests/dist_zip.mac_mas.x64.manifest +++ b/script/zip_manifests/dist_zip.mac_mas.x64.manifest @@ -3,6 +3,7 @@ Electron.app/Contents/ Electron.app/Contents/Frameworks/ Electron.app/Contents/Frameworks/Electron Framework.framework/ Electron.app/Contents/Frameworks/Electron Framework.framework/Electron Framework +Electron.app/Contents/Frameworks/Electron Framework.framework/Helpers Electron.app/Contents/Frameworks/Electron Framework.framework/Libraries Electron.app/Contents/Frameworks/Electron Framework.framework/Resources Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/ diff --git a/shell/app/electron_content_client.cc b/shell/app/electron_content_client.cc index 89d1b756c7c..7e9730cb969 100644 --- a/shell/app/electron_content_client.cc +++ b/shell/app/electron_content_client.cc @@ -19,7 +19,7 @@ #include "electron/buildflags/buildflags.h" #include "extensions/common/constants.h" #include "ppapi/buildflags/buildflags.h" -#include "shell/browser/electron_paths.h" +#include "shell/common/electron_paths.h" #include "shell/common/options_switches.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" diff --git a/shell/app/electron_crash_reporter_client.cc b/shell/app/electron_crash_reporter_client.cc new file mode 100644 index 00000000000..5c211396e30 --- /dev/null +++ b/shell/app/electron_crash_reporter_client.cc @@ -0,0 +1,209 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "shell/app/electron_crash_reporter_client.h" + +#include +#include +#include + +#include "base/command_line.h" +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "build/build_config.h" +#include "chrome/common/chrome_paths.h" +#include "components/crash/core/common/crash_keys.h" +#include "components/upload_list/crash_upload_list.h" +#include "content/public/common/content_switches.h" +#include "electron/electron_version.h" +#include "services/service_manager/embedder/switches.h" +#include "shell/common/electron_paths.h" + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +#include "components/version_info/version_info_values.h" +#endif + +#if defined(OS_POSIX) +#include "base/debug/dump_without_crashing.h" +#endif + +namespace { + +ElectronCrashReporterClient* Instance() { + static base::NoDestructor crash_client; + return crash_client.get(); +} + +} // namespace + +// static +void ElectronCrashReporterClient::Create() { + crash_reporter::SetCrashReporterClient(Instance()); + + // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate + // location to write crash dumps can be set. + std::unique_ptr env(base::Environment::Create()); + std::string alternate_crash_dump_location; + base::FilePath crash_dumps_dir_path; + if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) { + crash_dumps_dir_path = + base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location); + } + if (!crash_dumps_dir_path.empty()) { + base::ThreadRestrictions::ScopedAllowIO allow_io; + base::PathService::Override(electron::DIR_CRASH_DUMPS, + crash_dumps_dir_path); + } +} + +// static +ElectronCrashReporterClient* ElectronCrashReporterClient::Get() { + return Instance(); +} + +void ElectronCrashReporterClient::SetCollectStatsConsent(bool upload_allowed) { + collect_stats_consent_ = upload_allowed; +} + +void ElectronCrashReporterClient::SetUploadUrl(const std::string& url) { + upload_url_ = url; +} + +void ElectronCrashReporterClient::SetShouldRateLimit(bool rate_limit) { + rate_limit_ = rate_limit; +} + +void ElectronCrashReporterClient::SetShouldCompressUploads(bool compress) { + compress_uploads_ = compress; +} + +void ElectronCrashReporterClient::SetGlobalAnnotations( + const std::map& annotations) { + global_annotations_ = annotations; +} + +ElectronCrashReporterClient::ElectronCrashReporterClient() {} + +ElectronCrashReporterClient::~ElectronCrashReporterClient() {} + +#if defined(OS_LINUX) +void ElectronCrashReporterClient::SetCrashReporterClientIdFromGUID( + const std::string& client_guid) { + crash_keys::SetMetricsClientIdFromGUID(client_guid); +} +void ElectronCrashReporterClient::GetProductNameAndVersion( + const char** product_name, + const char** version) { + DCHECK(product_name); + DCHECK(version); + *product_name = ELECTRON_PRODUCT_NAME; + *version = ELECTRON_VERSION_STRING; +} + +void ElectronCrashReporterClient::GetProductNameAndVersion( + std::string* product_name, + std::string* version, + std::string* channel) { + const char* c_product_name; + const char* c_version; + GetProductNameAndVersion(&c_product_name, &c_version); + *product_name = c_product_name; + *version = c_version; + *channel = ""; +} + +base::FilePath ElectronCrashReporterClient::GetReporterLogFilename() { + return base::FilePath(CrashUploadList::kReporterLogFilename); +} +#endif + +#if defined(OS_WIN) +void ElectronCrashReporterClient::GetProductNameAndVersion( + const base::string16& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name) { + *product_name = base::UTF8ToUTF16(ELECTRON_PRODUCT_NAME); + *version = base::UTF8ToUTF16(ELECTRON_VERSION_STRING); +} +#endif + +#if defined(OS_WIN) +bool ElectronCrashReporterClient::GetCrashDumpLocation( + base::string16* crash_dir_str) { + base::FilePath crash_dir; + if (!base::PathService::Get(electron::DIR_CRASH_DUMPS, &crash_dir)) + return false; + *crash_dir_str = crash_dir.value(); + return true; +} +#else +bool ElectronCrashReporterClient::GetCrashDumpLocation( + base::FilePath* crash_dir) { + return base::PathService::Get(electron::DIR_CRASH_DUMPS, crash_dir); +} +#endif + +#if defined(OS_MACOSX) || defined(OS_LINUX) +bool ElectronCrashReporterClient::GetCrashMetricsLocation( + base::FilePath* metrics_dir) { + return base::PathService::Get(electron::DIR_USER_DATA, metrics_dir); +} +#endif // OS_MACOSX || OS_LINUX + +bool ElectronCrashReporterClient::IsRunningUnattended() { + return false; +} + +bool ElectronCrashReporterClient::GetCollectStatsConsent() { + return collect_stats_consent_; +} + +#if defined(OS_MACOSX) +bool ElectronCrashReporterClient::ReportingIsEnforcedByPolicy( + bool* breakpad_enabled) { + return false; +} +#endif + +bool ElectronCrashReporterClient::GetShouldRateLimit() { + return rate_limit_; +} + +bool ElectronCrashReporterClient::GetShouldCompressUploads() { + return compress_uploads_; +} + +void ElectronCrashReporterClient::GetProcessSimpleAnnotations( + std::map* annotations) { + *annotations = global_annotations_; + (*annotations)["prod"] = ELECTRON_PRODUCT_NAME; + (*annotations)["ver"] = ELECTRON_VERSION_STRING; +} + +#if defined(OS_LINUX) || defined(OS_MACOSX) +bool ElectronCrashReporterClient::ShouldMonitorCrashHandlerExpensively() { + return false; +} +#endif // OS_LINUX + +bool ElectronCrashReporterClient::GetUploadUrl(std::string* url) { + *url = upload_url_; + return true; +} + +bool ElectronCrashReporterClient::EnableBreakpadForProcess( + const std::string& process_type) { + return process_type == switches::kRendererProcess || + process_type == switches::kPpapiPluginProcess || + process_type == service_manager::switches::kZygoteProcess || + process_type == switches::kGpuProcess || + process_type == switches::kUtilityProcess || process_type == "node"; +} diff --git a/shell/app/electron_crash_reporter_client.h b/shell/app/electron_crash_reporter_client.h new file mode 100644 index 00000000000..0083301b584 --- /dev/null +++ b/shell/app/electron_crash_reporter_client.h @@ -0,0 +1,96 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHELL_APP_ELECTRON_CRASH_REPORTER_CLIENT_H_ +#define SHELL_APP_ELECTRON_CRASH_REPORTER_CLIENT_H_ + +#include +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/no_destructor.h" +#include "build/build_config.h" +#include "components/crash/core/app/crash_reporter_client.h" + +class ElectronCrashReporterClient : public crash_reporter::CrashReporterClient { + public: + static void Create(); + + static ElectronCrashReporterClient* Get(); + void SetCollectStatsConsent(bool upload_allowed); + void SetUploadUrl(const std::string& url); + void SetShouldRateLimit(bool rate_limit); + void SetShouldCompressUploads(bool compress_uploads); + void SetGlobalAnnotations( + const std::map& annotations); + + // crash_reporter::CrashReporterClient implementation. +#if defined(OS_LINUX) + void SetCrashReporterClientIdFromGUID( + const std::string& client_guid) override; + void GetProductNameAndVersion(const char** product_name, + const char** version) override; + void GetProductNameAndVersion(std::string* product_name, + std::string* version, + std::string* channel) override; + base::FilePath GetReporterLogFilename() override; +#endif + +#if defined(OS_WIN) + void GetProductNameAndVersion(const base::string16& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name) override; +#endif + +#if defined(OS_WIN) + bool GetCrashDumpLocation(base::string16* crash_dir) override; +#else + bool GetCrashDumpLocation(base::FilePath* crash_dir) override; +#endif + +#if defined(OS_MACOSX) || defined(OS_LINUX) + bool GetCrashMetricsLocation(base::FilePath* metrics_dir) override; +#endif + + bool IsRunningUnattended() override; + + bool GetCollectStatsConsent() override; + + bool GetShouldRateLimit() override; + bool GetShouldCompressUploads() override; + + void GetProcessSimpleAnnotations( + std::map* annotations) override; + +#if defined(OS_MACOSX) + bool ReportingIsEnforcedByPolicy(bool* breakpad_enabled) override; +#endif + +#if defined(OS_MACOSX) || defined(OS_LINUX) + bool ShouldMonitorCrashHandlerExpensively() override; +#endif + + bool EnableBreakpadForProcess(const std::string& process_type) override; + + bool GetUploadUrl(std::string* url) override; + + private: + friend class base::NoDestructor; + + std::string upload_url_; + bool collect_stats_consent_; + bool rate_limit_ = false; + bool compress_uploads_ = false; + std::map global_annotations_; + + ElectronCrashReporterClient(); + ~ElectronCrashReporterClient() override; + + DISALLOW_COPY_AND_ASSIGN(ElectronCrashReporterClient); +}; + +#endif // SHELL_APP_ELECTRON_CRASH_REPORTER_CLIENT_H_ diff --git a/shell/app/electron_main.cc b/shell/app/electron_main.cc index 6982c4eb6cb..3cd1f2bcb8d 100644 --- a/shell/app/electron_main.cc +++ b/shell/app/electron_main.cc @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #if defined(OS_WIN) @@ -21,11 +23,15 @@ #include "base/process/launch.h" #include "base/strings/utf_string_conversions.h" #include "base/win/windows_version.h" +#include "components/browser_watcher/exit_code_watcher_win.h" +#include "components/crash/core/app/crash_switches.h" +#include "components/crash/core/app/run_as_crashpad_handler_win.h" #include "content/public/app/sandbox_helper_win.h" #include "sandbox/win/src/sandbox_types.h" #include "shell/app/command_line_args.h" #include "shell/app/electron_main_delegate.h" -#include "shell/common/crash_reporter/win/crash_service_main.h" +#include "third_party/crashpad/crashpad/util/win/initial_client_data.h" + #elif defined(OS_LINUX) // defined(OS_WIN) #include #include @@ -51,6 +57,13 @@ namespace { +#if defined(OS_WIN) +// Redefined here so we don't have to introduce a dependency on //content +// from //electron:electron_app +const char kUserDataDir[] = "user-data-dir"; +const char kProcessType[] = "type"; +#endif + ALLOW_UNUSED_TYPE bool IsEnvSet(const char* name) { #if defined(OS_WIN) size_t required_size; @@ -138,10 +151,49 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { #endif base::CommandLine::Init(argv.size(), argv.data()); - const base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess(); - if (cmd_line.GetSwitchValueASCII("type") == - crash_reporter::kCrashpadProcess) { - return crash_service::Main(&argv); + const base::CommandLine* command_line = + base::CommandLine::ForCurrentProcess(); + + const std::string process_type = + command_line->GetSwitchValueASCII(kProcessType); + + if (process_type == crash_reporter::switches::kCrashpadHandler) { + // Check if we should monitor the exit code of this process + std::unique_ptr exit_code_watcher; + + // Retrieve the client process from the command line + crashpad::InitialClientData initial_client_data; + if (initial_client_data.InitializeFromString( + command_line->GetSwitchValueASCII("initial-client-data"))) { + // Setup exit code watcher to monitor the parent process + HANDLE duplicate_handle = INVALID_HANDLE_VALUE; + if (DuplicateHandle( + ::GetCurrentProcess(), initial_client_data.client_process(), + ::GetCurrentProcess(), &duplicate_handle, + PROCESS_QUERY_INFORMATION, FALSE, DUPLICATE_SAME_ACCESS)) { + base::Process parent_process(duplicate_handle); + exit_code_watcher = + std::make_unique(); + if (exit_code_watcher->Initialize(std::move(parent_process))) { + exit_code_watcher->StartWatching(); + } + } + } + + // The handler process must always be passed the user data dir on the + // command line. + DCHECK(command_line->HasSwitch(kUserDataDir)); + + base::FilePath user_data_dir = + command_line->GetSwitchValuePath(kUserDataDir); + int crashpad_status = crash_reporter::RunAsCrashpadHandler( + *command_line, user_data_dir, kProcessType, kUserDataDir); + if (crashpad_status != 0 && exit_code_watcher) { + // Crashpad failed to initialize, explicitly stop the exit code watcher + // so the crashpad-handler process can exit with an error + exit_code_watcher->StopWatching(); + } + return crashpad_status; } if (!electron::CheckCommandLineArguments(arguments.argc, arguments.argv)) diff --git a/shell/app/electron_main_delegate.cc b/shell/app/electron_main_delegate.cc index 3e608d60316..a9515c068a8 100644 --- a/shell/app/electron_main_delegate.cc +++ b/shell/app/electron_main_delegate.cc @@ -15,11 +15,16 @@ #include "base/command_line.h" #include "base/debug/stack_trace.h" #include "base/environment.h" +#include "base/files/file_util.h" #include "base/logging.h" #include "base/mac/bundle_locations.h" #include "base/path_service.h" +#include "base/strings/string_split.h" #include "chrome/common/chrome_paths.h" #include "components/content_settings/core/common/content_settings_pattern.h" +#include "components/crash/core/app/crashpad.h" +#include "components/crash/core/common/crash_key.h" +#include "components/crash/core/common/crash_keys.h" #include "content/public/common/content_switches.h" #include "electron/buildflags/buildflags.h" #include "extensions/common/constants.h" @@ -28,10 +33,14 @@ #include "services/service_manager/sandbox/switches.h" #include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h" #include "shell/app/electron_content_client.h" +#include "shell/app/electron_crash_reporter_client.h" +#include "shell/browser/api/electron_api_crash_reporter.h" #include "shell/browser/electron_browser_client.h" #include "shell/browser/electron_gpu_client.h" #include "shell/browser/feature_list.h" #include "shell/browser/relauncher.h" +#include "shell/common/crash_keys.h" +#include "shell/common/electron_paths.h" #include "shell/common/options_switches.h" #include "shell/renderer/electron_renderer_client.h" #include "shell/renderer/electron_sandboxed_renderer_client.h" @@ -46,9 +55,13 @@ #if defined(OS_WIN) #include "base/win/win_util.h" -#if defined(_WIN64) -#include "shell/common/crash_reporter/crash_reporter_win.h" +#include "chrome/child/v8_crashpad_support_win.h" #endif + +#if defined(OS_LINUX) +#include "components/crash/core/app/breakpad_linux.h" +#include "v8/include/v8-wasm-trap-handler-posix.h" +#include "v8/include/v8.h" #endif namespace electron { @@ -71,7 +84,7 @@ bool IsSandboxEnabled(base::CommandLine* command_line) { // and resources loaded. bool SubprocessNeedsResourceBundle(const std::string& process_type) { return -#if defined(OS_POSIX) && !defined(OS_MACOSX) +#if defined(OS_LINUX) // The zygote process opens the resources for the renderers. process_type == service_manager::switches::kZygoteProcess || #endif @@ -98,6 +111,41 @@ void InvalidParameterHandler(const wchar_t*, } // namespace +// TODO(nornagon): move path provider overriding to its own file in +// shell/common +namespace electron { + +bool GetDefaultCrashDumpsPath(base::FilePath* path) { + base::FilePath cur; + if (!base::PathService::Get(DIR_USER_DATA, &cur)) + return false; +#if defined(OS_MACOSX) || defined(OS_WIN) + cur = cur.Append(FILE_PATH_LITERAL("Crashpad")); +#else + cur = cur.Append(FILE_PATH_LITERAL("Crash Reports")); +#endif + // TODO(bauerb): http://crbug.com/259796 + base::ThreadRestrictions::ScopedAllowIO allow_io; + if (!base::PathExists(cur) && !base::CreateDirectory(cur)) + return false; + *path = cur; + return true; +} + +bool ElectronPathProvider(int key, base::FilePath* path) { + if (key == DIR_CRASH_DUMPS) { + return GetDefaultCrashDumpsPath(path); + } + return false; +} + +void RegisterPathProvider() { + base::PathService::RegisterProvider(ElectronPathProvider, PATH_START, + PATH_END); +} + +} // namespace electron + void LoadResourceBundle(const std::string& locale) { const bool initialized = ui::ResourceBundle::HasSharedInstance(); if (initialized) @@ -134,9 +182,7 @@ bool ElectronMainDelegate::BasicStartupComplete(int* exit_code) { logging::LoggingSettings settings; #if defined(OS_WIN) -#if defined(_WIN64) - crash_reporter::CrashReporterWin::SetUnhandledExceptionFilter(); -#endif + v8_crashpad_support::SetUp(); // On Windows the terminal returns immediately, so we add a new line to // prevent output in the same line as the prompt. @@ -185,6 +231,8 @@ bool ElectronMainDelegate::BasicStartupComplete(int* exit_code) { tracing::TracingSamplerProfiler::CreateOnMainThread(); chrome::RegisterPathProvider(); + electron::RegisterPathProvider(); + #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) ContentSettingsPattern::SetNonWildcardDomainNonPortSchemes( kNonWildcardDomainNonPortSchemes, kNonWildcardDomainNonPortSchemesSize); @@ -272,6 +320,10 @@ void ElectronMainDelegate::PreSandboxStartup() { std::string process_type = command_line->GetSwitchValueASCII(::switches::kProcessType); +#if !defined(MAS_BUILD) + crash_reporter::InitializeCrashKeys(); +#endif + // Initialize ResourceBundle which handles files loaded from external // sources. The language should have been passed in to us from the // browser process as a command line flag. @@ -280,17 +332,40 @@ void ElectronMainDelegate::PreSandboxStartup() { LoadResourceBundle(locale); } - // Only append arguments for browser process. - if (!IsBrowserProcess(command_line)) - return; +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(MAS_BUILD)) + // In the main process, we wait for JS to call crashReporter.start() before + // initializing crashpad. If we're in the renderer, we want to initialize it + // immediately at boot. + if (!process_type.empty()) { + ElectronCrashReporterClient::Create(); + crash_reporter::InitializeCrashpad(false, process_type); + } +#endif - // Allow file:// URIs to read other file:// URIs by default. - command_line->AppendSwitch(::switches::kAllowFileAccessFromFiles); +#if defined(OS_LINUX) + if (process_type != service_manager::switches::kZygoteProcess && + !process_type.empty()) { + ElectronCrashReporterClient::Create(); + breakpad::InitCrashReporter(process_type); + } +#endif + +#if !defined(MAS_BUILD) + crash_keys::SetCrashKeysFromCommandLine(*command_line); + crash_keys::SetPlatformCrashKey(); +#endif + + if (IsBrowserProcess(command_line)) { + // Only append arguments for browser process. + + // Allow file:// URIs to read other file:// URIs by default. + command_line->AppendSwitch(::switches::kAllowFileAccessFromFiles); #if defined(OS_MACOSX) - // Enable AVFoundation. - command_line->AppendSwitch("enable-avfoundation"); + // Enable AVFoundation. + command_line->AppendSwitch("enable-avfoundation"); #endif + } } void ElectronMainDelegate::PreCreateMainMessageLoop() { @@ -350,4 +425,20 @@ bool ElectronMainDelegate::ShouldLockSchemeRegistry() { return false; } +#if defined(OS_LINUX) +void ElectronMainDelegate::ZygoteForked() { + // Needs to be called after we have DIR_USER_DATA. BrowserMain sets + // this up for the browser process in a different manner. + ElectronCrashReporterClient::Create(); + const base::CommandLine* command_line = + base::CommandLine::ForCurrentProcess(); + std::string process_type = + command_line->GetSwitchValueASCII(::switches::kProcessType); + breakpad::InitCrashReporter(process_type); + + // Reset the command line for the newly spawned process. + crash_keys::SetCrashKeysFromCommandLine(*command_line); +} +#endif // defined(OS_LINUX) + } // namespace electron diff --git a/shell/app/electron_main_delegate.h b/shell/app/electron_main_delegate.h index eefb7a6b335..2bd4fcd143b 100644 --- a/shell/app/electron_main_delegate.h +++ b/shell/app/electron_main_delegate.h @@ -41,6 +41,9 @@ class ElectronMainDelegate : public content::ContentMainDelegate { const content::MainFunctionParams& main_function_params) override; bool ShouldCreateFeatureList() override; bool ShouldLockSchemeRegistry() override; +#if defined(OS_LINUX) + void ZygoteForked() override; +#endif private: #if defined(OS_MACOSX) diff --git a/shell/app/node_main.cc b/shell/app/node_main.cc index 93515f3506f..bc1257758b1 100644 --- a/shell/app/node_main.cc +++ b/shell/app/node_main.cc @@ -4,33 +4,43 @@ #include "shell/app/node_main.h" +#include #include #include #include #include #include +#include "base/base_switches.h" #include "base/command_line.h" #include "base/feature_list.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/task/thread_pool/thread_pool_instance.h" #include "base/threading/thread_task_runner_handle.h" +#include "components/crash/core/app/crashpad.h" +#include "content/public/common/content_switches.h" #include "electron/electron_version.h" #include "gin/array_buffer.h" #include "gin/public/isolate_holder.h" #include "gin/v8_initializer.h" +#include "shell/app/electron_crash_reporter_client.h" #include "shell/app/uv_task_runner.h" +#include "shell/browser/api/electron_api_crash_reporter.h" #include "shell/browser/javascript_environment.h" #include "shell/browser/node_debugger.h" #include "shell/common/api/electron_bindings.h" -#include "shell/common/crash_reporter/crash_reporter.h" +#include "shell/common/crash_keys.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/node_bindings.h" #include "shell/common/node_includes.h" -#if defined(_WIN64) -#include "shell/common/crash_reporter/crash_reporter_win.h" +#if defined(OS_LINUX) +#include "components/crash/core/app/breakpad_linux.h" +#endif + +#if defined(OS_WIN) +#include "chrome/child/v8_crashpad_support_win.h" #endif namespace { @@ -78,19 +88,64 @@ void SetNodeCliFlags() { namespace electron { -#if !defined(OS_LINUX) -void AddExtraParameter(const std::string& key, const std::string& value) { - crash_reporter::CrashReporter::GetInstance()->AddExtraParameter(key, value); -} +#if defined(OS_LINUX) +void CrashReporterStart(gin_helper::Dictionary options) { + std::string submit_url; + bool upload_to_server = true; + bool ignore_system_crash_handler = false; + bool rate_limit = false; + bool compress = false; + std::map global_extra; + std::map extra; + options.Get("submitURL", &submit_url); + options.Get("uploadToServer", &upload_to_server); + options.Get("ignoreSystemCrashHandler", &ignore_system_crash_handler); + options.Get("rateLimit", &rate_limit); + options.Get("compress", &compress); + options.Get("extra", &extra); + options.Get("globalExtra", &global_extra); -void RemoveExtraParameter(const std::string& key) { - crash_reporter::CrashReporter::GetInstance()->RemoveExtraParameter(key); + std::string product_name; + if (options.Get("productName", &product_name)) + global_extra["_productName"] = product_name; + std::string company_name; + if (options.Get("companyName", &company_name)) + global_extra["_companyName"] = company_name; + api::crash_reporter::Start(submit_url, upload_to_server, + ignore_system_crash_handler, rate_limit, compress, + global_extra, extra, true); } #endif +v8::Local GetParameters(v8::Isolate* isolate) { + std::map keys; +#if !defined(MAS_BUILD) + electron::crash_keys::GetCrashKeys(&keys); +#endif + return gin::ConvertToV8(isolate, keys); +} + int NodeMain(int argc, char* argv[]) { base::CommandLine::Init(argc, argv); +#if defined(OS_WIN) + v8_crashpad_support::SetUp(); +#endif + +#if !defined(MAS_BUILD) + ElectronCrashReporterClient::Create(); +#endif + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(MAS_BUILD)) + crash_reporter::InitializeCrashpad(false, "node"); +#endif + +#if !defined(MAS_BUILD) + crash_keys::SetCrashKeysFromCommandLine( + *base::CommandLine::ForCurrentProcess()); + crash_keys::SetPlatformCrashKey(); +#endif + int exit_code = 1; { // Feed gin::PerIsolateData with a task runner. @@ -104,10 +159,6 @@ int NodeMain(int argc, char* argv[]) { feature_list->InitializeFromCommandLine("", ""); base::FeatureList::SetInstance(std::move(feature_list)); -#if defined(_WIN64) - crash_reporter::CrashReporterWin::SetUnhandledExceptionFilter(); -#endif - // We do not want to double-set the error level and promise rejection // callback. node::g_standalone_mode = false; @@ -159,16 +210,18 @@ int NodeMain(int argc, char* argv[]) { #endif process.SetMethod("crash", &ElectronBindings::Crash); - // Setup process.crashReporter.start in child node processes + // Setup process.crashReporter in child node processes gin_helper::Dictionary reporter = gin::Dictionary::CreateEmpty(isolate); - reporter.SetMethod("start", - &crash_reporter::CrashReporter::StartInstance); - -#if !defined(OS_LINUX) - reporter.SetMethod("addExtraParameter", &AddExtraParameter); - reporter.SetMethod("removeExtraParameter", &RemoveExtraParameter); +#if defined(OS_LINUX) + reporter.SetMethod("start", &CrashReporterStart); #endif + reporter.SetMethod("getParameters", &GetParameters); + reporter.SetMethod("addExtraParameter", + &electron::crash_keys::SetCrashKey); + reporter.SetMethod("removeExtraParameter", + &electron::crash_keys::ClearCrashKey); + process.Set("crashReporter", reporter); gin_helper::Dictionary versions; diff --git a/shell/browser/api/electron_api_app.cc b/shell/browser/api/electron_api_app.cc index 702dc902fa0..1ebe6211cf0 100644 --- a/shell/browser/api/electron_api_app.cc +++ b/shell/browser/api/electron_api_app.cc @@ -39,11 +39,11 @@ #include "shell/browser/api/gpuinfo_manager.h" #include "shell/browser/electron_browser_context.h" #include "shell/browser/electron_browser_main_parts.h" -#include "shell/browser/electron_paths.h" #include "shell/browser/login_handler.h" #include "shell/browser/relauncher.h" #include "shell/common/application_info.h" #include "shell/common/electron_command_line.h" +#include "shell/common/electron_paths.h" #include "shell/common/gin_converters/callback_converter.h" #include "shell/common/gin_converters/file_path_converter.h" #include "shell/common/gin_converters/gurl_converter.h" @@ -403,6 +403,8 @@ int GetPathConstant(const std::string& name) { return DIR_USER_CACHE; else if (name == "logs") return DIR_APP_LOGS; + else if (name == "crashDumps") + return DIR_CRASH_DUMPS; else if (name == "home") return base::DIR_HOME; else if (name == "temp") diff --git a/shell/browser/api/electron_api_app_mac.mm b/shell/browser/api/electron_api_app_mac.mm index 4d6bc27377d..c317e9d40a2 100644 --- a/shell/browser/api/electron_api_app_mac.mm +++ b/shell/browser/api/electron_api_app_mac.mm @@ -6,7 +6,7 @@ #include "base/path_service.h" #include "shell/browser/api/electron_api_app.h" -#include "shell/browser/electron_paths.h" +#include "shell/common/electron_paths.h" #import diff --git a/shell/browser/api/electron_api_auto_updater.cc b/shell/browser/api/electron_api_auto_updater.cc index e1d8628e40d..884b2dae5bc 100644 --- a/shell/browser/api/electron_api_auto_updater.cc +++ b/shell/browser/api/electron_api_auto_updater.cc @@ -9,28 +9,12 @@ #include "shell/browser/native_window.h" #include "shell/browser/window_list.h" #include "shell/common/gin_converters/callback_converter.h" +#include "shell/common/gin_converters/time_converter.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/event_emitter_caller.h" #include "shell/common/gin_helper/object_template_builder.h" #include "shell/common/node_includes.h" -namespace gin { - -template <> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const base::Time& val) { - v8::MaybeLocal date = - v8::Date::New(isolate->GetCurrentContext(), val.ToJsTime()); - if (date.IsEmpty()) - return v8::Null(isolate); - else - return date.ToLocalChecked(); - } -}; - -} // namespace gin - namespace electron { namespace api { diff --git a/shell/browser/api/electron_api_crash_reporter.cc b/shell/browser/api/electron_api_crash_reporter.cc new file mode 100644 index 00000000000..cc09c82da8c --- /dev/null +++ b/shell/browser/api/electron_api_crash_reporter.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/browser/api/electron_api_crash_reporter.h" + +#include +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/no_destructor.h" +#include "base/path_service.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "chrome/browser/crash_upload_list/crash_upload_list_crashpad.h" +#include "chrome/common/chrome_paths.h" +#include "components/crash/core/app/crashpad.h" +#include "components/crash/core/common/crash_key.h" +#include "components/upload_list/crash_upload_list.h" +#include "components/upload_list/text_log_upload_list.h" +#include "content/public/common/content_switches.h" +#include "gin/arguments.h" +#include "gin/data_object_builder.h" +#include "services/service_manager/embedder/switches.h" +#include "shell/app/electron_crash_reporter_client.h" +#include "shell/common/crash_keys.h" +#include "shell/common/electron_paths.h" +#include "shell/common/gin_converters/callback_converter.h" +#include "shell/common/gin_converters/file_path_converter.h" +#include "shell/common/gin_converters/time_converter.h" +#include "shell/common/gin_helper/dictionary.h" +#include "shell/common/node_includes.h" +#include "third_party/crashpad/crashpad/client/crashpad_info.h" + +#if defined(OS_LINUX) +#include "components/crash/core/app/breakpad_linux.h" +#include "v8/include/v8-wasm-trap-handler-posix.h" +#include "v8/include/v8.h" +#endif + +namespace { + +#if defined(OS_LINUX) +std::map& GetGlobalCrashKeysMutable() { + static base::NoDestructor> + global_crash_keys; + return *global_crash_keys; +} +#endif // defined(OS_LINUX) + +bool g_crash_reporter_initialized = false; + +} // namespace + +namespace electron { + +namespace api { + +namespace crash_reporter { + +bool IsCrashReporterEnabled() { + return g_crash_reporter_initialized; +} + +#if defined(OS_LINUX) +const std::map& GetGlobalCrashKeys() { + return GetGlobalCrashKeysMutable(); +} +#endif + +void Start(const std::string& submit_url, + bool upload_to_server, + bool ignore_system_crash_handler, + bool rate_limit, + bool compress, + const std::map& global_extra, + const std::map& extra, + bool is_node_process) { +#if !defined(MAS_BUILD) + if (g_crash_reporter_initialized) + return; + g_crash_reporter_initialized = true; + ElectronCrashReporterClient::Create(); + ElectronCrashReporterClient::Get()->SetUploadUrl(submit_url); + ElectronCrashReporterClient::Get()->SetCollectStatsConsent(upload_to_server); + ElectronCrashReporterClient::Get()->SetShouldRateLimit(rate_limit); + ElectronCrashReporterClient::Get()->SetShouldCompressUploads(compress); + ElectronCrashReporterClient::Get()->SetGlobalAnnotations(global_extra); + auto* command_line = base::CommandLine::ForCurrentProcess(); + std::string process_type = + is_node_process + ? "node" + : command_line->GetSwitchValueASCII(::switches::kProcessType); +#if defined(OS_LINUX) + auto& global_crash_keys = GetGlobalCrashKeysMutable(); + for (const auto& pair : global_extra) { + global_crash_keys[pair.first] = pair.second; + } + for (const auto& pair : extra) + electron::crash_keys::SetCrashKey(pair.first, pair.second); + for (const auto& pair : global_extra) + electron::crash_keys::SetCrashKey(pair.first, pair.second); + breakpad::InitCrashReporter(process_type); +#elif defined(OS_MACOSX) + for (const auto& pair : extra) + electron::crash_keys::SetCrashKey(pair.first, pair.second); + ::crash_reporter::InitializeCrashpad(process_type.empty(), process_type); + if (ignore_system_crash_handler) { + crashpad::CrashpadInfo::GetCrashpadInfo() + ->set_system_crash_reporter_forwarding(crashpad::TriState::kDisabled); + } +#elif defined(OS_WIN) + for (const auto& pair : extra) + electron::crash_keys::SetCrashKey(pair.first, pair.second); + base::FilePath user_data_dir; + base::PathService::Get(DIR_USER_DATA, &user_data_dir); + ::crash_reporter::InitializeCrashpadWithEmbeddedHandler( + process_type.empty(), process_type, + base::UTF16ToUTF8(user_data_dir.value()), base::FilePath()); +#endif +#endif +} + +} // namespace crash_reporter + +} // namespace api + +} // namespace electron + +namespace { + +#if defined(MAS_BUILD) +void GetUploadedReports( + base::OnceCallback)> callback) { + std::move(callback).Run(v8::Array::New(v8::Isolate::GetCurrent())); +} +#else +scoped_refptr CreateCrashUploadList() { +#if defined(OS_MACOSX) || defined(OS_WIN) + return new CrashUploadListCrashpad(); +#else + base::FilePath crash_dir_path; + base::PathService::Get(electron::DIR_CRASH_DUMPS, &crash_dir_path); + base::FilePath upload_log_path = + crash_dir_path.AppendASCII(CrashUploadList::kReporterLogFilename); + return new TextLogUploadList(upload_log_path); +#endif // defined(OS_MACOSX) || defined(OS_WIN) +} + +v8::Local GetUploadedReports(v8::Isolate* isolate) { + auto list = CreateCrashUploadList(); + // TODO(nornagon): switch to using Load() instead of LoadSync() once the + // synchronous version of getUploadedReports is deprecated so we can remove + // our patch. + { + base::ThreadRestrictions::ScopedAllowIO allow_io; + list->LoadSync(); + } + std::vector uploads; + constexpr size_t kMaxUploadReportsToList = std::numeric_limits::max(); + list->GetUploads(kMaxUploadReportsToList, &uploads); + std::vector> result; + for (const auto& upload : uploads) { + result.push_back(gin::DataObjectBuilder(isolate) + .Set("date", upload.upload_time) + .Set("id", upload.upload_id) + .Build()); + } + v8::Local v8_result = gin::ConvertToV8(isolate, result); + return v8_result; +} +#endif + +void SetUploadToServer(bool upload) { +#if !defined(MAS_BUILD) + ElectronCrashReporterClient::Get()->SetCollectStatsConsent(upload); +#endif +} + +bool GetUploadToServer() { +#if defined(MAS_BUILD) + return false; +#else + return ElectronCrashReporterClient::Get()->GetCollectStatsConsent(); +#endif +} + +v8::Local GetParameters(v8::Isolate* isolate) { + std::map keys; +#if !defined(MAS_BUILD) + electron::crash_keys::GetCrashKeys(&keys); +#endif + return gin::ConvertToV8(isolate, keys); +} + +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { + gin_helper::Dictionary dict(context->GetIsolate(), exports); + dict.SetMethod("start", &electron::api::crash_reporter::Start); + dict.SetMethod("addExtraParameter", &electron::crash_keys::SetCrashKey); + dict.SetMethod("removeExtraParameter", &electron::crash_keys::ClearCrashKey); + dict.SetMethod("getParameters", &GetParameters); + dict.SetMethod("getUploadedReports", &GetUploadedReports); + dict.SetMethod("setUploadToServer", &SetUploadToServer); + dict.SetMethod("getUploadToServer", &GetUploadToServer); +} + +} // namespace + +NODE_LINKED_MODULE_CONTEXT_AWARE(electron_browser_crash_reporter, Initialize) diff --git a/shell/browser/api/electron_api_crash_reporter.h b/shell/browser/api/electron_api_crash_reporter.h new file mode 100644 index 00000000000..c9a2e759d41 --- /dev/null +++ b/shell/browser/api/electron_api_crash_reporter.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020 Slack Technologies, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef SHELL_BROWSER_API_ELECTRON_API_CRASH_REPORTER_H_ +#define SHELL_BROWSER_API_ELECTRON_API_CRASH_REPORTER_H_ + +#include +#include +#include "base/files/file_path.h" + +namespace electron { + +namespace api { + +namespace crash_reporter { + +bool IsCrashReporterEnabled(); + +#if defined(OS_LINUX) +const std::map& GetGlobalCrashKeys(); +#endif + +// JS bindings API; exposed publicly because it's also called from node_main.cc +void Start(const std::string& submit_url, + bool upload_to_server, + bool ignore_system_crash_handler, + bool rate_limit, + bool compress, + const std::map& global_extra, + const std::map& extra, + bool is_node_process); + +} // namespace crash_reporter + +} // namespace api + +} // namespace electron + +#endif // SHELL_BROWSER_API_ELECTRON_API_CRASH_REPORTER_H_ diff --git a/shell/browser/browser.cc b/shell/browser/browser.cc index eded680b595..5bb6e74e2e7 100644 --- a/shell/browser/browser.cc +++ b/shell/browser/browser.cc @@ -17,11 +17,11 @@ #include "base/threading/thread_task_runner_handle.h" #include "shell/browser/browser_observer.h" #include "shell/browser/electron_browser_main_parts.h" -#include "shell/browser/electron_paths.h" #include "shell/browser/login_handler.h" #include "shell/browser/native_window.h" #include "shell/browser/window_list.h" #include "shell/common/application_info.h" +#include "shell/common/electron_paths.h" #include "shell/common/gin_helper/arguments.h" namespace electron { diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index ca0456b7f03..dab37630425 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -11,6 +11,7 @@ #include #include +#include "base/base_switches.h" #include "base/command_line.h" #include "base/environment.h" #include "base/files/file_util.h" @@ -24,6 +25,7 @@ #include "base/strings/utf_string_conversions.h" #include "base/task/post_task.h" #include "chrome/browser/browser_process.h" +#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_version.h" #include "components/net_log/chrome_net_log.h" #include "components/network_hints/common/network_hints.mojom.h" @@ -37,6 +39,7 @@ #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/site_instance.h" +#include "content/public/common/content_descriptors.h" #include "content/public/common/content_paths.h" #include "content/public/common/content_switches.h" #include "content/public/common/service_names.mojom.h" @@ -47,6 +50,7 @@ #include "extensions/browser/extension_navigation_ui_data.h" #include "extensions/browser/extension_protocols.h" #include "extensions/common/constants.h" +#include "extensions/common/switches.h" #include "net/base/escape.h" #include "net/ssl/ssl_cert_request_info.h" #include "ppapi/buildflags/buildflags.h" @@ -58,6 +62,7 @@ #include "services/service_manager/public/cpp/binder_map.h" #include "shell/app/manifests.h" #include "shell/browser/api/electron_api_app.h" +#include "shell/browser/api/electron_api_crash_reporter.h" #include "shell/browser/api/electron_api_protocol.h" #include "shell/browser/api/electron_api_session.h" #include "shell/browser/api/electron_api_web_contents.h" @@ -67,7 +72,6 @@ #include "shell/browser/electron_browser_context.h" #include "shell/browser/electron_browser_main_parts.h" #include "shell/browser/electron_navigation_throttle.h" -#include "shell/browser/electron_paths.h" #include "shell/browser/electron_quota_permission_context.h" #include "shell/browser/electron_speech_recognition_manager_delegate.h" #include "shell/browser/font_defaults.h" @@ -89,6 +93,7 @@ #include "shell/browser/window_list.h" #include "shell/common/api/api.mojom.h" #include "shell/common/application_info.h" +#include "shell/common/electron_paths.h" #include "shell/common/options_switches.h" #include "shell/common/platform_util.h" #include "third_party/blink/public/common/loader/url_loader_throttle.h" @@ -164,6 +169,14 @@ #include "content/public/common/child_process_host.h" #endif +#if defined(OS_LINUX) +#include "base/debug/leak_annotations.h" +#include "components/crash/content/browser/crash_handler_host_linux.h" +#include "components/crash/core/app/breakpad_linux.h" +#include "components/crash/core/app/crash_switches.h" +#include "components/crash/core/app/crashpad.h" +#endif + using content::BrowserThread; namespace electron { @@ -265,6 +278,64 @@ const extensions::Extension* GetEnabledExtensionFromEffectiveURL( } #endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) +#if defined(OS_LINUX) +breakpad::CrashHandlerHostLinux* CreateCrashHandlerHost( + const std::string& process_type) { + base::FilePath dumps_path; + base::PathService::Get(electron::DIR_CRASH_DUMPS, &dumps_path); + { + ANNOTATE_SCOPED_MEMORY_LEAK; + breakpad::CrashHandlerHostLinux* crash_handler = + new breakpad::CrashHandlerHostLinux(process_type, dumps_path, true); + crash_handler->StartUploaderThread(); + return crash_handler; + } +} + +int GetCrashSignalFD(const base::CommandLine& command_line) { + // Extensions have the same process type as renderers. + if (command_line.HasSwitch(extensions::switches::kExtensionProcess)) { + static breakpad::CrashHandlerHostLinux* crash_handler = nullptr; + if (!crash_handler) + crash_handler = CreateCrashHandlerHost("extension"); + return crash_handler->GetDeathSignalSocket(); + } + + std::string process_type = + command_line.GetSwitchValueASCII(::switches::kProcessType); + + if (process_type == ::switches::kRendererProcess) { + static breakpad::CrashHandlerHostLinux* crash_handler = nullptr; + if (!crash_handler) + crash_handler = CreateCrashHandlerHost(process_type); + return crash_handler->GetDeathSignalSocket(); + } + + if (process_type == ::switches::kPpapiPluginProcess) { + static breakpad::CrashHandlerHostLinux* crash_handler = nullptr; + if (!crash_handler) + crash_handler = CreateCrashHandlerHost(process_type); + return crash_handler->GetDeathSignalSocket(); + } + + if (process_type == ::switches::kGpuProcess) { + static breakpad::CrashHandlerHostLinux* crash_handler = nullptr; + if (!crash_handler) + crash_handler = CreateCrashHandlerHost(process_type); + return crash_handler->GetDeathSignalSocket(); + } + + if (process_type == ::switches::kUtilityProcess) { + static breakpad::CrashHandlerHostLinux* crash_handler = nullptr; + if (!crash_handler) + crash_handler = CreateCrashHandlerHost(process_type); + return crash_handler->GetDeathSignalSocket(); + } + + return -1; +} +#endif // defined(OS_LINUX) + } // namespace // static @@ -649,6 +720,23 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches( std::string process_type = command_line->GetSwitchValueASCII(::switches::kProcessType); +#if defined(OS_LINUX) + bool enable_crash_reporter = false; + enable_crash_reporter = breakpad::IsCrashReporterEnabled(); + if (enable_crash_reporter) { + command_line->AppendSwitch(::switches::kEnableCrashReporter); + std::string switch_value; + for (const auto& pair : api::crash_reporter::GetGlobalCrashKeys()) { + if (!switch_value.empty()) + switch_value += ","; + switch_value += pair.first; + switch_value += "="; + switch_value += pair.second; + } + command_line->AppendSwitchASCII(switches::kGlobalCrashKeys, switch_value); + } +#endif + if (process_type == ::switches::kUtilityProcess || process_type == ::switches::kRendererProcess) { // Copy following switches to child process. @@ -1530,6 +1618,18 @@ void ElectronBrowserClient::RegisterBrowserInterfaceBindersForFrame( #endif } +#if defined(OS_LINUX) +void ElectronBrowserClient::GetAdditionalMappedFilesForChildProcess( + const base::CommandLine& command_line, + int child_process_id, + content::PosixFileDescriptorInfo* mappings) { + int crash_signal_fd = GetCrashSignalFD(command_line); + if (crash_signal_fd >= 0) { + mappings->Share(service_manager::kCrashDumpSignal, crash_signal_fd); + } +} +#endif + std::unique_ptr ElectronBrowserClient::CreateLoginDelegate( const net::AuthChallengeInfo& auth_info, diff --git a/shell/browser/electron_browser_client.h b/shell/browser/electron_browser_client.h index 1ac828b3230..d02aad23065 100644 --- a/shell/browser/electron_browser_client.h +++ b/shell/browser/electron_browser_client.h @@ -71,6 +71,12 @@ class ElectronBrowserClient : public content::ContentBrowserClient, content::RenderFrameHost* render_frame_host, service_manager::BinderMapWithContext* map) override; +#if defined(OS_LINUX) + void GetAdditionalMappedFilesForChildProcess( + const base::CommandLine& command_line, + int child_process_id, + content::PosixFileDescriptorInfo* mappings) override; +#endif std::string GetUserAgent() override; void SetUserAgent(const std::string& user_agent); diff --git a/shell/browser/electron_browser_context.cc b/shell/browser/electron_browser_context.cc index 64cf7bca0a2..7b597af03eb 100644 --- a/shell/browser/electron_browser_context.cc +++ b/shell/browser/electron_browser_context.cc @@ -38,7 +38,6 @@ #include "shell/browser/electron_browser_client.h" #include "shell/browser/electron_browser_main_parts.h" #include "shell/browser/electron_download_manager_delegate.h" -#include "shell/browser/electron_paths.h" #include "shell/browser/electron_permission_manager.h" #include "shell/browser/net/resolve_proxy_helper.h" #include "shell/browser/pref_store_delegate.h" @@ -48,6 +47,7 @@ #include "shell/browser/web_view_manager.h" #include "shell/browser/zoom_level_delegate.h" #include "shell/common/application_info.h" +#include "shell/common/electron_paths.h" #include "shell/common/options_switches.h" #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) diff --git a/shell/browser/electron_browser_main_parts.cc b/shell/browser/electron_browser_main_parts.cc index eeeb48a18ac..c78a19edf6d 100644 --- a/shell/browser/electron_browser_main_parts.cc +++ b/shell/browser/electron_browser_main_parts.cc @@ -37,7 +37,6 @@ #include "shell/browser/browser_process_impl.h" #include "shell/browser/electron_browser_client.h" #include "shell/browser/electron_browser_context.h" -#include "shell/browser/electron_paths.h" #include "shell/browser/electron_web_ui_controller_factory.h" #include "shell/browser/feature_list.h" #include "shell/browser/javascript_environment.h" @@ -47,6 +46,7 @@ #include "shell/common/api/electron_bindings.h" #include "shell/common/application_info.h" #include "shell/common/asar/asar_util.h" +#include "shell/common/electron_paths.h" #include "shell/common/gin_helper/trackable_object.h" #include "shell/common/node_bindings.h" #include "shell/common/node_includes.h" diff --git a/shell/browser/electron_browser_main_parts_mac.mm b/shell/browser/electron_browser_main_parts_mac.mm index 3b6f398d388..26834e65685 100644 --- a/shell/browser/electron_browser_main_parts_mac.mm +++ b/shell/browser/electron_browser_main_parts_mac.mm @@ -7,9 +7,9 @@ #include "base/mac/bundle_locations.h" #include "base/mac/foundation_util.h" #include "base/path_service.h" -#include "shell/browser/electron_paths.h" #import "shell/browser/mac/electron_application.h" #include "shell/browser/mac/electron_application_delegate.h" +#include "shell/common/electron_paths.h" #include "ui/base/l10n/l10n_util_mac.h" namespace electron { diff --git a/shell/browser/ui/devtools_manager_delegate.cc b/shell/browser/ui/devtools_manager_delegate.cc index 4a7d3e96a4c..d691c3ed745 100644 --- a/shell/browser/ui/devtools_manager_delegate.cc +++ b/shell/browser/ui/devtools_manager_delegate.cc @@ -27,7 +27,7 @@ #include "net/base/net_errors.h" #include "net/socket/stream_socket.h" #include "net/socket/tcp_server_socket.h" -#include "shell/browser/electron_paths.h" +#include "shell/common/electron_paths.h" #include "ui/base/resource/resource_bundle.h" namespace electron { diff --git a/shell/common/api/api.mojom b/shell/common/api/api.mojom index 95f802f9a1b..8ff1efd2d14 100644 --- a/shell/common/api/api.mojom +++ b/shell/common/api/api.mojom @@ -15,8 +15,6 @@ interface ElectronRenderer { ReceivePostMessage(string channel, blink.mojom.TransferableMessage message); - UpdateCrashpadPipeName(string pipe_name); - // This is an API specific to the "remote" module, and will ultimately be // replaced by generic IPC once WeakRef is generally available. [EnableIf=enable_remote_module] diff --git a/shell/common/api/electron_api_crash_reporter.cc b/shell/common/api/electron_api_crash_reporter.cc deleted file mode 100644 index eacb66a53b3..00000000000 --- a/shell/common/api/electron_api_crash_reporter.cc +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include -#include - -#include "base/bind.h" -#include "gin/data_object_builder.h" -#include "shell/common/crash_reporter/crash_reporter.h" -#include "shell/common/gin_converters/callback_converter.h" -#include "shell/common/gin_converters/file_path_converter.h" -#include "shell/common/gin_helper/dictionary.h" - -#include "shell/common/node_includes.h" - -using crash_reporter::CrashReporter; - -namespace gin { - -template <> -struct Converter { - static v8::Local ToV8( - v8::Isolate* isolate, - const CrashReporter::UploadReportResult& reports) { - return gin::DataObjectBuilder(isolate) - .Set("date", - v8::Date::New(isolate->GetCurrentContext(), reports.first * 1000.0) - .ToLocalChecked()) - .Set("id", reports.second) - .Build(); - } -}; - -} // namespace gin - -namespace { - -void Initialize(v8::Local exports, - v8::Local unused, - v8::Local context, - void* priv) { - auto reporter = base::Unretained(CrashReporter::GetInstance()); - gin_helper::Dictionary dict(context->GetIsolate(), exports); - dict.SetMethod("start", base::BindRepeating(&CrashReporter::Start, reporter)); - dict.SetMethod( - "addExtraParameter", - base::BindRepeating(&CrashReporter::AddExtraParameter, reporter)); - dict.SetMethod( - "removeExtraParameter", - base::BindRepeating(&CrashReporter::RemoveExtraParameter, reporter)); - dict.SetMethod("getParameters", - base::BindRepeating(&CrashReporter::GetParameters, reporter)); - dict.SetMethod( - "getUploadedReports", - base::BindRepeating(&CrashReporter::GetUploadedReports, reporter)); - dict.SetMethod( - "setUploadToServer", - base::BindRepeating(&CrashReporter::SetUploadToServer, reporter)); - dict.SetMethod( - "getUploadToServer", - base::BindRepeating(&CrashReporter::GetUploadToServer, reporter)); -} - -} // namespace - -NODE_LINKED_MODULE_CONTEXT_AWARE(electron_common_crash_reporter, Initialize) diff --git a/shell/common/crash_keys.cc b/shell/common/crash_keys.cc new file mode 100644 index 00000000000..a5d92497c9b --- /dev/null +++ b/shell/common/crash_keys.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2020 Slack Technologies, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/common/crash_keys.h" + +#include +#include +#include + +#include "base/command_line.h" +#include "base/environment.h" +#include "base/no_destructor.h" +#include "base/strings/string_split.h" +#include "components/crash/core/common/crash_key.h" +#include "content/public/common/content_switches.h" +#include "shell/common/electron_constants.h" +#include "shell/common/options_switches.h" +#include "third_party/crashpad/crashpad/client/annotation.h" + +namespace electron { + +namespace crash_keys { + +namespace { + +using ExtraCrashKeys = std::deque>; +ExtraCrashKeys& GetExtraCrashKeys() { + static base::NoDestructor extra_keys; + return *extra_keys; +} + +std::deque& GetExtraCrashKeyNames() { + static base::NoDestructor> crash_key_names; + return *crash_key_names; +} + +} // namespace + +constexpr uint32_t kMaxCrashKeyNameLength = 40; +#if defined(OS_LINUX) +static_assert(kMaxCrashKeyNameLength <= + crash_reporter::internal::kCrashKeyStorageKeySize, + "max crash key name length above what breakpad supports"); +#else +static_assert(kMaxCrashKeyNameLength <= crashpad::Annotation::kNameMaxLength, + "max crash key name length above what crashpad supports"); +#endif + +void SetCrashKey(const std::string& key, const std::string& value) { + // Chrome DCHECK()s if we try to set an annotation with a name longer than + // the max. + // TODO(nornagon): warn the developer (via console.warn) when this happens. + if (key.size() >= kMaxCrashKeyNameLength) + return; + auto& crash_key_names = GetExtraCrashKeyNames(); + + auto iter = std::find(crash_key_names.begin(), crash_key_names.end(), key); + if (iter == crash_key_names.end()) { + crash_key_names.emplace_back(key); + GetExtraCrashKeys().emplace_back(crash_key_names.back().c_str()); + iter = crash_key_names.end() - 1; + } + GetExtraCrashKeys()[iter - crash_key_names.begin()].Set(value); +} + +void ClearCrashKey(const std::string& key) { + const auto& crash_key_names = GetExtraCrashKeyNames(); + + auto iter = std::find(crash_key_names.begin(), crash_key_names.end(), key); + if (iter != crash_key_names.end()) { + GetExtraCrashKeys()[iter - crash_key_names.begin()].Clear(); + } +} + +void GetCrashKeys(std::map* keys) { + const auto& crash_key_names = GetExtraCrashKeyNames(); + const auto& crash_keys = GetExtraCrashKeys(); + int i = 0; + for (const auto& key : crash_key_names) { + const auto& value = crash_keys[i++]; + if (value.is_set()) { + keys->emplace(key, value.value()); + } + } +} + +namespace { +bool IsRunningAsNode() { +#if BUILDFLAG(ENABLE_RUN_AS_NODE) + return base::Environment::Create()->HasVar(electron::kRunAsNode); +#else + return false; +#endif +} +} // namespace + +void SetCrashKeysFromCommandLine(const base::CommandLine& command_line) { +#if defined(OS_LINUX) + if (command_line.HasSwitch(switches::kGlobalCrashKeys)) { + std::vector> global_crash_keys; + base::SplitStringIntoKeyValuePairs( + command_line.GetSwitchValueASCII(switches::kGlobalCrashKeys), '=', ',', + &global_crash_keys); + for (const auto& pair : global_crash_keys) { + SetCrashKey(pair.first, pair.second); + } + } +#endif + + // NB. this is redundant with the 'ptype' key that //components/crash + // reports; it's present for backwards compatibility. + static crash_reporter::CrashKeyString<16> process_type_key("process_type"); + if (IsRunningAsNode()) { + process_type_key.Set("node"); + } else { + std::string process_type = + command_line.GetSwitchValueASCII(::switches::kProcessType); + if (process_type.empty()) { + process_type_key.Set("browser"); + } else { + process_type_key.Set(process_type); + } + } +} + +void SetPlatformCrashKey() { + // TODO(nornagon): this is redundant with the 'plat' key that + // //components/crash already includes. Remove it. + static crash_reporter::CrashKeyString<8> platform_key("platform"); +#if defined(OS_WIN) + platform_key.Set("win32"); +#elif defined(OS_MACOSX) + platform_key.Set("darwin"); +#elif defined(OS_LINUX) + platform_key.Set("linux"); +#else + platform_key.Set("unknown"); +#endif +} + +} // namespace crash_keys + +} // namespace electron diff --git a/shell/common/crash_keys.h b/shell/common/crash_keys.h new file mode 100644 index 00000000000..5ad954ed252 --- /dev/null +++ b/shell/common/crash_keys.h @@ -0,0 +1,30 @@ +// Copyright (c) 2020 Slack Technologies, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef SHELL_COMMON_CRASH_KEYS_H_ +#define SHELL_COMMON_CRASH_KEYS_H_ + +#include +#include + +namespace base { +class CommandLine; +} + +namespace electron { + +namespace crash_keys { + +void SetCrashKey(const std::string& key, const std::string& value); +void ClearCrashKey(const std::string& key); +void GetCrashKeys(std::map* keys); + +void SetCrashKeysFromCommandLine(const base::CommandLine& command_line); +void SetPlatformCrashKey(); + +} // namespace crash_keys + +} // namespace electron + +#endif // SHELL_COMMON_CRASH_KEYS_H_ diff --git a/shell/common/crash_reporter/crash_reporter.cc b/shell/common/crash_reporter/crash_reporter.cc deleted file mode 100644 index 57519f2d9d1..00000000000 --- a/shell/common/crash_reporter/crash_reporter.cc +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "shell/common/crash_reporter/crash_reporter.h" - -#include - -#include "base/command_line.h" -#include "base/environment.h" -#include "base/files/file_util.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_split.h" -#include "base/threading/thread_restrictions.h" -#include "content/public/common/content_switches.h" -#include "electron/electron_version.h" -#include "shell/browser/browser.h" -#include "shell/common/electron_constants.h" -#include "shell/common/gin_converters/file_path_converter.h" -#include "shell/common/gin_helper/dictionary.h" - -namespace crash_reporter { - -const char kCrashpadProcess[] = "crash-handler"; -const char kCrashesDirectoryKey[] = "crashes-directory"; - -CrashReporter::CrashReporter() { -#if BUILDFLAG(ENABLE_RUN_AS_NODE) - bool run_as_node = base::Environment::Create()->HasVar(electron::kRunAsNode); -#else - bool run_as_node = false; -#endif - - if (run_as_node) { - process_type_ = "node"; - } else { - auto* cmd = base::CommandLine::ForCurrentProcess(); - process_type_ = cmd->GetSwitchValueASCII(switches::kProcessType); - } - // process_type_ will be empty for browser process -} - -CrashReporter::~CrashReporter() = default; - -bool CrashReporter::IsInitialized() { - return is_initialized_; -} - -void CrashReporter::Start(const std::string& submit_url, - const base::FilePath& crashes_dir, - bool upload_to_server, - bool skip_system_crash_handler, - bool rate_limit, - bool compress, - const StringMap& extra_parameters) { - is_initialized_ = true; - SetUploadParameters(extra_parameters); - - Init(submit_url, crashes_dir, upload_to_server, skip_system_crash_handler, - rate_limit, compress); -} - -void CrashReporter::SetUploadParameters(const StringMap& parameters) { - upload_parameters_ = parameters; - upload_parameters_["process_type"] = - process_type_.empty() ? "browser" : process_type_; - upload_parameters_["prod"] = ELECTRON_PRODUCT_NAME; - upload_parameters_["ver"] = ELECTRON_VERSION_STRING; - - // Setting platform dependent parameters. - SetUploadParameters(); -} - -std::vector -CrashReporter::GetUploadedReports(const base::FilePath& crashes_dir) { - base::ThreadRestrictions::ScopedAllowIO allow_io; - std::string file_content; - std::vector result; - base::FilePath uploads_path = - crashes_dir.Append(FILE_PATH_LITERAL("uploads.log")); - if (base::ReadFileToString(uploads_path, &file_content)) { - std::vector reports = base::SplitString( - file_content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - for (const std::string& report : reports) { - std::vector report_item = base::SplitString( - report, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - int report_time = 0; - if (report_item.size() >= 2 && - base::StringToInt(report_item[0], &report_time)) { - result.emplace_back(report_time, report_item[1]); - } - } - } - return result; -} - -std::map CrashReporter::GetParameters() const { - return upload_parameters_; -} - -#if defined(OS_MACOSX) && defined(MAS_BUILD) -class DummyCrashReporter : public CrashReporter { - public: - ~DummyCrashReporter() override {} - - void SetUploadToServer(bool upload_to_server) override {} - bool GetUploadToServer() override { return false; } - void AddExtraParameter(const std::string& key, - const std::string& value) override {} - void RemoveExtraParameter(const std::string& key) override {} - - void Init(const std::string& submit_url, - const base::FilePath& crashes_dir, - bool upload_to_server, - bool skip_system_crash_handler, - bool rate_limit, - bool compress) override {} - void SetUploadParameters() override {} -}; - -// static -CrashReporter* CrashReporter::GetInstance() { - static DummyCrashReporter crash_reporter; - return &crash_reporter; -} -#endif - -void CrashReporter::StartInstance(const gin_helper::Dictionary& options) { - auto* reporter = GetInstance(); - if (!reporter) - return; - - std::string product_name; - options.Get("productName", &product_name); - std::string company_name; - options.Get("companyName", &company_name); - std::string submit_url; - options.Get("submitURL", &submit_url); - base::FilePath crashes_dir; - options.Get("crashesDirectory", &crashes_dir); - StringMap extra_parameters; - options.Get("extra", &extra_parameters); - bool rate_limit = false; - options.Get("rateLimit", &rate_limit); - bool compress = false; - options.Get("compress", &compress); - - extra_parameters["_productName"] = product_name; - extra_parameters["_companyName"] = company_name; - - bool upload_to_server = true; - bool skip_system_crash_handler = false; - - reporter->Start(submit_url, crashes_dir, upload_to_server, - skip_system_crash_handler, rate_limit, compress, - extra_parameters); -} - -} // namespace crash_reporter diff --git a/shell/common/crash_reporter/crash_reporter.h b/shell/common/crash_reporter/crash_reporter.h deleted file mode 100644 index 0884ba868bf..00000000000 --- a/shell/common/crash_reporter/crash_reporter.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_H_ -#define SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_H_ - -#include -#include -#include -#include - -#include "base/files/file_path.h" -#include "base/macros.h" - -namespace gin_helper { -class Dictionary; -} - -namespace crash_reporter { - -extern const char kCrashpadProcess[]; -extern const char kCrashesDirectoryKey[]; - -class CrashReporter { - public: - typedef std::map StringMap; - typedef std::pair UploadReportResult; // upload-date, id - - static CrashReporter* GetInstance(); - // FIXME(zcbenz): We should not do V8 in this file, this method should only - // accept C++ struct as parameter, and atom_api_crash_reporter.cc is - // responsible for parsing the parameter from JavaScript. - static void StartInstance(const gin_helper::Dictionary& options); - - bool IsInitialized(); - void Start(const std::string& submit_url, - const base::FilePath& crashes_dir, - bool upload_to_server, - bool skip_system_crash_handler, - bool rate_limit, - bool compress, - const StringMap& extra_parameters); - - virtual std::vector GetUploadedReports( - const base::FilePath& crashes_dir); - - virtual void SetUploadToServer(bool upload_to_server) = 0; - virtual bool GetUploadToServer() = 0; - virtual void AddExtraParameter(const std::string& key, - const std::string& value) = 0; - virtual void RemoveExtraParameter(const std::string& key) = 0; - virtual std::map GetParameters() const; - - protected: - CrashReporter(); - virtual ~CrashReporter(); - - virtual void Init(const std::string& submit_url, - const base::FilePath& crashes_dir, - bool upload_to_server, - bool skip_system_crash_handler, - bool rate_limit, - bool compress) = 0; - virtual void SetUploadParameters() = 0; - - StringMap upload_parameters_; - std::string process_type_; - - private: - bool is_initialized_ = false; - void SetUploadParameters(const StringMap& parameters); - - DISALLOW_COPY_AND_ASSIGN(CrashReporter); -}; - -} // namespace crash_reporter - -#endif // SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_H_ diff --git a/shell/common/crash_reporter/crash_reporter_crashpad.cc b/shell/common/crash_reporter/crash_reporter_crashpad.cc deleted file mode 100644 index 95c07165be2..00000000000 --- a/shell/common/crash_reporter/crash_reporter_crashpad.cc +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2019 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "shell/common/crash_reporter/crash_reporter_crashpad.h" - -#include -#include - -#include "base/files/file_util.h" -#include "base/strings/string_piece.h" -#include "base/strings/stringprintf.h" -#include "base/strings/sys_string_conversions.h" -#include "base/threading/thread_restrictions.h" -#include "third_party/crashpad/crashpad/client/settings.h" - -namespace crash_reporter { - -CrashReporterCrashpad::CrashReporterCrashpad() {} - -CrashReporterCrashpad::~CrashReporterCrashpad() {} - -bool CrashReporterCrashpad::GetUploadToServer() { - bool enabled = true; - if (database_) { - database_->GetSettings()->GetUploadsEnabled(&enabled); - } - return enabled; -} - -void CrashReporterCrashpad::SetUploadToServer(const bool upload_to_server) { - if (database_) { - database_->GetSettings()->SetUploadsEnabled(upload_to_server); - } -} - -void CrashReporterCrashpad::SetCrashKeyValue(base::StringPiece key, - base::StringPiece value) { - simple_string_dictionary_->SetKeyValue(key.data(), value.data()); -} - -void CrashReporterCrashpad::SetInitialCrashKeyValues() { - for (const auto& upload_parameter : upload_parameters_) - SetCrashKeyValue(upload_parameter.first, upload_parameter.second); -} - -void CrashReporterCrashpad::AddExtraParameter(const std::string& key, - const std::string& value) { - if (simple_string_dictionary_) { - SetCrashKeyValue(key, value); - } else { - upload_parameters_[key] = value; - } -} - -void CrashReporterCrashpad::RemoveExtraParameter(const std::string& key) { - if (simple_string_dictionary_) - simple_string_dictionary_->RemoveKey(key.data()); - else - upload_parameters_.erase(key); -} - -std::map CrashReporterCrashpad::GetParameters() - const { - if (simple_string_dictionary_) { - std::map ret; - crashpad::SimpleStringDictionary::Iterator iter(*simple_string_dictionary_); - for (;;) { - auto* const entry = iter.Next(); - if (!entry) - break; - ret[entry->key] = entry->value; - } - return ret; - } - return upload_parameters_; -} - -std::vector -CrashReporterCrashpad::GetUploadedReports(const base::FilePath& crashes_dir) { - std::vector uploaded_reports; - - { - base::ThreadRestrictions::ScopedAllowIO allow_io; - if (!base::PathExists(crashes_dir)) { - return uploaded_reports; - } - } - // Load crashpad database. - std::unique_ptr database = - crashpad::CrashReportDatabase::Initialize(crashes_dir); - DCHECK(database); - - std::vector completed_reports; - crashpad::CrashReportDatabase::OperationStatus status = - database->GetCompletedReports(&completed_reports); - if (status != crashpad::CrashReportDatabase::kNoError) { - return uploaded_reports; - } - - for (const crashpad::CrashReportDatabase::Report& completed_report : - completed_reports) { - if (completed_report.uploaded) { - uploaded_reports.push_back( - UploadReportResult(static_cast(completed_report.creation_time), - completed_report.id)); - } - } - - auto sort_by_time = [](const UploadReportResult& a, - const UploadReportResult& b) { - return a.first > b.first; - }; - std::sort(uploaded_reports.begin(), uploaded_reports.end(), sort_by_time); - return uploaded_reports; -} - -} // namespace crash_reporter diff --git a/shell/common/crash_reporter/crash_reporter_crashpad.h b/shell/common/crash_reporter/crash_reporter_crashpad.h deleted file mode 100644 index d54d990eb6d..00000000000 --- a/shell/common/crash_reporter/crash_reporter_crashpad.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_CRASHPAD_H_ -#define SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_CRASHPAD_H_ - -#include -#include -#include -#include - -#include "base/compiler_specific.h" -#include "base/strings/string_piece.h" -#include "shell/common/crash_reporter/crash_reporter.h" -#include "third_party/crashpad/crashpad/client/crash_report_database.h" -#include "third_party/crashpad/crashpad/client/simple_string_dictionary.h" - -namespace crash_reporter { - -class CrashReporterCrashpad : public CrashReporter { - public: - void SetUploadToServer(bool upload_to_server) override; - bool GetUploadToServer() override; - void AddExtraParameter(const std::string& key, - const std::string& value) override; - void RemoveExtraParameter(const std::string& key) override; - std::map GetParameters() const override; - - protected: - CrashReporterCrashpad(); - ~CrashReporterCrashpad() override; - - void SetUploadsEnabled(bool enable_uploads); - void SetCrashKeyValue(base::StringPiece key, base::StringPiece value); - void SetInitialCrashKeyValues(); - - std::vector GetUploadedReports( - const base::FilePath& crashes_dir) override; - - std::unique_ptr simple_string_dictionary_; - std::unique_ptr database_; - - DISALLOW_COPY_AND_ASSIGN(CrashReporterCrashpad); -}; - -} // namespace crash_reporter - -#endif // SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_CRASHPAD_H_ diff --git a/shell/common/crash_reporter/crash_reporter_linux.cc b/shell/common/crash_reporter/crash_reporter_linux.cc deleted file mode 100644 index 6f91228d9de..00000000000 --- a/shell/common/crash_reporter/crash_reporter_linux.cc +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) 2014 GitHub, Inc. -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "shell/common/crash_reporter/crash_reporter_linux.h" - -#include -#include - -#include - -#include - -#include "base/debug/crash_logging.h" -#include "base/files/file_path.h" -#include "base/files/file_util.h" -#include "base/linux_util.h" -#include "base/logging.h" -#include "base/memory/singleton.h" -#include "base/process/memory.h" -#include "base/threading/thread_restrictions.h" -#include "breakpad/src/client/linux/handler/exception_handler.h" -#include "breakpad/src/common/linux/linux_libc_support.h" - -using google_breakpad::ExceptionHandler; -using google_breakpad::MinidumpDescriptor; - -namespace crash_reporter { - -namespace { - -// Define a preferred limit on minidump sizes, because Crash Server currently -// throws away any larger than 1.2MB (1.2 * 1024 * 1024). A value of -1 means -// no limit. -static const off_t kMaxMinidumpFileSize = 1258291; - -} // namespace - -CrashReporterLinux::CrashReporterLinux() : pid_(getpid()) { - // Set the base process start time value. - struct timeval tv; - if (!gettimeofday(&tv, nullptr)) { - uint64_t ret = tv.tv_sec; - ret *= 1000; - ret += tv.tv_usec / 1000; - process_start_time_ = ret; - } - - { - base::ThreadRestrictions::ScopedAllowIO allow_io; - // Make base::g_linux_distro work. - base::SetLinuxDistro(base::GetLinuxDistro()); - } -} - -CrashReporterLinux::~CrashReporterLinux() = default; - -void CrashReporterLinux::Init(const std::string& submit_url, - const base::FilePath& crashes_dir, - bool upload_to_server, - bool skip_system_crash_handler, - bool rate_limit, - bool compress) { - EnableCrashDumping(crashes_dir); - - upload_url_ = submit_url; - upload_to_server_ = upload_to_server; - - crash_keys_ = std::make_unique(); - for (StringMap::const_iterator iter = upload_parameters_.begin(); - iter != upload_parameters_.end(); ++iter) - crash_keys_->SetKeyValue(iter->first.c_str(), iter->second.c_str()); -} - -void CrashReporterLinux::SetUploadParameters() { - upload_parameters_["platform"] = "linux"; -} - -void CrashReporterLinux::SetUploadToServer(const bool upload_to_server) { - upload_to_server_ = upload_to_server; -} - -bool CrashReporterLinux::GetUploadToServer() { - return upload_to_server_; -} - -void CrashReporterLinux::AddExtraParameter(const std::string& key, - const std::string& value) {} - -void CrashReporterLinux::RemoveExtraParameter(const std::string& key) {} - -void CrashReporterLinux::EnableCrashDumping(const base::FilePath& crashes_dir) { - { - base::ThreadRestrictions::ScopedAllowIO allow_io; - base::CreateDirectory(crashes_dir); - } - std::string log_file = crashes_dir.Append("uploads.log").value(); - strncpy(g_crash_log_path, log_file.c_str(), sizeof(g_crash_log_path)); - - MinidumpDescriptor minidump_descriptor(crashes_dir.value()); - minidump_descriptor.set_size_limit(kMaxMinidumpFileSize); - - breakpad_ = std::make_unique(minidump_descriptor, nullptr, - CrashDone, this, - true, // Install handlers. - -1); -} - -bool CrashReporterLinux::CrashDone(const MinidumpDescriptor& minidump, - void* context, - const bool succeeded) { - CrashReporterLinux* self = static_cast(context); - - // WARNING: this code runs in a compromised context. It may not call into - // libc nor allocate memory normally. - if (!succeeded) { - const char msg[] = "Failed to generate minidump."; - WriteLog(msg, sizeof(msg) - 1); - return false; - } - - DCHECK(!minidump.IsFD()); - - BreakpadInfo info = {0}; - info.filename = minidump.path(); - info.fd = minidump.fd(); - info.distro = base::g_linux_distro; - info.distro_length = my_strlen(base::g_linux_distro); - info.upload = self->upload_to_server_; - info.process_start_time = self->process_start_time_; - info.oom_size = base::g_oom_size; - info.pid = self->pid_; - info.upload_url = self->upload_url_.c_str(); - info.crash_keys = self->crash_keys_.get(); - HandleCrashDump(info); - return true; -} - -// static -CrashReporterLinux* CrashReporterLinux::GetInstance() { - return base::Singleton::get(); -} - -// static -CrashReporter* CrashReporter::GetInstance() { - return CrashReporterLinux::GetInstance(); -} - -} // namespace crash_reporter diff --git a/shell/common/crash_reporter/crash_reporter_linux.h b/shell/common/crash_reporter/crash_reporter_linux.h deleted file mode 100644 index 395007d49e1..00000000000 --- a/shell/common/crash_reporter/crash_reporter_linux.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2014 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_LINUX_H_ -#define SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_LINUX_H_ - -#include -#include - -#include "base/compiler_specific.h" -#include "shell/common/crash_reporter/crash_reporter.h" -#include "shell/common/crash_reporter/linux/crash_dump_handler.h" - -namespace base { -template -struct DefaultSingletonTraits; -} - -namespace google_breakpad { -class ExceptionHandler; -class MinidumpDescriptor; -} // namespace google_breakpad - -namespace crash_reporter { - -class CrashReporterLinux : public CrashReporter { - public: - static CrashReporterLinux* GetInstance(); - - void Init(const std::string& submit_url, - const base::FilePath& crashes_dir, - bool upload_to_server, - bool skip_system_crash_handler, - bool rate_limit, - bool compress) override; - void SetUploadToServer(bool upload_to_server) override; - void SetUploadParameters() override; - bool GetUploadToServer() override; - void AddExtraParameter(const std::string& key, - const std::string& value) override; - void RemoveExtraParameter(const std::string& key) override; - - private: - friend struct base::DefaultSingletonTraits; - - CrashReporterLinux(); - ~CrashReporterLinux() override; - - void EnableCrashDumping(const base::FilePath& crashes_dir); - - static bool CrashDone(const google_breakpad::MinidumpDescriptor& minidump, - void* context, - const bool succeeded); - - std::unique_ptr breakpad_; - std::unique_ptr crash_keys_; - - uint64_t process_start_time_ = 0; - pid_t pid_ = 0; - std::string upload_url_; - bool upload_to_server_ = true; - - DISALLOW_COPY_AND_ASSIGN(CrashReporterLinux); -}; -} // namespace crash_reporter - -#endif // SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_LINUX_H_ diff --git a/shell/common/crash_reporter/crash_reporter_mac.h b/shell/common/crash_reporter/crash_reporter_mac.h deleted file mode 100644 index 5b684c384ed..00000000000 --- a/shell/common/crash_reporter/crash_reporter_mac.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_ -#define SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_ - -#include -#include - -#include "shell/common/crash_reporter/crash_reporter_crashpad.h" - -namespace base { -template -struct DefaultSingletonTraits; -} - -namespace crash_reporter { - -class CrashReporterMac : public CrashReporterCrashpad { - public: - static CrashReporterMac* GetInstance(); - - void Init(const std::string& submit_url, - const base::FilePath& crashes_dir, - bool upload_to_server, - bool skip_system_crash_handler, - bool rate_limit, - bool compress) override; - void SetUploadParameters() override; - - private: - friend struct base::DefaultSingletonTraits; - - CrashReporterMac(); - ~CrashReporterMac() override; - - DISALLOW_COPY_AND_ASSIGN(CrashReporterMac); -}; - -} // namespace crash_reporter - -#endif // SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_ diff --git a/shell/common/crash_reporter/crash_reporter_mac.mm b/shell/common/crash_reporter/crash_reporter_mac.mm deleted file mode 100644 index 8f253adc3bb..00000000000 --- a/shell/common/crash_reporter/crash_reporter_mac.mm +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "shell/common/crash_reporter/crash_reporter_mac.h" - -#include -#include -#include -#include -#include - -#include "base/mac/bundle_locations.h" -#include "base/mac/mac_util.h" -#include "base/memory/singleton.h" -#include "third_party/crashpad/crashpad/client/crashpad_client.h" -#include "third_party/crashpad/crashpad/client/crashpad_info.h" - -namespace crash_reporter { - -CrashReporterMac::CrashReporterMac() {} - -CrashReporterMac::~CrashReporterMac() {} - -void CrashReporterMac::Init(const std::string& submit_url, - const base::FilePath& crashes_dir, - bool upload_to_server, - bool skip_system_crash_handler, - bool rate_limit, - bool compress) { - // check whether crashpad has been initialized. - // Only need to initialize once. - if (simple_string_dictionary_) - return; - - if (process_type_.empty()) { // browser process - @autoreleasepool { - base::FilePath framework_bundle_path = base::mac::FrameworkBundlePath(); - base::FilePath handler_path = - framework_bundle_path.Append("Resources").Append("crashpad_handler"); - - std::vector args; - if (!rate_limit) - args.emplace_back("--no-rate-limit"); - if (!compress) - args.emplace_back("--no-upload-gzip"); - - crashpad::CrashpadClient crashpad_client; - crashpad_client.StartHandler(handler_path, crashes_dir, crashes_dir, - submit_url, StringMap(), args, true, false); - } // @autoreleasepool - } - - crashpad::CrashpadInfo* crashpad_info = - crashpad::CrashpadInfo::GetCrashpadInfo(); - if (skip_system_crash_handler) { - crashpad_info->set_system_crash_reporter_forwarding( - crashpad::TriState::kDisabled); - } - - simple_string_dictionary_.reset(new crashpad::SimpleStringDictionary()); - crashpad_info->set_simple_annotations(simple_string_dictionary_.get()); - - SetInitialCrashKeyValues(); - if (process_type_.empty()) { // browser process - database_ = crashpad::CrashReportDatabase::Initialize(crashes_dir); - SetUploadToServer(upload_to_server); - } -} - -void CrashReporterMac::SetUploadParameters() { - upload_parameters_["platform"] = "darwin"; -} - -// static -CrashReporterMac* CrashReporterMac::GetInstance() { - return base::Singleton::get(); -} - -// static -CrashReporter* CrashReporter::GetInstance() { - return CrashReporterMac::GetInstance(); -} - -} // namespace crash_reporter diff --git a/shell/common/crash_reporter/crash_reporter_win.cc b/shell/common/crash_reporter/crash_reporter_win.cc deleted file mode 100644 index 69f26bd1293..00000000000 --- a/shell/common/crash_reporter/crash_reporter_win.cc +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "shell/common/crash_reporter/crash_reporter_win.h" - -#include -#include - -#include "base/environment.h" -#include "base/memory/singleton.h" -#include "base/path_service.h" -#include "base/strings/stringprintf.h" -#include "base/strings/utf_string_conversions.h" -#include "electron/shell/common/api/api.mojom.h" -#include "mojo/public/cpp/bindings/associated_remote.h" -#include "shell/browser/ui/inspectable_web_contents_impl.h" -#include "shell/common/electron_constants.h" -#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" -#include "third_party/crashpad/crashpad/client/crashpad_client.h" -#include "third_party/crashpad/crashpad/client/crashpad_info.h" - -#if defined(_WIN64) -#include "gin/public/debug.h" -#endif - -namespace { - -#if defined(_WIN64) -int CrashForException(EXCEPTION_POINTERS* info) { - auto* reporter = crash_reporter::CrashReporterWin::GetInstance(); - if (reporter->IsInitialized()) { - reporter->GetCrashpadClient().DumpAndCrash(info); - return EXCEPTION_CONTINUE_SEARCH; - } - - // When there is exception and we do not have crashReporter set up, we just - // let the execution continue and crash, which is the default behavior. - // - // We must not return EXCEPTION_CONTINUE_SEARCH here, as it would end up with - // busy loop when there is no exception handler in the program. - return EXCEPTION_CONTINUE_EXECUTION; -} -#endif - -} // namespace - -namespace crash_reporter { - -CrashReporterWin::CrashReporterWin() {} - -CrashReporterWin::~CrashReporterWin() {} - -#if defined(_WIN64) -void CrashReporterWin::SetUnhandledExceptionFilter() { - gin::Debug::SetUnhandledExceptionCallback(&CrashForException); -} -#endif - -void CrashReporterWin::Init(const std::string& submit_url, - const base::FilePath& crashes_dir, - bool upload_to_server, - bool skip_system_crash_handler, - bool rate_limit, - bool compress) { - // check whether crashpad has been initialized. - // Only need to initialize once. - if (simple_string_dictionary_) - return; - if (process_type_.empty()) { // browser process - base::FilePath handler_path; - base::PathService::Get(base::FILE_EXE, &handler_path); - - std::vector args; - if (!rate_limit) - args.emplace_back("--no-rate-limit"); - if (!compress) - args.emplace_back("--no-upload-gzip"); - args.push_back(base::StringPrintf("--type=%s", kCrashpadProcess)); - args.push_back( - base::StringPrintf("--%s=%s", kCrashesDirectoryKey, - base::UTF16ToUTF8(crashes_dir.value()).c_str())); - crashpad_client_.StartHandler(handler_path, crashes_dir, crashes_dir, - submit_url, StringMap(), args, true, false); - UpdatePipeName(); - } else { - std::unique_ptr env(base::Environment::Create()); - std::string pipe_name_utf8; - if (env->GetVar(electron::kCrashpadPipeName, &pipe_name_utf8)) { - base::string16 pipe_name = base::UTF8ToUTF16(pipe_name_utf8); - if (!crashpad_client_.SetHandlerIPCPipe(pipe_name)) - LOG(ERROR) << "Failed to set handler IPC pipe name: " << pipe_name; - } else { - LOG(ERROR) << "Unable to get pipe name for crashpad"; - } - } - crashpad::CrashpadInfo* crashpad_info = - crashpad::CrashpadInfo::GetCrashpadInfo(); - if (skip_system_crash_handler) { - crashpad_info->set_system_crash_reporter_forwarding( - crashpad::TriState::kDisabled); - } - simple_string_dictionary_.reset(new crashpad::SimpleStringDictionary()); - crashpad_info->set_simple_annotations(simple_string_dictionary_.get()); - - SetInitialCrashKeyValues(); - if (process_type_.empty()) { // browser process - database_ = crashpad::CrashReportDatabase::Initialize(crashes_dir); - SetUploadToServer(upload_to_server); - } -} - -void CrashReporterWin::SetUploadParameters() { - upload_parameters_["platform"] = "win32"; -} - -crashpad::CrashpadClient& CrashReporterWin::GetCrashpadClient() { - return crashpad_client_; -} - -void CrashReporterWin::UpdatePipeName() { - std::string pipe_name = - base::UTF16ToUTF8(crashpad_client_.GetHandlerIPCPipe()); - std::unique_ptr env(base::Environment::Create()); - env->SetVar(electron::kCrashpadPipeName, pipe_name); - - // Notify all WebContents of the pipe name. - const auto& pages = electron::InspectableWebContentsImpl::GetAll(); - for (auto* page : pages) { - auto* frame_host = page->GetWebContents()->GetMainFrame(); - if (!frame_host) - continue; - - mojo::AssociatedRemote electron_renderer; - frame_host->GetRemoteAssociatedInterfaces()->GetInterface( - &electron_renderer); - electron_renderer->UpdateCrashpadPipeName(pipe_name); - } -} - -// static -CrashReporterWin* CrashReporterWin::GetInstance() { - return base::Singleton::get(); -} - -// static -CrashReporter* CrashReporter::GetInstance() { - return CrashReporterWin::GetInstance(); -} - -} // namespace crash_reporter diff --git a/shell/common/crash_reporter/crash_reporter_win.h b/shell/common/crash_reporter/crash_reporter_win.h deleted file mode 100644 index e112d53f9b5..00000000000 --- a/shell/common/crash_reporter/crash_reporter_win.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_WIN_H_ -#define SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_WIN_H_ - -#include -#include - -#include "shell/common/crash_reporter/crash_reporter_crashpad.h" -#include "third_party/crashpad/crashpad/client/crashpad_client.h" - -namespace base { -template -struct DefaultSingletonTraits; -} - -namespace crash_reporter { - -class CrashReporterWin : public CrashReporterCrashpad { - public: - static CrashReporterWin* GetInstance(); -#if defined(_WIN64) - static void SetUnhandledExceptionFilter(); -#endif - - void Init(const std::string& submit_url, - const base::FilePath& crashes_dir, - bool upload_to_server, - bool skip_system_crash_handler, - bool rate_limit, - bool compress) override; - void SetUploadParameters() override; - - crashpad::CrashpadClient& GetCrashpadClient(); - - private: - friend struct base::DefaultSingletonTraits; - CrashReporterWin(); - ~CrashReporterWin() override; - - void UpdatePipeName(); - - crashpad::CrashpadClient crashpad_client_; - - DISALLOW_COPY_AND_ASSIGN(CrashReporterWin); -}; - -} // namespace crash_reporter - -#endif // SHELL_COMMON_CRASH_REPORTER_CRASH_REPORTER_WIN_H_ diff --git a/shell/common/crash_reporter/linux/crash_dump_handler.cc b/shell/common/crash_reporter/linux/crash_dump_handler.cc deleted file mode 100644 index 8eaf8bfadce..00000000000 --- a/shell/common/crash_reporter/linux/crash_dump_handler.cc +++ /dev/null @@ -1,753 +0,0 @@ -// Copyright (c) 2014 GitHub, Inc. -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -// For linux_syscall_support.h. This makes it safe to call embedded system -// calls when in seccomp mode. - -#include "shell/common/crash_reporter/linux/crash_dump_handler.h" - -#include - -#include - -#include "base/posix/eintr_wrapper.h" -#include "breakpad/src/client/linux/minidump_writer/directory_reader.h" -#include "breakpad/src/common/linux/linux_libc_support.h" -#include "breakpad/src/common/memory_allocator.h" - -#include "third_party/lss/linux_syscall_support.h" - -// Some versions of gcc are prone to warn about unused return values. In cases -// where we either a) know the call cannot fail, or b) there is nothing we -// can do when a call fails, we mark the return code as ignored. This avoids -// spurious compiler warnings. -#define IGNORE_RET(x) \ - do { \ - if (x) \ - ; \ - } while (0) - -namespace crash_reporter { - -namespace { - -// String buffer size to use to convert a uint64_t to string. -const size_t kUint64StringSize = 21; - -// Writes the value |v| as 16 hex characters to the memory pointed at by -// |output|. -void write_uint64_hex(char* output, uint64_t v) { - static const char hextable[] = "0123456789abcdef"; - - for (int i = 15; i >= 0; --i) { - output[i] = hextable[v & 15]; - v >>= 4; - } -} - -// uint64_t version of my_int_len() from -// breakpad/src/common/linux/linux_libc_support.h. Return the length of the -// given, non-negative integer when expressed in base 10. -unsigned my_uint64_len(uint64_t i) { - if (!i) - return 1; - - unsigned len = 0; - while (i) { - len++; - i /= 10; - } - - return len; -} - -// uint64_t version of my_uitos() from -// breakpad/src/common/linux/linux_libc_support.h. Convert a non-negative -// integer to a string (not null-terminated). -void my_uint64tos(char* output, uint64_t i, unsigned i_len) { - for (unsigned index = i_len; index; --index, i /= 10) - output[index - 1] = '0' + (i % 10); -} - -// Converts a struct timeval to milliseconds. -uint64_t kernel_timeval_to_ms(struct kernel_timeval* tv) { - uint64_t ret = tv->tv_sec; // Avoid overflow by explicitly using a uint64_t. - ret *= 1000; - ret += tv->tv_usec / 1000; - return ret; -} - -bool my_isxdigit(char c) { - return (c >= '0' && c <= '9') || ((c | 0x20) >= 'a' && (c | 0x20) <= 'f'); -} - -size_t LengthWithoutTrailingSpaces(const char* str, size_t len) { - while (len > 0 && str[len - 1] == ' ') { - len--; - } - return len; -} - -// MIME substrings. -const char g_rn[] = "\r\n"; -const char g_form_data_msg[] = "Content-Disposition: form-data; name=\""; -const char g_quote_msg[] = "\""; -const char g_dashdash_msg[] = "--"; -const char g_dump_msg[] = "upload_file_minidump\"; filename=\"dump\""; -const char g_content_type_msg[] = "Content-Type: application/octet-stream"; - -// MimeWriter manages an iovec for writing MIMEs to a file. -class MimeWriter { - public: - static const int kIovCapacity = 30; - static const size_t kMaxCrashChunkSize = 64; - - MimeWriter(int fd, const char* const mime_boundary); - ~MimeWriter(); - - // Append boundary. - virtual void AddBoundary(); - - // Append end of file boundary. - virtual void AddEnd(); - - // Append key/value pair with specified sizes. - virtual void AddPairData(const char* msg_type, - size_t msg_type_size, - const char* msg_data, - size_t msg_data_size); - - // Append key/value pair. - void AddPairString(const char* msg_type, const char* msg_data) { - AddPairData(msg_type, my_strlen(msg_type), msg_data, my_strlen(msg_data)); - } - - // Append key/value pair, splitting value into chunks no larger than - // |chunk_size|. |chunk_size| cannot be greater than |kMaxCrashChunkSize|. - // The msg_type string will have a counter suffix to distinguish each chunk. - virtual void AddPairDataInChunks(const char* msg_type, - size_t msg_type_size, - const char* msg_data, - size_t msg_data_size, - size_t chunk_size, - bool strip_trailing_spaces); - - // Add binary file contents to be uploaded with the specified filename. - virtual void AddFileContents(const char* filename_msg, - uint8_t* file_data, - size_t file_size); - - // Flush any pending iovecs to the output file. - void Flush() { - IGNORE_RET(sys_writev(fd_, iov_, iov_index_)); - iov_index_ = 0; - } - - protected: - void AddItem(const void* base, size_t size); - // Minor performance trade-off for easier-to-maintain code. - void AddString(const char* str) { AddItem(str, my_strlen(str)); } - void AddItemWithoutTrailingSpaces(const void* base, size_t size); - - struct kernel_iovec iov_[kIovCapacity]; - int iov_index_ = 0; - - // Output file descriptor. - int fd_ = -1; - - const char* const mime_boundary_; - - private: - DISALLOW_COPY_AND_ASSIGN(MimeWriter); -}; - -MimeWriter::MimeWriter(int fd, const char* const mime_boundary) - : fd_(fd), mime_boundary_(mime_boundary) {} - -MimeWriter::~MimeWriter() = default; - -void MimeWriter::AddBoundary() { - AddString(mime_boundary_); - AddString(g_rn); -} - -void MimeWriter::AddEnd() { - AddString(mime_boundary_); - AddString(g_dashdash_msg); - AddString(g_rn); -} - -void MimeWriter::AddPairData(const char* msg_type, - size_t msg_type_size, - const char* msg_data, - size_t msg_data_size) { - AddString(g_form_data_msg); - AddItem(msg_type, msg_type_size); - AddString(g_quote_msg); - AddString(g_rn); - AddString(g_rn); - AddItem(msg_data, msg_data_size); - AddString(g_rn); -} - -void MimeWriter::AddPairDataInChunks(const char* msg_type, - size_t msg_type_size, - const char* msg_data, - size_t msg_data_size, - size_t chunk_size, - bool strip_trailing_spaces) { - if (chunk_size > kMaxCrashChunkSize) - return; - - unsigned i = 0; - size_t done = 0, msg_length = msg_data_size; - - while (msg_length) { - char num[kUint64StringSize]; - const unsigned num_len = my_uint_len(++i); - my_uitos(num, i, num_len); - - size_t chunk_len = std::min(chunk_size, msg_length); - - AddString(g_form_data_msg); - AddItem(msg_type, msg_type_size); - AddItem(num, num_len); - AddString(g_quote_msg); - AddString(g_rn); - AddString(g_rn); - if (strip_trailing_spaces) { - AddItemWithoutTrailingSpaces(msg_data + done, chunk_len); - } else { - AddItem(msg_data + done, chunk_len); - } - AddString(g_rn); - AddBoundary(); - Flush(); - - done += chunk_len; - msg_length -= chunk_len; - } -} - -void MimeWriter::AddFileContents(const char* filename_msg, - uint8_t* file_data, - size_t file_size) { - AddString(g_form_data_msg); - AddString(filename_msg); - AddString(g_rn); - AddString(g_content_type_msg); - AddString(g_rn); - AddString(g_rn); - AddItem(file_data, file_size); - AddString(g_rn); -} - -void MimeWriter::AddItem(const void* base, size_t size) { - // Check if the iovec is full and needs to be flushed to output file. - if (iov_index_ == kIovCapacity) { - Flush(); - } - iov_[iov_index_].iov_base = const_cast(base); - iov_[iov_index_].iov_len = size; - ++iov_index_; -} - -void MimeWriter::AddItemWithoutTrailingSpaces(const void* base, size_t size) { - AddItem(base, - LengthWithoutTrailingSpaces(static_cast(base), size)); -} - -void LoadDataFromFD(google_breakpad::PageAllocator* allocator, - int fd, - bool close_fd, - uint8_t** file_data, - size_t* size) { - struct kernel_stat st; - if (sys_fstat(fd, &st) != 0) { - static const char msg[] = "Cannot upload crash dump: stat failed\n"; - WriteLog(msg, sizeof(msg) - 1); - if (close_fd) - IGNORE_RET(sys_close(fd)); - return; - } - - *file_data = reinterpret_cast(allocator->Alloc(st.st_size)); - if (!(*file_data)) { - static const char msg[] = "Cannot upload crash dump: cannot alloc\n"; - WriteLog(msg, sizeof(msg) - 1); - if (close_fd) - IGNORE_RET(sys_close(fd)); - return; - } - my_memset(*file_data, 0xf, st.st_size); - - *size = st.st_size; - int byte_read = sys_read(fd, *file_data, *size); - if (byte_read == -1) { - static const char msg[] = "Cannot upload crash dump: read failed\n"; - WriteLog(msg, sizeof(msg) - 1); - if (close_fd) - IGNORE_RET(sys_close(fd)); - return; - } - - if (close_fd) - IGNORE_RET(sys_close(fd)); -} - -void LoadDataFromFile(google_breakpad::PageAllocator* allocator, - const char* filename, - int* fd, - uint8_t** file_data, - size_t* size) { - // WARNING: this code runs in a compromised context. It may not call into - // libc nor allocate memory normally. - *fd = sys_open(filename, O_RDONLY, 0); - *size = 0; - - if (*fd < 0) { - static const char msg[] = "Cannot upload crash dump: failed to open\n"; - WriteLog(msg, sizeof(msg) - 1); - return; - } - - LoadDataFromFD(allocator, *fd, true, file_data, size); -} - -// Spawn the appropriate upload process for the current OS: -// - generic Linux invokes wget. -// - ChromeOS invokes crash_reporter. -// |dumpfile| is the path to the dump data file. -// |mime_boundary| is only used on Linux. -// |exe_buf| is only used on CrOS and is the crashing process' name. -void ExecUploadProcessOrTerminate(const BreakpadInfo& info, - const char* dumpfile, - const char* mime_boundary, - const char* exe_buf, - google_breakpad::PageAllocator* allocator) { - // The --header argument to wget looks like: - // --header=Content-Type: multipart/form-data; boundary=XYZ - // where the boundary has two fewer leading '-' chars - static const char header_msg[] = - "--header=Content-Type: multipart/form-data; boundary="; - char* const header = reinterpret_cast( - allocator->Alloc(sizeof(header_msg) - 1 + strlen(mime_boundary) - 2 + 1)); - memcpy(header, header_msg, sizeof(header_msg) - 1); - memcpy(header + sizeof(header_msg) - 1, mime_boundary + 2, - strlen(mime_boundary) - 2); - // We grab the NUL byte from the end of |mime_boundary|. - - // The --post-file argument to wget looks like: - // --post-file=/tmp/... - static const char post_file_msg[] = "--post-file="; - char* const post_file = reinterpret_cast( - allocator->Alloc(sizeof(post_file_msg) - 1 + strlen(dumpfile) + 1)); - memcpy(post_file, post_file_msg, sizeof(post_file_msg) - 1); - memcpy(post_file + sizeof(post_file_msg) - 1, dumpfile, strlen(dumpfile)); - - static const char kWgetBinary[] = "/usr/bin/wget"; - const char* args[] = { - kWgetBinary, header, post_file, info.upload_url, - "--timeout=60", // Set a timeout so we don't hang forever. - "--tries=1", // Don't retry if the upload fails. - "--quiet", // Be silent. - "-O", // output reply to /dev/null. - "/dev/fd/3", nullptr, - }; - static const char msg[] = - "Cannot upload crash dump: cannot exec " - "/usr/bin/wget\n"; - execve(args[0], const_cast(args), environ); - WriteLog(msg, sizeof(msg) - 1); - sys__exit(1); -} - -// Runs in the helper process to wait for the upload process running -// ExecUploadProcessOrTerminate() to finish. Returns the number of bytes written -// to |fd| and save the written contents to |buf|. -// |buf| needs to be big enough to hold |bytes_to_read| + 1 characters. -size_t WaitForCrashReportUploadProcess(int fd, - size_t bytes_to_read, - char* buf) { - size_t bytes_read = 0; - - // Upload should finish in about 10 seconds. Add a few more 500 ms - // internals to account for process startup time. - for (size_t wait_count = 0; wait_count < 24; ++wait_count) { - struct kernel_pollfd poll_fd; - poll_fd.fd = fd; - poll_fd.events = POLLIN | POLLPRI | POLLERR; - int ret = sys_poll(&poll_fd, 1, 500); - if (ret < 0) { - // Error - break; - } else if (ret > 0) { - // There is data to read. - ssize_t len = HANDLE_EINTR( - sys_read(fd, buf + bytes_read, bytes_to_read - bytes_read)); - if (len < 0) - break; - bytes_read += len; - if (bytes_read == bytes_to_read) - break; - } - // |ret| == 0 -> timed out, continue waiting. - // or |bytes_read| < |bytes_to_read| still, keep reading. - } - buf[bytes_to_read] = 0; // Always NUL terminate the buffer. - return bytes_read; -} - -// |buf| should be |expected_len| + 1 characters in size and NULL terminated. -bool IsValidCrashReportId(const char* buf, - size_t bytes_read, - size_t expected_len) { - if (bytes_read != expected_len) - return false; - for (size_t i = 0; i < bytes_read; ++i) { - if (!my_isxdigit(buf[i]) && buf[i] != '-') - return false; - } - return true; -} - -// |buf| should be |expected_len| + 1 characters in size and NULL terminated. -void HandleCrashReportId(const char* buf, - size_t bytes_read, - size_t expected_len) { - if (!IsValidCrashReportId(buf, bytes_read, expected_len)) { - static const char msg[] = "Failed to get crash dump id."; - WriteLog(msg, sizeof(msg) - 1); - WriteNewline(); - - static const char id_msg[] = "Report Id: "; - WriteLog(id_msg, sizeof(id_msg) - 1); - WriteLog(buf, bytes_read); - WriteNewline(); - return; - } - - // Write crash dump id to stderr. - static const char msg[] = "Crash dump id: "; - WriteLog(msg, sizeof(msg) - 1); - WriteLog(buf, my_strlen(buf)); - WriteNewline(); - - // Write crash dump id to crash log as: seconds_since_epoch,crash_id - struct kernel_timeval tv; - if (!sys_gettimeofday(&tv, nullptr)) { - uint64_t time = kernel_timeval_to_ms(&tv) / 1000; - char time_str[kUint64StringSize]; - const unsigned time_len = my_uint64_len(time); - my_uint64tos(time_str, time, time_len); - - const int kLogOpenFlags = O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC; - int log_fd = sys_open(g_crash_log_path, kLogOpenFlags, 0600); - if (log_fd > 0) { - sys_write(log_fd, time_str, time_len); - sys_write(log_fd, ",", 1); - sys_write(log_fd, buf, my_strlen(buf)); - sys_write(log_fd, "\n", 1); - IGNORE_RET(sys_close(log_fd)); - } - } -} - -} // namespace - -char g_crash_log_path[256]; - -void HandleCrashDump(const BreakpadInfo& info) { - int dumpfd; - bool keep_fd = false; - size_t dump_size; - uint8_t* dump_data; - google_breakpad::PageAllocator allocator; - const char* exe_buf = nullptr; - - if (info.fd != -1) { - // Dump is provided with an open FD. - keep_fd = true; - dumpfd = info.fd; - - // The FD is pointing to the end of the file. - // Rewind, we'll read the data next. - if (lseek(dumpfd, 0, SEEK_SET) == -1) { - static const char msg[] = - "Cannot upload crash dump: failed to " - "reposition minidump FD\n"; - WriteLog(msg, sizeof(msg) - 1); - IGNORE_RET(sys_close(dumpfd)); - return; - } - LoadDataFromFD(&allocator, info.fd, false, &dump_data, &dump_size); - } else { - // Dump is provided with a path. - keep_fd = false; - LoadDataFromFile(&allocator, info.filename, &dumpfd, &dump_data, - &dump_size); - } - - // We need to build a MIME block for uploading to the server. Since we are - // going to fork and run wget, it needs to be written to a temp file. - const int ufd = sys_open("/dev/urandom", O_RDONLY, 0); - if (ufd < 0) { - static const char msg[] = - "Cannot upload crash dump because /dev/urandom" - " is missing\n"; - WriteLog(msg, sizeof(msg) - 1); - return; - } - - static const char temp_file_template[] = - "/tmp/chromium-upload-XXXXXXXXXXXXXXXX"; - char temp_file[sizeof(temp_file_template)]; - int temp_file_fd = -1; - if (keep_fd) { - temp_file_fd = dumpfd; - // Rewind the destination, we are going to overwrite it. - if (lseek(dumpfd, 0, SEEK_SET) == -1) { - static const char msg[] = - "Cannot upload crash dump: failed to " - "reposition minidump FD (2)\n"; - WriteLog(msg, sizeof(msg) - 1); - IGNORE_RET(sys_close(dumpfd)); - return; - } - } else { - if (info.upload) { - memcpy(temp_file, temp_file_template, sizeof(temp_file_template)); - - for (unsigned i = 0; i < 10; ++i) { - uint64_t t; - sys_read(ufd, &t, sizeof(t)); - write_uint64_hex(temp_file + sizeof(temp_file) - (16 + 1), t); - - temp_file_fd = sys_open(temp_file, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (temp_file_fd >= 0) - break; - } - - if (temp_file_fd < 0) { - static const char msg[] = - "Failed to create temporary file in /tmp: " - "cannot upload crash dump\n"; - WriteLog(msg, sizeof(msg) - 1); - IGNORE_RET(sys_close(ufd)); - return; - } - } else { - temp_file_fd = sys_open(info.filename, O_WRONLY, 0600); - if (temp_file_fd < 0) { - static const char msg[] = "Failed to save crash dump: failed to open\n"; - WriteLog(msg, sizeof(msg) - 1); - IGNORE_RET(sys_close(ufd)); - return; - } - } - } - - // The MIME boundary is 28 hyphens, followed by a 64-bit nonce and a NUL. - char mime_boundary[28 + 16 + 1]; - my_memset(mime_boundary, '-', 28); - uint64_t boundary_rand; - sys_read(ufd, &boundary_rand, sizeof(boundary_rand)); - write_uint64_hex(mime_boundary + 28, boundary_rand); - mime_boundary[28 + 16] = 0; - IGNORE_RET(sys_close(ufd)); - - // The MIME block looks like this: - // BOUNDARY \r\n - // Content-Disposition: form-data; name="prod" \r\n \r\n - // Chrome_Linux \r\n - // BOUNDARY \r\n - // Content-Disposition: form-data; name="ver" \r\n \r\n - // 1.2.3.4 \r\n - // BOUNDARY \r\n - // - // zero or one: - // Content-Disposition: form-data; name="ptime" \r\n \r\n - // abcdef \r\n - // BOUNDARY \r\n - // - // zero or one: - // Content-Disposition: form-data; name="ptype" \r\n \r\n - // abcdef \r\n - // BOUNDARY \r\n - // - // zero or one: - // Content-Disposition: form-data; name="lsb-release" \r\n \r\n - // abcdef \r\n - // BOUNDARY \r\n - // - // zero or one: - // Content-Disposition: form-data; name="oom-size" \r\n \r\n - // 1234567890 \r\n - // BOUNDARY \r\n - // - // zero or more (up to CrashKeyStorage::num_entries = 64): - // Content-Disposition: form-data; name=crash-key-name \r\n - // crash-key-value \r\n - // BOUNDARY \r\n - // - // Content-Disposition: form-data; name="dump"; filename="dump" \r\n - // Content-Type: application/octet-stream \r\n \r\n - // - // \r\n BOUNDARY -- \r\n - - MimeWriter writer(temp_file_fd, mime_boundary); - { - writer.AddBoundary(); - if (info.pid > 0) { - char pid_value_buf[kUint64StringSize]; - uint64_t pid_value_len = my_uint64_len(info.pid); - my_uint64tos(pid_value_buf, info.pid, pid_value_len); - static const char pid_key_name[] = "pid"; - writer.AddPairData(pid_key_name, sizeof(pid_key_name) - 1, pid_value_buf, - pid_value_len); - writer.AddBoundary(); - } - writer.Flush(); - } - - if (info.process_start_time > 0) { - struct kernel_timeval tv; - if (!sys_gettimeofday(&tv, nullptr)) { - uint64_t time = kernel_timeval_to_ms(&tv); - if (time > info.process_start_time) { - time -= info.process_start_time; - char time_str[kUint64StringSize]; - const unsigned time_len = my_uint64_len(time); - my_uint64tos(time_str, time, time_len); - - static const char process_time_msg[] = "ptime"; - writer.AddPairData(process_time_msg, sizeof(process_time_msg) - 1, - time_str, time_len); - writer.AddBoundary(); - writer.Flush(); - } - } - } - - if (info.distro_length) { - static const char distro_msg[] = "lsb-release"; - writer.AddPairString(distro_msg, info.distro); - writer.AddBoundary(); - writer.Flush(); - } - - if (info.oom_size) { - char oom_size_str[kUint64StringSize]; - const unsigned oom_size_len = my_uint64_len(info.oom_size); - my_uint64tos(oom_size_str, info.oom_size, oom_size_len); - static const char oom_size_msg[] = "oom-size"; - writer.AddPairData(oom_size_msg, sizeof(oom_size_msg) - 1, oom_size_str, - oom_size_len); - writer.AddBoundary(); - writer.Flush(); - } - - if (info.crash_keys) { - CrashKeyStorage::Iterator crash_key_iterator(*info.crash_keys); - const CrashKeyStorage::Entry* entry; - while ((entry = crash_key_iterator.Next())) { - writer.AddPairString(entry->key, entry->value); - writer.AddBoundary(); - writer.Flush(); - } - } - - writer.AddFileContents(g_dump_msg, dump_data, dump_size); - writer.AddEnd(); - writer.Flush(); - - IGNORE_RET(sys_close(temp_file_fd)); - - if (!info.upload) - return; - - const pid_t child = sys_fork(); - if (!child) { - // Spawned helper process. - // - // This code is called both when a browser is crashing (in which case, - // nothing really matters any more) and when a renderer/plugin crashes, in - // which case we need to continue. - // - // Since we are a multithreaded app, if we were just to fork(), we might - // grab file descriptors which have just been created in another thread and - // hold them open for too long. - // - // Thus, we have to loop and try and close everything. - const int fd = sys_open("/proc/self/fd", O_DIRECTORY | O_RDONLY, 0); - if (fd < 0) { - for (unsigned i = 3; i < 8192; ++i) - IGNORE_RET(sys_close(i)); - } else { - google_breakpad::DirectoryReader reader(fd); - const char* name; - while (reader.GetNextEntry(&name)) { - int i; - if (my_strtoui(&i, name) && i > 2 && i != fd) - IGNORE_RET(sys_close(i)); - reader.PopEntry(); - } - - IGNORE_RET(sys_close(fd)); - } - - IGNORE_RET(sys_setsid()); - - // Leave one end of a pipe in the upload process and watch for it getting - // closed by the upload process exiting. - int fds[2]; - if (sys_pipe(fds) >= 0) { - const pid_t upload_child = sys_fork(); - if (!upload_child) { - // Upload process. - IGNORE_RET(sys_close(fds[0])); - IGNORE_RET(sys_dup2(fds[1], 3)); - ExecUploadProcessOrTerminate(info, temp_file, mime_boundary, exe_buf, - &allocator); - } - - // Helper process. - if (upload_child > 0) { - IGNORE_RET(sys_close(fds[1])); - - const size_t kCrashIdLength = 36; - char id_buf[kCrashIdLength + 1]; - size_t bytes_read = - WaitForCrashReportUploadProcess(fds[0], kCrashIdLength, id_buf); - HandleCrashReportId(id_buf, bytes_read, kCrashIdLength); - - if (sys_waitpid(upload_child, nullptr, WNOHANG) == 0) { - // Upload process is still around, kill it. - sys_kill(upload_child, SIGKILL); - } - } - } - - // Helper process. - IGNORE_RET(sys_unlink(info.filename)); - IGNORE_RET(sys_unlink(temp_file)); - sys__exit(0); - } - - // Main browser process. - if (child <= 0) - return; - (void)HANDLE_EINTR(sys_waitpid(child, nullptr, 0)); -} - -size_t WriteLog(const char* buf, size_t nbytes) { - return sys_write(2, buf, nbytes); -} - -size_t WriteNewline() { - return WriteLog("\n", 1); -} - -} // namespace crash_reporter diff --git a/shell/common/crash_reporter/linux/crash_dump_handler.h b/shell/common/crash_reporter/linux/crash_dump_handler.h deleted file mode 100644 index da53c517dec..00000000000 --- a/shell/common/crash_reporter/linux/crash_dump_handler.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2014 GitHub, Inc. -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef SHELL_COMMON_CRASH_REPORTER_LINUX_CRASH_DUMP_HANDLER_H_ -#define SHELL_COMMON_CRASH_REPORTER_LINUX_CRASH_DUMP_HANDLER_H_ - -#include -#include -#include - -#include "base/macros.h" -#include "breakpad/src/common/simple_string_dictionary.h" - -namespace crash_reporter { - -typedef google_breakpad::NonAllocatingMap<256, 256, 64> CrashKeyStorage; - -// BreakpadInfo describes a crash report. -// The minidump information can either be contained in a file descriptor (fd) or -// in a file (whose path is in filename). -struct BreakpadInfo { - int fd; // File descriptor to the Breakpad dump data. - const char* filename; // Path to the Breakpad dump data. - const char* distro; // Linux distro string. - unsigned distro_length; // Length of |distro|. - bool upload; // Whether to upload or save crash dump. - uint64_t process_start_time; // Uptime of the crashing process. - size_t oom_size; // Amount of memory requested if OOM. - uint64_t pid; // PID where applicable. - const char* upload_url; // URL to upload the minidump. - CrashKeyStorage* crash_keys; -}; - -void HandleCrashDump(const BreakpadInfo& info); - -size_t WriteLog(const char* buf, size_t nbytes); -size_t WriteNewline(); - -// Global variable storing the path of upload log. -extern char g_crash_log_path[256]; - -} // namespace crash_reporter - -#endif // SHELL_COMMON_CRASH_REPORTER_LINUX_CRASH_DUMP_HANDLER_H_ diff --git a/shell/common/crash_reporter/win/crash_service_main.cc b/shell/common/crash_reporter/win/crash_service_main.cc deleted file mode 100644 index cd5fb81fc54..00000000000 --- a/shell/common/crash_reporter/win/crash_service_main.cc +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "shell/common/crash_reporter/win/crash_service_main.h" - -#include - -#include "base/at_exit.h" -#include "base/command_line.h" -#include "base/files/file_util.h" -#include "base/logging.h" -#include "base/strings/string_util.h" -#include "base/strings/utf_string_conversions.h" -#include "shell/common/crash_reporter/crash_reporter.h" -#include "third_party/crashpad/crashpad/handler/handler_main.h" - -namespace crash_service { - -namespace { - -const wchar_t kStandardLogFile[] = L"operation_log.txt"; - -void InvalidParameterHandler(const wchar_t*, - const wchar_t*, - const wchar_t*, - unsigned int, - uintptr_t) { - // noop. -} - -bool CreateCrashServiceDirectory(const base::FilePath& temp_dir) { - if (!base::PathExists(temp_dir)) { - if (!base::CreateDirectory(temp_dir)) - return false; - } - return true; -} - -void RemoveArgs(std::vector* args) { - args->erase( - std::remove_if(args->begin(), args->end(), [](const std::string& str) { - return base::StartsWith(str, "--type", base::CompareCase::SENSITIVE) || - base::StartsWith( - str, - std::string("--") + crash_reporter::kCrashesDirectoryKey, - base::CompareCase::INSENSITIVE_ASCII); - })); -} - -} // namespace. - -int Main(std::vector* args) { - // Ignore invalid parameter errors. - _set_invalid_parameter_handler(InvalidParameterHandler); - - // Initialize all Chromium things. - base::AtExitManager exit_manager; - base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); - // We use/create a directory under the user's temp folder, for logging. - base::FilePath operating_dir( - cmd_line->GetSwitchValueNative(crash_reporter::kCrashesDirectoryKey)); - CreateCrashServiceDirectory(operating_dir); - base::FilePath log_file_path = operating_dir.Append(kStandardLogFile); - - // Logging to stderr (to help with debugging failures on the - // buildbots) and to a file. - logging::LoggingSettings settings; - settings.logging_dest = logging::LOG_TO_ALL; - settings.log_file_path = log_file_path.value().c_str(); - logging::InitLogging(settings); - // Logging with pid, tid and timestamp. - logging::SetLogItems(true, true, true, false); - - // Crashpad cannot handle unknown arguments, so we need to remove it - RemoveArgs(args); - return crashpad::HandlerMain(args->size(), args->data(), nullptr); -} - -} // namespace crash_service diff --git a/shell/common/crash_reporter/win/crash_service_main.h b/shell/common/crash_reporter/win/crash_service_main.h deleted file mode 100644 index 70e88af3ee8..00000000000 --- a/shell/common/crash_reporter/win/crash_service_main.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef SHELL_COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_MAIN_H_ -#define SHELL_COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_MAIN_H_ - -#include - -namespace crash_service { - -// Program entry, should be called by main(); -int Main(std::vector* args); - -} // namespace crash_service - -#endif // SHELL_COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_MAIN_H_ diff --git a/shell/common/electron_constants.cc b/shell/common/electron_constants.cc index 991e69309de..5d53425a3fc 100644 --- a/shell/common/electron_constants.cc +++ b/shell/common/electron_constants.cc @@ -25,10 +25,6 @@ const char kSecureProtocolDescription[] = "The connection to this site is using a strong protocol version " "and cipher suite."; -#if defined(OS_WIN) -const char kCrashpadPipeName[] = "ELECTRON_CRASHPAD_PIPE_NAME"; -#endif - #if BUILDFLAG(ENABLE_RUN_AS_NODE) const char kRunAsNode[] = "ELECTRON_RUN_AS_NODE"; #endif diff --git a/shell/common/electron_constants.h b/shell/common/electron_constants.h index baeeab86b31..ee888baf836 100644 --- a/shell/common/electron_constants.h +++ b/shell/common/electron_constants.h @@ -25,11 +25,6 @@ extern const char kValidCertificateDescription[]; extern const char kSecureProtocol[]; extern const char kSecureProtocolDescription[]; -#if defined(OS_WIN) -// Crashpad pipe name. -extern const char kCrashpadPipeName[]; -#endif - #if BUILDFLAG(ENABLE_RUN_AS_NODE) extern const char kRunAsNode[]; #endif diff --git a/shell/browser/electron_paths.h b/shell/common/electron_paths.h similarity index 86% rename from shell/browser/electron_paths.h rename to shell/common/electron_paths.h index 070cdb74d6a..680287e2d0d 100644 --- a/shell/browser/electron_paths.h +++ b/shell/common/electron_paths.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef SHELL_BROWSER_ELECTRON_PATHS_H_ -#define SHELL_BROWSER_ELECTRON_PATHS_H_ +#ifndef SHELL_COMMON_ELECTRON_PATHS_H_ +#define SHELL_COMMON_ELECTRON_PATHS_H_ #include "base/base_paths.h" @@ -30,6 +30,8 @@ enum { DIR_APP_DATA, // Application Data directory under the user profile. #endif + DIR_CRASH_DUMPS, // c.f. chrome::DIR_CRASH_DUMPS + PATH_END, // End of new paths. Those that follow redirect to base::DIR_* #if !defined(OS_LINUX) @@ -47,4 +49,4 @@ static_assert(PATH_START < PATH_END, "invalid PATH boundaries"); } // namespace electron -#endif // SHELL_BROWSER_ELECTRON_PATHS_H_ +#endif // SHELL_COMMON_ELECTRON_PATHS_H_ diff --git a/shell/common/gin_converters/time_converter.cc b/shell/common/gin_converters/time_converter.cc new file mode 100644 index 00000000000..c4a9267b915 --- /dev/null +++ b/shell/common/gin_converters/time_converter.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2020 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/common/gin_converters/time_converter.h" + +#include "base/time/time.h" + +namespace gin { + +v8::Local Converter::ToV8(v8::Isolate* isolate, + const base::Time& val) { + v8::Local date; + if (v8::Date::New(isolate->GetCurrentContext(), val.ToJsTime()) + .ToLocal(&date)) + return date; + else + return v8::Null(isolate); +} + +} // namespace gin diff --git a/shell/common/gin_converters/time_converter.h b/shell/common/gin_converters/time_converter.h new file mode 100644 index 00000000000..0b0923f911a --- /dev/null +++ b/shell/common/gin_converters/time_converter.h @@ -0,0 +1,25 @@ +// Copyright (c) 2020 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef SHELL_COMMON_GIN_CONVERTERS_TIME_CONVERTER_H_ +#define SHELL_COMMON_GIN_CONVERTERS_TIME_CONVERTER_H_ + +#include + +#include "gin/converter.h" + +namespace base { +class Time; +} + +namespace gin { + +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, const base::Time& val); +}; + +} // namespace gin + +#endif // SHELL_COMMON_GIN_CONVERTERS_TIME_CONVERTER_H_ diff --git a/shell/common/node_bindings.cc b/shell/common/node_bindings.cc index 7987194cea4..5ecdb19d52b 100644 --- a/shell/common/node_bindings.cc +++ b/shell/common/node_bindings.cc @@ -37,6 +37,7 @@ V(electron_browser_auto_updater) \ V(electron_browser_browser_view) \ V(electron_browser_content_tracing) \ + V(electron_browser_crash_reporter) \ V(electron_browser_dialog) \ V(electron_browser_event) \ V(electron_browser_event_emitter) \ @@ -52,15 +53,14 @@ V(electron_browser_system_preferences) \ V(electron_browser_top_level_window) \ V(electron_browser_tray) \ + V(electron_browser_view) \ V(electron_browser_web_contents) \ V(electron_browser_web_contents_view) \ - V(electron_browser_view) \ V(electron_browser_web_view_manager) \ V(electron_browser_window) \ V(electron_common_asar) \ V(electron_common_clipboard) \ V(electron_common_command_line) \ - V(electron_common_crash_reporter) \ V(electron_common_features) \ V(electron_common_native_image) \ V(electron_common_native_theme) \ @@ -69,6 +69,7 @@ V(electron_common_shell) \ V(electron_common_v8_util) \ V(electron_renderer_context_bridge) \ + V(electron_renderer_crash_reporter) \ V(electron_renderer_ipc) \ V(electron_renderer_web_frame) diff --git a/shell/common/options_switches.cc b/shell/common/options_switches.cc index 3056a779156..96eebf01c08 100644 --- a/shell/common/options_switches.cc +++ b/shell/common/options_switches.cc @@ -286,6 +286,8 @@ const char kEnableSpellcheck[] = "enable-spellcheck"; const char kEnableRemoteModule[] = "enable-remote-module"; #endif +const char kGlobalCrashKeys[] = "global-crash-keys"; + } // namespace switches } // namespace electron diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h index 7f4d140d573..202e28e2c0b 100644 --- a/shell/common/options_switches.h +++ b/shell/common/options_switches.h @@ -149,6 +149,8 @@ extern const char kEnableSpellcheck[]; extern const char kEnableRemoteModule[]; #endif +extern const char kGlobalCrashKeys[]; + } // namespace switches } // namespace electron diff --git a/shell/renderer/api/electron_api_crash_reporter_renderer.cc b/shell/renderer/api/electron_api_crash_reporter_renderer.cc new file mode 100644 index 00000000000..624a28e4330 --- /dev/null +++ b/shell/renderer/api/electron_api_crash_reporter_renderer.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2020 Slack Technologies, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "shell/common/crash_keys.h" +#include "shell/common/gin_helper/dictionary.h" +#include "shell/common/node_includes.h" + +namespace { + +v8::Local GetParameters(v8::Isolate* isolate) { + std::map keys; +#if !defined(MAS_BUILD) + electron::crash_keys::GetCrashKeys(&keys); +#endif + return gin::ConvertToV8(isolate, keys); +} + +#if defined(MAS_BUILD) +void SetCrashKeyStub(const std::string& key, const std::string& value) {} +void ClearCrashKeyStub(const std::string& key) {} +#endif + +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { + gin_helper::Dictionary dict(context->GetIsolate(), exports); +#if defined(MAS_BUILD) + dict.SetMethod("addExtraParameter", &SetCrashKeyStub); + dict.SetMethod("removeExtraParameter", &ClearCrashKeyStub); +#else + dict.SetMethod("addExtraParameter", &electron::crash_keys::SetCrashKey); + dict.SetMethod("removeExtraParameter", &electron::crash_keys::ClearCrashKey); +#endif + dict.SetMethod("getParameters", &GetParameters); +} + +} // namespace + +NODE_LINKED_MODULE_CONTEXT_AWARE(electron_renderer_crash_reporter, Initialize) diff --git a/shell/renderer/electron_api_service_impl.cc b/shell/renderer/electron_api_service_impl.cc index daa6ad3140d..5999dd9c0bc 100644 --- a/shell/renderer/electron_api_service_impl.cc +++ b/shell/renderer/electron_api_service_impl.cc @@ -236,14 +236,6 @@ void ElectronApiServiceImpl::DereferenceRemoteJSCallback( } #endif -void ElectronApiServiceImpl::UpdateCrashpadPipeName( - const std::string& pipe_name) { -#if defined(OS_WIN) - std::unique_ptr env(base::Environment::Create()); - env->SetVar(kCrashpadPipeName, pipe_name); -#endif -} - void ElectronApiServiceImpl::TakeHeapSnapshot( mojo::ScopedHandle file, TakeHeapSnapshotCallback callback) { diff --git a/shell/renderer/electron_api_service_impl.h b/shell/renderer/electron_api_service_impl.h index 766a29b776c..1b48d10bbf3 100644 --- a/shell/renderer/electron_api_service_impl.h +++ b/shell/renderer/electron_api_service_impl.h @@ -39,7 +39,6 @@ class ElectronApiServiceImpl : public mojom::ElectronRenderer, void DereferenceRemoteJSCallback(const std::string& context_id, int32_t object_id) override; #endif - void UpdateCrashpadPipeName(const std::string& pipe_name) override; void TakeHeapSnapshot(mojo::ScopedHandle file, TakeHeapSnapshotCallback callback) override; diff --git a/spec-main/api-crash-reporter-spec.ts b/spec-main/api-crash-reporter-spec.ts index 797691b48e8..c33beee6684 100644 --- a/spec-main/api-crash-reporter-spec.ts +++ b/spec-main/api-crash-reporter-spec.ts @@ -4,17 +4,16 @@ import * as http from 'http'; import * as Busboy from 'busboy'; import * as path from 'path'; import { ifdescribe, ifit } from './spec-helpers'; -import * as temp from 'temp'; import { app } from 'electron/main'; import { crashReporter } from 'electron/common'; import { AddressInfo } from 'net'; import { EventEmitter } from 'events'; import * as fs from 'fs'; import * as v8 from 'v8'; - -temp.track(); +import * as uuid from 'uuid'; const isWindowsOnArm = process.platform === 'win32' && process.arch === 'arm64'; +const isLinuxOnArm = process.platform === 'linux' && process.arch.includes('arm'); const afterTest: ((() => void) | (() => Promise))[] = []; async function cleanup () { @@ -29,37 +28,41 @@ type CrashInfo = { prod: string ver: string process_type: string // eslint-disable-line camelcase + ptype: string platform: string - extra1: string - extra2: string - extra3: undefined _productName: string - _companyName: string _version: string upload_file_minidump: Buffer // eslint-disable-line camelcase + mainProcessSpecific: 'mps' | undefined + rendererSpecific: 'rs' | undefined + globalParam: 'globalValue' | undefined + addedThenRemoved: 'to-be-removed' | undefined + longParam: string | undefined } function checkCrash (expectedProcessType: string, fields: CrashInfo) { - expect(String(fields.prod)).to.equal('Electron'); - expect(String(fields.ver)).to.equal(process.versions.electron); - expect(String(fields.process_type)).to.equal(expectedProcessType); - expect(String(fields.platform)).to.equal(process.platform); - expect(String(fields._productName)).to.equal('Zombies'); - expect(String(fields._companyName)).to.equal('Umbrella Corporation'); - expect(String(fields._version)).to.equal(app.getVersion()); + expect(String(fields.prod)).to.equal('Electron', 'prod'); + expect(String(fields.ver)).to.equal(process.versions.electron, 'ver'); + expect(String(fields.ptype)).to.equal(expectedProcessType, 'ptype'); + expect(String(fields.process_type)).to.equal(expectedProcessType, 'process_type'); + expect(String(fields.platform)).to.equal(process.platform, 'platform'); + expect(String(fields._productName)).to.equal('Zombies', '_productName'); + expect(String(fields._version)).to.equal(app.getVersion(), '_version'); expect(fields.upload_file_minidump).to.be.an.instanceOf(Buffer); - expect(fields.upload_file_minidump.length).to.be.greaterThan(0); -} -function checkCrashExtra (fields: CrashInfo) { - expect(String(fields.extra1)).to.equal('extra1'); - expect(String(fields.extra2)).to.equal('extra2'); - expect(fields.extra3).to.be.undefined(); + // TODO(nornagon): minidumps are sometimes (not always) turning up empty on + // 32-bit Linux. Figure out why. + if (!(process.platform === 'linux' && process.arch === 'ia32')) { + expect(fields.upload_file_minidump.length).to.be.greaterThan(0); + } } const startRemoteControlApp = async () => { const appPath = path.join(__dirname, 'fixtures', 'apps', 'remote-control'); const appProcess = childProcess.spawn(process.execPath, [appPath]); + appProcess.stderr.on('data', d => { + process.stderr.write(d); + }); const port = await new Promise(resolve => { appProcess.stdout.on('data', d => { const m = /Listening: (\d+)/.exec(d.toString()); @@ -90,8 +93,11 @@ const startRemoteControlApp = async () => { req.end(); }); } + function remotely (script: Function, ...args: any[]): Promise { + return remoteEval(`(${script})(...${JSON.stringify(args)})`); + } afterTest.push(() => { appProcess.kill('SIGINT'); }); - return { remoteEval }; + return { remoteEval, remotely }; }; const startServer = async () => { @@ -123,7 +129,8 @@ const startServer = async () => { fields[fieldname] = val; }); busboy.on('finish', () => { - const reportId = 'abc-123-def-456-abc-789-abc-123-abcd'; + // breakpad id must be 16 hex digits. + const reportId = Math.random().toString(16).split('.')[1].padStart(16, '0'); res.end(reportId, async () => { req.socket.destroy(); emitter.emit('crash', { ...fields, ...files }); @@ -179,189 +186,400 @@ function waitForNewFileInDir (dir: string): Promise { }); } -// TODO(alexeykuzmin): [Ch66] This test fails on Linux. Fix it and enable back. -ifdescribe(!process.mas && !process.env.DISABLE_CRASH_REPORTER_TESTS && process.platform !== 'linux')('crashReporter module', function () { +// TODO(nornagon): Fix tests on linux/arm. +ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_TESTS)('crashReporter module', function () { afterEach(cleanup); - it('should send minidump when renderer crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('renderer', port); - const crash = await waitForCrash(); - checkCrash('renderer', crash); + describe('should send minidump', () => { + it('when renderer crashes', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('renderer', port); + const crash = await waitForCrash(); + checkCrash('renderer', crash); + expect(crash.mainProcessSpecific).to.be.undefined(); + }); + + it('when sandboxed renderer crashes', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('sandboxed-renderer', port); + const crash = await waitForCrash(); + checkCrash('renderer', crash); + expect(crash.mainProcessSpecific).to.be.undefined(); + }); + + // TODO(nornagon): Minidump generation in main/node process on Linux/Arm is + // broken (//components/crash prints "Failed to generate minidump"). Figure + // out why. + ifit(!isLinuxOnArm)('when main process crashes', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('main', port); + const crash = await waitForCrash(); + checkCrash('browser', crash); + expect(crash.mainProcessSpecific).to.equal('mps'); + }); + + ifit(!isLinuxOnArm)('when a node process crashes', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('node', port); + const crash = await waitForCrash(); + checkCrash('node', crash); + expect(crash.mainProcessSpecific).to.be.undefined(); + expect(crash.rendererSpecific).to.be.undefined(); + }); + + describe('with extra parameters', () => { + it('when renderer crashes', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('renderer', port, ['--set-extra-parameters-in-renderer']); + const crash = await waitForCrash(); + checkCrash('renderer', crash); + expect(crash.mainProcessSpecific).to.be.undefined(); + expect(crash.rendererSpecific).to.equal('rs'); + expect(crash.addedThenRemoved).to.be.undefined(); + }); + + it('when sandboxed renderer crashes', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('sandboxed-renderer', port, ['--set-extra-parameters-in-renderer']); + const crash = await waitForCrash(); + checkCrash('renderer', crash); + expect(crash.mainProcessSpecific).to.be.undefined(); + expect(crash.rendererSpecific).to.equal('rs'); + expect(crash.addedThenRemoved).to.be.undefined(); + }); + }); }); - it('should send minidump when sandboxed renderer crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('sandboxed-renderer', port); - const crash = await waitForCrash(); - checkCrash('renderer', crash); - checkCrashExtra(crash); + ifdescribe(!isLinuxOnArm)('extra parameter limits', () => { + it('should truncate extra values longer than 127 characters', async () => { + const { port, waitForCrash } = await startServer(); + const { remotely } = await startRemoteControlApp(); + remotely((port: number) => { + require('electron').crashReporter.start({ + submitURL: `http://127.0.0.1:${port}`, + ignoreSystemCrashHandler: true, + extra: { 'longParam': 'a'.repeat(130) } + }); + setTimeout(() => process.crash()); + }, port); + const crash = await waitForCrash(); + expect(crash).to.have.property('longParam', 'a'.repeat(127)); + }); + + it('should omit extra keys with names longer than the maximum', async () => { + const kKeyLengthMax = 39; + const { port, waitForCrash } = await startServer(); + const { remotely } = await startRemoteControlApp(); + remotely((port: number, kKeyLengthMax: number) => { + require('electron').crashReporter.start({ + submitURL: `http://127.0.0.1:${port}`, + ignoreSystemCrashHandler: true, + extra: { + ['a'.repeat(kKeyLengthMax + 10)]: 'value', + ['b'.repeat(kKeyLengthMax)]: 'value', + 'not-long': 'not-long-value' + } + }); + require('electron').crashReporter.addExtraParameter('c'.repeat(kKeyLengthMax + 10), 'value'); + setTimeout(() => process.crash()); + }, port, kKeyLengthMax); + const crash = await waitForCrash(); + expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax + 10)); + expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax)); + expect(crash).to.have.property('b'.repeat(kKeyLengthMax), 'value'); + expect(crash).to.have.property('not-long', 'not-long-value'); + expect(crash).not.to.have.property('c'.repeat(kKeyLengthMax + 10)); + expect(crash).not.to.have.property('c'.repeat(kKeyLengthMax)); + }); }); - it('should send minidump with updated parameters when renderer crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('renderer', port, ['--set-extra-parameters-in-renderer']); - const crash = await waitForCrash(); - checkCrash('renderer', crash); - expect(crash.extra1).to.be.undefined(); - expect(crash.extra2).to.equal('extra2'); - expect(crash.extra3).to.equal('added'); + describe('globalExtra', () => { + ifit(!isLinuxOnArm)('should be sent with main process dumps', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('main', port, ['--add-global-param=globalParam:globalValue']); + const crash = await waitForCrash(); + expect(crash.globalParam).to.equal('globalValue'); + }); + + it('should be sent with renderer process dumps', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('renderer', port, ['--add-global-param=globalParam:globalValue']); + const crash = await waitForCrash(); + expect(crash.globalParam).to.equal('globalValue'); + }); + + it('should be sent with sandboxed renderer process dumps', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('sandboxed-renderer', port, ['--add-global-param=globalParam:globalValue']); + const crash = await waitForCrash(); + expect(crash.globalParam).to.equal('globalValue'); + }); + + ifit(!isLinuxOnArm)('should not be overridden by extra in main process', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('main', port, ['--add-global-param=mainProcessSpecific:global']); + const crash = await waitForCrash(); + expect(crash.mainProcessSpecific).to.equal('global'); + }); + + ifit(!isLinuxOnArm)('should not be overridden by extra in renderer process', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('main', port, ['--add-global-param=rendererSpecific:global']); + const crash = await waitForCrash(); + expect(crash.rendererSpecific).to.equal('global'); + }); }); - it('should send minidump with updated parameters when sandboxed renderer crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('sandboxed-renderer', port, ['--set-extra-parameters-in-renderer']); - const crash = await waitForCrash(); - checkCrash('renderer', crash); - expect(crash.extra1).to.be.undefined(); - expect(crash.extra2).to.equal('extra2'); - expect(crash.extra3).to.equal('added'); - }); - - it('should send minidump when main process crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('main', port); - const crash = await waitForCrash(); - checkCrash('browser', crash); - checkCrashExtra(crash); - }); - - it('should send minidump when a node process crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('node', port); - const crash = await waitForCrash(); - checkCrash('node', crash); - checkCrashExtra(crash); - }); - - // TODO(jeremy): re-enable on woa + // TODO(nornagon): also test crashing main / sandboxed renderers. ifit(!isWindowsOnArm)('should not send a minidump when uploadToServer is false', async () => { - const { port, getCrashes } = await startServer(); - const crashesDir = path.join(app.getPath('temp'), 'Zombies Crashes'); - const completedCrashesDir = path.join(crashesDir, 'completed'); - const crashAppeared = waitForNewFileInDir(completedCrashesDir); + const { port, waitForCrash, getCrashes } = await startServer(); + waitForCrash().then(() => expect.fail('expected not to receive a dump')); await runCrashApp('renderer', port, ['--no-upload']); - await crashAppeared; - // wait a sec in case crashpad is about to upload a crash + // wait a sec in case the crash reporter is about to upload a crash await new Promise(resolve => setTimeout(resolve, 1000)); expect(getCrashes()).to.have.length(0); }); describe('start() option validation', () => { - it('requires that the companyName option be specified', () => { + it('requires that the submitURL option be specified', () => { expect(() => { - crashReporter.start({ companyName: 'dummy' } as any); + crashReporter.start({} as any); }).to.throw('submitURL is a required option to crashReporter.start'); }); - it('requires that the submitURL option be specified', () => { - expect(() => { - crashReporter.start({ submitURL: 'dummy' } as any); - }).to.throw('companyName is a required option to crashReporter.start'); - }); - it('can be called twice', async () => { - const { remoteEval } = await startRemoteControlApp(); - - await remoteEval(`require('electron').crashReporter.start({companyName: "Umbrella Corporation", submitURL: "http://127.0.0.1"})`); - await remoteEval(`require('electron').crashReporter.start({companyName: "Umbrella Corporation", submitURL: "http://127.0.0.1"})`); - }); - }); - - describe('getCrashesDirectory', () => { - it('correctly returns the directory', async () => { - const { remoteEval } = await startRemoteControlApp(); - await remoteEval(`require('electron').crashReporter.start({companyName: "Umbrella Corporation", submitURL: "http://127.0.0.1"})`); - const crashesDir = await remoteEval(`require('electron').crashReporter.getCrashesDirectory()`); - const dir = path.join(app.getPath('temp'), 'remote-control Crashes'); - expect(crashesDir).to.equal(dir); + const { remotely } = await startRemoteControlApp(); + await expect(remotely(() => { + const { crashReporter } = require('electron'); + crashReporter.start({ submitURL: 'http://127.0.0.1' }); + crashReporter.start({ submitURL: 'http://127.0.0.1' }); + })).to.be.fulfilled(); }); }); describe('getUploadedReports', () => { it('returns an array of reports', async () => { - const { remoteEval } = await startRemoteControlApp(); - await remoteEval(`require('electron').crashReporter.start({companyName: "Umbrella Corporation", submitURL: "http://127.0.0.1"})`); - const reports = await remoteEval(`require('electron').crashReporter.getUploadedReports()`); + const { remotely } = await startRemoteControlApp(); + await remotely(() => { + require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' }); + }); + const reports = await remotely(() => require('electron').crashReporter.getUploadedReports()); expect(reports).to.be.an('array'); }); }); - // TODO(jeremy): re-enable on woa + // TODO(nornagon): re-enable on woa ifdescribe(!isWindowsOnArm)('getLastCrashReport', () => { it('returns the last uploaded report', async () => { - const { remoteEval } = await startRemoteControlApp(); + const { remotely } = await startRemoteControlApp(); const { port, waitForCrash } = await startServer(); // 0. clear the crash reports directory. - const dir = path.join(app.getPath('temp'), 'remote-control Crashes'); + const dir = await remotely(() => require('electron').app.getPath('crashDumps')); try { fs.rmdirSync(dir, { recursive: true }); + fs.mkdirSync(dir); } catch (e) { /* ignore */ } // 1. start the crash reporter. - await remoteEval(`require('electron').crashReporter.start({companyName: "Umbrella Corporation", submitURL: "http://127.0.0.1:${port}", ignoreSystemCrashHandler: true})`); - // 2. generate a crash. - remoteEval(`(function() { const {BrowserWindow} = require('electron'); const bw = new BrowserWindow({show: false, webPreferences: {nodeIntegration: true}}); bw.loadURL('about:blank'); bw.webContents.executeJavaScript('process.crash()') })()`); + await remotely((port: number) => { + require('electron').crashReporter.start({ + submitURL: `http://127.0.0.1:${port}`, + ignoreSystemCrashHandler: true + }); + }, [port]); + // 2. generate a crash in the renderer. + remotely(() => { + const { BrowserWindow } = require('electron'); + const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }); + bw.loadURL('about:blank'); + bw.webContents.executeJavaScript('process.crash()'); + }); await waitForCrash(); // 3. get the crash from getLastCrashReport. - const firstReport = await remoteEval(`require('electron').crashReporter.getLastCrashReport()`); + const firstReport = await remotely(() => require('electron').crashReporter.getLastCrashReport()); expect(firstReport).to.not.be.null(); expect(firstReport.date).to.be.an.instanceOf(Date); + expect((+new Date()) - (+firstReport.date)).to.be.lessThan(30000); }); }); describe('getUploadToServer()', () => { it('returns true when uploadToServer is set to true (by default)', async () => { - const { remoteEval } = await startRemoteControlApp(); + const { remotely } = await startRemoteControlApp(); - await remoteEval(`require('electron').crashReporter.start({companyName: "Umbrella Corporation", submitURL: "http://127.0.0.1"})`); - const uploadToServer = await remoteEval(`require('electron').crashReporter.getUploadToServer()`); + await remotely(() => { require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' }); }); + const uploadToServer = await remotely(() => require('electron').crashReporter.getUploadToServer()); expect(uploadToServer).to.be.true(); }); it('returns false when uploadToServer is set to false in init', async () => { - const { remoteEval } = await startRemoteControlApp(); - await remoteEval(`require('electron').crashReporter.start({companyName: "Umbrella Corporation", submitURL: "http://127.0.0.1", uploadToServer: false})`); - const uploadToServer = await remoteEval(`require('electron').crashReporter.getUploadToServer()`); + const { remotely } = await startRemoteControlApp(); + await remotely(() => { require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false }); }); + const uploadToServer = await remotely(() => require('electron').crashReporter.getUploadToServer()); expect(uploadToServer).to.be.false(); }); it('is updated by setUploadToServer', async () => { - const { remoteEval } = await startRemoteControlApp(); - await remoteEval(`require('electron').crashReporter.start({companyName: "Umbrella Corporation", submitURL: "http://127.0.0.1"})`); - await remoteEval(`require('electron').crashReporter.setUploadToServer(false)`); - expect(await remoteEval(`require('electron').crashReporter.getUploadToServer()`)).to.be.false(); - await remoteEval(`require('electron').crashReporter.setUploadToServer(true)`); - expect(await remoteEval(`require('electron').crashReporter.getUploadToServer()`)).to.be.true(); + const { remotely } = await startRemoteControlApp(); + await remotely(() => { require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' }); }); + await remotely(() => { require('electron').crashReporter.setUploadToServer(false); }); + expect(await remotely(() => require('electron').crashReporter.getUploadToServer())).to.be.false(); + await remotely(() => { require('electron').crashReporter.setUploadToServer(true); }); + expect(await remotely(() => require('electron').crashReporter.getUploadToServer())).to.be.true(); }); }); - describe('Parameters', () => { + describe('getParameters', () => { it('returns all of the current parameters', async () => { - const { remoteEval } = await startRemoteControlApp(); - await remoteEval(`require('electron').crashReporter.start({companyName: "Umbrella Corporation", submitURL: "http://127.0.0.1", extra: {"extra1": "hi"}})`); - const parameters = await remoteEval(`require('electron').crashReporter.getParameters()`); - expect(parameters).to.be.an('object'); - expect(parameters.extra1).to.equal('hi'); + const { remotely } = await startRemoteControlApp(); + await remotely(() => { + require('electron').crashReporter.start({ + submitURL: 'http://127.0.0.1', + extra: { 'extra1': 'hi' } + }); + }); + const parameters = await remotely(() => require('electron').crashReporter.getParameters()); + expect(parameters).to.have.property('extra1', 'hi'); }); - it('adds and removes parameters', async () => { - const { remoteEval } = await startRemoteControlApp(); - await remoteEval(`require('electron').crashReporter.start({companyName: "Umbrella Corporation", submitURL: "http://127.0.0.1"})`); - await remoteEval(`require('electron').crashReporter.addExtraParameter('hello', 'world')`); + it('reflects added and removed parameters', async () => { + const { remotely } = await startRemoteControlApp(); + await remotely(() => { + require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' }); + require('electron').crashReporter.addExtraParameter('hello', 'world'); + }); { - const parameters = await remoteEval(`require('electron').crashReporter.getParameters()`); - expect(parameters).to.have.property('hello'); - expect(parameters.hello).to.equal('world'); + const parameters = await remotely(() => require('electron').crashReporter.getParameters()); + expect(parameters).to.have.property('hello', 'world'); } + await remotely(() => { require('electron').crashReporter.removeExtraParameter('hello'); }); + { - await remoteEval(`require('electron').crashReporter.removeExtraParameter('hello')`); - const parameters = await remoteEval(`require('electron').crashReporter.getParameters()`); + const parameters = await remotely(() => require('electron').crashReporter.getParameters()); expect(parameters).not.to.have.property('hello'); } }); + + it('can be called in the renderer', async () => { + const { remotely } = await startRemoteControlApp(); + const rendererParameters = await remotely(async () => { + const { crashReporter, BrowserWindow } = require('electron'); + crashReporter.start({ submitURL: 'http://' }); + const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }); + bw.loadURL('about:blank'); + await bw.webContents.executeJavaScript(`require('electron').crashReporter.addExtraParameter('hello', 'world')`); + return bw.webContents.executeJavaScript(`require('electron').crashReporter.getParameters()`); + }); + if (process.platform === 'linux') { + // On Linux, 'getParameters' will also include the global parameters, + // because breakpad doesn't support global parameters. + expect(rendererParameters).to.have.property('hello', 'world'); + } else { + expect(rendererParameters).to.deep.equal({ hello: 'world' }); + } + }); + + it('can be called in a node child process', async () => { + function slurp (stream: NodeJS.ReadableStream): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + stream.on('data', chunk => { chunks.push(chunk); }); + stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); + stream.on('error', e => reject(e)); + }); + } + const child = childProcess.fork(path.join(__dirname, 'fixtures', 'module', 'print-crash-parameters.js'), [], { silent: true }); + const output = await slurp(child.stdout!); + expect(JSON.parse(output)).to.deep.equal({ hello: 'world' }); + }); + }); + + describe('crash dumps directory', () => { + it('is set by default', () => { + expect(app.getPath('crashDumps')).to.be.a('string'); + }); + + it('is inside the user data dir', () => { + expect(app.getPath('crashDumps')).to.include(app.getPath('userData')); + }); + + it('matches getCrashesDirectory', async () => { + expect(app.getPath('crashDumps')).to.equal(require('electron').crashReporter.getCrashesDirectory()); + }); + + function crash (processType: string, remotely: Function) { + if (processType === 'main') { + return remotely(() => { + setTimeout(() => { process.crash(); }); + }); + } else if (processType === 'renderer') { + return remotely(() => { + const { BrowserWindow } = require('electron'); + const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }); + bw.loadURL('about:blank'); + bw.webContents.executeJavaScript('process.crash()'); + }); + } else if (processType === 'sandboxed-renderer') { + const preloadPath = path.join(__dirname, 'fixtures', 'apps', 'crash', 'sandbox-preload.js'); + return remotely((preload: string) => { + const { BrowserWindow } = require('electron'); + const bw = new BrowserWindow({ show: false, webPreferences: { sandbox: true, preload } }); + bw.loadURL('about:blank'); + }, preloadPath); + } else if (processType === 'node') { + const crashScriptPath = path.join(__dirname, 'fixtures', 'apps', 'crash', 'node-crash.js'); + return remotely((crashScriptPath: string) => { + const { app } = require('electron'); + const childProcess = require('child_process'); + const version = app.getVersion(); + const url = 'http://127.0.0.1'; + childProcess.fork(crashScriptPath, [url, version], { silent: true }); + }, crashScriptPath); + } + } + + for (const crashingProcess of ['main', 'renderer', 'sandboxed-renderer', 'node']) { + // TODO(nornagon): breakpad on linux disables itself when uploadToServer + // is false, so we should figure out a different way to test the crash + // dump dir on linux. + ifdescribe(process.platform !== 'linux')(`when ${crashingProcess} crashes`, () => { + it('stores crashes in the crash dump directory when uploadToServer: false', async () => { + const { remotely } = await startRemoteControlApp(); + const crashesDir = await remotely(() => { + const { crashReporter } = require('electron'); + crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false, ignoreSystemCrashHandler: true }); + return crashReporter.getCrashesDirectory(); + }); + const reportsDir = process.platform === 'darwin' ? path.join(crashesDir, 'completed') : path.join(crashesDir, 'reports'); + const newFileAppeared = waitForNewFileInDir(reportsDir); + crash(crashingProcess, remotely); + const newFiles = await newFileAppeared; + expect(newFiles).to.have.length(1); + expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.dmp$/); + }); + + it('respects an overridden crash dump directory', async () => { + const { remotely } = await startRemoteControlApp(); + const crashesDir = path.join(app.getPath('temp'), uuid.v4()); + const remoteCrashesDir = await remotely((crashesDir: string) => { + const { crashReporter, app } = require('electron'); + app.setPath('crashDumps', crashesDir); + crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false, ignoreSystemCrashHandler: true }); + return crashReporter.getCrashesDirectory(); + }, crashesDir); + expect(remoteCrashesDir).to.equal(crashesDir); + + const reportsDir = process.platform === 'darwin' ? path.join(crashesDir, 'completed') : path.join(crashesDir, 'reports'); + const newFileAppeared = waitForNewFileInDir(reportsDir); + crash(crashingProcess, remotely); + const newFiles = await newFileAppeared; + expect(newFiles).to.have.length(1, `Files that appeared: ${JSON.stringify(newFiles)}`); + expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.dmp$/); + }); + }); + } }); describe('when not started', () => { diff --git a/spec-main/fixtures/apps/crash/main.js b/spec-main/fixtures/apps/crash/main.js index 32ee7ff8b23..056a51f28c7 100644 --- a/spec-main/fixtures/apps/crash/main.js +++ b/spec-main/fixtures/apps/crash/main.js @@ -7,6 +7,7 @@ app.setVersion('0.1.0'); const url = app.commandLine.getSwitchValue('crash-reporter-url'); const uploadToServer = !app.commandLine.hasSwitch('no-upload'); const setExtraParameters = app.commandLine.hasSwitch('set-extra-parameters-in-renderer'); +const addGlobalParam = app.commandLine.getSwitchValue('add-global-param')?.split(':'); crashReporter.start({ productName: 'Zombies', @@ -15,9 +16,9 @@ crashReporter.start({ submitURL: url, ignoreSystemCrashHandler: true, extra: { - extra1: 'extra1', - extra2: 'extra2' - } + mainProcessSpecific: 'mps' + }, + globalExtra: addGlobalParam[0] ? { [addGlobalParam[0]]: addGlobalParam[1] } : {} }); app.whenReady().then(() => { @@ -28,21 +29,11 @@ app.whenReady().then(() => { } else if (crashType === 'renderer') { const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }); w.loadURL('about:blank'); - w.webContents.executeJavaScript(`require('electron').crashReporter.start({ - productName: 'Zombies', - companyName: 'Umbrella Corporation', - uploadToServer: true, - ignoreSystemCrashHandler: true, - submitURL: '', - extra: { - 'extra1': 'extra1', - 'extra2': 'extra2', - } - })`); if (setExtraParameters) { w.webContents.executeJavaScript(` - require('electron').crashReporter.addExtraParameter('extra3', 'added'); - require('electron').crashReporter.removeExtraParameter('extra1'); + require('electron').crashReporter.addExtraParameter('rendererSpecific', 'rs'); + require('electron').crashReporter.addExtraParameter('addedThenRemoved', 'to-be-removed'); + require('electron').crashReporter.removeExtraParameter('addedThenRemoved'); `); } w.webContents.executeJavaScript('process.crash()'); diff --git a/spec-main/fixtures/apps/crash/node-crash.js b/spec-main/fixtures/apps/crash/node-crash.js index 0df7a318481..fbefddadc8b 100644 --- a/spec-main/fixtures/apps/crash/node-crash.js +++ b/spec-main/fixtures/apps/crash/node-crash.js @@ -1,20 +1,10 @@ -process.crashReporter.start({ - productName: 'Zombies', - companyName: 'Umbrella Corporation', - crashesDirectory: process.argv[4], - submitURL: process.argv[2], - ignoreSystemCrashHandler: true, - extra: { - extra1: 'extra1', - extra2: 'extra2', - _version: process.argv[3] - } -}); - -if (process.platform !== 'linux') { - process.crashReporter.addExtraParameter('newExtra', 'newExtra'); - process.crashReporter.addExtraParameter('removeExtra', 'removeExtra'); - process.crashReporter.removeExtraParameter('removeExtra'); +if (process.platform === 'linux') { + process.crashReporter.start({ + submitURL: process.argv[2], + productName: 'Zombies', + globalExtra: { + _version: process.argv[3] + } + }); } - process.nextTick(() => process.crash()); diff --git a/spec-main/fixtures/apps/crash/sandbox-preload.js b/spec-main/fixtures/apps/crash/sandbox-preload.js index 432806cf70f..a1aca9dc3e0 100644 --- a/spec-main/fixtures/apps/crash/sandbox-preload.js +++ b/spec-main/fixtures/apps/crash/sandbox-preload.js @@ -1,18 +1,10 @@ const { crashReporter } = require('electron'); -crashReporter.start({ - productName: 'Zombies', - companyName: 'Umbrella Corporation', - uploadToServer: true, - ignoreSystemCrashHandler: true, - submitURL: '', - extra: { - 'extra1': 'extra1', - 'extra2': 'extra2' - } -}); + const params = new URLSearchParams(location.search); if (params.get('set_extra') === '1') { - crashReporter.addExtraParameter('extra3', 'added'); - crashReporter.removeExtraParameter('extra1'); + crashReporter.addExtraParameter('rendererSpecific', 'rs'); + crashReporter.addExtraParameter('addedThenRemoved', 'to-be-removed'); + crashReporter.removeExtraParameter('addedThenRemoved'); } + process.crash(); diff --git a/spec-main/fixtures/apps/remote-control/main.js b/spec-main/fixtures/apps/remote-control/main.js index 22fa110c656..c960cac4656 100644 --- a/spec-main/fixtures/apps/remote-control/main.js +++ b/spec-main/fixtures/apps/remote-control/main.js @@ -6,12 +6,14 @@ const server = http.createServer((req, res) => { req.on('data', chunk => { chunks.push(chunk); }); req.on('end', () => { const js = Buffer.concat(chunks).toString('utf8'); - try { - const result = eval(js); // eslint-disable-line no-eval - res.end(v8.serialize({ result })); - } catch (e) { - res.end(v8.serialize({ error: e.stack })); - } + (async () => { + try { + const result = await Promise.resolve(eval(js)); // eslint-disable-line no-eval + res.end(v8.serialize({ result })); + } catch (e) { + res.end(v8.serialize({ error: e.stack })); + } + })(); }); }).listen(0, '127.0.0.1', () => { process.stdout.write(`Listening: ${server.address().port}\n`); diff --git a/spec-main/fixtures/module/print-crash-parameters.js b/spec-main/fixtures/module/print-crash-parameters.js new file mode 100644 index 00000000000..94fde0fbd5c --- /dev/null +++ b/spec-main/fixtures/module/print-crash-parameters.js @@ -0,0 +1,2 @@ +process.crashReporter.addExtraParameter('hello', 'world'); +process.stdout.write(JSON.stringify(process.crashReporter.getParameters()) + '\n');