| // 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/lazy_instance.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 { |
| |
| namespace { |
| |
| base::LazyInstance<crashpad::CrashpadClient>::Leaky g_crashpad_client = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| } // namespace |
| |
| 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); |
| (*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) { |
| 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); |
| |
| bool is_per_user_install = |
| crash_reporter_client->GetIsPerUserInstall(exe_file.value()); |
| if (crash_reporter_client->GetShouldDumpLargerDumps(is_per_user_install)) { |
| 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> arguments; |
| if (embedded_handler) { |
| arguments.push_back(std::string("--type=") + switches::kCrashpadHandler); |
| // 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. |
| arguments.push_back("/prefetch:7"); |
| } else { |
| base::FilePath exe_dir = exe_file.DirName(); |
| exe_file = exe_dir.Append(FILE_PATH_LITERAL("crashpad_handler.exe")); |
| } |
| |
| if (!g_crashpad_client.Get().StartHandler( |
| exe_file, database_path, metrics_path, url, process_annotations, |
| arguments, false, true)) { |
| // This means that CreateThread() failed, so this process is very messed |
| // up. This should be effectively unreachable. It is unlikely that there |
| // is any utility to ever making this non-fatal, however, if this is done, |
| // calls to BlockUntilHandlerStarted() will have to be amended. |
| LOG(FATAL) << "synchronous part of handler startup failed"; |
| } |
| |
| // 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(g_crashpad_client.Get().GetHandlerIPCPipe())); |
| } else { |
| std::string pipe_name_utf8; |
| if (env->GetVar(kPipeNameVar, &pipe_name_utf8)) { |
| g_crashpad_client.Get().SetHandlerIPCPipe( |
| base::UTF8ToUTF16(pipe_name_utf8)); |
| } |
| } |
| |
| return database_path; |
| } |
| |
| // TODO(scottmg): http://crbug.com/546288 These exported functions are for |
| // compatibility with how Breakpad worked, but it seems like there's no need to |
| // do the CreateRemoteThread() dance with a minor extension of the Crashpad API |
| // (to just pass the pid we want a dump for). We should add that and then modify |
| // hang_crash_dump_win.cc to work in a more direct manner. |
| |
| // Used for dumping a process state when there is no crash. |
| extern "C" void __declspec(dllexport) __cdecl DumpProcessWithoutCrash() { |
| CRASHPAD_SIMULATE_CRASH(); |
| } |
| |
| namespace { |
| |
| // We need to prevent ICF from folding DumpForHangDebuggingThread(), |
| // DumpProcessForHungInputThread(), DumpProcessForHungInputNoCrashKeysThread() |
| // and DumpProcessWithoutCrashThread() 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) |
| |
| // Note that this function must be in a namespace for the [Renderer hang] |
| // annotations to work on the crash server. |
| DWORD WINAPI DumpProcessWithoutCrashThread(void*) { |
| DumpProcessWithoutCrash(); |
| return 0; |
| } |
| |
| // 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); |
| } |
| } |
| DumpProcessWithoutCrash(); |
| 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) |
| |
| DumpProcessWithoutCrash(); |
| return 0; |
| } |
| |
| // TODO(yzshen): Remove when enough information is collected and the hang rate |
| // of pepper/renderer processes is reduced. |
| DWORD WINAPI DumpForHangDebuggingThread(void*) { |
| DumpProcessWithoutCrash(); |
| VLOG(1) << "dumped for hang debugging"; |
| return 0; |
| } |
| |
| MSVC_POP_WARNING() |
| MSVC_ENABLE_OPTIMIZE() |
| |
| } // namespace |
| |
| } // namespace internal |
| |
| void BlockUntilHandlerStarted() { |
| // We know that the StartHandler() at least started asynchronous startup if |
| // we're here, as if it doesn't, we abort. |
| const unsigned int kTimeoutMS = 5000; |
| if (!internal::g_crashpad_client.Get().WaitForHandlerStart(kTimeoutMS)) { |
| LOG(ERROR) << "Crashpad handler failed to start, crash reporting disabled"; |
| } |
| } |
| |
| } // 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::internal::g_crashpad_client.Get().DumpAndCrash(info); |
| return EXCEPTION_CONTINUE_SEARCH; |
| } |
| |
| // Injects a thread into a remote process to dump state when there is no crash. |
| HANDLE __declspec(dllexport) __cdecl InjectDumpProcessWithoutCrash( |
| HANDLE process) { |
| return CreateRemoteThread( |
| process, nullptr, 0, |
| crash_reporter::internal::DumpProcessWithoutCrashThread, nullptr, 0, |
| nullptr); |
| } |
| |
| // 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); |
| } |
| |
| HANDLE __declspec(dllexport) __cdecl InjectDumpForHangDebugging( |
| HANDLE process) { |
| return CreateRemoteThread( |
| process, nullptr, 0, crash_reporter::internal::DumpForHangDebuggingThread, |
| 0, 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" |