kicad/thirdparty/sentry-native/external/crashpad/client/crashpad_client_win.cc

1156 lines
43 KiB
C++
Raw Normal View History

// Copyright 2015 The Crashpad Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "client/crashpad_client.h"
#include <windows.h>
#include <werapi.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
#include <memory>
#include "base/atomicops.h"
#include "base/logging.h"
#include "base/scoped_generic.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "util/file/file_io.h"
#include "util/misc/capture_context.h"
#include "util/misc/from_pointer_cast.h"
#include "util/misc/random_string.h"
#include "util/win/address_types.h"
#include "util/win/command_line.h"
#include "util/win/context_wrappers.h"
#include "util/win/critical_section_with_debug_info.h"
2022-08-16 00:48:53 +00:00
#include "util/win/exception_codes.h"
#include "util/win/get_function.h"
#include "util/win/handle.h"
#include "util/win/initial_client_data.h"
#include "util/win/loader_lock.h"
#include "util/win/nt_internals.h"
#include "util/win/ntstatus_logging.h"
#include "util/win/process_info.h"
#include "util/win/registration_protocol_win.h"
#include "util/win/safe_terminate_process.h"
#include "util/win/scoped_process_suspend.h"
#include "util/win/termination_codes.h"
#include "util/win/xp_compat.h"
namespace crashpad {
namespace {
// This handle is never closed. This is used to signal to the server that a dump
// should be taken in the event of a crash.
HANDLE g_signal_exception = INVALID_HANDLE_VALUE;
// Where we store the exception information that the crash handler reads.
ExceptionInformation g_crash_exception_information;
2023-12-18 02:39:10 +00:00
CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr;
// Guards multiple simultaneous calls to DumpWithoutCrash() in the client.
2022-08-16 00:48:53 +00:00
base::Lock* g_non_crash_dump_lock = nullptr;
class CrashDumpAutoReleaser {
public:
~CrashDumpAutoReleaser() {
if (g_non_crash_dump_lock) {
delete g_non_crash_dump_lock;
g_non_crash_dump_lock = nullptr;
}
}
};
static CrashDumpAutoReleaser gAutoReleaser;
// Where we store a pointer to the context information when taking a non-crash
// dump.
ExceptionInformation g_non_crash_exception_information;
// Context for the out-of-process exception handler module and holds non-crash
// dump handles. Handles are never closed once created.
WerRegistration g_wer_registration = {WerRegistration::kWerRegistrationVersion,
INVALID_HANDLE_VALUE,
INVALID_HANDLE_VALUE,
false,
nullptr,
{0},
{0},
{0}};
enum class StartupState : int {
kNotReady = 0, // This must be value 0 because it is the initial value of a
// global AtomicWord.
kSucceeded = 1, // The CreateProcess() for the handler succeeded.
kFailed = 2, // The handler failed to start.
};
// This is a tri-state of type StartupState. It starts at 0 == kNotReady, and
// when the handler is known to have started successfully, or failed to start
// the value will be updated. The unhandled exception filter will not proceed
// until one of those two cases happens.
base::subtle::AtomicWord g_handler_startup_state;
// A CRITICAL_SECTION initialized with
// RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO to force it to be allocated with a
// valid .DebugInfo field. The address of this critical section is given to the
// handler. All critical sections with debug info are linked in a doubly-linked
// list, so this allows the handler to capture all of them.
CRITICAL_SECTION g_critical_section_with_debug_info;
void SetHandlerStartupState(StartupState state) {
DCHECK(state == StartupState::kSucceeded || state == StartupState::kFailed);
base::subtle::Release_Store(&g_handler_startup_state,
static_cast<base::subtle::AtomicWord>(state));
}
StartupState BlockUntilHandlerStartedOrFailed() {
// Wait until we know the handler has either succeeded or failed to start.
base::subtle::AtomicWord startup_state;
while (
(startup_state = base::subtle::Acquire_Load(&g_handler_startup_state)) ==
static_cast<int>(StartupState::kNotReady)) {
Sleep(1);
}
return static_cast<StartupState>(startup_state);
}
#if defined(ADDRESS_SANITIZER)
extern "C" LONG __asan_unhandled_exception_filter(EXCEPTION_POINTERS* info);
#endif
LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
#if defined(ADDRESS_SANITIZER)
// In ASan builds, delegate to the ASan exception filter.
LONG status = __asan_unhandled_exception_filter(exception_pointers);
if (status != EXCEPTION_CONTINUE_SEARCH)
return status;
#endif
if (BlockUntilHandlerStartedOrFailed() == StartupState::kFailed) {
// If we know for certain that the handler has failed to start, then abort
// here, rather than trying to signal to a handler that will never arrive,
// and then sleeping unnecessarily.
LOG(ERROR) << "crash server failed to launch, self-terminating";
SafeTerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump);
return EXCEPTION_CONTINUE_SEARCH;
}
if (first_chance_handler_ && first_chance_handler_(exception_pointers)) {
return EXCEPTION_CONTINUE_SEARCH;
}
// Otherwise, we know the handler startup has succeeded, and we can continue.
// Tracks whether a thread has already entered UnhandledExceptionHandler.
static base::subtle::AtomicWord have_crashed;
// This is a per-process handler. While this handler is being invoked, other
// threads are still executing as usual, so multiple threads could enter at
// the same time. Because we're in a crashing state, we shouldn't be doing
// anything that might cause allocations, call into kernel mode, etc. So, we
// don't want to take a critical section here to avoid simultaneous access to
// the global exception pointers in ExceptionInformation. Because the crash
// handler will record all threads, it's fine to simply have the second and
// subsequent entrants block here. They will soon be suspended by the crash
// handler, and then the entire process will be terminated below. This means
// that we won't save the exception pointers from the second and further
// crashes, but contention here is very unlikely, and we'll still have a stack
// that's blocked at this location.
if (base::subtle::Barrier_AtomicIncrement(&have_crashed, 1) > 1) {
SleepEx(INFINITE, false);
}
// Otherwise, we're the first thread, so record the exception pointer and
// signal the crash handler.
g_crash_exception_information.thread_id = GetCurrentThreadId();
g_crash_exception_information.exception_pointers =
FromPointerCast<WinVMAddress>(exception_pointers);
// Now signal the crash server, which will take a dump and then terminate us
// when it's complete.
SetEvent(g_signal_exception);
// Time to wait for the handler to create a dump.
constexpr DWORD kMillisecondsUntilTerminate = 60 * 1000;
// Sleep for a while to allow it to process us. Eventually, we terminate
// ourselves in case the crash server is gone, so that we don't leave zombies
// around. This would ideally never happen.
Sleep(kMillisecondsUntilTerminate);
LOG(ERROR) << "crash server did not respond, self-terminating";
SafeTerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump);
return EXCEPTION_CONTINUE_SEARCH;
}
2023-12-18 02:39:10 +00:00
LONG WINAPI HandleHeapCorruption(EXCEPTION_POINTERS* exception_pointers) {
if (exception_pointers->ExceptionRecord->ExceptionCode ==
STATUS_HEAP_CORRUPTION) {
return UnhandledExceptionHandler(exception_pointers);
}
return EXCEPTION_CONTINUE_SEARCH;
}
void HandleAbortSignal(int signum) {
DCHECK_EQ(signum, SIGABRT);
CONTEXT context;
CaptureContext(&context);
EXCEPTION_RECORD record = {};
record.ExceptionCode = STATUS_FATAL_APP_EXIT;
record.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
record.ExceptionAddress = ProgramCounterFromCONTEXT(&context);
EXCEPTION_POINTERS exception_pointers;
exception_pointers.ContextRecord = &context;
exception_pointers.ExceptionRecord = &record;
UnhandledExceptionHandler(&exception_pointers);
}
std::wstring FormatArgumentString(const std::string& name,
const std::wstring& value) {
return std::wstring(L"--") + base::UTF8ToWide(name) + L"=" + value;
}
struct ScopedProcThreadAttributeListTraits {
static PPROC_THREAD_ATTRIBUTE_LIST InvalidValue() { return nullptr; }
static void Free(PPROC_THREAD_ATTRIBUTE_LIST proc_thread_attribute_list) {
// This is able to use GET_FUNCTION_REQUIRED() instead of GET_FUNCTION()
// because it will only be called if InitializeProcThreadAttributeList() and
// UpdateProcThreadAttribute() are present.
static const auto delete_proc_thread_attribute_list =
GET_FUNCTION_REQUIRED(L"kernel32.dll", ::DeleteProcThreadAttributeList);
delete_proc_thread_attribute_list(proc_thread_attribute_list);
}
};
using ScopedProcThreadAttributeList =
base::ScopedGeneric<PPROC_THREAD_ATTRIBUTE_LIST,
ScopedProcThreadAttributeListTraits>;
bool IsInheritableHandle(HANDLE handle) {
if (!handle || handle == INVALID_HANDLE_VALUE)
return false;
// File handles (FILE_TYPE_DISK) and pipe handles (FILE_TYPE_PIPE) are known
// to be inheritable. Console handles (FILE_TYPE_CHAR) are not inheritable via
// PROC_THREAD_ATTRIBUTE_HANDLE_LIST. See
// https://crashpad.chromium.org/bug/77.
DWORD handle_type = GetFileType(handle);
return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE;
}
// Adds |handle| to |handle_list| if it appears valid, and is not already in
// |handle_list|.
//
// Invalid handles (including INVALID_HANDLE_VALUE and null handles) cannot be
// added to a PPROC_THREAD_ATTRIBUTE_LISTs PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
// If INVALID_HANDLE_VALUE appears, CreateProcess() will fail with
// ERROR_INVALID_PARAMETER. If a null handle appears, the child process will
// silently not inherit any handles.
//
// Use this function to add handles with uncertain validities.
void AddHandleToListIfValidAndInheritable(std::vector<HANDLE>* handle_list,
HANDLE handle) {
// There doesn't seem to be any documentation of this, but if there's a handle
// duplicated in this list, CreateProcess() fails with
// ERROR_INVALID_PARAMETER.
if (IsInheritableHandle(handle) &&
std::find(handle_list->begin(), handle_list->end(), handle) ==
handle_list->end()) {
handle_list->push_back(handle);
}
}
void AddUint32(std::vector<unsigned char>* data_vector, uint32_t data) {
data_vector->push_back(static_cast<unsigned char>(data & 0xff));
data_vector->push_back(static_cast<unsigned char>((data & 0xff00) >> 8));
data_vector->push_back(static_cast<unsigned char>((data & 0xff0000) >> 16));
data_vector->push_back(static_cast<unsigned char>((data & 0xff000000) >> 24));
}
void AddUint64(std::vector<unsigned char>* data_vector, uint64_t data) {
AddUint32(data_vector, static_cast<uint32_t>(data & 0xffffffffULL));
AddUint32(data_vector,
static_cast<uint32_t>((data & 0xffffffff00000000ULL) >> 32));
}
//! \brief Creates a randomized pipe name to listen for client registrations
//! on and returns its name.
//!
//! \param[out] pipe_name The pipe name that will be listened on.
//! \param[out] pipe_handle The first pipe instance corresponding for the pipe.
void CreatePipe(std::wstring* pipe_name, ScopedFileHANDLE* pipe_instance) {
int tries = 5;
std::string pipe_name_base = base::StringPrintf(
#if defined(WINDOWS_UWP)
"\\\\.\\pipe\\LOCAL\\crashpad_%lu_",
#else
"\\\\.\\pipe\\crashpad_%lu_",
#endif
GetCurrentProcessId());
do {
*pipe_name = base::UTF8ToWide(pipe_name_base + RandomString());
pipe_instance->reset(CreateNamedPipeInstance(*pipe_name, true));
// CreateNamedPipe() is documented as setting the error to
// ERROR_ACCESS_DENIED if FILE_FLAG_FIRST_PIPE_INSTANCE is specified and the
// pipe name is already in use. However it may set the error to other codes
// such as ERROR_PIPE_BUSY (if the pipe already exists and has reached its
// maximum instance count) or ERROR_INVALID_PARAMETER (if the pipe already
// exists and its attributes differ from those specified to
// CreateNamedPipe()). Some of these errors may be ambiguous: for example,
// ERROR_INVALID_PARAMETER may also occur if CreateNamedPipe() is called
// incorrectly even in the absence of an existing pipe by the same name.
// Rather than chasing down all of the possible errors that might indicate
// that a pipe name is already in use, retry up to a few times on any error.
} while (!pipe_instance->is_valid() && --tries);
PCHECK(pipe_instance->is_valid()) << "CreateNamedPipe";
}
struct BackgroundHandlerStartThreadData {
BackgroundHandlerStartThreadData(
const base::FilePath& handler,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
2023-12-18 02:39:10 +00:00
const std::string& http_proxy,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
const std::vector<base::FilePath>& attachments,
const std::wstring& ipc_pipe,
ScopedFileHANDLE ipc_pipe_handle)
: handler(handler),
database(database),
metrics_dir(metrics_dir),
url(url),
2023-12-18 02:39:10 +00:00
http_proxy(http_proxy),
annotations(annotations),
arguments(arguments),
attachments(attachments),
ipc_pipe(ipc_pipe),
ipc_pipe_handle(std::move(ipc_pipe_handle)) {}
base::FilePath handler;
base::FilePath database;
base::FilePath metrics_dir;
std::string url;
2023-12-18 02:39:10 +00:00
std::string http_proxy;
std::map<std::string, std::string> annotations;
std::vector<std::string> arguments;
std::vector<base::FilePath> attachments;
std::wstring ipc_pipe;
ScopedFileHANDLE ipc_pipe_handle;
};
// Ensures that SetHandlerStartupState() is called on scope exit. Assumes
// failure, and on success, SetSuccessful() should be called.
class ScopedCallSetHandlerStartupState {
public:
ScopedCallSetHandlerStartupState() : successful_(false) {}
ScopedCallSetHandlerStartupState(const ScopedCallSetHandlerStartupState&) =
delete;
ScopedCallSetHandlerStartupState& operator=(
const ScopedCallSetHandlerStartupState&) = delete;
~ScopedCallSetHandlerStartupState() {
SetHandlerStartupState(successful_ ? StartupState::kSucceeded
: StartupState::kFailed);
}
void SetSuccessful() { successful_ = true; }
private:
bool successful_;
};
bool StartHandlerProcess(
std::unique_ptr<BackgroundHandlerStartThreadData> data) {
CHECK(!IsThreadInLoaderLock());
ScopedCallSetHandlerStartupState scoped_startup_state_caller;
std::wstring command_line;
AppendCommandLineArgument(data->handler.value(), &command_line);
for (const std::string& argument : data->arguments) {
AppendCommandLineArgument(base::UTF8ToWide(argument), &command_line);
}
if (!data->database.value().empty()) {
AppendCommandLineArgument(
FormatArgumentString("database", data->database.value()),
&command_line);
}
if (!data->metrics_dir.value().empty()) {
AppendCommandLineArgument(
FormatArgumentString("metrics-dir", data->metrics_dir.value()),
&command_line);
}
if (!data->url.empty()) {
AppendCommandLineArgument(
FormatArgumentString("url", base::UTF8ToWide(data->url)),
&command_line);
}
2023-12-18 02:39:10 +00:00
if (!data->http_proxy.empty()) {
AppendCommandLineArgument(
FormatArgumentString("http-proxy", base::UTF8ToWide(data->http_proxy)),
&command_line);
}
for (const auto& kv : data->annotations) {
AppendCommandLineArgument(
FormatArgumentString("annotation",
base::UTF8ToWide(kv.first + '=' + kv.second)),
&command_line);
}
for (const base::FilePath& attachment : data->attachments) {
AppendCommandLineArgument(
2022-08-16 00:48:53 +00:00
FormatArgumentString("attachment", attachment.value()), &command_line);
}
ScopedKernelHANDLE this_process(
OpenProcess(kXPProcessAllAccess, true, GetCurrentProcessId()));
if (!this_process.is_valid()) {
PLOG(ERROR) << "OpenProcess";
return false;
}
InitialClientData initial_client_data(
g_signal_exception,
g_wer_registration.dump_without_crashing,
g_wer_registration.dump_completed,
data->ipc_pipe_handle.get(),
this_process.get(),
FromPointerCast<WinVMAddress>(&g_crash_exception_information),
FromPointerCast<WinVMAddress>(&g_non_crash_exception_information),
FromPointerCast<WinVMAddress>(&g_critical_section_with_debug_info));
AppendCommandLineArgument(
base::UTF8ToWide(std::string("--initial-client-data=") +
initial_client_data.StringRepresentation()),
&command_line);
BOOL rv;
DWORD creation_flags;
STARTUPINFOEX startup_info = {};
startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
startup_info.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startup_info.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
startup_info.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
std::vector<HANDLE> handle_list;
std::unique_ptr<uint8_t[]> proc_thread_attribute_list_storage;
ScopedProcThreadAttributeList proc_thread_attribute_list_owner;
static const auto initialize_proc_thread_attribute_list =
GET_FUNCTION(L"kernel32.dll", ::InitializeProcThreadAttributeList);
static const auto update_proc_thread_attribute =
initialize_proc_thread_attribute_list
? GET_FUNCTION(L"kernel32.dll", ::UpdateProcThreadAttribute)
: nullptr;
if (!initialize_proc_thread_attribute_list || !update_proc_thread_attribute) {
// The OS doesnt allow handle inheritance to be restricted, so the handler
// will inherit every inheritable handle.
creation_flags = 0;
startup_info.StartupInfo.cb = sizeof(startup_info.StartupInfo);
} else {
// Restrict handle inheritance to just those needed in the handler.
creation_flags = EXTENDED_STARTUPINFO_PRESENT;
startup_info.StartupInfo.cb = sizeof(startup_info);
SIZE_T size;
rv = initialize_proc_thread_attribute_list(nullptr, 1, 0, &size);
if (rv) {
LOG(ERROR) << "InitializeProcThreadAttributeList (size) succeeded, "
"expected failure";
return false;
} else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
PLOG(ERROR) << "InitializeProcThreadAttributeList (size)";
return false;
}
proc_thread_attribute_list_storage.reset(new uint8_t[size]);
startup_info.lpAttributeList =
reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(
proc_thread_attribute_list_storage.get());
rv = initialize_proc_thread_attribute_list(
startup_info.lpAttributeList, 1, 0, &size);
if (!rv) {
PLOG(ERROR) << "InitializeProcThreadAttributeList";
return false;
}
proc_thread_attribute_list_owner.reset(startup_info.lpAttributeList);
handle_list.reserve(8);
handle_list.push_back(g_signal_exception);
handle_list.push_back(g_wer_registration.dump_without_crashing);
handle_list.push_back(g_wer_registration.dump_completed);
handle_list.push_back(data->ipc_pipe_handle.get());
handle_list.push_back(this_process.get());
AddHandleToListIfValidAndInheritable(&handle_list,
startup_info.StartupInfo.hStdInput);
AddHandleToListIfValidAndInheritable(&handle_list,
startup_info.StartupInfo.hStdOutput);
AddHandleToListIfValidAndInheritable(&handle_list,
startup_info.StartupInfo.hStdError);
rv = update_proc_thread_attribute(
startup_info.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
&handle_list[0],
handle_list.size() * sizeof(handle_list[0]),
nullptr,
nullptr);
if (!rv) {
PLOG(ERROR) << "UpdateProcThreadAttribute";
return false;
}
}
// If the embedded crashpad handler is being started via an entry point in a
// DLL (the handler executable is rundll32.exe), then don't pass
// the application name to CreateProcess as this appears to generate an
// invalid command line where the first argument needed by rundll32 is not in
// the correct format as required in:
// https://support.microsoft.com/en-ca/help/164787/info-windows-rundll-and-rundll32-interface
const base::WStringPiece kRunDll32Exe(L"rundll32.exe");
bool is_embedded_in_dll = false;
if (data->handler.value().size() >= kRunDll32Exe.size() &&
_wcsicmp(data->handler.value()
.substr(data->handler.value().size() - kRunDll32Exe.size())
.c_str(),
kRunDll32Exe.data()) == 0) {
is_embedded_in_dll = true;
}
PROCESS_INFORMATION process_info;
rv = CreateProcess(
is_embedded_in_dll ? nullptr : data->handler.value().c_str(),
&command_line[0],
nullptr,
nullptr,
true,
creation_flags,
nullptr,
nullptr,
&startup_info.StartupInfo,
&process_info);
if (!rv) {
PLOG(ERROR) << "CreateProcess";
return false;
}
rv = CloseHandle(process_info.hThread);
PLOG_IF(WARNING, !rv) << "CloseHandle thread";
rv = CloseHandle(process_info.hProcess);
PLOG_IF(WARNING, !rv) << "CloseHandle process";
// It is important to close our side of the pipe here before confirming that
// we can communicate with the server. By doing so, the only remaining copy of
// the server side of the pipe belongs to the exception handler process we
// just spawned. Otherwise, the pipe will continue to exist indefinitely, so
// the connection loop will not detect that it will never be serviced.
data->ipc_pipe_handle.reset();
// Confirm that the server is waiting for connections before continuing.
ClientToServerMessage message = {};
message.type = ClientToServerMessage::kPing;
ServerToClientMessage response = {};
if (!SendToCrashHandlerServer(data->ipc_pipe, message, &response)) {
return false;
}
scoped_startup_state_caller.SetSuccessful();
return true;
}
DWORD WINAPI BackgroundHandlerStartThreadProc(void* data) {
std::unique_ptr<BackgroundHandlerStartThreadData> data_as_ptr(
reinterpret_cast<BackgroundHandlerStartThreadData*>(data));
return StartHandlerProcess(std::move(data_as_ptr)) ? 0 : 1;
}
void CommonInProcessInitialization() {
// We create this dummy CRITICAL_SECTION with the
// RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO flag set to have an entry point
// into the doubly-linked list of RTL_CRITICAL_SECTION_DEBUG objects. This
// allows us to walk the list at crash time to gather data for !locks. A
// debugger would instead inspect ntdll!RtlCriticalSectionList to get the head
// of the list. But that is not an exported symbol, so on an arbitrary client
// machine, we don't have a way of getting that pointer.
InitializeCriticalSectionWithDebugInfoIfPossible(
&g_critical_section_with_debug_info);
g_non_crash_dump_lock = new base::Lock();
}
void RegisterHandlers() {
SetUnhandledExceptionFilter(&UnhandledExceptionHandler);
2023-12-18 02:39:10 +00:00
// Windows swallows heap corruption failures but we can intercept them with
// a vectored exception handler.
#if defined(ADDRESS_SANITIZER)
// Let ASAN have first go.
bool go_first = false;
#else
bool go_first = true;
#endif
AddVectoredExceptionHandler(go_first, HandleHeapCorruption);
// The Windows CRT's signal.h lists:
// - SIGINT
// - SIGILL
// - SIGFPE
// - SIGSEGV
// - SIGTERM
// - SIGBREAK
// - SIGABRT
// SIGILL and SIGTERM are documented as not being generated. SIGBREAK and
// SIGINT are for Ctrl-Break and Ctrl-C, and aren't something for which
// capturing a dump is warranted. SIGFPE and SIGSEGV are captured as regular
// exceptions through the unhandled exception filter. This leaves SIGABRT. In
// the standard CRT, abort() is implemented as a synchronous call to the
// SIGABRT signal handler if installed, but after doing so, the unhandled
// exception filter is not triggered (it instead __fastfail()s). So, register
// to handle SIGABRT to catch abort() calls, as client code might use this and
// expect it to cause a crash dump. This will only work when the abort()
// that's called in client code is the same (or has the same behavior) as the
// one in use here.
void (*rv)(int) = signal(SIGABRT, HandleAbortSignal);
DCHECK_NE(rv, SIG_ERR);
}
} // namespace
CrashpadClient::CrashpadClient() : ipc_pipe_(), handler_start_thread_() {}
CrashpadClient::~CrashpadClient() {}
bool CrashpadClient::StartHandler(
const base::FilePath& handler,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
2023-12-18 02:39:10 +00:00
const std::string& http_proxy,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
bool restartable,
bool asynchronous_start,
const std::vector<base::FilePath>& attachments) {
DCHECK(ipc_pipe_.empty());
// Both the pipe and the signalling events have to be created on the main
// thread (not the spawning thread) so that they're valid after we return from
// this function.
ScopedFileHANDLE ipc_pipe_handle;
CreatePipe(&ipc_pipe_, &ipc_pipe_handle);
SECURITY_ATTRIBUTES security_attributes = {0};
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.bInheritHandle = true;
g_signal_exception =
CreateEvent(&security_attributes, false /* auto reset */, false, nullptr);
g_wer_registration.dump_without_crashing =
CreateEvent(&security_attributes, false /* auto reset */, false, nullptr);
g_wer_registration.dump_completed =
CreateEvent(&security_attributes, false /* auto reset */, false, nullptr);
CommonInProcessInitialization();
RegisterHandlers();
auto data = new BackgroundHandlerStartThreadData(handler,
database,
metrics_dir,
url,
2023-12-18 02:39:10 +00:00
http_proxy,
annotations,
arguments,
attachments,
ipc_pipe_,
std::move(ipc_pipe_handle));
if (asynchronous_start) {
// It is important that the current thread not be synchronized with the
// thread that is created here. StartHandler() needs to be callable inside a
// DllMain(). In that case, the background thread will not start until the
// current DllMain() completes, which would cause deadlock if it was waited
// upon.
handler_start_thread_.reset(CreateThread(nullptr,
0,
&BackgroundHandlerStartThreadProc,
reinterpret_cast<void*>(data),
0,
nullptr));
if (!handler_start_thread_.is_valid()) {
PLOG(ERROR) << "CreateThread";
SetHandlerStartupState(StartupState::kFailed);
return false;
}
// In asynchronous mode, we can't report on the overall success or failure
// of initialization at this point.
return true;
} else {
return StartHandlerProcess(
std::unique_ptr<BackgroundHandlerStartThreadData>(data));
}
}
bool CrashpadClient::SetHandlerIPCPipe(const std::wstring& ipc_pipe) {
DCHECK(ipc_pipe_.empty());
DCHECK(!ipc_pipe.empty());
ipc_pipe_ = ipc_pipe;
DCHECK(!ipc_pipe_.empty());
DCHECK_EQ(g_signal_exception, INVALID_HANDLE_VALUE);
DCHECK_EQ(g_wer_registration.dump_without_crashing, INVALID_HANDLE_VALUE);
DCHECK_EQ(g_wer_registration.dump_completed, INVALID_HANDLE_VALUE);
DCHECK(!g_critical_section_with_debug_info.DebugInfo);
DCHECK(!g_non_crash_dump_lock);
ClientToServerMessage message;
memset(&message, 0, sizeof(message));
message.type = ClientToServerMessage::kRegister;
message.registration.version = RegistrationRequest::kMessageVersion;
message.registration.client_process_id = GetCurrentProcessId();
message.registration.crash_exception_information =
FromPointerCast<WinVMAddress>(&g_crash_exception_information);
message.registration.non_crash_exception_information =
FromPointerCast<WinVMAddress>(&g_non_crash_exception_information);
CommonInProcessInitialization();
message.registration.critical_section_address =
FromPointerCast<WinVMAddress>(&g_critical_section_with_debug_info);
ServerToClientMessage response = {};
if (!SendToCrashHandlerServer(ipc_pipe_, message, &response)) {
return false;
}
SetHandlerStartupState(StartupState::kSucceeded);
RegisterHandlers();
// The server returns these already duplicated to be valid in this process.
g_signal_exception =
IntToHandle(response.registration.request_crash_dump_event);
g_wer_registration.dump_without_crashing =
IntToHandle(response.registration.request_non_crash_dump_event);
g_wer_registration.dump_completed =
IntToHandle(response.registration.non_crash_dump_completed_event);
return true;
}
std::wstring CrashpadClient::GetHandlerIPCPipe() const {
DCHECK(!ipc_pipe_.empty());
return ipc_pipe_;
}
bool CrashpadClient::WaitForHandlerStart(unsigned int timeout_ms) {
DCHECK(handler_start_thread_.is_valid());
DWORD result = WaitForSingleObject(handler_start_thread_.get(), timeout_ms);
if (result == WAIT_TIMEOUT) {
LOG(ERROR) << "WaitForSingleObject timed out";
return false;
} else if (result == WAIT_ABANDONED) {
LOG(ERROR) << "WaitForSingleObject abandoned";
return false;
} else if (result != WAIT_OBJECT_0) {
PLOG(ERROR) << "WaitForSingleObject";
return false;
}
DWORD exit_code;
if (!GetExitCodeThread(handler_start_thread_.get(), &exit_code)) {
PLOG(ERROR) << "GetExitCodeThread";
return false;
}
handler_start_thread_.reset();
return exit_code == 0;
}
bool CrashpadClient::RegisterWerModule(const std::wstring& path) {
if (g_wer_registration.dump_completed == INVALID_HANDLE_VALUE ||
g_wer_registration.dump_without_crashing == INVALID_HANDLE_VALUE) {
LOG(ERROR) << "not connected";
return false;
}
// We cannot point (*context).exception_pointers to our pointers yet as it
// might get used for other non-crash dumps.
g_wer_registration.crashpad_exception_info =
&g_non_crash_exception_information;
// we can point these as we are the only users.
g_wer_registration.pointers.ExceptionRecord = &g_wer_registration.exception;
g_wer_registration.pointers.ContextRecord = &g_wer_registration.context;
HRESULT res =
WerRegisterRuntimeExceptionModule(path.c_str(), &g_wer_registration);
return res == S_OK;
}
// static
void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) {
if (g_wer_registration.dump_without_crashing == INVALID_HANDLE_VALUE ||
g_wer_registration.dump_completed == INVALID_HANDLE_VALUE) {
LOG(ERROR) << "not connected";
return;
}
if (BlockUntilHandlerStartedOrFailed() == StartupState::kFailed) {
// If we know for certain that the handler has failed to start, then abort
// here, as we would otherwise wait indefinitely for the
// g_wer_registration.dump_completed event that would never be signalled.
LOG(ERROR) << "crash server failed to launch, no dump captured";
return;
}
// In the non-crashing case, we aren't concerned about avoiding calls into
// Win32 APIs, so just use regular locking here in case of multiple threads
// calling this function. If a crash occurs while we're in here, the worst
// that can happen is that the server captures a partial dump for this path
// because another threads crash processing finished and the process was
// terminated before this threads non-crash processing could be completed.
base::AutoLock lock(*g_non_crash_dump_lock);
// Create a fake EXCEPTION_POINTERS to give the handler something to work
// with.
EXCEPTION_POINTERS exception_pointers = {};
// This is logically const, but EXCEPTION_POINTERS does not declare it as
// const, so we have to cast that away from the argument.
exception_pointers.ContextRecord = const_cast<CONTEXT*>(&context);
// We include a fake exception and use a code of '0x517a7ed' (something like
// "simulated") so that it's relatively obvious in windbg that it's not
// actually an exception. Most values in
// https://msdn.microsoft.com/library/aa363082.aspx have some of the top
// nibble set, so we make sure to pick a value that doesn't, so as to be
// unlikely to conflict.
constexpr uint32_t kSimulatedExceptionCode = 0x517a7ed;
EXCEPTION_RECORD record = {};
record.ExceptionCode = kSimulatedExceptionCode;
record.ExceptionAddress = ProgramCounterFromCONTEXT(&context);
exception_pointers.ExceptionRecord = &record;
g_non_crash_exception_information.thread_id = GetCurrentThreadId();
g_non_crash_exception_information.exception_pointers =
FromPointerCast<WinVMAddress>(&exception_pointers);
g_wer_registration.in_dump_without_crashing = true;
bool set_event_result = !!SetEvent(g_wer_registration.dump_without_crashing);
PLOG_IF(ERROR, !set_event_result) << "SetEvent";
DWORD wfso_result =
WaitForSingleObject(g_wer_registration.dump_completed, INFINITE);
PLOG_IF(ERROR, wfso_result != WAIT_OBJECT_0) << "WaitForSingleObject";
g_wer_registration.in_dump_without_crashing = false;
}
// static
void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* exception_pointers) {
if (g_signal_exception == INVALID_HANDLE_VALUE) {
LOG(ERROR) << "not connected";
SafeTerminateProcess(GetCurrentProcess(),
kTerminationCodeNotConnectedToHandler);
return;
}
// We don't need to check for handler startup here, as
// UnhandledExceptionHandler() necessarily does that.
UnhandledExceptionHandler(exception_pointers);
}
// static
bool CrashpadClient::DumpAndCrashTargetProcess(HANDLE process,
HANDLE blame_thread,
DWORD exception_code) {
// Confirm we're on Vista or later.
const DWORD version = GetVersion();
const DWORD major_version = LOBYTE(LOWORD(version));
if (major_version < 6) {
LOG(ERROR) << "unavailable before Vista";
return false;
}
// Confirm that our bitness is the same as the process we're crashing.
ProcessInfo process_info;
if (!process_info.Initialize(process)) {
LOG(ERROR) << "ProcessInfo::Initialize";
return false;
}
#if defined(ARCH_CPU_64_BITS)
if (!process_info.Is64Bit()) {
LOG(ERROR) << "DumpAndCrashTargetProcess currently not supported x64->x86";
return false;
}
#endif // ARCH_CPU_64_BITS
ScopedProcessSuspend suspend(process);
// If no thread handle was provided, or the thread has already exited, we pass
// 0 to the handler, which indicates no fake exception record to be created.
DWORD thread_id = 0;
if (blame_thread) {
// Now that we've suspended the process, if our thread hasn't exited, we
// know we're relatively safe to pass the thread id through.
if (WaitForSingleObject(blame_thread, 0) == WAIT_TIMEOUT) {
static const auto get_thread_id =
GET_FUNCTION_REQUIRED(L"kernel32.dll", ::GetThreadId);
thread_id = get_thread_id(blame_thread);
}
}
constexpr size_t kInjectBufferSize = 4 * 1024;
WinVMAddress inject_memory =
FromPointerCast<WinVMAddress>(VirtualAllocEx(process,
nullptr,
kInjectBufferSize,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE));
if (!inject_memory) {
PLOG(ERROR) << "VirtualAllocEx";
return false;
}
// Because we're the same bitness as our target, we can rely kernel32 being
// loaded at the same address in our process as the target, and just look up
// its address here.
WinVMAddress raise_exception_address =
FromPointerCast<WinVMAddress>(&RaiseException);
WinVMAddress code_entry_point = 0;
std::vector<unsigned char> data_to_write;
if (process_info.Is64Bit()) {
// Data written is first, the data for the 4th argument (lpArguments) to
// RaiseException(). A two element array:
//
// DWORD64: thread_id
// DWORD64: exception_code
//
// Following that, code which sets the arguments to RaiseException() and
// then calls it:
//
// mov r9, <data_array_address>
// mov r8d, 2 ; nNumberOfArguments
// mov edx, 1 ; dwExceptionFlags = EXCEPTION_NONCONTINUABLE
// mov ecx, 0xcca11ed ; dwExceptionCode, interpreted specially by the
// ; handler.
// jmp <address_of_RaiseException>
//
// Note that the first three arguments to RaiseException() are DWORDs even
// on x64, so only the 4th argument (a pointer) is a full-width register.
//
// We also don't need to set up a stack or use call, since the only
// registers modified are volatile ones, and we can just jmp straight to
// RaiseException().
// The data array.
AddUint64(&data_to_write, thread_id);
AddUint64(&data_to_write, exception_code);
// The thread entry point.
code_entry_point = inject_memory + data_to_write.size();
// r9 = pointer to data.
data_to_write.push_back(0x49);
data_to_write.push_back(0xb9);
AddUint64(&data_to_write, inject_memory);
// r8d = 2 for nNumberOfArguments.
data_to_write.push_back(0x41);
data_to_write.push_back(0xb8);
AddUint32(&data_to_write, 2);
// edx = 1 for dwExceptionFlags.
data_to_write.push_back(0xba);
AddUint32(&data_to_write, 1);
// ecx = kTriggeredExceptionCode for dwExceptionCode.
data_to_write.push_back(0xb9);
2022-08-16 00:48:53 +00:00
AddUint32(&data_to_write, ExceptionCodes::kTriggeredExceptionCode);
// jmp to RaiseException() via rax.
data_to_write.push_back(0x48); // mov rax, imm.
data_to_write.push_back(0xb8);
AddUint64(&data_to_write, raise_exception_address);
data_to_write.push_back(0xff); // jmp rax.
data_to_write.push_back(0xe0);
} else {
// Data written is first, the data for the 4th argument (lpArguments) to
// RaiseException(). A two element array:
//
// DWORD: thread_id
// DWORD: exception_code
//
// Following that, code which pushes our arguments to RaiseException() and
// then calls it:
//
// push <data_array_address>
// push 2 ; nNumberOfArguments
// push 1 ; dwExceptionFlags = EXCEPTION_NONCONTINUABLE
// push 0xcca11ed ; dwExceptionCode, interpreted specially by the handler.
// call <address_of_RaiseException>
// ud2 ; Generate invalid opcode to make sure we still crash if we return
// ; for some reason.
//
// No need to clean up the stack, as RaiseException() is __stdcall.
// The data array.
AddUint32(&data_to_write, thread_id);
AddUint32(&data_to_write, exception_code);
// The thread entry point.
code_entry_point = inject_memory + data_to_write.size();
// Push data address.
data_to_write.push_back(0x68);
AddUint32(&data_to_write, static_cast<uint32_t>(inject_memory));
// Push 2 for nNumberOfArguments.
data_to_write.push_back(0x6a);
data_to_write.push_back(2);
// Push 1 for dwExceptionCode.
data_to_write.push_back(0x6a);
data_to_write.push_back(1);
// Push dwExceptionFlags.
data_to_write.push_back(0x68);
AddUint32(&data_to_write, kTriggeredExceptionCode);
// Relative call to RaiseException().
int64_t relative_address_to_raise_exception =
raise_exception_address - (inject_memory + data_to_write.size() + 5);
data_to_write.push_back(0xe8);
AddUint32(&data_to_write,
static_cast<uint32_t>(relative_address_to_raise_exception));
// ud2.
data_to_write.push_back(0x0f);
data_to_write.push_back(0x0b);
}
DCHECK_LT(data_to_write.size(), kInjectBufferSize);
SIZE_T bytes_written;
if (!WriteProcessMemory(process,
reinterpret_cast<void*>(inject_memory),
data_to_write.data(),
data_to_write.size(),
&bytes_written)) {
PLOG(ERROR) << "WriteProcessMemory";
return false;
}
if (bytes_written != data_to_write.size()) {
LOG(ERROR) << "WriteProcessMemory unexpected number of bytes";
return false;
}
if (!FlushInstructionCache(
process, reinterpret_cast<void*>(inject_memory), bytes_written)) {
PLOG(ERROR) << "FlushInstructionCache";
return false;
}
DWORD old_protect;
if (!VirtualProtectEx(process,
reinterpret_cast<void*>(inject_memory),
kInjectBufferSize,
PAGE_EXECUTE_READ,
&old_protect)) {
PLOG(ERROR) << "VirtualProtectEx";
return false;
}
// Cause an exception in the target process by creating a thread which calls
// RaiseException with our arguments above. Note that we cannot get away with
// using DebugBreakProcess() (nothing happens unless a debugger is attached)
// and we cannot get away with CreateRemoteThread() because it doesn't work if
// the target is hung waiting for the loader lock. We use NtCreateThreadEx()
// with the SKIP_THREAD_ATTACH flag, which skips various notifications,
// letting this cause an exception, even when the target is stuck in the
// loader lock.
HANDLE injected_thread;
// This is what DebugBreakProcess() uses.
constexpr size_t kStackSize = 0x4000;
NTSTATUS status = NtCreateThreadEx(&injected_thread,
STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL,
nullptr,
process,
reinterpret_cast<void*>(code_entry_point),
nullptr,
THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH,
0,
kStackSize,
0,
nullptr);
if (!NT_SUCCESS(status)) {
NTSTATUS_LOG(ERROR, status) << "NtCreateThreadEx";
return false;
}
// The injected thread raises an exception and ultimately results in process
// termination. The suspension must be made aware that the process may be
// terminating, otherwise itll log an extraneous error.
suspend.TolerateTermination();
bool result = true;
if (WaitForSingleObject(injected_thread, 60 * 1000) != WAIT_OBJECT_0) {
PLOG(ERROR) << "WaitForSingleObject";
result = false;
}
status = NtClose(injected_thread);
if (!NT_SUCCESS(status)) {
NTSTATUS_LOG(ERROR, status) << "NtClose";
result = false;
}
return result;
}
// static
void CrashpadClient::SetFirstChanceExceptionHandler(
2023-12-18 02:39:10 +00:00
FirstChanceHandler handler) {
first_chance_handler_ = handler;
}
} // namespace crashpad