Commit 21b19421 authored by siggi's avatar siggi Committed by Commit bot

A simple, practically zero cost fallback crash handler for Crashpad handler process.

This implements a fallback crash handler launcher. The launcher will be instantiated in the Crashpad handler process, where it pre-computes a command line to be launched on crash. This will be triggered off the UEF, under some form of single-instance locking.

BUG=678959

Review-Url: https://codereview.chromium.org/2596463002
Cr-Original-Original-Commit-Position: refs/heads/master@{#442596}
Committed: https://chromium.googlesource.com/chromium/src/+/d4e43bb5146c7a59123ff5f3b6e853d8e8c29130
Review-Url: https://codereview.chromium.org/2596463002
Cr-Original-Commit-Position: refs/heads/master@{#442698}
Committed: https://chromium.googlesource.com/chromium/src/+/f87fdaa12b09ff286d873e7a3ea459507d7adb83
Review-Url: https://codereview.chromium.org/2596463002
Cr-Commit-Position: refs/heads/master@{#442974}
parent 7fb95358
...@@ -66,6 +66,8 @@ if (is_win) { ...@@ -66,6 +66,8 @@ if (is_win) {
sources = [ sources = [
"crash_switches.cc", "crash_switches.cc",
"crash_switches.h", "crash_switches.h",
"fallback_crash_handler_launcher_win.cc",
"fallback_crash_handler_launcher_win.h",
"run_as_crashpad_handler_win.cc", "run_as_crashpad_handler_win.cc",
"run_as_crashpad_handler_win.h", "run_as_crashpad_handler_win.h",
] ]
...@@ -213,15 +215,20 @@ source_set("unit_tests") { ...@@ -213,15 +215,20 @@ source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"crash_keys_win_unittest.cc", "crash_keys_win_unittest.cc",
"fallback_crash_handler_launcher_win_unittest.cc",
] ]
deps = [ deps = [
":lib", ":lib",
"//base", "//base",
"//base/test:test_support",
"//testing/gmock", "//testing/gmock",
"//testing/gtest", "//testing/gtest",
] ]
if (is_win) { if (is_win) {
deps += [ "//breakpad:client" ] deps += [
":run_as_crashpad_handler",
"//breakpad:client",
]
} }
} }
// Copyright 2017 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/app/fallback_crash_handler_launcher_win.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/win/win_util.h"
namespace crash_reporter {
namespace {
// The number of characters reserved at the tail of the command line for the
// thread ID parameter.
const size_t kCommandLineTailSize = 32;
} // namespace
FallbackCrashHandlerLauncher::FallbackCrashHandlerLauncher() {
memset(&exception_pointers_, 0, sizeof(exception_pointers_));
}
FallbackCrashHandlerLauncher::~FallbackCrashHandlerLauncher() {}
bool FallbackCrashHandlerLauncher::Initialize(
const base::CommandLine& program,
const base::FilePath& crashpad_database) {
// Open an inheritable handle to self. This will be inherited to the handler.
const DWORD kAccessMask =
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_DUP_HANDLE;
self_process_handle_.Set(
OpenProcess(kAccessMask, TRUE, ::GetCurrentProcessId()));
if (!self_process_handle_.IsValid())
return false;
// Setup the startup info for inheriting the self process handle into the
// fallback crash handler.
if (!startup_info_.InitializeProcThreadAttributeList(1))
return false;
HANDLE raw_self_process_handle = self_process_handle_.Get();
if (!startup_info_.UpdateProcThreadAttribute(
PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &raw_self_process_handle,
sizeof(raw_self_process_handle))) {
return false;
}
// Build the command line from a copy of the command line passed in.
base::CommandLine cmd_line(program);
cmd_line.AppendSwitchPath("database", crashpad_database);
cmd_line.AppendSwitchASCII(
"exception-pointers",
base::Uint64ToString(reinterpret_cast<uintptr_t>(&exception_pointers_)));
cmd_line.AppendSwitchASCII(
"process", base::UintToString(
base::win::HandleToUint32(self_process_handle_.Get())));
std::wstring str_cmd_line = cmd_line.GetCommandLineString();
// Append the - for now abortive - thread argument manually.
str_cmd_line.append(L" --thread=");
// Store the command line string for easy use later.
cmd_line_.assign(str_cmd_line.begin(), str_cmd_line.end());
// Resize the vector to reserve space for the thread ID.
cmd_line_.resize(cmd_line_.size() + kCommandLineTailSize, '\0');
return true;
}
DWORD FallbackCrashHandlerLauncher::LaunchAndWaitForHandler(
EXCEPTION_POINTERS* exception_pointers) {
DCHECK(!cmd_line_.empty());
DCHECK_EQ('=', cmd_line_[cmd_line_.size() - kCommandLineTailSize - 1]);
// This program has crashed. Try and not use anything but the stack.
// Append the current thread's ID to the command line in-place.
int chars_appended = wsprintf(&cmd_line_.back() - kCommandLineTailSize + 1,
L"%d", GetCurrentThreadId());
DCHECK_GT(static_cast<int>(kCommandLineTailSize), chars_appended);
// Copy the exception pointers to our member variable, whose address is
// already baked into the command line.
exception_pointers_ = *exception_pointers;
// Launch the pre-cooked command line.
PROCESS_INFORMATION process_info = {};
if (!CreateProcess(nullptr, // Application name.
&cmd_line_[0], // Command line.
nullptr, // Process attributes.
nullptr, // Thread attributes.
true, // Inherit handles.
0, // Creation flags.
nullptr, // Environment.
nullptr, // Current directory.
startup_info_.startup_info(), // Startup info.
&process_info)) {
return GetLastError();
}
// Wait on the fallback crash handler process. The expectation is that this
// will never return, as the fallback crash handler will terminate this
// process. For testing, and full-on belt and suspenders, cover for this
// returning.
DWORD error = WaitForSingleObject(process_info.hProcess, INFINITE);
if (error != WAIT_OBJECT_0) {
// This should never happen, barring handle abuse.
// TODO(siggi): Record an UMA metric here.
NOTREACHED();
error = GetLastError();
} else {
// On successful wait, return the exit code of the fallback crash handler
// process.
if (!GetExitCodeProcess(process_info.hProcess, &error)) {
// This should never happen, barring handle abuse.
NOTREACHED();
error = GetLastError();
}
}
// Close the handles returned from CreateProcess.
CloseHandle(process_info.hProcess);
CloseHandle(process_info.hThread);
return error;
}
} // namespace crash_reporter
// Copyright 2017 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_APP_FALLBACK_CRASH_HANDLER_LAUNCHER_WIN_H_
#define COMPONENTS_CRASH_CONTENT_APP_FALLBACK_CRASH_HANDLER_LAUNCHER_WIN_H_
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/win/scoped_handle.h"
#include "base/win/startup_information.h"
#include <windows.h>
namespace crash_reporter {
// This class is a last-ditch crash handler for the Crashpad handler process.
// It prepares and stores a command line, ready for dispatching when
// an exception occurs. Everything needed to call CreateProcess is pre-allocated
// to minimize the odds of re-faulting due to e.g. tripping over the same issue
// that caused the initial crash.
// This is still very much best-effort, as doing anything at all inside a
// process that's crashed is always going to be iffy^2.
class FallbackCrashHandlerLauncher {
public:
FallbackCrashHandlerLauncher();
~FallbackCrashHandlerLauncher();
// Initializes everything that's needed in LaunchAndWaitForHandler.
bool Initialize(const base::CommandLine& program,
const base::FilePath& crashpad_database);
// Launch the pre-computed command line for the fallback error handler.
// The expectation is that this function will never return, as the fallback
// error handler should terminate it with the exception code as the process
// exit code. The return value from this function is therefore academic in
// the normal case.
// However, for completeness, this function returns one of:
// - The error from CreateProcess if it fails to launch the fallback handler.
// - The error from waiting on the fallback crash handler process if it
// fails to wait for that process to exit.
// - The exit code from the fallback crash handler process.
// - The error encountered in retrieving the crash handler process' exit code.
// Note that the return value is used in testing.
DWORD LaunchAndWaitForHandler(EXCEPTION_POINTERS* pointers);
private:
// A copy of the actual exception pointers made at time of exception.
EXCEPTION_POINTERS exception_pointers_;
// The precomputed startup info and command line for launching the fallback
// handler.
base::win::StartupInformation startup_info_;
// Stores the pre-cooked command line, with an allotment of zeros at the back
// sufficient for writing in the thread id, just before launch.
std::vector<wchar_t> cmd_line_;
// An inheritable handle to our own process, the raw handle is necessary
// for pre-computing the startup info.
base::win::ScopedHandle self_process_handle_;
DISALLOW_COPY_AND_ASSIGN(FallbackCrashHandlerLauncher);
};
} // namespace crash_reporter
#endif // COMPONENTS_CRASH_CONTENT_APP_FALLBACK_CRASH_HANDLER_LAUNCHER_WIN_H_
// Copyright 2017 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/app/fallback_crash_handler_launcher_win.h"
#include <dbghelp.h>
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/process/process.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/multiprocess_test.h"
#include "base/win/win_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace crash_reporter {
namespace {
const wchar_t kFileName[] = L"crash.dmp";
// This function is called in the fallback handler process instance.
MULTIPROCESS_TEST_MAIN(FallbackCrashHandlerLauncherMain) {
base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
// Check for presence of the "process" argument.
CHECK(cmd_line->HasSwitch("process"));
// Retrieve and check the handle to verify that it was inherited in.
unsigned int uint_process = 0;
CHECK(base::StringToUint(cmd_line->GetSwitchValueASCII("process"),
&uint_process));
base::Process process(base::win::Uint32ToHandle(uint_process));
// Verify that the process handle points to our parent.
CHECK_EQ(base::GetParentProcessId(base::GetCurrentProcessHandle()),
process.Pid());
// Check the "thread" argument.
CHECK(cmd_line->HasSwitch("thread"));
// Retrieve the thread id.
unsigned int thread_id = 0;
CHECK(
base::StringToUint(cmd_line->GetSwitchValueASCII("thread"), &thread_id));
// Check the "exception-pointers" argument.
CHECK(cmd_line->HasSwitch("exception-pointers"));
uint64_t uint_exc_ptrs = 0;
CHECK(base::StringToUint64(
cmd_line->GetSwitchValueASCII("exception-pointers"), &uint_exc_ptrs));
EXCEPTION_POINTERS* exc_ptrs = reinterpret_cast<EXCEPTION_POINTERS*>(
static_cast<uintptr_t>(uint_exc_ptrs));
// Check the "database" argument.
CHECK(cmd_line->HasSwitch("database"));
base::FilePath database_dir = cmd_line->GetSwitchValuePath("database");
base::FilePath dump_path = database_dir.Append(kFileName);
// Create a dump file in the directory.
base::File dump(dump_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
CHECK(dump.IsValid());
const MINIDUMP_TYPE kMinidumpType = static_cast<MINIDUMP_TYPE>(
MiniDumpWithHandleData | MiniDumpWithUnloadedModules |
MiniDumpWithProcessThreadData | MiniDumpWithThreadInfo |
MiniDumpWithTokenInformation);
MINIDUMP_EXCEPTION_INFORMATION exc_info = {};
exc_info.ThreadId = thread_id;
exc_info.ExceptionPointers = exc_ptrs;
exc_info.ClientPointers = TRUE; // ExceptionPointers in client.
// Write the minidump as a direct test of the validity and permissions on the
// parent process handle.
if (!MiniDumpWriteDump(process.Handle(), // Process handle.
process.Pid(), // Process Id.
dump.GetPlatformFile(), // File handle.
kMinidumpType, // Minidump type.
&exc_info, // Exception Param
nullptr, // UserStreamParam,
nullptr)) { // CallbackParam
DWORD error = GetLastError();
dump.Close();
CHECK(base::DeleteFile(dump_path, false));
CHECK(false) << "Unable to write dump, error " << error;
}
return 0;
}
MULTIPROCESS_TEST_MAIN(TestCrashHandlerLauncherMain) {
base::ScopedTempDir database_dir;
CHECK(database_dir.CreateUniqueTempDir());
// Construct the base command line that diverts to the main function above.
base::CommandLine base_cmdline(
base::GetMultiProcessTestChildBaseCommandLine());
base_cmdline.AppendSwitchASCII(switches::kTestChildProcess,
"FallbackCrashHandlerLauncherMain");
FallbackCrashHandlerLauncher launcher;
CHECK(launcher.Initialize(base_cmdline, database_dir.GetPath()));
// Make like an access violation at the current place.
EXCEPTION_RECORD exc_record = {};
exc_record.ExceptionCode = EXCEPTION_ACCESS_VIOLATION;
CONTEXT ctx = {};
RtlCaptureContext(&ctx);
CHECK_NE(0UL, ctx.ContextFlags);
EXCEPTION_POINTERS exc_ptrs = {&exc_record, &ctx};
CHECK_EQ(0UL, launcher.LaunchAndWaitForHandler(&exc_ptrs));
// Check that the dump was written, e.g. it's an existing file with non-zero
// size.
base::File dump(database_dir.GetPath().Append(kFileName),
base::File::FLAG_OPEN | base::File::FLAG_READ);
CHECK(dump.IsValid());
CHECK_NE(0, dump.GetLength());
return 0;
}
class FallbackCrashHandlerLauncherTest : public base::MultiProcessTest {};
} // namespace
TEST_F(FallbackCrashHandlerLauncherTest, LaunchAndWaitForHandler) {
// Because this process is heavily multithreaded it's going to be flaky
// and generally fraught with peril to try and grab a minidump of it.
// Instead, fire off a sacrificial process to do the testing.
base::Process test_process = SpawnChild("TestCrashHandlerLauncherMain");
int exit_code = 0;
ASSERT_TRUE(test_process.WaitForExit(&exit_code));
ASSERT_EQ(0, exit_code);
}
} // namespace crash_reporter
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment