blob: 3379d00d2d6aacff3c733c31b942348c292aa261 [file] [log] [blame]
// Copyright 2022 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.
// See:
// https://docs.microsoft.com/en-us/windows/win32/api/werapi/nf-werapi-werregisterruntimeexceptionmodule
#include "handler/win/wer/crashpad_wer.h"
#include "util/misc/address_types.h"
#include "util/win/registration_protocol_win_structs.h"
#include <Windows.h>
#include <werapi.h>
namespace crashpad::wer {
namespace {
using crashpad::WerRegistration;
// bIsFatal and dwReserved fields are not present in SDK < 19041.
struct WER_RUNTIME_EXCEPTION_INFORMATION_19041 {
DWORD dwSize;
HANDLE hProcess;
HANDLE hThread;
EXCEPTION_RECORD exceptionRecord;
CONTEXT context;
PCWSTR pwszReportId;
BOOL bIsFatal;
DWORD dwReserved;
};
// We have our own version of this to avoid pulling in //base.
class ScopedHandle {
public:
ScopedHandle() : handle_(INVALID_HANDLE_VALUE) {}
ScopedHandle(HANDLE from) : handle_(from) {}
~ScopedHandle() {
if (IsValid())
CloseHandle(handle_);
}
bool IsValid() {
if (handle_ == INVALID_HANDLE_VALUE || handle_ == 0)
return false;
return true;
}
HANDLE Get() { return handle_; }
private:
HANDLE handle_;
};
ScopedHandle DuplicateFromTarget(HANDLE target_process, HANDLE target_handle) {
HANDLE hTmp;
if (!DuplicateHandle(target_process,
target_handle,
GetCurrentProcess(),
&hTmp,
SYNCHRONIZE | EVENT_MODIFY_STATE,
false,
0)) {
return ScopedHandle();
}
return ScopedHandle(hTmp);
}
bool ProcessException(const DWORD* handled_exceptions,
size_t num_handled_exceptions,
const PVOID pContext,
const PWER_RUNTIME_EXCEPTION_INFORMATION e_info) {
// Need to have been given a context.
if (!pContext)
return false;
// Older OSes might provide a smaller structure than SDK 19041 defines.
if (e_info->dwSize <=
offsetof(WER_RUNTIME_EXCEPTION_INFORMATION_19041, bIsFatal)) {
return false;
}
// If building with SDK < 19041 then the bIsFatal field isn't defined, so
// use our internal definition here.
if (!reinterpret_cast<const WER_RUNTIME_EXCEPTION_INFORMATION_19041*>(e_info)
->bIsFatal) {
return false;
}
// Only deal with exceptions that crashpad would not have handled.
bool found = false;
for (size_t i = 0; i < num_handled_exceptions; i++) {
if (handled_exceptions[i] == e_info->exceptionRecord.ExceptionCode) {
found = true;
break;
}
}
if (!found)
return false;
// Grab out the handles to the crashpad server.
WerRegistration target_registration = {};
if (!ReadProcessMemory(e_info->hProcess,
pContext,
&target_registration,
sizeof(target_registration),
nullptr)) {
return false;
}
// Validate version of registration struct.
if (target_registration.version != WerRegistration::kWerRegistrationVersion)
return false;
// Dupe handles for triggering the dump.
auto dump_start = DuplicateFromTarget(
e_info->hProcess, target_registration.dump_without_crashing);
auto dump_done =
DuplicateFromTarget(e_info->hProcess, target_registration.dump_completed);
if (!dump_start.IsValid() || !dump_done.IsValid())
return false;
// It's possible that the target crashed while inside a DumpWithoutCrashing
// call - either in the DumpWithoutCrashing call or in another thread - if so
// we cannot trigger the dump until the first call's crash is dealth with as
// the crashpad handler might be reading from structures we will write to. We
// give the event a short while to be triggered and give up if it is not
// signalled.
if (target_registration.in_dump_without_crashing) {
constexpr DWORD kOneSecondInMs = 1000;
DWORD wait_result = WaitForSingleObject(dump_done.Get(), kOneSecondInMs);
if (wait_result != WAIT_OBJECT_0)
return false;
}
// Set up the crashpad handler's info structure.
crashpad::ExceptionInformation target_non_crash_exception_info{};
target_non_crash_exception_info.thread_id = GetThreadId(e_info->hThread);
target_non_crash_exception_info.exception_pointers =
static_cast<crashpad::VMAddress>(reinterpret_cast<uintptr_t>(pContext)) +
offsetof(WerRegistration, pointers);
if (!WriteProcessMemory(e_info->hProcess,
target_registration.crashpad_exception_info,
&target_non_crash_exception_info,
sizeof(target_non_crash_exception_info),
nullptr)) {
return false;
}
// Write Exception & Context to the areas reserved by the client.
if (!WriteProcessMemory(
e_info->hProcess,
reinterpret_cast<PVOID>(target_registration.pointers.ExceptionRecord),
&e_info->exceptionRecord,
sizeof(e_info->exceptionRecord),
nullptr)) {
return false;
}
if (!WriteProcessMemory(
e_info->hProcess,
reinterpret_cast<PVOID>(target_registration.pointers.ContextRecord),
&e_info->context,
sizeof(e_info->context),
nullptr)) {
return false;
}
// Request dump.
if (!SetEvent(dump_start.Get()))
return false;
constexpr DWORD kTenSecondsInMs = 10 * 1000;
DWORD result = WaitForSingleObject(dump_done.Get(), kTenSecondsInMs);
if (result == WAIT_OBJECT_0) {
// The handler signalled that it has written a dump, so we can terminate the
// target - this takes over from WER, sorry WER.
TerminateProcess(e_info->hProcess, e_info->exceptionRecord.ExceptionCode);
return true;
}
// Maybe some other handler can have a go.
return false;
}
} // namespace
bool ExceptionEvent(
const DWORD* handled_exceptions,
size_t num_handled_exceptions,
const PVOID pContext,
const PWER_RUNTIME_EXCEPTION_INFORMATION pExceptionInformation) {
return ProcessException(handled_exceptions,
num_handled_exceptions,
pContext,
pExceptionInformation);
}
} // namespace crashpad::wer