diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-08-28 15:28:34 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-08-28 13:54:51 +0000 |
commit | 2a19c63448c84c1805fb1a585c3651318bb86ca7 (patch) | |
tree | eb17888e8531aa6ee5e85721bd553b832a7e5156 /chromium/components/crash | |
parent | b014812705fc80bff0a5c120dfcef88f349816dc (diff) | |
download | qtwebengine-chromium-2a19c63448c84c1805fb1a585c3651318bb86ca7.tar.gz |
BASELINE: Update Chromium to 69.0.3497.70
Change-Id: I2b7b56e4e7a8b26656930def0d4575dc32b900a0
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/crash')
19 files changed, 1020 insertions, 390 deletions
diff --git a/chromium/components/crash/android/OWNERS b/chromium/components/crash/android/OWNERS index 9c568c2eec8..1c0302aaf81 100644 --- a/chromium/components/crash/android/OWNERS +++ b/chromium/components/crash/android/OWNERS @@ -1,7 +1,3 @@ -# Java readability and UMA -mariakhomenko@chromium.org - -# Crash uploading mechanism gsennton@chromium.org isherman@chromium.org diff --git a/chromium/components/crash/content/app/BUILD.gn b/chromium/components/crash/content/app/BUILD.gn index 4d9d30aa891..d4fa4c3e6de 100644 --- a/chromium/components/crash/content/app/BUILD.gn +++ b/chromium/components/crash/content/app/BUILD.gn @@ -74,6 +74,7 @@ static_library("app") { "//content/public/common:result_codes", "//sandbox", "//third_party/breakpad:client", + "//third_party/crashpad/crashpad/snapshot", ] # Clang's -mstackrealign doesn't work well with diff --git a/chromium/components/crash/content/app/breakpad_linux.cc b/chromium/components/crash/content/app/breakpad_linux.cc index 76f15cb5b51..a7378204ffa 100644 --- a/chromium/components/crash/content/app/breakpad_linux.cc +++ b/chromium/components/crash/content/app/breakpad_linux.cc @@ -55,6 +55,7 @@ #include <sys/stat.h> #include "base/android/build_info.h" +#include "base/android/java_exception_reporter.h" #include "base/android/path_utils.h" #include "base/debug/leak_annotations.h" #endif @@ -109,6 +110,21 @@ uint32_t g_dumps_suppressed = 0; char* g_process_type = nullptr; ExceptionHandler* g_microdump = nullptr; int g_signal_code_pipe_fd = -1; +char* g_java_exception_info = nullptr; + +void SetJavaExceptionInfo(const char* exception) { + if (g_java_exception_info) { + // The old exception should be cleared before setting a new one. + DCHECK(!exception); + free(g_java_exception_info); + } + + if (exception) { + g_java_exception_info = strndup(exception, 5 * 4096); + } else { + g_java_exception_info = nullptr; + } +} class MicrodumpInfo { public: @@ -1745,9 +1761,8 @@ void HandleCrashDump(const BreakpadInfo& info) { WriteAndroidPackage(writer, android_build_info); writer.AddBoundary(); } - if (android_build_info->java_exception_info() != nullptr) { - writer.AddPairString(exception_info, - android_build_info->java_exception_info()); + if (g_java_exception_info != nullptr) { + writer.AddPairString(exception_info, g_java_exception_info); writer.AddBoundary(); } #endif @@ -1942,6 +1957,8 @@ void InitCrashReporter(const std::string& process_type, void InitCrashReporter(const std::string& process_type) { #endif // defined(OS_ANDROID) #if defined(OS_ANDROID) + base::android::SetJavaExceptionCallback(SetJavaExceptionInfo); + // This will guarantee that the BuildInfo has been initialized and subsequent // calls will not require memory allocation. base::android::BuildInfo::GetInstance(); @@ -2029,6 +2046,8 @@ void InitNonBrowserCrashReporterForAndroid( const base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + base::android::SetJavaExceptionCallback(SetJavaExceptionInfo); + // Handler registration is LIFO. Install the microdump handler first, such // that if conventional minidump crash reporting is enabled below, it takes // precedence (i.e. its handler is run first) over the microdump handler. diff --git a/chromium/components/crash/content/app/crash_reporter_client.cc b/chromium/components/crash/content/app/crash_reporter_client.cc index 464fe2efc0d..f69c6c3ff26 100644 --- a/chromium/components/crash/content/app/crash_reporter_client.cc +++ b/chromium/components/crash/content/app/crash_reporter_client.cc @@ -171,6 +171,17 @@ bool CrashReporterClient::ShouldEnableBreakpadMicrodumps() { } #endif +#if defined(OS_ANDROID) || defined(OS_LINUX) +void CrashReporterClient::GetSanitizationInformation( + const char* const** annotations_whitelist, + void** target_module, + bool* sanitize_stacks) { + *annotations_whitelist = nullptr; + *target_module = nullptr; + *sanitize_stacks = false; +} +#endif + bool CrashReporterClient::ShouldMonitorCrashHandlerExpensively() { return false; } diff --git a/chromium/components/crash/content/app/crash_reporter_client.h b/chromium/components/crash/content/app/crash_reporter_client.h index e6a09126bdb..485c2c8bf63 100644 --- a/chromium/components/crash/content/app/crash_reporter_client.h +++ b/chromium/components/crash/content/app/crash_reporter_client.h @@ -165,6 +165,22 @@ class CrashReporterClient { virtual bool ShouldEnableBreakpadMicrodumps(); #endif +#if defined(OS_ANDROID) || defined(OS_LINUX) + // Configures sanitization of crash dumps. + // |annotations_whitelist| is a nullptr terminated array of NUL-terminated + // strings of allowed annotation names or nullptr if all annotations are + // allowed. |target_module| is a pointer to a location inside a module to + // target or nullptr if there is no target module. Crash dumps are not + // produced when the crashing thread's stack and program counter do not + // reference the target module. |sanitize_stacks| is true if stacks should be + // sanitized for possible PII. If they are sanitized, only small integers and + // pointers to modules and stacks will be preserved. + virtual void GetSanitizationInformation( + const char* const** annotations_whitelist, + void** target_module, + bool* sanitize_stacks); +#endif + // 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/chromium/components/crash/content/app/crashpad_linux.cc b/chromium/components/crash/content/app/crashpad_linux.cc index d494877ca88..c8f87a85607 100644 --- a/chromium/components/crash/content/app/crashpad_linux.cc +++ b/chromium/components/crash/content/app/crashpad_linux.cc @@ -16,22 +16,43 @@ #include "base/files/scoped_file.h" #include "base/logging.h" #include "base/macros.h" +#include "base/no_destructor.h" #include "base/path_service.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/global_descriptors.h" +#include "base/strings/stringprintf.h" #include "build/build_config.h" #include "components/crash/content/app/crash_reporter_client.h" #include "content/public/common/content_descriptors.h" #include "sandbox/linux/services/syscall_wrappers.h" +#include "third_party/crashpad/crashpad/client/annotation.h" #include "third_party/crashpad/crashpad/client/crashpad_client.h" +#include "third_party/crashpad/crashpad/snapshot/sanitized/sanitization_information.h" #include "third_party/crashpad/crashpad/util/linux/exception_handler_client.h" #include "third_party/crashpad/crashpad/util/linux/exception_information.h" #include "third_party/crashpad/crashpad/util/misc/from_pointer_cast.h" #include "third_party/crashpad/crashpad/util/posix/signals.h" +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#include "base/android/java_exception_reporter.h" +#endif // OS_ANDROID + namespace crashpad { namespace { +bool SetSanitizationInfo(SanitizationInformation* info) { + const char* const* whitelist = nullptr; + void* target_module = nullptr; + bool sanitize_stacks = false; + crash_reporter::GetCrashReporterClient()->GetSanitizationInformation( + &whitelist, &target_module, &sanitize_stacks); + info->annotations_whitelist_address = FromPointerCast<VMAddress>(whitelist); + info->target_module_address = FromPointerCast<VMAddress>(target_module); + info->sanitize_stacks = sanitize_stacks; + return whitelist != nullptr || target_module != nullptr || sanitize_stacks; +} + // A signal handler for non-browser processes in the sandbox. // Sends a message to a crashpad::CrashHandlerHost to handle the crash. class SandboxedHandler { @@ -42,6 +63,7 @@ class SandboxedHandler { } bool Initialize() { + SetSanitizationInfo(&sanitization_); server_fd_ = base::GlobalDescriptors::GetInstance()->Get( service_manager::kCrashDumpSignal); @@ -107,6 +129,10 @@ class SandboxedHandler { FromPointerCast<decltype(info.exception_information_address)>( &exception_information); + info.sanitization_information_address = + FromPointerCast<decltype(info.sanitization_information_address)>( + &state->sanitization_); + ExceptionHandlerClient handler_client(connection.get()); handler_client.SetCanSetPtracer(false); handler_client.RequestCrashDump(info); @@ -115,6 +141,7 @@ class SandboxedHandler { Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); } + SanitizationInformation sanitization_; int server_fd_; DISALLOW_COPY_AND_ASSIGN(SandboxedHandler); @@ -123,6 +150,45 @@ class SandboxedHandler { } // namespace } // namespace crashpad +#if defined(OS_ANDROID) + +namespace { + +void SetJavaExceptionInfo(const char* info_string) { + static crashpad::StringAnnotation<5 * 4096> exception_info("exception_info"); + if (info_string) { + exception_info.Set(info_string); + } else { + exception_info.Clear(); + } +} + +void SetBuildInfoAnnotations(std::map<std::string, std::string>* annotations) { + base::android::BuildInfo* info = base::android::BuildInfo::GetInstance(); + + (*annotations)["android_build_id"] = info->android_build_id(); + (*annotations)["android_build_fp"] = info->android_build_fp(); + (*annotations)["device"] = info->device(); + (*annotations)["model"] = info->model(); + (*annotations)["brand"] = info->brand(); + (*annotations)["board"] = info->board(); + (*annotations)["installer_package_name"] = info->installer_package_name(); + (*annotations)["abi_name"] = info->abi_name(); + (*annotations)["custom_themes"] = info->custom_themes(); + (*annotations)["resources_verison"] = info->resources_version(); + (*annotations)["gms_core_version"] = info->gms_version_code(); + + if (info->firebase_app_id()[0] != '\0') { + (*annotations)["package"] = std::string(info->firebase_app_id()) + " v" + + info->package_version_code() + " (" + + info->package_version_name() + ")"; + } +} + +} // namespace + +#endif // OS_ANDROID + namespace crash_reporter { namespace internal { @@ -196,6 +262,10 @@ bool BuildHandlerArgs(base::FilePath* handler_path, (*process_annotations)["prod"] = product_name; (*process_annotations)["ver"] = product_version; +#if defined(OS_ANDROID) + SetBuildInfoAnnotations(process_annotations); +#endif // OS_ANDROID + #if defined(GOOGLE_CHROME_BUILD) // Empty means stable. const bool allow_empty_channel = true; @@ -234,6 +304,10 @@ base::FilePath PlatformCrashpadInitialization(bool initial_client, DCHECK(!embedded_handler); DCHECK(exe_path.empty()); +#if defined(OS_ANDROID) + base::android::SetJavaExceptionCallback(SetJavaExceptionInfo); +#endif // OS_ANDROID + if (browser_process) { base::FilePath handler_path; base::FilePath database_path; @@ -246,6 +320,13 @@ base::FilePath PlatformCrashpadInitialization(bool initial_client, return base::FilePath(); } + static base::NoDestructor<crashpad::SanitizationInformation> + sanitization_info; + if (crashpad::SetSanitizationInfo(sanitization_info.get())) { + arguments.push_back(base::StringPrintf("--sanitization-information=%p", + sanitization_info.get())); + } + bool result = GetCrashpadClient().StartHandlerAtCrash( handler_path, database_path, metrics_path, url, process_annotations, arguments); diff --git a/chromium/components/crash/content/browser/BUILD.gn b/chromium/components/crash/content/browser/BUILD.gn index 64761ead918..68c98d4cf66 100644 --- a/chromium/components/crash/content/browser/BUILD.gn +++ b/chromium/components/crash/content/browser/BUILD.gn @@ -11,12 +11,14 @@ assert(!is_fuchsia) source_set("browser") { sources = [ + "child_exit_observer_android.cc", + "child_exit_observer_android.h", "child_process_crash_observer_android.cc", "child_process_crash_observer_android.h", "crash_dump_manager_android.cc", "crash_dump_manager_android.h", - "crash_dump_observer_android.cc", - "crash_dump_observer_android.h", + "crash_metrics_reporter_android.cc", + "crash_metrics_reporter_android.h", ] deps = [ @@ -57,6 +59,7 @@ source_set("unit_tests") { testonly = true sources = [ "crash_dump_manager_android_unittest.cc", + "crash_metrics_reporter_android_unittest.cc", ] deps = [ ":browser", diff --git a/chromium/components/crash/content/browser/crash_dump_observer_android.cc b/chromium/components/crash/content/browser/child_exit_observer_android.cc index 7e2883d9c39..aa028dfe36a 100644 --- a/chromium/components/crash/content/browser/crash_dump_observer_android.cc +++ b/chromium/components/crash/content/browser/child_exit_observer_android.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/crash/content/browser/crash_dump_observer_android.h" +#include "components/crash/content/browser/child_exit_observer_android.h" #include <unistd.h> @@ -18,48 +18,54 @@ using content::BrowserThread; -namespace breakpad { +namespace crash_reporter { namespace { -base::LazyInstance<CrashDumpObserver>::DestructorAtExit g_instance = +base::LazyInstance<ChildExitObserver>::DestructorAtExit g_instance = LAZY_INSTANCE_INITIALIZER; void PopulateTerminationInfo( const content::ChildProcessTerminationInfo& content_info, - CrashDumpObserver::TerminationInfo* info) { - info->has_oom_protection_bindings = content_info.has_oom_protection_bindings; + ChildExitObserver::TerminationInfo* info) { + info->binding_state = content_info.binding_state; info->was_killed_intentionally_by_browser = content_info.was_killed_intentionally_by_browser; + info->remaining_process_with_strong_binding = + content_info.remaining_process_with_strong_binding; + info->remaining_process_with_moderate_binding = + content_info.remaining_process_with_moderate_binding; + info->remaining_process_with_waived_binding = + content_info.remaining_process_with_waived_binding; info->was_oom_protected_status = content_info.status == base::TERMINATION_STATUS_OOM_PROTECTED; } } // namespace -CrashDumpObserver::TerminationInfo::TerminationInfo() = default; -CrashDumpObserver::TerminationInfo::TerminationInfo( +ChildExitObserver::TerminationInfo::TerminationInfo() = default; +ChildExitObserver::TerminationInfo::TerminationInfo( const TerminationInfo& other) = default; -CrashDumpObserver::TerminationInfo& CrashDumpObserver::TerminationInfo:: +ChildExitObserver::TerminationInfo& ChildExitObserver::TerminationInfo:: operator=(const TerminationInfo& other) = default; // static -void CrashDumpObserver::Create() { +void ChildExitObserver::Create() { DCHECK_CURRENTLY_ON(BrowserThread::UI); // If this DCHECK fails in a unit test then a previously executing - // test that makes use of CrashDumpObserver forgot to create a + // test that makes use of ChildExitObserver forgot to create a // ShadowingAtExitManager. DCHECK(!g_instance.IsCreated()); g_instance.Get(); } // static -CrashDumpObserver* CrashDumpObserver::GetInstance() { +ChildExitObserver* ChildExitObserver::GetInstance() { DCHECK(g_instance.IsCreated()); return g_instance.Pointer(); } -CrashDumpObserver::CrashDumpObserver() { +ChildExitObserver::ChildExitObserver() { notification_registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED, content::NotificationService::AllSources()); @@ -72,17 +78,17 @@ CrashDumpObserver::CrashDumpObserver() { BrowserChildProcessObserver::Add(this); } -CrashDumpObserver::~CrashDumpObserver() { +ChildExitObserver::~ChildExitObserver() { BrowserChildProcessObserver::Remove(this); } -void CrashDumpObserver::RegisterClient(std::unique_ptr<Client> client) { +void ChildExitObserver::RegisterClient(std::unique_ptr<Client> client) { DCHECK_CURRENTLY_ON(BrowserThread::UI); base::AutoLock auto_lock(registered_clients_lock_); registered_clients_.push_back(std::move(client)); } -void CrashDumpObserver::OnChildExit(const TerminationInfo& info) { +void ChildExitObserver::OnChildExit(const TerminationInfo& info) { DCHECK_CURRENTLY_ON(BrowserThread::UI); std::vector<Client*> registered_clients_copy; { @@ -95,7 +101,7 @@ void CrashDumpObserver::OnChildExit(const TerminationInfo& info) { } } -void CrashDumpObserver::BrowserChildProcessStarted( +void ChildExitObserver::BrowserChildProcessStarted( int process_host_id, content::PosixFileDescriptorInfo* mappings) { std::vector<Client*> registered_clients_copy; @@ -109,7 +115,7 @@ void CrashDumpObserver::BrowserChildProcessStarted( } } -void CrashDumpObserver::BrowserChildProcessHostDisconnected( +void ChildExitObserver::BrowserChildProcessHostDisconnected( const content::ChildProcessData& data) { DCHECK_CURRENTLY_ON(BrowserThread::UI); TerminationInfo info; @@ -127,7 +133,7 @@ void CrashDumpObserver::BrowserChildProcessHostDisconnected( OnChildExit(info); } -void CrashDumpObserver::BrowserChildProcessKilled( +void ChildExitObserver::BrowserChildProcessKilled( const content::ChildProcessData& data, const content::ChildProcessTerminationInfo& content_info) { DCHECK_CURRENTLY_ON(BrowserThread::UI); @@ -142,7 +148,7 @@ void CrashDumpObserver::BrowserChildProcessKilled( // Subsequent BrowserChildProcessHostDisconnected will call OnChildExit. } -void CrashDumpObserver::Observe(int type, +void ChildExitObserver::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_CURRENTLY_ON(BrowserThread::UI); @@ -193,4 +199,4 @@ void CrashDumpObserver::Observe(int type, OnChildExit(info); } -} // namespace breakpad +} // namespace crash_reporter diff --git a/chromium/components/crash/content/browser/crash_dump_observer_android.h b/chromium/components/crash/content/browser/child_exit_observer_android.h index 2564e1ecbae..5e9f239c309 100644 --- a/chromium/components/crash/content/browser/crash_dump_observer_android.h +++ b/chromium/components/crash/content/browser/child_exit_observer_android.h @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_DUMP_OBSERVER_ANDROID_H_ -#define COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_DUMP_OBSERVER_ANDROID_H_ +#ifndef COMPONENTS_CRASH_CONTENT_BROWSER_CHILD_EXIT_OBSERVER_ANDROID_H_ +#define COMPONENTS_CRASH_CONTENT_BROWSER_CHILD_EXIT_OBSERVER_ANDROID_H_ #include <memory> #include <vector> #include "base/android/application_status_listener.h" +#include "base/android/child_process_binding_types.h" #include "base/lazy_instance.h" #include "base/memory/ref_counted.h" #include "base/process/process.h" @@ -23,12 +24,12 @@ namespace content { struct ChildProcessTerminationInfo; } -namespace breakpad { +namespace crash_reporter { // This class centralises the observation of child processes for the // purpose of reacting to child process crashes. -// The CrashDumpObserver instance exists on the browser main thread. -class CrashDumpObserver : public content::BrowserChildProcessObserver, +// The ChildExitObserver instance exists on the browser main thread. +class ChildExitObserver : public content::BrowserChildProcessObserver, public content::NotificationObserver { public: struct TerminationInfo { @@ -49,8 +50,12 @@ class CrashDumpObserver : public content::BrowserChildProcessObserver, // Values from ChildProcessTerminationInfo. // Note base::TerminationStatus and exit_code are missing intentionally // because those fields hold no useful information on Android. - bool has_oom_protection_bindings = false; + base::android::ChildBindingState binding_state = + base::android::ChildBindingState::UNBOUND; bool was_killed_intentionally_by_browser = false; + int remaining_process_with_strong_binding = 0; + int remaining_process_with_moderate_binding = 0; + int remaining_process_with_waived_binding = 0; // Note this is slightly different |has_oom_protection_bindings|. // This is equivalent to status == TERMINATION_STATUS_NORMAL_TERMINATION, @@ -70,7 +75,7 @@ class CrashDumpObserver : public content::BrowserChildProcessObserver, bool renderer_was_subframe = false; }; - // CrashDumpObserver client interface. + // ChildExitObserver client interface. // Client methods will be called synchronously in the order in which // clients were registered. It is the implementer's responsibility // to post tasks to the appropriate threads if required (and be @@ -93,31 +98,31 @@ class CrashDumpObserver : public content::BrowserChildProcessObserver, virtual ~Client() {} }; - // The global CrashDumpObserver instance is created by calling + // The global ChildExitObserver instance is created by calling // Create (on the UI thread), and lives until process exit. Tests // making use of this class should register an AtExitManager. static void Create(); - // Fetch a pointer to the global CrashDumpObserver instance. The + // Fetch a pointer to the global ChildExitObserver instance. The // global instance must have been created by the time GetInstance is // called. - static CrashDumpObserver* GetInstance(); + static ChildExitObserver* GetInstance(); void RegisterClient(std::unique_ptr<Client> client); // BrowserChildProcessStarted must be called from // ContentBrowserClient::GetAdditionalMappedFilesForChildProcess - // overrides, to notify the CrashDumpObserver of child process + // overrides, to notify the ChildExitObserver of child process // creation, and to allow clients to register any fd mappings they // need. void BrowserChildProcessStarted(int process_host_id, content::PosixFileDescriptorInfo* mappings); private: - friend struct base::LazyInstanceTraitsBase<CrashDumpObserver>; + friend struct base::LazyInstanceTraitsBase<ChildExitObserver>; - CrashDumpObserver(); - ~CrashDumpObserver() override; + ChildExitObserver(); + ~ChildExitObserver() override; // content::BrowserChildProcessObserver implementation: void BrowserChildProcessHostDisconnected( @@ -145,9 +150,9 @@ class CrashDumpObserver : public content::BrowserChildProcessObserver, // Key is process_host_id. Only used for BrowserChildProcessHost. std::map<int, TerminationInfo> browser_child_process_info_; - DISALLOW_COPY_AND_ASSIGN(CrashDumpObserver); + DISALLOW_COPY_AND_ASSIGN(ChildExitObserver); }; -} // namespace breakpad +} // namespace crash_reporter -#endif // COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_DUMP_OBSERVER_ANDROID_H_ +#endif // COMPONENTS_CRASH_CONTENT_BROWSER_CHILD_EXIT_OBSERVER_ANDROID_H_ diff --git a/chromium/components/crash/content/browser/child_process_crash_observer_android.cc b/chromium/components/crash/content/browser/child_process_crash_observer_android.cc index 83d10f0c0d2..28aada7b761 100644 --- a/chromium/components/crash/content/browser/child_process_crash_observer_android.cc +++ b/chromium/components/crash/content/browser/child_process_crash_observer_android.cc @@ -12,7 +12,7 @@ #include "components/crash/content/app/breakpad_linux.h" #include "components/crash/content/browser/crash_dump_manager_android.h" -namespace breakpad { +namespace crash_reporter { ChildProcessCrashObserver::ChildProcessCrashObserver( const base::FilePath crash_dump_dir, @@ -28,23 +28,23 @@ void ChildProcessCrashObserver::OnChildStart( return; base::ScopedFD file( - CrashDumpManager::GetInstance()->CreateMinidumpFileForChild( + breakpad::CrashDumpManager::GetInstance()->CreateMinidumpFileForChild( process_host_id)); if (file.is_valid()) mappings->Transfer(descriptor_id_, std::move(file)); } void ChildProcessCrashObserver::OnChildExit( - const CrashDumpObserver::TerminationInfo& info) { + const ChildExitObserver::TerminationInfo& info) { // This might be called twice for a given child process, with a // NOTIFICATION_RENDERER_PROCESS_TERMINATED and then with // NOTIFICATION_RENDERER_PROCESS_CLOSED. base::PostTaskWithTraits( FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND}, - base::Bind(&CrashDumpManager::ProcessMinidumpFileFromChild, - base::Unretained(CrashDumpManager::GetInstance()), + base::Bind(&breakpad::CrashDumpManager::ProcessMinidumpFileFromChild, + base::Unretained(breakpad::CrashDumpManager::GetInstance()), crash_dump_dir_, info)); } -} // namespace breakpad +} // namespace crash_reporter diff --git a/chromium/components/crash/content/browser/child_process_crash_observer_android.h b/chromium/components/crash/content/browser/child_process_crash_observer_android.h index 07a119cee8e..7710e6f9d9b 100644 --- a/chromium/components/crash/content/browser/child_process_crash_observer_android.h +++ b/chromium/components/crash/content/browser/child_process_crash_observer_android.h @@ -6,20 +6,21 @@ #define COMPONENTS_CRASH_CONTENT_BROWSER_CHILD_PROCESS_CRASH_OBSERVER_ANDROID_H_ #include "base/files/file_path.h" -#include "components/crash/content/browser/crash_dump_observer_android.h" +#include "components/crash/content/browser/child_exit_observer_android.h" -namespace breakpad { +namespace crash_reporter { -class ChildProcessCrashObserver : public breakpad::CrashDumpObserver::Client { +class ChildProcessCrashObserver + : public crash_reporter::ChildExitObserver::Client { public: ChildProcessCrashObserver(const base::FilePath crash_dump_dir, int descriptor_id); ~ChildProcessCrashObserver() override; - // breakpad::CrashDumpObserver::Client implementation: + // crash_reporter::ChildExitObserver::Client implementation: void OnChildStart(int process_host_id, content::PosixFileDescriptorInfo* mappings) override; - void OnChildExit(const CrashDumpObserver::TerminationInfo& info) override; + void OnChildExit(const ChildExitObserver::TerminationInfo& info) override; private: base::FilePath crash_dump_dir_; @@ -30,6 +31,6 @@ class ChildProcessCrashObserver : public breakpad::CrashDumpObserver::Client { DISALLOW_COPY_AND_ASSIGN(ChildProcessCrashObserver); }; -} // namespace breakpad +} // namespace crash_reporter #endif // COMPONENTS_CRASH_CONTENT_BROWSER_CHILD_PROCESS_CRASH_OBSERVER_ANDROID_H_ diff --git a/chromium/components/crash/content/browser/crash_dump_manager_android.cc b/chromium/components/crash/content/browser/crash_dump_manager_android.cc index 1cc00de0bb2..3e743de1503 100644 --- a/chromium/components/crash/content/browser/crash_dump_manager_android.cc +++ b/chromium/components/crash/content/browser/crash_dump_manager_android.cc @@ -22,133 +22,42 @@ #include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "components/crash/content/app/breakpad_linux.h" +#include "components/crash/content/browser/crash_metrics_reporter_android.h" #include "jni/CrashDumpManager_jni.h" namespace breakpad { namespace { + base::LazyInstance<CrashDumpManager>::Leaky g_instance = LAZY_INSTANCE_INITIALIZER; -// These values are persisted to logs. Entries should not be renumbered and -// numeric values should never be reused. -enum class ProcessedCrashCounts { - kGpuForegroundOom = 0, - kRendererForegroundVisibleOom = 1, - kRendererForegroundIntentionalKill = 2, - kRendererForegroundVisibleSubframeOom = 3, - kRendererForegroundVisibleSubframeIntentionalKill = 4, - kRendererForegroundVisibleCrash = 5, - kRendererForegroundVisibleSubframeCrash = 6, - kGpuCrashAll = 7, - kRendererCrashAll = 8, - kMaxValue = kRendererCrashAll -}; - -void LogCount(ProcessedCrashCounts type) { - UMA_HISTOGRAM_ENUMERATION("Stability.Android.ProcessedCrashCounts", type); -} - -void LogProcessedMetrics(const CrashDumpObserver::TerminationInfo& info, - bool has_crash_dump) { - const bool app_foreground = - info.app_state == - base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES || - info.app_state == base::android::APPLICATION_STATE_HAS_PAUSED_ACTIVITIES; - const bool intentional_kill = info.was_killed_intentionally_by_browser; - const bool android_oom_kill = !info.was_killed_intentionally_by_browser && - !has_crash_dump && !info.normal_termination; - const bool renderer_visible = info.renderer_has_visible_clients; - const bool renderer_sub_frame = info.renderer_was_subframe; - - if (info.process_type == content::PROCESS_TYPE_GPU) { - if (app_foreground && android_oom_kill) { - LogCount(ProcessedCrashCounts::kGpuForegroundOom); - } - if (has_crash_dump) { - LogCount(ProcessedCrashCounts::kGpuCrashAll); - } - } - - if (info.process_type == content::PROCESS_TYPE_RENDERER) { - if (app_foreground && renderer_visible) { - if (android_oom_kill) { - LogCount( - renderer_sub_frame - ? ProcessedCrashCounts::kRendererForegroundVisibleSubframeOom - : ProcessedCrashCounts::kRendererForegroundVisibleOom); - } - if (has_crash_dump) { - LogCount( - renderer_sub_frame - ? ProcessedCrashCounts::kRendererForegroundVisibleSubframeCrash - : ProcessedCrashCounts::kRendererForegroundVisibleCrash); - } - } - if (app_foreground && intentional_kill) { - LogCount(ProcessedCrashCounts::kRendererForegroundIntentionalKill); - if (renderer_visible && renderer_sub_frame) { - LogCount(ProcessedCrashCounts:: - kRendererForegroundVisibleSubframeIntentionalKill); - } - } - if (has_crash_dump) { - LogCount(ProcessedCrashCounts::kRendererCrashAll); - } +class DefaultUploader : public CrashDumpManager::Uploader { + public: + DefaultUploader() = default; + ~DefaultUploader() override = default; + + // CrashDumpManager::Uploader: + void TryToUploadCrashDump(const base::FilePath& crash_dump_path) override { + // Hop over to Java to attempt to attach the logcat to the crash. This may + // fail, which is ok -- if it does, the crash will still be uploaded on the + // next browser start. + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef<jstring> j_dump_path = + base::android::ConvertUTF8ToJavaString(env, crash_dump_path.value()); + Java_CrashDumpManager_tryToUploadMinidump(env, j_dump_path); } -} +}; } // namespace -CrashDumpManager::CrashDumpDetails::CrashDumpDetails( - int process_host_id, - content::ProcessType process_type, - bool was_oom_protected_status, - base::android::ApplicationState app_state) - : process_host_id(process_host_id), - process_type(process_type), - was_oom_protected_status(was_oom_protected_status), - app_state(app_state) {} - -CrashDumpManager::CrashDumpDetails::CrashDumpDetails() {} -CrashDumpManager::CrashDumpDetails::~CrashDumpDetails() {} - -CrashDumpManager::CrashDumpDetails::CrashDumpDetails( - const CrashDumpManager::CrashDumpDetails& other) - : CrashDumpDetails(other.process_host_id, - other.process_type, - other.was_oom_protected_status, - other.app_state) { - file_size = other.file_size; - status = other.status; -} - // static CrashDumpManager* CrashDumpManager::GetInstance() { return g_instance.Pointer(); } -// static -bool CrashDumpManager::IsForegroundOom(const CrashDumpDetails& details) { - // If the crash is size 0, it is an OOM. - return details.process_type == content::PROCESS_TYPE_RENDERER && - details.was_oom_protected_status && details.file_size == 0 && - details.app_state == - base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; -} - -void CrashDumpManager::AddObserver(Observer* observer) { - async_observers_->AddObserver(observer); -} - -void CrashDumpManager::RemoveObserver(Observer* observer) { - async_observers_->RemoveObserver(observer); -} - CrashDumpManager::CrashDumpManager() - : async_observers_( - base::MakeRefCounted< - base::ObserverListThreadSafe<CrashDumpManager::Observer>>()) {} + : uploader_(std::make_unique<DefaultUploader>()) {} CrashDumpManager::~CrashDumpManager() {} @@ -177,88 +86,48 @@ base::ScopedFD CrashDumpManager::CreateMinidumpFileForChild( void CrashDumpManager::ProcessMinidumpFileFromChild( base::FilePath crash_dump_dir, - const CrashDumpObserver::TerminationInfo& info) { + const crash_reporter::ChildExitObserver::TerminationInfo& info) { + CrashDumpManager::CrashDumpStatus status = + ProcessMinidumpFileFromChildInternal(crash_dump_dir, info); + crash_reporter::CrashMetricsReporter::GetInstance()->CrashDumpProcessed( + info, status); +} + +CrashDumpManager::CrashDumpStatus +CrashDumpManager::ProcessMinidumpFileFromChildInternal( + base::FilePath crash_dump_dir, + const crash_reporter::ChildExitObserver::TerminationInfo& info) { base::AssertBlockingAllowed(); - CrashDumpDetails details(info.process_host_id, info.process_type, - info.was_oom_protected_status, info.app_state); base::FilePath minidump_path; // If the minidump for a given child process has already been // processed, then there is no more work to do. if (!GetMinidumpPath(info.process_host_id, &minidump_path)) { - NotifyObservers(details); - return; + return CrashDumpStatus::kMissingDump; } int64_t file_size = 0; if (!base::PathExists(minidump_path)) { LOG(ERROR) << "minidump does not exist " << minidump_path.value(); - return; + return CrashDumpStatus::kMissingDump; } int r = base::GetFileSize(minidump_path, &file_size); DCHECK(r) << "Failed to retrieve size for minidump " << minidump_path.value(); - // TODO(wnwen): If these numbers match up to TabWebContentsObserver's - // TabRendererCrashStatus histogram, then remove that one as this is more - // accurate with more detail. - if ((info.process_type == content::PROCESS_TYPE_RENDERER || - info.process_type == content::PROCESS_TYPE_GPU) && - info.app_state != base::android::APPLICATION_STATE_UNKNOWN) { - ExitStatus exit_status; - bool is_running = (info.app_state == - base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES); - bool is_paused = (info.app_state == - base::android::APPLICATION_STATE_HAS_PAUSED_ACTIVITIES); - if (file_size == 0) { - if (is_running) { - exit_status = EMPTY_MINIDUMP_WHILE_RUNNING; - } else if (is_paused) { - exit_status = EMPTY_MINIDUMP_WHILE_PAUSED; - } else { - exit_status = EMPTY_MINIDUMP_WHILE_BACKGROUND; - } - } else { - if (is_running) { - exit_status = VALID_MINIDUMP_WHILE_RUNNING; - } else if (is_paused) { - exit_status = VALID_MINIDUMP_WHILE_PAUSED; - } else { - exit_status = VALID_MINIDUMP_WHILE_BACKGROUND; - } - } - if (info.process_type == content::PROCESS_TYPE_RENDERER) { - if (info.was_oom_protected_status) { - UMA_HISTOGRAM_ENUMERATION("Tab.RendererDetailedExitStatus", exit_status, - ExitStatus::MINIDUMP_STATUS_COUNT); - } else { - UMA_HISTOGRAM_ENUMERATION("Tab.RendererDetailedExitStatusUnbound", - exit_status, - ExitStatus::MINIDUMP_STATUS_COUNT); - } - } else if (info.process_type == content::PROCESS_TYPE_GPU) { - UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessDetailedExitStatus", exit_status, - ExitStatus::MINIDUMP_STATUS_COUNT); - } - } - - LogProcessedMetrics(info, file_size != 0); - if (file_size == 0) { // Empty minidump, this process did not crash. Just remove the file. r = base::DeleteFile(minidump_path, false); DCHECK(r) << "Failed to delete temporary minidump file " << minidump_path.value(); - details.status = CrashDumpStatus::kEmptyDump; - NotifyObservers(details); - return; + return CrashDumpStatus::kEmptyDump; } // We are dealing with a valid minidump. Copy it to the crash report // directory from where Java code will upload it later on. if (crash_dump_dir.empty()) { NOTREACHED() << "Failed to retrieve the crash dump directory."; - return; + return CrashDumpStatus::kDumpProcessingFailed; } const uint64_t rand = base::RandUint64(); const std::string filename = @@ -270,24 +139,12 @@ void CrashDumpManager::ProcessMinidumpFileFromChild( LOG(ERROR) << "Failed to move crash dump from " << minidump_path.value() << " to " << dest_path.value(); base::DeleteFile(minidump_path, false); - return; + return CrashDumpStatus::kDumpProcessingFailed; } VLOG(1) << "Crash minidump successfully generated: " << dest_path.value(); - // Hop over to Java to attempt to attach the logcat to the crash. This may - // fail, which is ok -- if it does, the crash will still be uploaded on the - // next browser start. - JNIEnv* env = base::android::AttachCurrentThread(); - base::android::ScopedJavaLocalRef<jstring> j_dest_path = - base::android::ConvertUTF8ToJavaString(env, dest_path.value()); - Java_CrashDumpManager_tryToUploadMinidump(env, j_dest_path); - details.status = CrashDumpStatus::kValidDump; - NotifyObservers(details); -} - -void CrashDumpManager::NotifyObservers(const CrashDumpDetails& details) { - async_observers_->Notify( - FROM_HERE, &CrashDumpManager::Observer::OnCrashDumpProcessed, details); + uploader_->TryToUploadCrashDump(dest_path); + return CrashDumpStatus::kValidDump; } void CrashDumpManager::SetMinidumpPath(int process_host_id, diff --git a/chromium/components/crash/content/browser/crash_dump_manager_android.h b/chromium/components/crash/content/browser/crash_dump_manager_android.h index 96f14e1b2c9..bc10af0c95e 100644 --- a/chromium/components/crash/content/browser/crash_dump_manager_android.h +++ b/chromium/components/crash/content/browser/crash_dump_manager_android.h @@ -6,15 +6,16 @@ #define COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_DUMP_MANAGER_ANDROID_H_ #include <map> +#include <memory> +#include <utility> #include "base/android/application_status_listener.h" #include "base/files/file_path.h" #include "base/files/platform_file.h" #include "base/lazy_instance.h" #include "base/memory/ref_counted.h" -#include "base/observer_list_threadsafe.h" #include "base/synchronization/lock.h" -#include "components/crash/content/browser/crash_dump_observer_android.h" +#include "components/crash/content/browser/child_exit_observer_android.h" #include "content/public/common/child_process_host.h" #include "content/public/common/process_type.h" @@ -32,78 +33,55 @@ namespace breakpad { // terminates. class CrashDumpManager { public: - // This enum is used to back a UMA histogram, and must be treated as - // append-only. - enum ExitStatus { - EMPTY_MINIDUMP_WHILE_RUNNING, - EMPTY_MINIDUMP_WHILE_PAUSED, - EMPTY_MINIDUMP_WHILE_BACKGROUND, - VALID_MINIDUMP_WHILE_RUNNING, - VALID_MINIDUMP_WHILE_PAUSED, - VALID_MINIDUMP_WHILE_BACKGROUND, - MINIDUMP_STATUS_COUNT - }; - enum class CrashDumpStatus { // The dump for this process did not have a path set. This can happen if the // dump was already processed or if crash dump generation is not turned on. - kNoDump, + kMissingDump, // The crash dump was empty. kEmptyDump, + // Minidump file was found, but could not be copied to crash dir. + kDumpProcessingFailed, + // The crash dump is valid. kValidDump, }; - struct CrashDumpDetails { - CrashDumpDetails(int process_host_id, - content::ProcessType process_type, - bool was_oom_protected_status, - base::android::ApplicationState app_state); - CrashDumpDetails(); - ~CrashDumpDetails(); - CrashDumpDetails(const CrashDumpDetails& other); - - int process_host_id = content::ChildProcessHost::kInvalidUniqueID; - - content::ProcessType process_type = content::PROCESS_TYPE_UNKNOWN; - bool was_oom_protected_status = false; - base::android::ApplicationState app_state; - int64_t file_size = 0; - CrashDumpStatus status = CrashDumpStatus::kNoDump; - }; - // Careful note: the CrashDumpManager observers are asynchronous, and are - // notified via PostTask. This could be problematic with a large number of - // observers. Consider using a middle-layer observer to fan out synchronously - // to leaf observers if you need many objects listening to these messages. - class Observer { + // Class which aids in uploading a crash dump. + class Uploader { public: - virtual void OnCrashDumpProcessed(const CrashDumpDetails& details) {} + virtual ~Uploader() = default; + + // Attempts to upload the specified child process minidump. + virtual void TryToUploadCrashDump( + const base::FilePath& crash_dump_path) = 0; }; static CrashDumpManager* GetInstance(); - // True when |details| is a foreground out of memory crash. - static bool IsForegroundOom(const CrashDumpDetails& details); - - // Can be called on any thread. - void AddObserver(Observer* observer); - void RemoveObserver(Observer* observer); - void ProcessMinidumpFileFromChild( base::FilePath crash_dump_dir, - const CrashDumpObserver::TerminationInfo& info); + const crash_reporter::ChildExitObserver::TerminationInfo& info); base::ScopedFD CreateMinidumpFileForChild(int process_host_id); + // Careful, |uploader_| is accessed on one of the task scheduler threads. + // Tests should set this before any other threads are spawned. + void set_uploader_for_testing(std::unique_ptr<Uploader> uploader) { + DCHECK(uploader); + uploader_ = std::move(uploader); + } + private: friend struct base::LazyInstanceTraitsBase<CrashDumpManager>; CrashDumpManager(); ~CrashDumpManager(); - void NotifyObservers(const CrashDumpDetails& details); + CrashDumpStatus ProcessMinidumpFileFromChildInternal( + base::FilePath crash_dump_dir, + const crash_reporter::ChildExitObserver::TerminationInfo& info); typedef std::map<int, base::FilePath> ChildProcessIDToMinidumpPath; @@ -111,8 +89,8 @@ class CrashDumpManager { const base::FilePath& minidump_path); bool GetMinidumpPath(int process_host_id, base::FilePath* minidump_path); - scoped_refptr<base::ObserverListThreadSafe<CrashDumpManager::Observer>> - async_observers_; + // Should never be nullptr. + std::unique_ptr<Uploader> uploader_; // This map should only be accessed with its lock aquired as it is accessed // from the PROCESS_LAUNCHER and UI threads. diff --git a/chromium/components/crash/content/browser/crash_dump_manager_android_unittest.cc b/chromium/components/crash/content/browser/crash_dump_manager_android_unittest.cc index a3ad2e198dc..83fd396f189 100644 --- a/chromium/components/crash/content/browser/crash_dump_manager_android_unittest.cc +++ b/chromium/components/crash/content/browser/crash_dump_manager_android_unittest.cc @@ -4,6 +4,7 @@ #include "components/crash/content/browser/crash_dump_manager_android.h" +#include <string> #include <utility> #include "base/at_exit.h" @@ -11,19 +12,63 @@ #include "base/bind_helpers.h" #include "base/callback.h" #include "base/files/file.h" +#include "base/files/file_util.h" #include "base/files/scoped_file.h" #include "base/files/scoped_temp_dir.h" #include "base/macros.h" +#include "base/memory/scoped_refptr.h" #include "base/optional.h" #include "base/run_loop.h" +#include "base/sequenced_task_runner.h" #include "base/task_scheduler/post_task.h" -#include "base/test/histogram_tester.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_task_environment.h" +#include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/thread_restrictions.h" +#include "components/crash/content/browser/crash_metrics_reporter_android.h" #include "testing/gtest/include/gtest/gtest.h" namespace breakpad { +class CrashDumpManagerTest; + +class CrashMetricsReporterObserver + : public crash_reporter::CrashMetricsReporter::Observer { + public: + CrashMetricsReporterObserver() {} + ~CrashMetricsReporterObserver() {} + + // crash_reporter::CrashMetricsReporter::Observer: + void OnCrashDumpProcessed( + int rph_id, + const crash_reporter::CrashMetricsReporter::ReportedCrashTypeSet& + reported_counts) override { + wait_run_loop_.QuitClosure().Run(); + } + + void WaitForProcessed() { wait_run_loop_.Run(); } + + private: + base::RunLoop wait_run_loop_; + DISALLOW_COPY_AND_ASSIGN(CrashMetricsReporterObserver); +}; + +class NoOpUploader : public CrashDumpManager::Uploader { + public: + NoOpUploader(scoped_refptr<base::SequencedTaskRunner> test_runner, + CrashDumpManagerTest* test_harness) + : test_runner_(std::move(test_runner)), test_harness_(test_harness) {} + ~NoOpUploader() override = default; + + // CrashDumpManager::Uploader: + void TryToUploadCrashDump(const base::FilePath& crash_dump_path) override; + + private: + scoped_refptr<base::SequencedTaskRunner> test_runner_; + CrashDumpManagerTest* test_harness_; + DISALLOW_COPY_AND_ASSIGN(NoOpUploader); +}; + class CrashDumpManagerTest : public testing::Test { public: CrashDumpManagerTest() @@ -34,130 +79,110 @@ class CrashDumpManagerTest : public testing::Test { void SetUp() override { // Initialize the manager. ASSERT_TRUE(CrashDumpManager::GetInstance()); + CrashDumpManager::GetInstance()->set_uploader_for_testing( + std::make_unique<NoOpUploader>(base::SequencedTaskRunnerHandle::Get(), + this)); } - // TODO(csharrison): This test harness is not robust enough. Namely, it does - // not support actually processing a non-empty crash dump, due to JNI calls. - static void CreateAndProcessEmptyMinidump( - const CrashDumpObserver::TerminationInfo& info) { + void OnUploadedCrashDump() { + dumps_uploaded_++; + if (!upload_waiter_.is_null()) + std::move(upload_waiter_).Run(); + } + + void WaitForCrashDumpsUploaded(int num_dumps) { + EXPECT_LE(num_dumps, dumps_uploaded_); + while (num_dumps < dumps_uploaded_) { + base::RunLoop run_loop; + upload_waiter_ = run_loop.QuitClosure(); + run_loop.Run(); + } + } + + static void CreateAndProcessCrashDump( + const crash_reporter::ChildExitObserver::TerminationInfo& info, + const std::string& data) { base::ScopedFD fd = CrashDumpManager::GetInstance()->CreateMinidumpFileForChild( info.process_host_id); EXPECT_TRUE(fd.is_valid()); + base::WriteFileDescriptor(fd.get(), data.data(), data.size()); base::ScopedTempDir dump_dir; EXPECT_TRUE(dump_dir.CreateUniqueTempDir()); CrashDumpManager::GetInstance()->ProcessMinidumpFileFromChild( dump_dir.GetPath(), info); } + protected: + int dumps_uploaded_ = 0; + private: base::ShadowingAtExitManager at_exit_; base::test::ScopedTaskEnvironment scoped_environment_; + base::OnceClosure upload_waiter_; DISALLOW_COPY_AND_ASSIGN(CrashDumpManagerTest); }; -class CrashDumpManagerObserver : public CrashDumpManager::Observer { - public: - CrashDumpManagerObserver() {} - ~CrashDumpManagerObserver() {} - - // CrashDumpManager::Observer: - void OnCrashDumpProcessed( - const CrashDumpManager::CrashDumpDetails& details) override { - last_details_ = details; - if (!wait_closure_.is_null()) - std::move(wait_closure_).Run(); - } - - const CrashDumpManager::CrashDumpDetails& last_details() const { - return last_details_.value(); - } - - void WaitForProcessed() { - base::RunLoop run_loop; - wait_closure_ = run_loop.QuitClosure(); - run_loop.Run(); - } - - private: - base::OnceClosure wait_closure_; - base::Optional<CrashDumpManager::CrashDumpDetails> last_details_; - DISALLOW_COPY_AND_ASSIGN(CrashDumpManagerObserver); -}; +void NoOpUploader::TryToUploadCrashDump(const base::FilePath& crash_dump_path) { + test_runner_->PostTask( + FROM_HERE, base::BindOnce(&CrashDumpManagerTest::OnUploadedCrashDump, + base::Unretained(test_harness_))); +} -TEST_F(CrashDumpManagerTest, SimpleOOM) { +TEST_F(CrashDumpManagerTest, NoDumpCreated) { base::HistogramTester histogram_tester; CrashDumpManager* manager = CrashDumpManager::GetInstance(); - CrashDumpManagerObserver crash_dump_observer; - manager->AddObserver(&crash_dump_observer); + CrashMetricsReporterObserver observer; + crash_reporter::CrashMetricsReporter::GetInstance()->AddObserver(&observer); - CrashDumpObserver::TerminationInfo termination_info; + crash_reporter::ChildExitObserver::TerminationInfo termination_info; termination_info.process_host_id = 1; termination_info.pid = base::kNullProcessHandle; termination_info.process_type = content::PROCESS_TYPE_RENDERER; termination_info.app_state = base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; termination_info.normal_termination = false; - termination_info.has_oom_protection_bindings = true; + termination_info.binding_state = base::android::ChildBindingState::STRONG; termination_info.was_killed_intentionally_by_browser = false; termination_info.was_oom_protected_status = true; base::PostTaskWithTraits( FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND}, - base::Bind(&CrashDumpManagerTest::CreateAndProcessEmptyMinidump, - termination_info)); - crash_dump_observer.WaitForProcessed(); - - const CrashDumpManager::CrashDumpDetails& details = - crash_dump_observer.last_details(); - EXPECT_EQ(termination_info.process_host_id, details.process_host_id); - EXPECT_EQ(content::PROCESS_TYPE_RENDERER, details.process_type); - EXPECT_TRUE(details.was_oom_protected_status); - EXPECT_EQ(base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES, - details.app_state); - EXPECT_EQ(0, details.file_size); - EXPECT_EQ(CrashDumpManager::CrashDumpStatus::kEmptyDump, details.status); + base::BindOnce(&CrashDumpManager::ProcessMinidumpFileFromChild, + base::Unretained(manager), base::FilePath(), + termination_info)); + observer.WaitForProcessed(); - histogram_tester.ExpectUniqueSample( - "Tab.RendererDetailedExitStatus", - CrashDumpManager::EMPTY_MINIDUMP_WHILE_RUNNING, 1); + histogram_tester.ExpectTotalCount("Tab.RendererDetailedExitStatus", 0); + EXPECT_EQ(0, dumps_uploaded_); } -TEST_F(CrashDumpManagerTest, NoDumpCreated) { +TEST_F(CrashDumpManagerTest, NonOomCrash) { base::HistogramTester histogram_tester; - CrashDumpManager* manager = CrashDumpManager::GetInstance(); - CrashDumpManagerObserver crash_dump_observer; - manager->AddObserver(&crash_dump_observer); + CrashMetricsReporterObserver observer; + crash_reporter::CrashMetricsReporter::GetInstance()->AddObserver(&observer); - CrashDumpObserver::TerminationInfo termination_info; + crash_reporter::ChildExitObserver::TerminationInfo termination_info; termination_info.process_host_id = 1; termination_info.pid = base::kNullProcessHandle; termination_info.process_type = content::PROCESS_TYPE_RENDERER; termination_info.app_state = base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; termination_info.normal_termination = false; - termination_info.has_oom_protection_bindings = true; + termination_info.binding_state = base::android::ChildBindingState::STRONG; termination_info.was_killed_intentionally_by_browser = false; termination_info.was_oom_protected_status = true; base::PostTaskWithTraits( FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND}, - base::Bind(&CrashDumpManager::ProcessMinidumpFileFromChild, - base::Unretained(manager), base::FilePath(), - termination_info)); - crash_dump_observer.WaitForProcessed(); - - const CrashDumpManager::CrashDumpDetails& details = - crash_dump_observer.last_details(); - EXPECT_EQ(termination_info.process_host_id, details.process_host_id); - EXPECT_EQ(content::PROCESS_TYPE_RENDERER, details.process_type); - EXPECT_TRUE(details.was_oom_protected_status); - EXPECT_EQ(base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES, - details.app_state); - EXPECT_EQ(0, details.file_size); - EXPECT_EQ(CrashDumpManager::CrashDumpStatus::kNoDump, details.status); + base::BindOnce(&CrashDumpManagerTest::CreateAndProcessCrashDump, + termination_info, "Some non-empty crash data")); + observer.WaitForProcessed(); - histogram_tester.ExpectTotalCount("Tab.RendererDetailedExitStatus", 0); + histogram_tester.ExpectUniqueSample( + "Tab.RendererDetailedExitStatus", + crash_reporter::CrashMetricsReporter::VALID_MINIDUMP_WHILE_RUNNING, 1); + WaitForCrashDumpsUploaded(1); } } // namespace breakpad diff --git a/chromium/components/crash/content/browser/crash_handler_host_linux.cc b/chromium/components/crash/content/browser/crash_handler_host_linux.cc index 57f710ab886..e672add8df1 100644 --- a/chromium/components/crash/content/browser/crash_handler_host_linux.cc +++ b/chromium/components/crash/content/browser/crash_handler_host_linux.cc @@ -48,7 +48,7 @@ #if !defined(OS_CHROMEOS) #include "components/crash/content/app/crashpad.h" -#include "third_party/crashpad/crashpad/client/crashpad_client.h" +#include "third_party/crashpad/crashpad/client/crashpad_client.h" // nogncheck #endif using content::BrowserThread; diff --git a/chromium/components/crash/content/browser/crash_metrics_reporter_android.cc b/chromium/components/crash/content/browser/crash_metrics_reporter_android.cc new file mode 100644 index 00000000000..3c2babbd6b6 --- /dev/null +++ b/chromium/components/crash/content/browser/crash_metrics_reporter_android.cc @@ -0,0 +1,271 @@ +// Copyright 2018 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 "components/crash/content/browser/crash_metrics_reporter_android.h" + +#include "base/logging.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/user_metrics.h" +#include "base/optional.h" +#include "components/crash/content/browser/crash_dump_manager_android.h" + +namespace crash_reporter { +namespace { + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class BindingStateCombo { + kNoWaivedNoModerateNoStrong = 0, + kNoWaivedNoModerateHasStrong = 1, + kNoWaivedHasModerateNoStrong = 2, + kNoWaivedHasModerateHasStrong = 3, + kHasWaivedNoModerateNoStrong = 4, + kHasWaivedNoModerateHasStrong = 5, + kHasWaivedHasModerateNoStrong = 6, + kHasWaivedHasModerateHasStrong = 7, + kMaxValue = kHasWaivedHasModerateHasStrong +}; + +void ReportCrashCount(CrashMetricsReporter::ProcessedCrashCounts crash_type, + CrashMetricsReporter::ReportedCrashTypeSet* counts) { + UMA_HISTOGRAM_ENUMERATION("Stability.Android.ProcessedCrashCounts", + crash_type); + counts->insert(crash_type); +} + +void ReportLegacyCrashUma(const ChildExitObserver::TerminationInfo& info, + bool has_valid_dump) { + // TODO(wnwen): If these numbers match up to TabWebContentsObserver's + // TabRendererCrashStatus histogram, then remove that one as this is more + // accurate with more detail. + if ((info.process_type == content::PROCESS_TYPE_RENDERER || + info.process_type == content::PROCESS_TYPE_GPU) && + info.app_state != base::android::APPLICATION_STATE_UNKNOWN) { + CrashMetricsReporter::ExitStatus exit_status; + bool is_running = (info.app_state == + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES); + bool is_paused = (info.app_state == + base::android::APPLICATION_STATE_HAS_PAUSED_ACTIVITIES); + if (!has_valid_dump) { + if (is_running) { + exit_status = CrashMetricsReporter::EMPTY_MINIDUMP_WHILE_RUNNING; + } else if (is_paused) { + exit_status = CrashMetricsReporter::EMPTY_MINIDUMP_WHILE_PAUSED; + } else { + exit_status = CrashMetricsReporter::EMPTY_MINIDUMP_WHILE_BACKGROUND; + } + } else { + if (is_running) { + exit_status = CrashMetricsReporter::VALID_MINIDUMP_WHILE_RUNNING; + } else if (is_paused) { + exit_status = CrashMetricsReporter::VALID_MINIDUMP_WHILE_PAUSED; + } else { + exit_status = CrashMetricsReporter::VALID_MINIDUMP_WHILE_BACKGROUND; + } + } + if (info.process_type == content::PROCESS_TYPE_RENDERER) { + if (info.was_oom_protected_status) { + UMA_HISTOGRAM_ENUMERATION( + "Tab.RendererDetailedExitStatus", exit_status, + CrashMetricsReporter::ExitStatus::MINIDUMP_STATUS_COUNT); + } else { + UMA_HISTOGRAM_ENUMERATION( + "Tab.RendererDetailedExitStatusUnbound", exit_status, + CrashMetricsReporter::ExitStatus::MINIDUMP_STATUS_COUNT); + } + } else if (info.process_type == content::PROCESS_TYPE_GPU) { + UMA_HISTOGRAM_ENUMERATION( + "GPU.GPUProcessDetailedExitStatus", exit_status, + CrashMetricsReporter::ExitStatus::MINIDUMP_STATUS_COUNT); + } + } +} + +} // namespace + +// static +CrashMetricsReporter* CrashMetricsReporter::GetInstance() { + static CrashMetricsReporter* instance = new CrashMetricsReporter(); + return instance; +} + +CrashMetricsReporter::CrashMetricsReporter() + : async_observers_( + base::MakeRefCounted< + base::ObserverListThreadSafe<CrashMetricsReporter::Observer>>()) { +} + +CrashMetricsReporter::~CrashMetricsReporter() {} + +void CrashMetricsReporter::AddObserver( + CrashMetricsReporter::Observer* observer) { + async_observers_->AddObserver(observer); +} + +void CrashMetricsReporter::RemoveObserver( + CrashMetricsReporter::Observer* observer) { + async_observers_->RemoveObserver(observer); +} + +void CrashMetricsReporter::CrashDumpProcessed( + const ChildExitObserver::TerminationInfo& info, + breakpad::CrashDumpManager::CrashDumpStatus status) { + ReportedCrashTypeSet reported_counts; + if (status == breakpad::CrashDumpManager::CrashDumpStatus::kMissingDump) { + NotifyObservers(info.process_host_id, reported_counts); + return; + } + + bool has_valid_dump = false; + switch (status) { + case breakpad::CrashDumpManager::CrashDumpStatus::kMissingDump: + NOTREACHED(); + break; + case breakpad::CrashDumpManager::CrashDumpStatus::kEmptyDump: + has_valid_dump = false; + break; + case breakpad::CrashDumpManager::CrashDumpStatus::kValidDump: + case breakpad::CrashDumpManager::CrashDumpStatus::kDumpProcessingFailed: + has_valid_dump = true; + break; + } + const bool app_foreground = + info.app_state == + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES || + info.app_state == base::android::APPLICATION_STATE_HAS_PAUSED_ACTIVITIES; + const bool intentional_kill = info.was_killed_intentionally_by_browser; + const bool android_oom_kill = !info.was_killed_intentionally_by_browser && + !has_valid_dump && !info.normal_termination; + const bool renderer_visible = info.renderer_has_visible_clients; + const bool renderer_subframe = info.renderer_was_subframe; + + if (info.process_type == content::PROCESS_TYPE_GPU && app_foreground && + android_oom_kill) { + ReportCrashCount(ProcessedCrashCounts::kGpuForegroundOom, &reported_counts); + } + + if (info.process_type == content::PROCESS_TYPE_RENDERER && app_foreground) { + if (renderer_visible) { + if (has_valid_dump) { + ReportCrashCount( + renderer_subframe + ? ProcessedCrashCounts::kRendererForegroundVisibleSubframeCrash + : ProcessedCrashCounts::kRendererForegroundVisibleCrash, + &reported_counts); + } else if (intentional_kill) { + ReportCrashCount( + renderer_subframe + ? ProcessedCrashCounts:: + kRendererForegroundVisibleSubframeIntentionalKill + : ProcessedCrashCounts:: + kRendererForegroundVisibleMainFrameIntentionalKill, + &reported_counts); + } else if (info.normal_termination) { + ReportCrashCount(ProcessedCrashCounts:: + kRendererForegroundVisibleNormalTermNoMinidump, + &reported_counts); + } else { + DCHECK(android_oom_kill); + if (renderer_subframe) { + ReportCrashCount( + ProcessedCrashCounts::kRendererForegroundVisibleSubframeOom, + &reported_counts); + } else { + ReportCrashCount(ProcessedCrashCounts::kRendererForegroundVisibleOom, + &reported_counts); + base::RecordAction( + base::UserMetricsAction("RendererForegroundMainFrameOOM")); + } + } + } else if (!has_valid_dump) { + // Record stats when renderer is not visible, but the process has oom + // protected bindings. This case occurs when a tab is switched or closed, + // the bindings are updated later than visibility on web contents. + switch (info.binding_state) { + case base::android::ChildBindingState::UNBOUND: + case base::android::ChildBindingState::WAIVED: + break; + case base::android::ChildBindingState::STRONG: + if (intentional_kill || info.normal_termination) { + ReportCrashCount( + ProcessedCrashCounts:: + kRendererForegroundInvisibleWithStrongBindingKilled, + &reported_counts); + } else { + ReportCrashCount( + ProcessedCrashCounts:: + kRendererForegroundInvisibleWithStrongBindingOom, + &reported_counts); + } + break; + case base::android::ChildBindingState::MODERATE: + if (intentional_kill || info.normal_termination) { + ReportCrashCount( + ProcessedCrashCounts:: + kRendererForegroundInvisibleWithModerateBindingKilled, + &reported_counts); + } else { + ReportCrashCount( + ProcessedCrashCounts:: + kRendererForegroundInvisibleWithModerateBindingOom, + &reported_counts); + } + break; + } + } + } + + if (info.process_type == content::PROCESS_TYPE_RENDERER && + (info.app_state == + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES || + info.app_state == + base::android::APPLICATION_STATE_HAS_PAUSED_ACTIVITIES) && + info.was_killed_intentionally_by_browser) { + ReportCrashCount(ProcessedCrashCounts::kRendererForegroundIntentionalKill, + &reported_counts); + } + + if (has_valid_dump) { + ReportCrashCount(info.process_type == content::PROCESS_TYPE_GPU + ? ProcessedCrashCounts::kGpuCrashAll + : ProcessedCrashCounts::kRendererCrashAll, + &reported_counts); + } + + if (app_foreground && android_oom_kill && + info.binding_state == base::android::ChildBindingState::STRONG) { + const bool has_waived = info.remaining_process_with_waived_binding > 0; + const bool has_moderate = info.remaining_process_with_moderate_binding > 0; + const bool has_strong = info.remaining_process_with_strong_binding > 0; + BindingStateCombo combo; + if (has_waived && has_moderate) { + combo = has_strong ? BindingStateCombo::kHasWaivedHasModerateHasStrong + : BindingStateCombo::kHasWaivedHasModerateNoStrong; + } else if (has_waived) { + combo = has_strong ? BindingStateCombo::kHasWaivedNoModerateHasStrong + : BindingStateCombo::kHasWaivedNoModerateNoStrong; + } else if (has_moderate) { + combo = has_strong ? BindingStateCombo::kNoWaivedHasModerateHasStrong + : BindingStateCombo::kNoWaivedHasModerateNoStrong; + } else { + combo = has_strong ? BindingStateCombo::kNoWaivedNoModerateHasStrong + : BindingStateCombo::kNoWaivedNoModerateNoStrong; + } + UMA_HISTOGRAM_ENUMERATION( + "Stability.Android.StrongBindingOomRemainingBindingState", combo); + } + + ReportLegacyCrashUma(info, has_valid_dump); + NotifyObservers(info.process_host_id, reported_counts); +} + +void CrashMetricsReporter::NotifyObservers( + int rph_id, + const CrashMetricsReporter::ReportedCrashTypeSet& reported_counts) { + async_observers_->Notify( + FROM_HERE, &CrashMetricsReporter::Observer::OnCrashDumpProcessed, rph_id, + reported_counts); +} + +} // namespace crash_reporter diff --git a/chromium/components/crash/content/browser/crash_metrics_reporter_android.h b/chromium/components/crash/content/browser/crash_metrics_reporter_android.h new file mode 100644 index 00000000000..3dcf044cabb --- /dev/null +++ b/chromium/components/crash/content/browser/crash_metrics_reporter_android.h @@ -0,0 +1,91 @@ +// Copyright 2018 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 COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_METRICS_REPORTER_ANDROID_H_ +#define COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_METRICS_REPORTER_ANDROID_H_ + +#include "base/containers/flat_set.h" +#include "base/observer_list_threadsafe.h" +#include "components/crash/content/browser/child_exit_observer_android.h" +#include "components/crash/content/browser/crash_dump_manager_android.h" + +namespace crash_reporter { + +// Reports crash metrics about child processes to UMA, which is used as ground +// truth for child process stability. This class should be used by any code that +// wants to observe reason for the death of a child process. +class CrashMetricsReporter { + public: + // This enum is used to back a UMA histogram, and must be treated as + // append-only. + enum ExitStatus { + EMPTY_MINIDUMP_WHILE_RUNNING, + EMPTY_MINIDUMP_WHILE_PAUSED, + EMPTY_MINIDUMP_WHILE_BACKGROUND, + VALID_MINIDUMP_WHILE_RUNNING, + VALID_MINIDUMP_WHILE_PAUSED, + VALID_MINIDUMP_WHILE_BACKGROUND, + MINIDUMP_STATUS_COUNT + }; + + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + enum class ProcessedCrashCounts { + kGpuForegroundOom = 0, + kRendererForegroundVisibleOom = 1, + kRendererForegroundIntentionalKill = 2, + kRendererForegroundVisibleSubframeOom = 3, + kRendererForegroundVisibleSubframeIntentionalKill = 4, + kRendererForegroundVisibleCrash = 5, + kRendererForegroundVisibleSubframeCrash = 6, + kGpuCrashAll = 7, + kRendererCrashAll = 8, + kRendererForegroundVisibleMainFrameIntentionalKill = 9, + kRendererForegroundVisibleNormalTermNoMinidump = 10, + kRendererForegroundInvisibleWithStrongBindingKilled = 11, + kRendererForegroundInvisibleWithStrongBindingOom = 12, + kRendererForegroundInvisibleWithModerateBindingKilled = 13, + kRendererForegroundInvisibleWithModerateBindingOom = 14, + kMaxValue = kRendererForegroundInvisibleWithModerateBindingOom + }; + using ReportedCrashTypeSet = base::flat_set<ProcessedCrashCounts>; + + // Careful note: the CrashMetricsReporter observers are asynchronous, and are + // notified via PostTask. This could be problematic with a large number of + // observers. Consider using a middle-layer observer to fan out synchronously + // to leaf observers if you need many objects listening to these messages. + class Observer { + public: + // Called when child process is dead and minidump was processed. + // |reported_counts| is a set of recorded metrics about child process + // crashes. It could be empty if no metrics were recorded. + virtual void OnCrashDumpProcessed( + int rph_id, + const ReportedCrashTypeSet& reported_counts) = 0; + }; + + static CrashMetricsReporter* GetInstance(); + + // Can be called on any thread. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + void CrashDumpProcessed(const ChildExitObserver::TerminationInfo& info, + breakpad::CrashDumpManager::CrashDumpStatus status); + + private: + CrashMetricsReporter(); + ~CrashMetricsReporter(); + + void NotifyObservers(int rph_id, const ReportedCrashTypeSet& reported_counts); + + scoped_refptr<base::ObserverListThreadSafe<CrashMetricsReporter::Observer>> + async_observers_; + + DISALLOW_COPY_AND_ASSIGN(CrashMetricsReporter); +}; + +} // namespace crash_reporter + +#endif // COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_METRICS_REPORTER_ANDROID_H_ diff --git a/chromium/components/crash/content/browser/crash_metrics_reporter_android_unittest.cc b/chromium/components/crash/content/browser/crash_metrics_reporter_android_unittest.cc new file mode 100644 index 00000000000..4367690dcdc --- /dev/null +++ b/chromium/components/crash/content/browser/crash_metrics_reporter_android_unittest.cc @@ -0,0 +1,255 @@ +// Copyright 2018 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 "components/crash/content/browser/crash_metrics_reporter_android.h" + +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/run_loop.h" +#include "base/task_scheduler/post_task.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace crash_reporter { + +class CrashMetricsReporterObserver : public CrashMetricsReporter::Observer { + public: + CrashMetricsReporterObserver() {} + ~CrashMetricsReporterObserver() {} + + // CrashMetricsReporter::Observer: + void OnCrashDumpProcessed(int rph_id, + const CrashMetricsReporter::ReportedCrashTypeSet& + reported_counts) override { + recorded_crash_types_ = reported_counts; + wait_run_loop_.QuitClosure().Run(); + } + + const CrashMetricsReporter::ReportedCrashTypeSet& recorded_crash_types() + const { + return recorded_crash_types_; + } + + void WaitForProcessed() { wait_run_loop_.Run(); } + + private: + base::RunLoop wait_run_loop_; + CrashMetricsReporter::ReportedCrashTypeSet recorded_crash_types_; + DISALLOW_COPY_AND_ASSIGN(CrashMetricsReporterObserver); +}; + +class CrashMetricsReporterTest : public testing::Test { + public: + CrashMetricsReporterTest() + : scoped_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::UI) {} + ~CrashMetricsReporterTest() override {} + + static void CreateAndProcessCrashDump( + const ChildExitObserver::TerminationInfo& info, + const std::string& data) { + base::ScopedFD fd = + breakpad::CrashDumpManager::GetInstance()->CreateMinidumpFileForChild( + info.process_host_id); + EXPECT_TRUE(fd.is_valid()); + base::WriteFileDescriptor(fd.get(), data.data(), data.size()); + base::ScopedTempDir dump_dir; + EXPECT_TRUE(dump_dir.CreateUniqueTempDir()); + breakpad::CrashDumpManager::GetInstance()->ProcessMinidumpFileFromChild( + dump_dir.GetPath(), info); + } + + protected: + void TestOomCrashProcessing( + const ChildExitObserver::TerminationInfo& termination_info, + CrashMetricsReporter::ReportedCrashTypeSet expected_crash_types, + const char* histogram_name) { + base::HistogramTester histogram_tester; + + CrashMetricsReporterObserver crash_dump_observer; + CrashMetricsReporter::GetInstance()->AddObserver(&crash_dump_observer); + + CrashMetricsReporter::GetInstance()->CrashDumpProcessed( + termination_info, + breakpad::CrashDumpManager::CrashDumpStatus::kEmptyDump); + crash_dump_observer.WaitForProcessed(); + + EXPECT_EQ(expected_crash_types, crash_dump_observer.recorded_crash_types()); + + if (histogram_name) { + histogram_tester.ExpectUniqueSample( + histogram_name, CrashMetricsReporter::EMPTY_MINIDUMP_WHILE_RUNNING, + 1); + } + } + + private: + base::test::ScopedTaskEnvironment scoped_environment_; + DISALLOW_COPY_AND_ASSIGN(CrashMetricsReporterTest); +}; + +TEST_F(CrashMetricsReporterTest, RendereMainFrameOOM) { + ChildExitObserver::TerminationInfo termination_info; + termination_info.process_host_id = 1; + termination_info.pid = base::kNullProcessHandle; + termination_info.process_type = content::PROCESS_TYPE_RENDERER; + termination_info.app_state = + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; + termination_info.normal_termination = false; + termination_info.binding_state = base::android::ChildBindingState::STRONG; + termination_info.was_killed_intentionally_by_browser = false; + termination_info.was_oom_protected_status = true; + termination_info.renderer_has_visible_clients = true; + TestOomCrashProcessing(termination_info, + {CrashMetricsReporter::ProcessedCrashCounts:: + kRendererForegroundVisibleOom}, + "Tab.RendererDetailedExitStatus"); +} + +TEST_F(CrashMetricsReporterTest, GpuProcessOOM) { + ChildExitObserver::TerminationInfo termination_info; + termination_info.process_host_id = 1; + termination_info.pid = base::kNullProcessHandle; + termination_info.process_type = content::PROCESS_TYPE_GPU; + termination_info.app_state = + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; + termination_info.normal_termination = false; + termination_info.binding_state = base::android::ChildBindingState::STRONG; + termination_info.was_killed_intentionally_by_browser = false; + termination_info.was_oom_protected_status = true; + termination_info.renderer_has_visible_clients = true; + TestOomCrashProcessing( + termination_info, + {CrashMetricsReporter::ProcessedCrashCounts::kGpuForegroundOom}, + "GPU.GPUProcessDetailedExitStatus"); +} + +TEST_F(CrashMetricsReporterTest, RendererSubframeOOM) { + ChildExitObserver::TerminationInfo termination_info; + termination_info.process_host_id = 1; + termination_info.pid = base::kNullProcessHandle; + termination_info.process_type = content::PROCESS_TYPE_RENDERER; + termination_info.app_state = + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; + termination_info.normal_termination = false; + termination_info.binding_state = base::android::ChildBindingState::STRONG; + termination_info.was_killed_intentionally_by_browser = false; + termination_info.was_oom_protected_status = true; + termination_info.renderer_has_visible_clients = true; + termination_info.renderer_was_subframe = true; + TestOomCrashProcessing(termination_info, + {CrashMetricsReporter::ProcessedCrashCounts:: + kRendererForegroundVisibleSubframeOom}, + "Tab.RendererDetailedExitStatus"); +} + +TEST_F(CrashMetricsReporterTest, RendererNonVisibleStrongOOM) { + ChildExitObserver::TerminationInfo termination_info; + termination_info.process_host_id = 1; + termination_info.pid = base::kNullProcessHandle; + termination_info.process_type = content::PROCESS_TYPE_RENDERER; + termination_info.app_state = + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; + termination_info.normal_termination = false; + termination_info.binding_state = base::android::ChildBindingState::STRONG; + termination_info.was_oom_protected_status = true; + termination_info.was_killed_intentionally_by_browser = false; + termination_info.renderer_has_visible_clients = false; + TestOomCrashProcessing(termination_info, + {CrashMetricsReporter::ProcessedCrashCounts:: + kRendererForegroundInvisibleWithStrongBindingOom}, + "Tab.RendererDetailedExitStatus"); +} + +TEST_F(CrashMetricsReporterTest, RendererNonVisibleModerateOOM) { + ChildExitObserver::TerminationInfo termination_info; + termination_info.process_host_id = 1; + termination_info.pid = base::kNullProcessHandle; + termination_info.process_type = content::PROCESS_TYPE_RENDERER; + termination_info.app_state = + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; + termination_info.normal_termination = false; + termination_info.binding_state = base::android::ChildBindingState::MODERATE; + termination_info.was_oom_protected_status = true; + termination_info.was_killed_intentionally_by_browser = false; + termination_info.renderer_has_visible_clients = false; + TestOomCrashProcessing( + termination_info, + {CrashMetricsReporter::ProcessedCrashCounts:: + kRendererForegroundInvisibleWithModerateBindingOom}, + "Tab.RendererDetailedExitStatus"); +} + +TEST_F(CrashMetricsReporterTest, IntentionalKillIsNotOOM) { + ChildExitObserver::TerminationInfo termination_info; + termination_info.process_host_id = 1; + termination_info.pid = base::kNullProcessHandle; + termination_info.process_type = content::PROCESS_TYPE_RENDERER; + termination_info.app_state = + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; + termination_info.normal_termination = false; + termination_info.binding_state = base::android::ChildBindingState::STRONG; + termination_info.was_killed_intentionally_by_browser = true; + termination_info.was_oom_protected_status = true; + termination_info.renderer_has_visible_clients = true; + TestOomCrashProcessing( + termination_info, + {CrashMetricsReporter::ProcessedCrashCounts:: + kRendererForegroundIntentionalKill, + CrashMetricsReporter::ProcessedCrashCounts:: + kRendererForegroundVisibleMainFrameIntentionalKill}, + "Tab.RendererDetailedExitStatus"); +} + +TEST_F(CrashMetricsReporterTest, NormalTerminationIsNotOOM) { + ChildExitObserver::TerminationInfo termination_info; + termination_info.process_host_id = 1; + termination_info.pid = base::kNullProcessHandle; + termination_info.process_type = content::PROCESS_TYPE_RENDERER; + termination_info.app_state = + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; + termination_info.normal_termination = true; + termination_info.binding_state = base::android::ChildBindingState::STRONG; + termination_info.was_killed_intentionally_by_browser = false; + termination_info.was_oom_protected_status = true; + termination_info.renderer_has_visible_clients = true; + TestOomCrashProcessing(termination_info, + {CrashMetricsReporter::ProcessedCrashCounts:: + kRendererForegroundVisibleNormalTermNoMinidump}, + nullptr); +} + +TEST_F(CrashMetricsReporterTest, RendererForegroundCrash) { + ChildExitObserver::TerminationInfo termination_info; + termination_info.process_host_id = 1; + termination_info.pid = base::kNullProcessHandle; + termination_info.process_type = content::PROCESS_TYPE_RENDERER; + termination_info.app_state = + base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES; + termination_info.normal_termination = true; + termination_info.binding_state = base::android::ChildBindingState::STRONG; + termination_info.was_killed_intentionally_by_browser = true; + termination_info.was_oom_protected_status = true; + termination_info.renderer_has_visible_clients = true; + + CrashMetricsReporterObserver crash_dump_observer; + CrashMetricsReporter::GetInstance()->AddObserver(&crash_dump_observer); + + CrashMetricsReporter::GetInstance()->CrashDumpProcessed( + termination_info, + breakpad::CrashDumpManager::CrashDumpStatus::kValidDump); + crash_dump_observer.WaitForProcessed(); + + EXPECT_EQ( + CrashMetricsReporter::ReportedCrashTypeSet( + {CrashMetricsReporter::ProcessedCrashCounts:: + kRendererForegroundIntentionalKill, + CrashMetricsReporter::ProcessedCrashCounts:: + kRendererForegroundVisibleCrash, + CrashMetricsReporter::ProcessedCrashCounts::kRendererCrashAll}), + crash_dump_observer.recorded_crash_types()); +} + +} // namespace crash_reporter diff --git a/chromium/components/crash/content/tools/generate_breakpad_symbols.py b/chromium/components/crash/content/tools/generate_breakpad_symbols.py index 24250f81fa1..2032f1991ad 100755 --- a/chromium/components/crash/content/tools/generate_breakpad_symbols.py +++ b/chromium/components/crash/content/tools/generate_breakpad_symbols.py @@ -29,24 +29,6 @@ BINARY_INFO = collections.namedtuple('BINARY_INFO', ['platform', 'arch', 'hash', 'name']) -def GetCommandOutput(command, env=None): - """Runs the command list, returning its output (stdout). - - Args: - command: (list of strings) a command with arguments - - env: (dict or None) optional environment for the command. If None, - inherits the existing environment, otherwise completely overrides it. - - From chromium_utils. - """ - devnull = open(os.devnull, 'w') - proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull, - bufsize=1, env=env) - output = proc.communicate()[0] - return output - - def GetDumpSymsBinary(build_dir=None): """Returns the path to the dump_syms binary.""" DUMP_SYMS = 'dump_syms' @@ -70,8 +52,7 @@ def Resolve(path, exe_path, loader_path, rpaths): path = path.replace('@executable_path', exe_path) if path.find('@rpath') != -1: for rpath in rpaths: - new_path = Resolve(path.replace('@rpath', rpath), exe_path, loader_path, - []) + new_path = path.replace('@rpath', rpath) if os.access(new_path, os.X_OK): return new_path return '' @@ -82,7 +63,10 @@ def GetSharedLibraryDependenciesLinux(binary): """Return absolute paths to all shared library dependecies of the binary. This implementation assumes that we're running on a Linux system.""" - ldd = GetCommandOutput(['ldd', binary]) + # TODO(thakis): Figure out how to make this work for android + # (https://crbug.com/849904) and use check_output(). + p = subprocess.Popen(['ldd', binary], stdout=subprocess.PIPE) + ldd = p.communicate()[0] lib_re = re.compile('\t.* => (.+) \(.*\)$') result = [] for line in ldd.splitlines(): @@ -106,7 +90,7 @@ def GetDeveloperDirMac(): if 'DEVELOPER_DIR' in os.environ: candidate_paths.append(os.environ['DEVELOPER_DIR']) candidate_paths.extend([ - GetCommandOutput(['xcode-select', '-p']).strip(), + subprocess.check_output(['xcode-select', '-p']).strip(), # Most Mac 10.1[0-2] bots have at least one Xcode installed. '/Applications/Xcode.app', '/Applications/Xcode9.0.app', @@ -125,27 +109,57 @@ def GetSharedLibraryDependenciesMac(binary, exe_path): """Return absolute paths to all shared library dependecies of the binary. This implementation assumes that we're running on a Mac system.""" - loader_path = os.path.dirname(binary) + # realpath() serves two purposes: + # 1. If an executable is linked against a framework, it links against + # Framework.framework/Framework, which is a symlink to + # Framework.framework/Framework/Versions/A/Framework. rpaths are relative + # to the real location, so resolving the symlink is important. + # 2. It converts binary to an absolute path. If binary is just + # "foo.dylib" in the current directory, dirname() would return an empty + # string, causing "@loader_path/foo" to incorrectly expand to "/foo". + loader_path = os.path.dirname(os.path.realpath(binary)) env = os.environ.copy() developer_dir = GetDeveloperDirMac() if developer_dir: env['DEVELOPER_DIR'] = developer_dir - otool = GetCommandOutput(['otool', '-l', binary], env=env).splitlines() + otool = subprocess.check_output(['otool', '-l', binary], env=env).splitlines() rpaths = [] + dylib_id = None for idx, line in enumerate(otool): if line.find('cmd LC_RPATH') != -1: m = re.match(' *path (.*) \(offset .*\)$', otool[idx+2]) - rpaths.append(m.group(1)) - - otool = GetCommandOutput(['otool', '-L', binary], env=env).splitlines() + rpath = m.group(1) + rpath = rpath.replace('@loader_path', loader_path) + rpath = rpath.replace('@executable_path', exe_path) + rpaths.append(rpath) + elif line.find('cmd LC_ID_DYLIB') != -1: + m = re.match(' *name (.*) \(offset .*\)$', otool[idx+2]) + dylib_id = m.group(1) + # `man dyld` says that @rpath is resolved against a stack of LC_RPATHs from + # all executable images leading to the load of the current module. This is + # intentionally not implemented here, since we require that every .dylib + # contains all the rpaths it needs on its own, without relying on rpaths of + # the loading executables. + + otool = subprocess.check_output(['otool', '-L', binary], env=env).splitlines() lib_re = re.compile('\t(.*) \(compatibility .*\)$') deps = [] for line in otool: m = lib_re.match(line) if m: + # For frameworks and shared libraries, `otool -L` prints the LC_ID_DYLIB + # as the first line. Filter that out. + if m.group(1) == dylib_id: + continue dep = Resolve(m.group(1), exe_path, loader_path, rpaths) if dep: deps.append(os.path.normpath(dep)) + else: + print >>sys.stderr, ( + 'ERROR: failed to resolve %s, exe_path %s, loader_path %s, ' + 'rpaths %s' % (m.group(1), exe_path, loader_path, + ', '.join(rpaths))) + sys.exit(1) return deps @@ -211,7 +225,7 @@ def GenerateSymbols(options, binaries): break binary_info = GetBinaryInfoFromHeaderInfo( - GetCommandOutput([dump_syms, '-i', binary]).splitlines()[0]) + subprocess.check_output([dump_syms, '-i', binary]).splitlines()[0]) if not binary_info: should_dump_syms = False reason = "Could not obtain binary information." |