// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright // 2016 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 "libcef/common/crash_reporting.h" #include "include/cef_crash_util.h" #include "libcef/common/cef_switches.h" #include "libcef/features/runtime.h" #include "base/base_switches.h" #include "base/command_line.h" #include "base/debug/crash_logging.h" #include "base/logging.h" #include "base/stl_util.h" #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "chrome/common/crash_keys.h" #include "components/crash/core/common/crash_key.h" #include "components/crash/core/common/crash_keys.h" #include "content/public/common/content_switches.h" #if defined(OS_MAC) #include "base/mac/foundation_util.h" #include "components/crash/core/app/crashpad.h" #include "components/crash/core/common/crash_keys.h" #include "content/public/common/content_paths.h" #endif #if defined(OS_POSIX) #include "base/lazy_instance.h" #include "libcef/common/crash_reporter_client.h" #endif #if defined(OS_POSIX) && !defined(OS_MAC) #include "components/crash/core/app/breakpad_linux.h" #include "v8/include/v8-wasm-trap-handler-posix.h" #endif namespace crash_reporting { namespace { #if defined(OS_WIN) const base::FilePath::CharType kChromeElfDllName[] = FILE_PATH_LITERAL("chrome_elf.dll"); // exported in crash_reporter_client.cc: // int __declspec(dllexport) __cdecl SetCrashKeyValueImpl. typedef int(__cdecl* SetCrashKeyValue)(const char*, size_t, const char*, size_t); // int __declspec(dllexport) __cdecl IsCrashReportingEnabledImpl. typedef int(__cdecl* IsCrashReportingEnabled)(); bool SetCrashKeyValueTrampoline(const base::StringPiece& key, const base::StringPiece& value) { static SetCrashKeyValue set_crash_key = []() { HMODULE elf_module = GetModuleHandle(kChromeElfDllName); return reinterpret_cast( elf_module ? GetProcAddress(elf_module, "SetCrashKeyValueImpl") : nullptr); }(); if (set_crash_key) { return !!(set_crash_key)(key.data(), key.size(), value.data(), value.size()); } return false; } bool IsCrashReportingEnabledTrampoline() { static IsCrashReportingEnabled is_crash_reporting_enabled = []() { HMODULE elf_module = GetModuleHandle(kChromeElfDllName); return reinterpret_cast( elf_module ? GetProcAddress(elf_module, "IsCrashReportingEnabledImpl") : nullptr); }(); if (is_crash_reporting_enabled) { return !!(is_crash_reporting_enabled)(); } return false; } #endif // defined(OS_WIN) bool g_crash_reporting_enabled = false; #if defined(OS_POSIX) base::LazyInstance::Leaky g_crash_reporter_client = LAZY_INSTANCE_INITIALIZER; void InitCrashReporter(const base::CommandLine& command_line, const std::string& process_type) { CefCrashReporterClient* crash_client = g_crash_reporter_client.Pointer(); if (!crash_client->HasCrashConfigFile()) return; crash_reporter::SetCrashReporterClient(crash_client); #if defined(OS_MAC) // TODO(mark): Right now, InitializeCrashpad() needs to be called after // CommandLine::Init() and configuration of chrome::DIR_CRASH_DUMPS. Ideally, // Crashpad initialization could occur sooner, preferably even before the // framework dylib is even loaded, to catch potential early crashes. crash_reporter::InitializeCrashpad(process_type.empty(), process_type); if (base::mac::AmIBundled()) { // Mac Chrome is packaged with a main app bundle and a helper app bundle. // The main app bundle should only be used for the browser process, so it // should never see a --type switch (switches::kProcessType). Likewise, // the helper should always have a --type switch. // // This check is done this late so there is already a call to // base::mac::IsBackgroundOnlyProcess(), so there is no change in // startup/initialization order. // The helper's Info.plist marks it as a background only app. if (base::mac::IsBackgroundOnlyProcess()) { CHECK(command_line.HasSwitch(switches::kProcessType) && !process_type.empty()) << "Helper application requires --type."; } else { CHECK(!command_line.HasSwitch(switches::kProcessType) && process_type.empty()) << "Main application forbids --type, saw " << process_type; } } g_crash_reporting_enabled = true; #else // !defined(OS_MAC) if (process_type != switches::kZygoteProcess) { // Crash reporting for subprocesses created using the zygote will be // initialized in ZygoteForked. breakpad::InitCrashReporter(process_type); g_crash_reporting_enabled = true; } #endif // !defined(OS_MAC) } #endif // defined(OS_POSIX) // Used to exclude command-line flags from crash reporting. bool IsBoringCEFSwitch(const std::string& flag) { if (crash_keys::IsBoringChromeSwitch(flag)) return true; static const char* const kIgnoreSwitches[] = { // CEF internals. switches::kLogFile, // Chromium internals. "content-image-texture-target", "mojo-platform-channel-handle", "primordial-pipe-token", "service-pipe-token", "service-request-channel-token", }; if (!base::StartsWith(flag, "--", base::CompareCase::SENSITIVE)) return false; size_t end = flag.find("="); size_t len = (end == std::string::npos) ? flag.length() - 2 : end - 2; for (size_t i = 0; i < base::size(kIgnoreSwitches); ++i) { if (flag.compare(2, len, kIgnoreSwitches[i]) == 0) return true; } return false; } } // namespace bool Enabled() { return g_crash_reporting_enabled; } bool SetCrashKeyValue(const base::StringPiece& key, const base::StringPiece& value) { if (!g_crash_reporting_enabled) return false; #if defined(OS_WIN) return SetCrashKeyValueTrampoline(key, value); #else return g_crash_reporter_client.Pointer()->SetCrashKeyValue(key, value); #endif } #if defined(OS_POSIX) // Be aware that logging is not initialized at the time this method is called. void BasicStartupComplete(base::CommandLine* command_line) { CefCrashReporterClient* crash_client = g_crash_reporter_client.Pointer(); if (crash_client->ReadCrashConfigFile()) { #if !defined(OS_MAC) // Breakpad requires this switch. command_line->AppendSwitch(switches::kEnableCrashReporter); breakpad::SetFirstChanceExceptionHandler(v8::TryHandleWebAssemblyTrapPosix); #endif } } #endif void PreSandboxStartup(const base::CommandLine& command_line, const std::string& process_type) { #if defined(OS_POSIX) // Initialize crash reporting here on macOS and Linux. Crash reporting on // Windows is initialized from context.cc. InitCrashReporter(command_line, process_type); #elif defined(OS_WIN) g_crash_reporting_enabled = IsCrashReportingEnabledTrampoline(); #endif if (g_crash_reporting_enabled) { LOG(INFO) << "Crash reporting enabled for process: " << (process_type.empty() ? "browser" : process_type.c_str()); } crash_reporter::InitializeCrashKeys(); // After platform crash reporting have been initialized, store the command // line for crash reporting. crash_keys::SetSwitchesFromCommandLine(command_line, &IsBoringCEFSwitch); } #if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_MAC) void ZygoteForked(base::CommandLine* command_line, const std::string& process_type) { CefCrashReporterClient* crash_client = g_crash_reporter_client.Pointer(); if (crash_client->HasCrashConfigFile()) { // Breakpad requires this switch. command_line->AppendSwitch(switches::kEnableCrashReporter); } InitCrashReporter(*command_line, process_type); if (g_crash_reporting_enabled) { LOG(INFO) << "Crash reporting enabled for process: " << process_type; } // Reset the command line for the newly spawned process. crash_keys::SetSwitchesFromCommandLine(*command_line, &IsBoringCEFSwitch); } #endif } // namespace crash_reporting bool CefCrashReportingEnabled() { return crash_reporting::Enabled(); } void CefSetCrashKeyValue(const CefString& key, const CefString& value) { if (!crash_reporting::SetCrashKeyValue(key.ToString(), value.ToString())) { LOG(WARNING) << "Failed to set crash key: " << key.ToString() << " with value: " << value.ToString(); } } // From libcef/features/runtime.h: namespace cef { bool IsCrashReportingEnabled() { return crash_reporting::Enabled(); } } // namespace cef