From 168c44ce90012516dcfa91ac80a6d0a200513531 Mon Sep 17 00:00:00 2001 From: gunsch Date: Wed, 8 Oct 2014 21:37:06 -0700 Subject: [PATCH] Chromecast: adds crash handling for Android build. Cast for Android TV behavior includes the following: * On crash, immediately attempt to upload crash dump + logs from current process, then delete the dump file. * On startup, attempt to upload any crash dump files that are still left on the device. R=thestig@chromium.org,byungchul@chromium.org BUG=400876 Review URL: https://codereview.chromium.org/620673003 Cr-Commit-Position: refs/heads/master@{#298767} --- chromecast/android/DEPS | 4 +- chromecast/android/cast_jni_registrar.cc | 2 + chromecast/android/chromecast_config_android.h | 4 + .../android/chromecast_config_android_stub.cc | 15 ++ chromecast/chromecast.gyp | 12 ++ chromecast/common/global_descriptors.h | 1 + chromecast/crash/DEPS | 4 + chromecast/crash/android/DEPS | 3 + .../android/cast_crash_reporter_client_android.cc | 72 +++++++ .../android/cast_crash_reporter_client_android.h | 35 ++++ chromecast/crash/android/crash_handler.cc | 145 ++++++++++++++ chromecast/crash/android/crash_handler.h | 72 +++++++ .../chromecast/shell/CastCrashHandler.java | 32 +++ .../chromecast/shell/CastCrashUploader.java | 223 +++++++++++++++++++++ chromecast/shell/app/DEPS | 1 + chromecast/shell/app/cast_main_delegate.cc | 15 ++ chromecast/shell/browser/DEPS | 1 + .../shell/browser/cast_browser_main_parts.cc | 12 ++ chromecast/shell/browser/cast_browser_process.cc | 12 ++ chromecast/shell/browser/cast_browser_process.h | 6 + .../shell/browser/cast_content_browser_client.cc | 18 ++ 21 files changed, 688 insertions(+), 1 deletion(-) create mode 100644 chromecast/android/chromecast_config_android_stub.cc create mode 100644 chromecast/crash/DEPS create mode 100644 chromecast/crash/android/DEPS create mode 100644 chromecast/crash/android/cast_crash_reporter_client_android.cc create mode 100644 chromecast/crash/android/cast_crash_reporter_client_android.h create mode 100644 chromecast/crash/android/crash_handler.cc create mode 100644 chromecast/crash/android/crash_handler.h create mode 100644 chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashHandler.java create mode 100644 chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashUploader.java diff --git a/chromecast/android/DEPS b/chromecast/android/DEPS index d4419344bcab..62e5a663c798 100644 --- a/chromecast/android/DEPS +++ b/chromecast/android/DEPS @@ -1,4 +1,6 @@ include_rules = [ - # Include for JNI. + # Includes for JNI. + "+chromecast/android", + "+chromecast/crash/android", "+chromecast/shell/browser/android", ] diff --git a/chromecast/android/cast_jni_registrar.cc b/chromecast/android/cast_jni_registrar.cc index de919c616c74..81735d826a39 100644 --- a/chromecast/android/cast_jni_registrar.cc +++ b/chromecast/android/cast_jni_registrar.cc @@ -6,6 +6,7 @@ #include "base/android/jni_android.h" #include "base/android/jni_registrar.h" +#include "chromecast/crash/android/crash_handler.h" #include "chromecast/shell/browser/android/cast_window_android.h" #include "chromecast/shell/browser/android/cast_window_manager.h" #include "chromecast/shell/browser/android/external_video_surface_container_impl.h" @@ -20,6 +21,7 @@ static base::android::RegistrationMethod kMethods[] = { { "CastWindowManager", shell::RegisterCastWindowManager }, { "ExternalVideoSurfaceContainer", shell::RegisterExternalVideoSurfaceContainer }, + { "CrashHandler", CrashHandler::RegisterCastCrashJni }, }; } // namespace diff --git a/chromecast/android/chromecast_config_android.h b/chromecast/android/chromecast_config_android.h index 627259e9943e..e09d657acc92 100644 --- a/chromecast/android/chromecast_config_android.h +++ b/chromecast/android/chromecast_config_android.h @@ -18,6 +18,10 @@ class ChromecastConfigAndroid { public: static ChromecastConfigAndroid* GetInstance(); + // Returns whether or not the user has allowed sending usage stats and + // crash reports. + bool CanSendUsageStats(); + // Registers a handler to be notified when SendUsageStats is changed. void SetSendUsageStatsChangedCallback( const base::Callback& callback); diff --git a/chromecast/android/chromecast_config_android_stub.cc b/chromecast/android/chromecast_config_android_stub.cc new file mode 100644 index 000000000000..dc8bdca2d6af --- /dev/null +++ b/chromecast/android/chromecast_config_android_stub.cc @@ -0,0 +1,15 @@ +// Copyright 2014 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 "chromecast/android/chromecast_config_android.h" + +namespace chromecast { +namespace android { + +bool ChromecastConfigAndroid::CanSendUsageStats() { + return false; +} + +} // namespace android +} // namespace chromecast diff --git a/chromecast/chromecast.gyp b/chromecast/chromecast.gyp index cdb1edef595b..7ca308a435f0 100644 --- a/chromecast/chromecast.gyp +++ b/chromecast/chromecast.gyp @@ -327,18 +327,28 @@ 'cast_shell_pak', 'cast_version_header', '../base/base.gyp:base', + '../breakpad/breakpad.gyp:breakpad_client', + '../components/components.gyp:breakpad_host', + '../components/components.gyp:crash_component', '../content/content.gyp:content_app_browser', '../content/content.gyp:content', '../skia/skia.gyp:skia', '../ui/gfx/gfx.gyp:gfx', '../ui/gl/gl.gyp:gl', ], + 'include_dirs': [ + '../breakpad/src', + ], 'sources': [ 'android/cast_jni_registrar.cc', 'android/cast_jni_registrar.h', 'android/chromecast_config_android.cc', 'android/chromecast_config_android.h', 'android/platform_jni_loader.h', + 'crash/android/cast_crash_reporter_client_android.cc', + 'crash/android/cast_crash_reporter_client_android.h', + 'crash/android/crash_handler.cc', + 'crash/android/crash_handler.h', 'shell/app/android/cast_jni_loader.cc', 'shell/browser/android/cast_window_android.cc', 'shell/browser/android/cast_window_android.h', @@ -354,6 +364,7 @@ ], }, { 'sources': [ + 'android/chromecast_config_android_stub.cc', 'android/platform_jni_loader_stub.cc', ], }] @@ -407,6 +418,7 @@ 'target_name': 'cast_jni_headers', 'type': 'none', 'sources': [ + 'shell/android/apk/src/org/chromium/chromecast/shell/CastCrashHandler.java', 'shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java', 'shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java', 'shell/android/apk/src/org/chromium/chromecast/shell/ExternalVideoSurfaceContainer.java', diff --git a/chromecast/common/global_descriptors.h b/chromecast/common/global_descriptors.h index cbbbb2ce930e..b380158ac751 100644 --- a/chromecast/common/global_descriptors.h +++ b/chromecast/common/global_descriptors.h @@ -15,6 +15,7 @@ enum { kDummyValue = kContentIPCDescriptorMax + 1, #if defined(OS_ANDROID) kAndroidPakDescriptor, + kAndroidMinidumpDescriptor, #endif // defined(OS_ANDROID) }; diff --git a/chromecast/crash/DEPS b/chromecast/crash/DEPS new file mode 100644 index 000000000000..4389c51b6d27 --- /dev/null +++ b/chromecast/crash/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+breakpad", + "+components/crash", +] diff --git a/chromecast/crash/android/DEPS b/chromecast/crash/android/DEPS new file mode 100644 index 000000000000..5021862892ef --- /dev/null +++ b/chromecast/crash/android/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+chromecast/android", +] diff --git a/chromecast/crash/android/cast_crash_reporter_client_android.cc b/chromecast/crash/android/cast_crash_reporter_client_android.cc new file mode 100644 index 000000000000..2137faeea9bf --- /dev/null +++ b/chromecast/crash/android/cast_crash_reporter_client_android.cc @@ -0,0 +1,72 @@ +// Copyright 2014 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 "chromecast/crash/android/cast_crash_reporter_client_android.h" + +#include "base/base_paths.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "chromecast/android/chromecast_config_android.h" +#include "chromecast/common/global_descriptors.h" +#include "chromecast/common/version.h" +#include "content/public/common/content_switches.h" + +namespace chromecast { + +CastCrashReporterClientAndroid::CastCrashReporterClientAndroid() { +} + +CastCrashReporterClientAndroid::~CastCrashReporterClientAndroid() { +} + +void CastCrashReporterClientAndroid::GetProductNameAndVersion( + const char** product_name, + const char** version) { + *product_name = "media_shell"; + *version = PRODUCT_VERSION +#if CAST_IS_DEBUG_BUILD + ".debug" +#endif + "." CAST_BUILD_REVISION; +} + +base::FilePath CastCrashReporterClientAndroid::GetReporterLogFilename() { + return base::FilePath(FILE_PATH_LITERAL("uploads.log")); +} + +bool CastCrashReporterClientAndroid::GetCrashDumpLocation( + base::FilePath* crash_dir) { + base::FilePath crash_dir_local; + if (!PathService::Get(base::DIR_ANDROID_APP_DATA, &crash_dir_local)) { + return false; + } + crash_dir_local = crash_dir_local.Append("crashes"); + + if (!base::DirectoryExists(crash_dir_local)) { + if (!base::CreateDirectory(crash_dir_local)) { + return false; + } + } + + // Provide value to crash_dir once directory is known to be a valid path. + *crash_dir = crash_dir_local; + return true; +} + +bool CastCrashReporterClientAndroid::GetCollectStatsConsent() { + return android::ChromecastConfigAndroid::GetInstance()->CanSendUsageStats(); +} + +int CastCrashReporterClientAndroid::GetAndroidMinidumpDescriptor() { + return kAndroidMinidumpDescriptor; +} + +bool CastCrashReporterClientAndroid::EnableBreakpadForProcess( + const std::string& process_type) { + return process_type == switches::kRendererProcess || + process_type == switches::kGpuProcess; +} + +} // namespace chromecast diff --git a/chromecast/crash/android/cast_crash_reporter_client_android.h b/chromecast/crash/android/cast_crash_reporter_client_android.h new file mode 100644 index 000000000000..4524ae70a266 --- /dev/null +++ b/chromecast/crash/android/cast_crash_reporter_client_android.h @@ -0,0 +1,35 @@ +// Copyright 2014 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 CHROMECAST_CRASH_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_ +#define CHROMECAST_CRASH_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_ + +#include "base/compiler_specific.h" +#include "components/crash/app/crash_reporter_client.h" + +namespace chromecast { + +class CastCrashReporterClientAndroid + : public crash_reporter::CrashReporterClient { + public: + CastCrashReporterClientAndroid(); + virtual ~CastCrashReporterClientAndroid(); + + // crash_reporter::CrashReporterClient implementation: + virtual void GetProductNameAndVersion(const char** product_name, + const char** version) override; + virtual base::FilePath GetReporterLogFilename() override; + virtual bool GetCrashDumpLocation(base::FilePath* crash_dir) override; + virtual int GetAndroidMinidumpDescriptor() override; + virtual bool GetCollectStatsConsent() override; + virtual bool EnableBreakpadForProcess( + const std::string& process_type) override; + + private: + DISALLOW_COPY_AND_ASSIGN(CastCrashReporterClientAndroid); +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_ diff --git a/chromecast/crash/android/crash_handler.cc b/chromecast/crash/android/crash_handler.cc new file mode 100644 index 000000000000..a73e31352b7b --- /dev/null +++ b/chromecast/crash/android/crash_handler.cc @@ -0,0 +1,145 @@ +// Copyright 2014 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 "chromecast/crash/android/crash_handler.h" + +#include +#include + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "breakpad/src/client/linux/handler/exception_handler.h" +#include "breakpad/src/client/linux/handler/minidump_descriptor.h" +#include "chromecast/common/version.h" +#include "chromecast/crash/android/cast_crash_reporter_client_android.h" +#include "components/crash/app/breakpad_linux.h" +#include "components/crash/app/crash_reporter_client.h" +#include "content/public/common/content_switches.h" +#include "jni/CastCrashHandler_jni.h" + +namespace { + +chromecast::CrashHandler* g_crash_handler = NULL; + +// ExceptionHandler requires a HandlerCallback as a function pointer. This +// function exists to proxy into the global CrashHandler instance. +bool HandleCrash(const void* /* crash_context */, + size_t /* crash_context_size */, + void* /* context */) { + DCHECK(g_crash_handler); + if (g_crash_handler->CanUploadCrashDump()) { + g_crash_handler->AttemptUploadCrashDump(); + } else { + g_crash_handler->RemoveCrashDumps(); + } + + // Let the exception continue to propagate up to the system. + return false; +} + +} // namespace + +namespace chromecast { + +// static +void CrashHandler::Initialize(const std::string& process_type, + const base::FilePath& log_file_path) { + DCHECK(!g_crash_handler); + g_crash_handler = new CrashHandler(log_file_path); + g_crash_handler->Initialize(process_type); +} + +// static +bool CrashHandler::GetCrashDumpLocation(base::FilePath* crash_dir) { + DCHECK(g_crash_handler); + return g_crash_handler->crash_reporter_client_-> + GetCrashDumpLocation(crash_dir); +} + +// static +bool CrashHandler::RegisterCastCrashJni(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +CrashHandler::CrashHandler(const base::FilePath& log_file_path) + : log_file_path_(log_file_path), + crash_reporter_client_(new CastCrashReporterClientAndroid) { + if (!crash_reporter_client_->GetCrashDumpLocation(&crash_dump_path_)) { + LOG(ERROR) << "Could not get crash dump location"; + } + SetCrashReporterClient(crash_reporter_client_.get()); +} + +CrashHandler::~CrashHandler() { + DCHECK(g_crash_handler); + g_crash_handler = NULL; +} + +void CrashHandler::Initialize(const std::string& process_type) { + if (process_type != switches::kZygoteProcess) { + if (process_type.empty()) { + // ExceptionHandlers are called on crash in reverse order of + // instantiation. This ExceptionHandler will attempt to upload crashes + // and the log file written out by the main process. + + // Dummy MinidumpDescriptor just to start up another ExceptionHandler. + google_breakpad::MinidumpDescriptor dummy(crash_dump_path_.value()); + crash_uploader_.reset(new google_breakpad::ExceptionHandler( + dummy, NULL, NULL, NULL, true, -1)); + crash_uploader_->set_crash_handler(&::HandleCrash); + + breakpad::InitCrashReporter(process_type); + } else { + breakpad::InitNonBrowserCrashReporterForAndroid(process_type); + } + } + + UploadCrashDumpsAsync(); +} + +bool CrashHandler::CanUploadCrashDump() { + DCHECK(crash_reporter_client_); + return crash_reporter_client_->GetCollectStatsConsent(); +} + +void CrashHandler::AttemptUploadCrashDump() { + VLOG(1) << "Attempting to upload current process crash"; + JNIEnv* env = base::android::AttachCurrentThread(); + // Crash dump location + base::android::ScopedJavaLocalRef crash_dump_path_java = + base::android::ConvertUTF8ToJavaString(env, + crash_dump_path_.value()); + // Current log file location + base::android::ScopedJavaLocalRef log_file_path_java = + base::android::ConvertUTF8ToJavaString(env, log_file_path_.value()); + Java_CastCrashHandler_uploadCurrentProcessDumpSync( + env, + crash_dump_path_java.obj(), + log_file_path_java.obj(), + CAST_IS_DEBUG_BUILD); +} + +void CrashHandler::UploadCrashDumpsAsync() { + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef crash_dump_path_java = + base::android::ConvertUTF8ToJavaString(env, + crash_dump_path_.value()); + Java_CastCrashHandler_uploadCrashDumpsAsync(env, + crash_dump_path_java.obj(), + CAST_IS_DEBUG_BUILD); +} + +void CrashHandler::RemoveCrashDumps() { + VLOG(1) << "Removing crash dumps instead of uploading"; + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef crash_dump_path_java = + base::android::ConvertUTF8ToJavaString(env, + crash_dump_path_.value()); + Java_CastCrashHandler_removeCrashDumpsSync( + env, crash_dump_path_java.obj(), CAST_IS_DEBUG_BUILD); +} + +} // namespace chromecast diff --git a/chromecast/crash/android/crash_handler.h b/chromecast/crash/android/crash_handler.h new file mode 100644 index 000000000000..246c5bb8c66e --- /dev/null +++ b/chromecast/crash/android/crash_handler.h @@ -0,0 +1,72 @@ +// Copyright 2014 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 CHROMECAST_CRASH_ANDROID_CRASH_HANDLER_H_ +#define CHROMECAST_CRASH_ANDROID_CRASH_HANDLER_H_ + +#include +#include + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace google_breakpad { +class ExceptionHandler; +} + +namespace chromecast { +class CastCrashReporterClientAndroid; + +class CrashHandler { + public: + // Initializes the crash handler for attempting to upload crash dumps with + // the current process's log file. + // Must not be called more than once. + static void Initialize(const std::string& process_type, + const base::FilePath& log_file_path); + + // Returns the directory location for crash dumps. + static bool GetCrashDumpLocation(base::FilePath* crash_dir); + + // Registers JNI methods for this module. + static bool RegisterCastCrashJni(JNIEnv* env); + + // Returns whether or not the user has allowed for uploading crash dumps. + bool CanUploadCrashDump(); + + // Callback with which to create a breakpad::ExceptionHandler that will + // attempt synchronously uploading crash dumps and logs at crash time. + void AttemptUploadCrashDump(); + + // Callback for breakpad::ExceptionHandler to delete crash dumps created by + // the Chrome crash component. Chrome's crash component does not query + // for user consent after initializing breakpad. + void RemoveCrashDumps(); + + private: + CrashHandler(const base::FilePath& log_file_path); + ~CrashHandler(); + + void Initialize(const std::string& process_type); + + // Starts a background thread to look for any past crash dumps and upload them + // to the crash server. + void UploadCrashDumpsAsync(); + + // Path to the current process's log file. + base::FilePath log_file_path_; + + // Location to which crash dumps should be written. + base::FilePath crash_dump_path_; + + scoped_ptr crash_reporter_client_; + scoped_ptr crash_uploader_; + + DISALLOW_COPY_AND_ASSIGN(CrashHandler); +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_ANDROID_CRASH_HANDLER_H_ diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashHandler.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashHandler.java new file mode 100644 index 000000000000..20fed194c8fb --- /dev/null +++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashHandler.java @@ -0,0 +1,32 @@ +// Copyright 2014 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. + +package org.chromium.chromecast.shell; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; + +/** + * JNI wrapper class for accessing CastCrashHandler. + */ +@JNINamespace("chromecast") +public final class CastCrashHandler { + + @CalledByNative + public static void removeCrashDumpsSync(String crashDumpPath, boolean isDebugBuild) { + new CastCrashUploader(crashDumpPath, isDebugBuild).removeCrashDumpsSync(); + } + + @CalledByNative + public static void uploadCurrentProcessDumpSync(String crashDumpPath, String logFilePath, + boolean isDebugBuild) { + new CastCrashUploader(crashDumpPath, isDebugBuild). + uploadCurrentProcessDumpSync(logFilePath); + } + + @CalledByNative + public static void uploadCrashDumpsAsync(String crashDumpPath, boolean isDebugBuild) { + new CastCrashUploader(crashDumpPath, isDebugBuild).uploadRecentCrashesAsync(); + } +} diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashUploader.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashUploader.java new file mode 100644 index 000000000000..bc1631ccb62f --- /dev/null +++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashUploader.java @@ -0,0 +1,223 @@ +// Copyright 2014 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. + +package org.chromium.chromecast.shell; + +import android.net.http.AndroidHttpClient; +import android.util.Log; + +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.InputStreamEntity; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.SequenceInputStream; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Crash crashdump uploader. Scans the crash dump location provided by CastCrashReporterClient + * for dump files, attempting to upload all crash dumps to the crash server. + * + * Uploading is intended to happen in a background thread, and this method will likely be called + * on startup, looking for crash dumps from previous runs, since Chromium's crash code + * explicitly blocks any post-dump hooks or uploading for Android builds. + */ +public final class CastCrashUploader { + private static final String TAG = "CastCrashUploader"; + private static final String CRASH_REPORT_HOST = "clients2.google.com"; + private static final String CAST_SHELL_USER_AGENT = android.os.Build.MODEL + "/CastShell"; + + private final ExecutorService mExecutorService; + private final String mCrashDumpPath; + private final String mCrashReportUploadUrl; + + public CastCrashUploader(String crashDumpPath, boolean isDebugBuild) { + this.mCrashDumpPath = crashDumpPath; + mCrashReportUploadUrl = isDebugBuild ? + "http://clients2.google.com/cr/staging_report" : + "http://clients2.google.com/cr/report"; + mExecutorService = Executors.newFixedThreadPool(1); + } + + public void removeCrashDumpsSync() { + mExecutorService.submit(new Runnable() { + @Override + public void run() { + File crashDumpDirectory = new File(mCrashDumpPath); + for (File potentialDump : crashDumpDirectory.listFiles()) { + if (potentialDump.getName().matches(".*\\.dmp\\d*")) { + potentialDump.delete(); + } + } + } + }); + waitForTasksToFinish(); + } + + /** + * Synchronously uploads the crash dump from the current process, along with an attached + * log file. + * @param logFilePath Full path to the log file for the current process. + */ + public void uploadCurrentProcessDumpSync(String logFilePath) { + int pid = android.os.Process.myPid(); + Log.d(TAG, "Immediately attempting a crash upload with logs, looking for: .dmp" + pid); + + queueAllCrashDumpUpload(".*\\.dmp" + pid, new File(logFilePath)); + waitForTasksToFinish(); + } + + private void waitForTasksToFinish() { + try { + mExecutorService.shutdown(); + boolean finished = mExecutorService.awaitTermination(60, TimeUnit.SECONDS); + if (!finished) { + Log.d(TAG, "Crash dump handling did not finish executing in time. Exiting."); + } + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted waiting for asynchronous execution", e); + } + } + + /** + * Scans the given location for crash dump files and queues background + * uploads of each file. + */ + public void uploadRecentCrashesAsync() { + mExecutorService.submit(new Runnable() { + @Override + public void run() { + // Multipart dump filename has format "[random string].dmp[pid]", e.g. + // 20597a65-b822-008e-31f8fc8e-02bb45c0.dmp18169 + queueAllCrashDumpUpload(".*\\.dmp\\d+", null); + } + }); + } + + /** + * Searches for files matching the given regex in the crash dump folder, queueing each + * one for upload. + * @param dumpFileRegex Regex to test against the name of each file in the crash dump folder. + * @param logFile Log file to include, if any. + */ + private void queueAllCrashDumpUpload(String dumpFileRegex, File logFile) { + if (mCrashDumpPath == null) return; + final AndroidHttpClient httpClient = AndroidHttpClient.newInstance(CAST_SHELL_USER_AGENT); + File crashDumpDirectory = new File(mCrashDumpPath); + for (File potentialDump : crashDumpDirectory.listFiles()) { + if (potentialDump.getName().matches(dumpFileRegex)) { + queueCrashDumpUpload(httpClient, potentialDump, logFile); + } + } + + // When finished uploading all of the queued dumps, release httpClient + mExecutorService.submit(new Runnable() { + @Override + public void run() { + httpClient.close(); + } + }); + } + + /** + * Enqueues a background task to upload a single crash dump file. + */ + private void queueCrashDumpUpload(final AndroidHttpClient httpClient, final File dumpFile, + final File logFile) { + mExecutorService.submit(new Runnable() { + @Override + public void run() { + Log.d(TAG, "Uploading dump crash log: " + dumpFile.getName()); + + try { + // Dump file is already in multipart MIME format and has a boundary throughout. + // Scrape the first line, remove two dashes, call that the "boundary" and add it + // to the content-type. + FileInputStream dumpFileStream = new FileInputStream(dumpFile); + String dumpFirstLine = getFirstLine(dumpFileStream); + String mimeBoundary = dumpFirstLine.substring(2); + + InputStream uploadCrashDumpStream = new FileInputStream(dumpFile); + InputStream logFileStream = null; + + if (logFile != null) { + Log.d(TAG, "Including log file: " + logFile.getName()); + StringBuffer logHeader = new StringBuffer(); + logHeader.append(dumpFirstLine); + logHeader.append("\n"); + logHeader.append( + "Content-Disposition: form-data; name=\"log\"; filename=\"log\"\n"); + logHeader.append("Content-Type: text/plain\n\n"); + InputStream logHeaderStream = + new ByteArrayInputStream(logHeader.toString().getBytes()); + logFileStream = new FileInputStream(logFile); + + // Upload: prepend the log file for uploading + uploadCrashDumpStream = new SequenceInputStream( + new SequenceInputStream(logHeaderStream, logFileStream), + uploadCrashDumpStream); + } + + InputStreamEntity entity = new InputStreamEntity(uploadCrashDumpStream, -1); + entity.setContentType("multipart/form-data; boundary=" + mimeBoundary); + + HttpPost uploadRequest = new HttpPost(mCrashReportUploadUrl); + uploadRequest.setEntity(entity); + HttpResponse response = + httpClient.execute(new HttpHost(CRASH_REPORT_HOST), uploadRequest); + + // Expect a report ID as the entire response + String responseLine = getFirstLine(response.getEntity().getContent()); + + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + Log.e(TAG, "Failed response (" + statusCode + "): " + responseLine); + return; + } + + Log.d(TAG, "Successfully uploaded as report ID: " + responseLine); + + // Delete the file so we don't re-upload it next time. + dumpFileStream.close(); + dumpFile.delete(); + } catch (IOException e) { + Log.e(TAG, "File I/O error trying to upload crash dump", e); + } + } + }); + } + + /** + * Gets the first line from an input stream, opening and closing readers to do so. + * We're targeting Java 6, so this is still tedious to do. + * @return First line of the input stream. + * @throws IOException + */ + private String getFirstLine(InputStream inputStream) throws IOException { + InputStreamReader streamReader = null; + BufferedReader reader = null; + try { + streamReader = new InputStreamReader(inputStream); + reader = new BufferedReader(streamReader); + return reader.readLine(); + } finally { + if (reader != null) { + reader.close(); + } + if (streamReader != null) { + streamReader.close(); + } + } + } +} diff --git a/chromecast/shell/app/DEPS b/chromecast/shell/app/DEPS index efc610d59237..3dad0898cb04 100644 --- a/chromecast/shell/app/DEPS +++ b/chromecast/shell/app/DEPS @@ -1,4 +1,5 @@ include_rules = [ + "+components/crash", "+content/public/app", "+content/public/browser", ] diff --git a/chromecast/shell/app/cast_main_delegate.cc b/chromecast/shell/app/cast_main_delegate.cc index 719cc2b719d6..e3dbfc103d70 100644 --- a/chromecast/shell/app/cast_main_delegate.cc +++ b/chromecast/shell/app/cast_main_delegate.cc @@ -4,6 +4,7 @@ #include "chromecast/shell/app/cast_main_delegate.h" +#include "base/command_line.h" #include "base/cpu.h" #include "base/logging.h" #include "base/path_service.h" @@ -17,6 +18,10 @@ #include "content/public/common/content_switches.h" #include "ui/base/resource/resource_bundle.h" +#if defined(OS_ANDROID) +#include "chromecast/crash/android/crash_handler.h" +#endif // defined(OS_ANDROID) + namespace chromecast { namespace shell { @@ -56,6 +61,16 @@ void CastMainDelegate::PreSandboxStartup() { base::CPU cpu_info; #endif + const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess()); + std::string process_type = + command_line->GetSwitchValueASCII(switches::kProcessType); + +#if defined(OS_ANDROID) + base::FilePath log_file; + PathService::Get(FILE_CAST_ANDROID_LOG, &log_file); + chromecast::CrashHandler::Initialize(process_type, log_file); +#endif // defined(OS_ANDROID) + InitializeResourceBundle(); } diff --git a/chromecast/shell/browser/DEPS b/chromecast/shell/browser/DEPS index 78e621dfd92e..b023f3038323 100644 --- a/chromecast/shell/browser/DEPS +++ b/chromecast/shell/browser/DEPS @@ -1,4 +1,5 @@ include_rules = [ + "+components/crash", "+content/public/browser", "+media/base", ] diff --git a/chromecast/shell/browser/cast_browser_main_parts.cc b/chromecast/shell/browser/cast_browser_main_parts.cc index 19b8b562f56e..ec3e2e9b973d 100644 --- a/chromecast/shell/browser/cast_browser_main_parts.cc +++ b/chromecast/shell/browser/cast_browser_main_parts.cc @@ -23,6 +23,8 @@ #include "media/base/media_switches.h" #if defined(OS_ANDROID) +#include "chromecast/crash/android/crash_handler.h" +#include "components/crash/browser/crash_dump_manager_android.h" #include "net/android/network_change_notifier_factory_android.h" #endif // defined(OS_ANDROID) @@ -109,6 +111,16 @@ void CastBrowserMainParts::PreMainMessageLoopRun() { content::BrowserThread::GetBlockingPool(), ChromecastConfig::GetInstance()->pref_service(), cast_browser_process_->browser_context()->GetRequestContext())); + +#if defined(OS_ANDROID) + base::FilePath crash_dumps_dir; + if (!chromecast::CrashHandler::GetCrashDumpLocation(&crash_dumps_dir)) { + LOG(ERROR) << "Could not find crash dump location."; + } + cast_browser_process_->SetCrashDumpManager( + new breakpad::CrashDumpManager(crash_dumps_dir)); +#endif + cast_browser_process_->SetRemoteDebuggingServer(new RemoteDebuggingServer()); InitializeWebUI(); diff --git a/chromecast/shell/browser/cast_browser_process.cc b/chromecast/shell/browser/cast_browser_process.cc index 70802aac0b4a..f89e6956ae79 100644 --- a/chromecast/shell/browser/cast_browser_process.cc +++ b/chromecast/shell/browser/cast_browser_process.cc @@ -10,6 +10,10 @@ #include "chromecast/shell/browser/cast_browser_context.h" #include "chromecast/shell/browser/devtools/remote_debugging_server.h" +#if defined(OS_ANDROID) +#include "components/crash/browser/crash_dump_manager_android.h" +#endif // defined(OS_ANDROID) + namespace chromecast { namespace shell { @@ -56,5 +60,13 @@ void CastBrowserProcess::SetMetricsServiceClient( metrics_service_client_.reset(metrics_service_client); } +#if defined(OS_ANDROID) +void CastBrowserProcess::SetCrashDumpManager( + breakpad::CrashDumpManager* crash_dump_manager) { + DCHECK(!crash_dump_manager_); + crash_dump_manager_.reset(crash_dump_manager); +} +#endif // defined(OS_ANDROID) + } // namespace shell } // namespace chromecast diff --git a/chromecast/shell/browser/cast_browser_process.h b/chromecast/shell/browser/cast_browser_process.h index 65830c2a3a09..ba625ac1a168 100644 --- a/chromecast/shell/browser/cast_browser_process.h +++ b/chromecast/shell/browser/cast_browser_process.h @@ -39,6 +39,9 @@ class CastBrowserProcess { void SetRemoteDebuggingServer(RemoteDebuggingServer* remote_debugging_server); void SetMetricsServiceClient( metrics::CastMetricsServiceClient* metrics_service_client); +#if defined(OS_ANDROID) + void SetCrashDumpManager(breakpad::CrashDumpManager* crash_dump_manager); +#endif // defined(OS_ANDROID) CastBrowserContext* browser_context() const { return browser_context_.get(); } CastService* cast_service() const { return cast_service_.get(); } @@ -50,6 +53,9 @@ class CastBrowserProcess { scoped_ptr browser_context_; scoped_ptr metrics_service_client_; scoped_ptr remote_debugging_server_; +#if defined(OS_ANDROID) + scoped_ptr crash_dump_manager_; +#endif // defined(OS_ANDROID) // Note: CastService must be destroyed before others. scoped_ptr cast_service_; diff --git a/chromecast/shell/browser/cast_content_browser_client.cc b/chromecast/shell/browser/cast_content_browser_client.cc index 546aa809b8c8..ad1ea3f23d67 100644 --- a/chromecast/shell/browser/cast_content_browser_client.cc +++ b/chromecast/shell/browser/cast_content_browser_client.cc @@ -17,6 +17,7 @@ #include "chromecast/shell/browser/devtools/cast_dev_tools_delegate.h" #include "chromecast/shell/browser/geolocation/cast_access_token_store.h" #include "chromecast/shell/browser/url_request_context_factory.h" +#include "components/crash/app/breakpad_linux.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/certificate_request_result_type.h" #include "content/public/browser/render_process_host.h" @@ -30,6 +31,10 @@ #include "chromecast/shell/browser/android/external_video_surface_container_impl.h" #endif // defined(OS_ANDROID) +#if defined(OS_ANDROID) +#include "components/crash/browser/crash_dump_manager_android.h" +#endif // defined(OS_ANDROID) + namespace chromecast { namespace shell { @@ -228,6 +233,19 @@ void CastContentBrowserClient::GetAdditionalMappedFilesForChildProcess( mappings->Transfer( kAndroidPakDescriptor, base::ScopedFD(pak_with_flags.TakePlatformFile())); + + if (breakpad::IsCrashReporterEnabled()) { + base::File minidump_file( + breakpad::CrashDumpManager::GetInstance()->CreateMinidumpFile( + child_process_id)); + if (!minidump_file.IsValid()) { + LOG(ERROR) << "Failed to create file for minidump, crash reporting will " + << "be disabled for this process."; + } else { + mappings->Transfer(kAndroidMinidumpDescriptor, + base::ScopedFD(minidump_file.TakePlatformFile())); + } + } #endif // defined(OS_ANDROID) } -- 2.11.4.GIT