Skip to content

Commit

Permalink
headless: Add support for minidump generation on Linux
Browse files Browse the repository at this point in the history
This patch adds support for minidump generation in headless mode. This
is controlled by two new browser settings:

- SetCrashReporterEnabled: Turns crash reporter on or off. Off by
  default.
- SetCrashDumpsDir: Controls where crash dumps are written. Uses the
  directory of the executable by default.

Headless Shell is also modified to accept the equivalent command line
flags: --enable-crash-reporter and --crash-dumps-dir. Note that we don't
enable crash dumps automatically because we currently can't determine
whether the user has opted into metrics reporting.

In official builds, the generated minidumps are also uploaded
automatically. This can be disabled either with --disable-breakpad or by
setting the CHROME_HEADLESS environment variable to indicate an
unattended testing mode (as with regular Chrome).

Design doc:
https://docs.google.com/document/d/1l6AGOOBLk99PaAKoZQW_DVhM8FQ6Fut27lD938CRbTM/edit#

BUG=691507

Review-Url: https://codereview.chromium.org/2693943004
Cr-Commit-Position: refs/heads/master@{#450655}
  • Loading branch information
skyostil authored and Commit bot committed Feb 15, 2017
1 parent a97d645 commit c3c9701
Show file tree
Hide file tree
Showing 16 changed files with 392 additions and 4 deletions.
4 changes: 4 additions & 0 deletions headless/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,15 @@ static_library("headless_lib") {
"lib/browser/headless_devtools_client_impl.h",
"lib/browser/headless_devtools_manager_delegate.cc",
"lib/browser/headless_devtools_manager_delegate.h",
"lib/browser/headless_macros.h",
"lib/browser/headless_platform_event_source.cc",
"lib/browser/headless_platform_event_source.h",
"lib/browser/headless_url_request_context_getter.cc",
"lib/browser/headless_url_request_context_getter.h",
"lib/browser/headless_web_contents_impl.cc",
"lib/browser/headless_web_contents_impl.h",
"lib/headless_crash_reporter_client.cc",
"lib/headless_crash_reporter_client.h",
"lib/headless_content_client.cc",
"lib/headless_content_client.h",
"lib/headless_content_main_delegate.cc",
Expand Down Expand Up @@ -276,6 +279,7 @@ static_library("headless_lib") {
":gen_devtools_client_api",
":version_header",
"//base",
"//components/crash/content/browser",
"//components/security_state/content",
"//components/security_state/core",
"//content/public/app:both",
Expand Down
2 changes: 2 additions & 0 deletions headless/DEPS
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
include_rules = [
"+components/crash/content/app",
"+components/crash/content/browser",
"+content/public/app",
"+content/public/browser",
"+content/public/common",
Expand Down
8 changes: 8 additions & 0 deletions headless/app/headless_shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <string>

#include "base/base64.h"
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
Expand Down Expand Up @@ -441,6 +442,13 @@ int HeadlessShellMain(int argc, const char** argv) {
if (!ValidateCommandLine(command_line))
return EXIT_FAILURE;

if (command_line.HasSwitch(::switches::kEnableCrashReporter))
builder.SetCrashReporterEnabled(true);
if (command_line.HasSwitch(switches::kCrashDumpsDir)) {
builder.SetCrashDumpsDir(
command_line.GetSwitchValuePath(switches::kCrashDumpsDir));
}

if (command_line.HasSwitch(::switches::kRemoteDebuggingPort)) {
std::string address = kDevToolsHttpServerAddress;
if (command_line.HasSwitch(switches::kRemoteDebuggingAddress)) {
Expand Down
3 changes: 3 additions & 0 deletions headless/app/headless_shell_switches.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
namespace headless {
namespace switches {

// The directory breakpad should store minidumps in.
const char kCrashDumpsDir[] = "crash-dumps-dir";

// Instructs headless_shell to cause network fetches to complete in order of
// creation. This removes a significant source of network related
// non-determinism at the cost of slower page loads.
Expand Down
1 change: 1 addition & 0 deletions headless/app/headless_shell_switches.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace headless {
namespace switches {
extern const char kCrashDumpsDir[];
extern const char kDeterministicFetch[];
extern const char kDumpDom[];
extern const char kHideScrollbars[];
Expand Down
92 changes: 92 additions & 0 deletions headless/lib/browser/headless_content_browser_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,99 @@
#include <memory>
#include <unordered_set>

#include "base/base_switches.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/service_names.mojom.h"
#include "headless/grit/headless_lib_resources.h"
#include "headless/lib/browser/headless_browser_context_impl.h"
#include "headless/lib/browser/headless_browser_impl.h"
#include "headless/lib/browser/headless_browser_main_parts.h"
#include "headless/lib/browser/headless_devtools_manager_delegate.h"
#include "headless/lib/headless_macros.h"
#include "storage/browser/quota/quota_settings.h"
#include "ui/base/resource/resource_bundle.h"

#if defined(HEADLESS_USE_BREAKPAD)
#include "base/debug/leak_annotations.h"
#include "components/crash/content/app/breakpad_linux.h"
#include "components/crash/content/browser/crash_handler_host_linux.h"
#include "content/public/common/content_descriptors.h"
#endif // defined(HEADLESS_USE_BREAKPAD)

namespace headless {

namespace {
const char kCapabilityPath[] =
"interface_provider_specs.navigation:frame.provides.renderer";

#if defined(HEADLESS_USE_BREAKPAD)
breakpad::CrashHandlerHostLinux* CreateCrashHandlerHost(
const std::string& process_type,
const HeadlessBrowser::Options& options) {
base::FilePath dumps_path = options.crash_dumps_dir;
if (dumps_path.empty()) {
bool ok = PathService::Get(base::DIR_MODULE, &dumps_path);
DCHECK(ok);
}

{
ANNOTATE_SCOPED_MEMORY_LEAK;
#if defined(OFFICIAL_BUILD)
// Upload crash dumps in official builds, unless we're running in unattended
// mode (not to be confused with headless mode in general -- see
// chrome/common/env_vars.cc).
static const char kHeadless[] = "CHROME_HEADLESS";
bool upload = (getenv(kHeadless) == nullptr);
#else
bool upload = false;
#endif
breakpad::CrashHandlerHostLinux* crash_handler =
new breakpad::CrashHandlerHostLinux(process_type, dumps_path, upload);
crash_handler->StartUploaderThread();
return crash_handler;
}
}

int GetCrashSignalFD(const base::CommandLine& command_line,
const HeadlessBrowser::Options& options) {
if (!breakpad::IsCrashReporterEnabled())
return -1;

std::string process_type =
command_line.GetSwitchValueASCII(switches::kProcessType);

if (process_type == switches::kRendererProcess) {
static breakpad::CrashHandlerHostLinux* crash_handler =
CreateCrashHandlerHost(process_type, options);
return crash_handler->GetDeathSignalSocket();
}

if (process_type == switches::kPpapiPluginProcess) {
static breakpad::CrashHandlerHostLinux* crash_handler =
CreateCrashHandlerHost(process_type, options);
return crash_handler->GetDeathSignalSocket();
}

if (process_type == switches::kGpuProcess) {
static breakpad::CrashHandlerHostLinux* crash_handler =
CreateCrashHandlerHost(process_type, options);
return crash_handler->GetDeathSignalSocket();
}

return -1;
}
#endif // defined(HEADLESS_USE_BREAKPAD)

} // namespace

HeadlessContentBrowserClient::HeadlessContentBrowserClient(
Expand Down Expand Up @@ -99,4 +170,25 @@ void HeadlessContentBrowserClient::GetQuotaSettings(
callback);
}

void HeadlessContentBrowserClient::GetAdditionalMappedFilesForChildProcess(
const base::CommandLine& command_line,
int child_process_id,
content::FileDescriptorInfo* mappings) {
#if defined(HEADLESS_USE_BREAKPAD)
int crash_signal_fd = GetCrashSignalFD(command_line, *browser_->options());
if (crash_signal_fd >= 0)
mappings->Share(kCrashDumpSignal, crash_signal_fd);
#endif // defined(HEADLESS_USE_BREAKPAD)
}

void HeadlessContentBrowserClient::AppendExtraCommandLineSwitches(
base::CommandLine* command_line,
int child_process_id) {
#if defined(HEADLESS_USE_BREAKPAD)
// This flag tells child processes to also turn on crash reporting.
if (breakpad::IsCrashReporterEnabled())
command_line->AppendSwitch(switches::kEnableCrashReporter);
#endif // defined(HEADLESS_USE_BREAKPAD)
}

} // namespace headless
6 changes: 6 additions & 0 deletions headless/lib/browser/headless_content_browser_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ class HeadlessContentBrowserClient : public content::ContentBrowserClient {
content::BrowserContext* context,
content::StoragePartition* partition,
const storage::OptionalQuotaSettingsCallback& callback) override;
void GetAdditionalMappedFilesForChildProcess(
const base::CommandLine& command_line,
int child_process_id,
content::FileDescriptorInfo* mappings) override;
void AppendExtraCommandLineSwitches(base::CommandLine* command_line,
int child_process_id) override;

private:
HeadlessBrowserImpl* browser_; // Not owned.
Expand Down
2 changes: 1 addition & 1 deletion headless/lib/embedder_mojo_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class EmbedderMojoTest : public HeadlessBrowserTest,
pak_path, ui::SCALE_FACTOR_NONE);
}

// HeadlessWebContentsObserver implementation:
// HeadlessWebContents::Observer implementation:
void DevToolsTargetReady() override {
EXPECT_TRUE(web_contents_->GetDevToolsTarget());
web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
Expand Down
88 changes: 88 additions & 0 deletions headless/lib/headless_browser_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
#include <memory>

#include "base/command_line.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "headless/lib/headless_macros.h"
#include "headless/public/devtools/domains/inspector.h"
#include "headless/public/devtools/domains/network.h"
#include "headless/public/devtools/domains/page.h"
#include "headless/public/headless_browser.h"
Expand Down Expand Up @@ -642,4 +646,88 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, MAYBE_RendererCommandPrefixTest) {
base::DeleteFile(launcher_stamp, false);
}

class CrashReporterTest : public HeadlessBrowserTest,
public HeadlessWebContents::Observer,
inspector::ExperimentalObserver {
public:
CrashReporterTest() : devtools_client_(HeadlessDevToolsClient::Create()) {}
~CrashReporterTest() override {}

void SetUp() override {
base::ThreadRestrictions::SetIOAllowed(true);
base::CreateNewTempDirectory("CrashReporterTest", &crash_dumps_dir_);
EXPECT_FALSE(options()->enable_crash_reporter);
options()->enable_crash_reporter = true;
options()->crash_dumps_dir = crash_dumps_dir_;
HeadlessBrowserTest::SetUp();
}

void TearDown() override {
base::ThreadRestrictions::SetIOAllowed(true);
base::DeleteFile(crash_dumps_dir_, /* recursive */ false);
}

// HeadlessWebContents::Observer implementation:
void DevToolsTargetReady() override {
EXPECT_TRUE(web_contents_->GetDevToolsTarget());
web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
devtools_client_->GetInspector()->GetExperimental()->AddObserver(this);
}

// inspector::ExperimentalObserver implementation:
void OnTargetCrashed(const inspector::TargetCrashedParams&) override {
FinishAsynchronousTest();
}

protected:
HeadlessBrowserContext* browser_context_ = nullptr;
HeadlessWebContents* web_contents_ = nullptr;
std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
base::FilePath crash_dumps_dir_;
};

// TODO(skyostil): Minidump generation currently is only supported on Linux.
#if defined(HEADLESS_USE_BREAKPAD)
#define MAYBE_GenerateMinidump GenerateMinidump
#else
#define MAYBE_GenerateMinidump DISABLED_GenerateMinidump
#endif // defined(HEADLESS_USE_BREAKPAD)
IN_PROC_BROWSER_TEST_F(CrashReporterTest, MAYBE_GenerateMinidump) {
// Navigates a tab to chrome://crash and checks that a minidump is generated.
// Note that we only test renderer crashes here -- browser crashes need to be
// tested with a separate harness.
//
// The case where crash reporting is disabled is covered by
// HeadlessCrashObserverTest.
browser_context_ = browser()->CreateBrowserContextBuilder().Build();

web_contents_ = browser_context_->CreateWebContentsBuilder()
.SetInitialURL(GURL(content::kChromeUICrashURL))
.Build();

web_contents_->AddObserver(this);
RunAsynchronousTest();

// The target has crashed and should no longer be there.
EXPECT_FALSE(web_contents_->GetDevToolsTarget());

// Check that one minidump got created.
{
base::ThreadRestrictions::SetIOAllowed(true);
base::FileEnumerator it(crash_dumps_dir_, /* recursive */ false,
base::FileEnumerator::FILES);
base::FilePath minidump = it.Next();
EXPECT_FALSE(minidump.empty());
EXPECT_EQ(".dmp", minidump.Extension());
EXPECT_TRUE(it.Next().empty());
}

web_contents_->RemoveObserver(this);
web_contents_->Close();
web_contents_ = nullptr;

browser_context_->Close();
browser_context_ = nullptr;
}

} // namespace headless
Loading

0 comments on commit c3c9701

Please sign in to comment.