blob: 3946d2a457695fed13c56b1d4db8f4c9ba2f0ec3 [file] [log] [blame]
// Copyright 2015 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/crashpad.h"
#include <memory>
#include "base/debug/crash_logging.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "components/crash/content/app/crash_reporter_client.h"
#include "components/crash/content/app/crash_switches.h"
#include "third_party/crashpad/crashpad/client/crashpad_client.h"
#include "third_party/crashpad/crashpad/client/crashpad_info.h"
#include "third_party/crashpad/crashpad/client/simulate_crash_win.h"
namespace crash_reporter {
namespace internal {
void GetPlatformCrashpadAnnotations(
std::map<std::string, std::string>* annotations) {
CrashReporterClient* crash_reporter_client = GetCrashReporterClient();
wchar_t exe_file[MAX_PATH] = {};
CHECK(::GetModuleFileName(nullptr, exe_file, arraysize(exe_file)));
base::string16 product_name, version, special_build, channel_name;
crash_reporter_client->GetProductNameAndVersion(
exe_file, &product_name, &version, &special_build, &channel_name);
(*annotations)["prod"] = base::UTF16ToUTF8(product_name);
(*annotations)["ver"] = base::UTF16ToUTF8(version);
#if defined(GOOGLE_CHROME_BUILD)
// Empty means stable.
const bool allow_empty_channel = true;
#else
const bool allow_empty_channel = false;
#endif
if (allow_empty_channel || !channel_name.empty())
(*annotations)["channel"] = base::UTF16ToUTF8(channel_name);
if (!special_build.empty())
(*annotations)["special"] = base::UTF16ToUTF8(special_build);
#if defined(ARCH_CPU_X86)
(*annotations)["plat"] = std::string("Win32");
#elif defined(ARCH_CPU_X86_64)
(*annotations)["plat"] = std::string("Win64");
#endif
}
base::FilePath PlatformCrashpadInitialization(
bool initial_client,
bool browser_process,
bool embedded_handler,
const std::string& user_data_dir) {
base::FilePath database_path; // Only valid in the browser process.
base::FilePath metrics_path; // Only valid in the browser process.
const char kPipeNameVar[] = "CHROME_CRASHPAD_PIPE_NAME";
const char kServerUrlVar[] = "CHROME_CRASHPAD_SERVER_URL";
std::unique_ptr<base::Environment> env(base::Environment::Create());
if (initial_client) {
CrashReporterClient* crash_reporter_client = GetCrashReporterClient();
base::string16 database_path_str;
if (crash_reporter_client->GetCrashDumpLocation(&database_path_str))
database_path = base::FilePath(database_path_str);
base::string16 metrics_path_str;
if (crash_reporter_client->GetCrashMetricsLocation(&metrics_path_str)) {
metrics_path = base::FilePath(metrics_path_str);
CHECK(base::CreateDirectoryAndGetError(metrics_path, nullptr));
}
std::map<std::string, std::string> process_annotations;
GetPlatformCrashpadAnnotations(&process_annotations);
#if defined(GOOGLE_CHROME_BUILD)
std::string url = "https://clients2.google.com/cr/report";
#else
std::string url;
#endif
// Allow the crash server to be overridden for testing. If the variable
// isn't present in the environment then the default URL will remain.
env->GetVar(kServerUrlVar, &url);
wchar_t exe_file_path[MAX_PATH] = {};
CHECK(
::GetModuleFileName(nullptr, exe_file_path, arraysize(exe_file_path)));
base::FilePath exe_file(exe_file_path);
if (crash_reporter_client->GetShouldDumpLargerDumps()) {
const uint32_t kIndirectMemoryLimit = 4 * 1024 * 1024;
crashpad::CrashpadInfo::GetCrashpadInfo()
->set_gather_indirectly_referenced_memory(
crashpad::TriState::kEnabled, kIndirectMemoryLimit);
}
// If the handler is embedded in the binary (e.g. chrome, setup), we
// reinvoke it with --type=crashpad-handler. Otherwise, we use the
// standalone crashpad_handler.exe (for tests, etc.).
std::vector<std::string> start_arguments;
if (embedded_handler) {
start_arguments.push_back(std::string("--type=") +
switches::kCrashpadHandler);
if (!user_data_dir.empty()) {
start_arguments.push_back(std::string("--user-data-dir=") +
user_data_dir);
}
// The prefetch argument added here has to be documented in
// chrome_switches.cc, below the kPrefetchArgument* constants. A constant
// can't be used here because crashpad can't depend on Chrome.
start_arguments.push_back("/prefetch:7");
} else {
base::FilePath exe_dir = exe_file.DirName();
exe_file = exe_dir.Append(FILE_PATH_LITERAL("crashpad_handler.exe"));
}
std::vector<std::string> arguments(start_arguments);
if (crash_reporter_client->ShouldMonitorCrashHandlerExpensively()) {
arguments.push_back("--monitor-self");
for (const std::string& start_argument : start_arguments) {
arguments.push_back(std::string("--monitor-self-argument=") +
start_argument);
}
}
// Set up --monitor-self-annotation even in the absence of --monitor-self so
// that minidumps produced by Crashpad's generate_dump tool will contain
// these annotations.
arguments.push_back(std::string("--monitor-self-annotation=ptype=") +
switches::kCrashpadHandler);
GetCrashpadClient().StartHandler(exe_file, database_path, metrics_path, url,
process_annotations, arguments, false,
false);
// If we're the browser, push the pipe name into the environment so child
// processes can connect to it. If we inherited another crashpad_handler's
// pipe name, we'll overwrite it here.
env->SetVar(kPipeNameVar,
base::UTF16ToUTF8(GetCrashpadClient().GetHandlerIPCPipe()));
} else {
std::string pipe_name_utf8;
if (env->GetVar(kPipeNameVar, &pipe_name_utf8)) {
GetCrashpadClient().SetHandlerIPCPipe(base::UTF8ToUTF16(pipe_name_utf8));
}
}
return database_path;
}
namespace {
// We need to prevent ICF from folding DumpProcessForHungInputThread(),
// DumpProcessForHungInputNoCrashKeysThread() together, since that makes them
// indistinguishable in crash dumps. We do this by making the function
// bodies unique, and prevent optimization from shuffling things around.
MSVC_DISABLE_OPTIMIZE()
MSVC_PUSH_DISABLE_WARNING(4748)
// TODO(dtapuska): Remove when enough information is gathered where the crash
// reports without crash keys come from.
DWORD WINAPI DumpProcessForHungInputThread(void* crash_keys_str) {
base::StringPairs crash_keys;
if (crash_keys_str && base::SplitStringIntoKeyValuePairs(
reinterpret_cast<const char*>(crash_keys_str), ':',
',', &crash_keys)) {
for (const auto& crash_key : crash_keys) {
base::debug::SetCrashKeyValue(crash_key.first, crash_key.second);
}
}
DumpWithoutCrashing();
return 0;
}
// TODO(dtapuska): Remove when enough information is gathered where the crash
// reports without crash keys come from.
DWORD WINAPI DumpProcessForHungInputNoCrashKeysThread(void* reason) {
#pragma warning(push)
#pragma warning(disable : 4311 4302)
base::debug::SetCrashKeyValue(
"hung-reason", base::IntToString(reinterpret_cast<int>(reason)));
#pragma warning(pop)
DumpWithoutCrashing();
return 0;
}
MSVC_POP_WARNING()
MSVC_ENABLE_OPTIMIZE()
} // namespace
} // namespace internal
} // namespace crash_reporter
extern "C" {
// Crashes the process after generating a dump for the provided exception. Note
// that the crash reporter should be initialized before calling this function
// for it to do anything.
// NOTE: This function is used by SyzyASAN to invoke a crash. If you change the
// the name or signature of this function you will break SyzyASAN instrumented
// releases of Chrome. Please contact syzygy-team@chromium.org before doing so!
int __declspec(dllexport) CrashForException(
EXCEPTION_POINTERS* info) {
crash_reporter::GetCrashpadClient().DumpAndCrash(info);
return EXCEPTION_CONTINUE_SEARCH;
}
// Injects a thread into a remote process to dump state when there is no crash.
// |serialized_crash_keys| is a nul terminated string that represents serialized
// crash keys sent from the browser. Keys and values are separated by ':', and
// key/value pairs are separated by ','. All keys should be previously
// registered as crash keys. This method is used solely to classify hung input.
HANDLE __declspec(dllexport) __cdecl InjectDumpForHungInput(
HANDLE process,
void* serialized_crash_keys) {
return CreateRemoteThread(
process, nullptr, 0,
crash_reporter::internal::DumpProcessForHungInputThread,
serialized_crash_keys, 0, nullptr);
}
// Injects a thread into a remote process to dump state when there is no crash.
// This method provides |reason| which will interpreted as an integer and logged
// as a crash key.
HANDLE __declspec(dllexport) __cdecl InjectDumpForHungInputNoCrashKeys(
HANDLE process,
int reason) {
return CreateRemoteThread(
process, nullptr, 0,
crash_reporter::internal::DumpProcessForHungInputNoCrashKeysThread,
reinterpret_cast<void*>(reason), 0, nullptr);
}
#if defined(ARCH_CPU_X86_64)
static int CrashForExceptionInNonABICompliantCodeRange(
PEXCEPTION_RECORD ExceptionRecord,
ULONG64 EstablisherFrame,
PCONTEXT ContextRecord,
PDISPATCHER_CONTEXT DispatcherContext) {
EXCEPTION_POINTERS info = { ExceptionRecord, ContextRecord };
return CrashForException(&info);
}
// See https://msdn.microsoft.com/en-us/library/ddssxxy8.aspx
typedef struct _UNWIND_INFO {
unsigned char Version : 3;
unsigned char Flags : 5;
unsigned char SizeOfProlog;
unsigned char CountOfCodes;
unsigned char FrameRegister : 4;
unsigned char FrameOffset : 4;
ULONG ExceptionHandler;
} UNWIND_INFO, *PUNWIND_INFO;
struct ExceptionHandlerRecord {
RUNTIME_FUNCTION runtime_function;
UNWIND_INFO unwind_info;
unsigned char thunk[12];
};
// These are GetProcAddress()d from V8 binding code.
void __declspec(dllexport) __cdecl RegisterNonABICompliantCodeRange(
void* start,
size_t size_in_bytes) {
ExceptionHandlerRecord* record =
reinterpret_cast<ExceptionHandlerRecord*>(start);
// We assume that the first page of the code range is executable and
// committed and reserved for breakpad. What could possibly go wrong?
// All addresses are 32bit relative offsets to start.
record->runtime_function.BeginAddress = 0;
record->runtime_function.EndAddress =
base::checked_cast<DWORD>(size_in_bytes);
record->runtime_function.UnwindData =
offsetof(ExceptionHandlerRecord, unwind_info);
// Create unwind info that only specifies an exception handler.
record->unwind_info.Version = 1;
record->unwind_info.Flags = UNW_FLAG_EHANDLER;
record->unwind_info.SizeOfProlog = 0;
record->unwind_info.CountOfCodes = 0;
record->unwind_info.FrameRegister = 0;
record->unwind_info.FrameOffset = 0;
record->unwind_info.ExceptionHandler =
offsetof(ExceptionHandlerRecord, thunk);
// Hardcoded thunk.
// mov imm64, rax
record->thunk[0] = 0x48;
record->thunk[1] = 0xb8;
void* handler = &CrashForExceptionInNonABICompliantCodeRange;
memcpy(&record->thunk[2], &handler, 8);
// jmp rax
record->thunk[10] = 0xff;
record->thunk[11] = 0xe0;
// Protect reserved page against modifications.
DWORD old_protect;
CHECK(VirtualProtect(
start, sizeof(ExceptionHandlerRecord), PAGE_EXECUTE_READ, &old_protect));
CHECK(RtlAddFunctionTable(
&record->runtime_function, 1, reinterpret_cast<DWORD64>(start)));
}
void __declspec(dllexport) __cdecl UnregisterNonABICompliantCodeRange(
void* start) {
ExceptionHandlerRecord* record =
reinterpret_cast<ExceptionHandlerRecord*>(start);
CHECK(RtlDeleteFunctionTable(&record->runtime_function));
}
#endif // ARCH_CPU_X86_64
} // extern "C"