v8/src/unwinding-info-win64.cc
Paolo Severini a4b01d74d6 Enable Crashpad integration of V8 x64 stack unwinding
This CL makes sure that Crashpad on Chromium will behave exactly like it did
before we added code to emit unwinding info, even when FLAG_win64_unwinding_info
is not set.
In particular, before merging the Chromium CL:
https://chromium-review.googlesource.com/c/chromium/src/+/1474703/
that modifies Crashpad to use the new function SetUnhandledExceptionCallback(),
we need to make sure that Isolate::Init() will call
win64_unwindinfo::RegisterNonABICompliantCodeRange() even when
FLAG_win64_unwinding_info is false.
In that case RegisterNonABICompliantCodeRange will only register unwind info to
invoke the Crashpad exception handler for unhandled exceptions.
Note that RegisterNonABICompliantCodeRange will be a no-op with the current
Crashpad code that never calls SetUnhandledExceptionCallback().

Bug: v8:8661
Change-Id: I63d845e9dca79ddd5978dfb43b626ace50078e80
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1554119
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#60757}
2019-04-10 22:18:48 +00:00

296 lines
9.7 KiB
C++

// Copyright 2019 the V8 project 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 "src/unwinding-info-win64.h"
#if defined(V8_OS_WIN_X64)
#include "src/allocation.h"
#include "src/macro-assembler.h"
#include "src/x64/assembler-x64.h"
namespace v8 {
namespace internal {
namespace win64_unwindinfo {
bool CanEmitUnwindInfoForBuiltins() { return FLAG_win64_unwinding_info; }
bool CanRegisterUnwindInfoForNonABICompliantCodeRange() {
return !FLAG_jitless;
}
bool RegisterUnwindInfoForExceptionHandlingOnly() {
DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());
return !IsWindows8OrGreater() || !FLAG_win64_unwinding_info;
}
#pragma pack(push, 1)
/*
* From Windows SDK ehdata.h, which does not compile with Clang.
* See https://msdn.microsoft.com/en-us/library/ddssxxy8.aspx.
*/
typedef union _UNWIND_CODE {
struct {
unsigned char CodeOffset;
unsigned char UnwindOp : 4;
unsigned char OpInfo : 4;
};
uint16_t FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
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;
} UNWIND_INFO, *PUNWIND_INFO;
struct V8UnwindData {
UNWIND_INFO unwind_info;
UNWIND_CODE unwind_codes[2];
V8UnwindData() {
static constexpr int kOpPushNonvol = 0;
static constexpr int kOpSetFPReg = 3;
unwind_info.Version = 1;
unwind_info.Flags = UNW_FLAG_EHANDLER;
unwind_info.SizeOfProlog = kRbpPrefixLength;
unwind_info.CountOfCodes = kRbpPrefixCodes;
unwind_info.FrameRegister = rbp.code();
unwind_info.FrameOffset = 0;
unwind_codes[0].CodeOffset = kRbpPrefixLength; // movq rbp, rsp
unwind_codes[0].UnwindOp = kOpSetFPReg;
unwind_codes[0].OpInfo = 0;
unwind_codes[1].CodeOffset = kPushRbpInstructionLength; // push rbp
unwind_codes[1].UnwindOp = kOpPushNonvol;
unwind_codes[1].OpInfo = rbp.code();
}
};
struct ExceptionHandlerUnwindData {
UNWIND_INFO unwind_info;
ExceptionHandlerUnwindData() {
unwind_info.Version = 1;
unwind_info.Flags = UNW_FLAG_EHANDLER;
unwind_info.SizeOfProlog = 0;
unwind_info.CountOfCodes = 0;
unwind_info.FrameRegister = 0;
unwind_info.FrameOffset = 0;
}
};
#pragma pack(pop)
v8::UnhandledExceptionCallback unhandled_exception_callback_g = nullptr;
void SetUnhandledExceptionCallback(
v8::UnhandledExceptionCallback unhandled_exception_callback) {
unhandled_exception_callback_g = unhandled_exception_callback;
}
// This function is registered as exception handler for V8-generated code as
// part of the registration of unwinding info. It is referenced by
// RegisterNonABICompliantCodeRange(), below, and by the unwinding info for
// builtins declared in the embedded blob.
extern "C" int CRASH_HANDLER_FUNCTION_NAME(
PEXCEPTION_RECORD ExceptionRecord, ULONG64 EstablisherFrame,
PCONTEXT ContextRecord, PDISPATCHER_CONTEXT DispatcherContext) {
if (unhandled_exception_callback_g != nullptr) {
EXCEPTION_POINTERS info = {ExceptionRecord, ContextRecord};
return unhandled_exception_callback_g(&info);
}
return EXCEPTION_CONTINUE_SEARCH;
}
static constexpr int kMaxExceptionThunkSize = 12;
struct CodeRangeUnwindingRecord {
RUNTIME_FUNCTION runtime_function;
V8UnwindData unwind_info;
uint32_t exception_handler;
uint8_t exception_thunk[kMaxExceptionThunkSize];
void* dynamic_table;
};
struct ExceptionHandlerRecord {
RUNTIME_FUNCTION runtime_function;
ExceptionHandlerUnwindData unwind_info;
uint32_t exception_handler;
uint8_t exception_thunk[kMaxExceptionThunkSize];
};
static decltype(
&::RtlAddGrowableFunctionTable) add_growable_function_table_func = nullptr;
static decltype(
&::RtlDeleteGrowableFunctionTable) delete_growable_function_table_func =
nullptr;
namespace {
void LoadNtdllUnwindingFunctions() {
static bool loaded = false;
if (loaded) {
return;
}
loaded = true;
// Load functions from the ntdll.dll module.
HMODULE ntdll_module =
LoadLibraryEx(L"ntdll.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
DCHECK_NOT_NULL(ntdll_module);
// This fails on Windows 7.
add_growable_function_table_func =
reinterpret_cast<decltype(&::RtlAddGrowableFunctionTable)>(
::GetProcAddress(ntdll_module, "RtlAddGrowableFunctionTable"));
DCHECK_IMPLIES(IsWindows8OrGreater(), add_growable_function_table_func);
delete_growable_function_table_func =
reinterpret_cast<decltype(&::RtlDeleteGrowableFunctionTable)>(
::GetProcAddress(ntdll_module, "RtlDeleteGrowableFunctionTable"));
DCHECK_IMPLIES(IsWindows8OrGreater(), delete_growable_function_table_func);
}
bool AddGrowableFunctionTable(PVOID* DynamicTable,
PRUNTIME_FUNCTION FunctionTable, DWORD EntryCount,
DWORD MaximumEntryCount, ULONG_PTR RangeBase,
ULONG_PTR RangeEnd) {
DCHECK(::IsWindows8OrGreater());
LoadNtdllUnwindingFunctions();
DCHECK_NOT_NULL(add_growable_function_table_func);
*DynamicTable = nullptr;
DWORD status =
add_growable_function_table_func(DynamicTable, FunctionTable, EntryCount,
MaximumEntryCount, RangeBase, RangeEnd);
DCHECK((status == 0 && *DynamicTable != nullptr) ||
status == 0xC000009A); // STATUS_INSUFFICIENT_RESOURCES
return (status == 0);
}
void DeleteGrowableFunctionTable(PVOID dynamic_table) {
DCHECK(::IsWindows8OrGreater());
LoadNtdllUnwindingFunctions();
DCHECK_NOT_NULL(delete_growable_function_table_func);
delete_growable_function_table_func(dynamic_table);
}
} // namespace
std::vector<uint8_t> GetUnwindInfoForBuiltinFunctions() {
V8UnwindData xdata;
return std::vector<uint8_t>(
reinterpret_cast<uint8_t*>(&xdata),
reinterpret_cast<uint8_t*>(&xdata) + sizeof(xdata));
}
template <typename Record>
void InitUnwindingRecord(Record* record, size_t code_size_in_bytes) {
// We assume that the first page of the code range is executable and
// committed and reserved to contain PDATA/XDATA.
// All addresses are 32bit relative offsets to start.
record->runtime_function.BeginAddress = 0;
record->runtime_function.EndAddress = static_cast<DWORD>(code_size_in_bytes);
record->runtime_function.UnwindData = offsetof(Record, unwind_info);
record->exception_handler = offsetof(Record, exception_thunk);
// Hardcoded thunk.
MacroAssembler masm(AssemblerOptions{}, NewAssemblerBuffer(64));
masm.movq(rax, reinterpret_cast<uint64_t>(&CRASH_HANDLER_FUNCTION_NAME));
masm.jmp(rax);
DCHECK_GE(masm.buffer_size(), sizeof(record->exception_thunk));
memcpy(&record->exception_thunk[0], masm.buffer_start(), masm.buffer_size());
}
void RegisterNonABICompliantCodeRange(void* start, size_t size_in_bytes) {
DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());
// When the --win64-unwinding-info flag is set, we call
// RtlAddGrowableFunctionTable to register unwinding info for the whole code
// range of an isolate or WASM module. This enables the Windows OS stack
// unwinder to work correctly with V8-generated code, enabling stack walking
// in Windows debuggers and performance tools. However, the
// RtlAddGrowableFunctionTable API is only supported on Windows 8 and above.
//
// On Windows 7, or when --win64-unwinding-info is not set, we may still need
// to call RtlAddFunctionTable to register a custom exception handler passed
// by the embedder (like Crashpad).
if (RegisterUnwindInfoForExceptionHandlingOnly()) {
if (unhandled_exception_callback_g) {
ExceptionHandlerRecord* record = new (start) ExceptionHandlerRecord();
InitUnwindingRecord(record, size_in_bytes);
CHECK(::RtlAddFunctionTable(&record->runtime_function, 1,
reinterpret_cast<DWORD64>(start)));
// Protect reserved page against modifications.
DWORD old_protect;
CHECK(VirtualProtect(start, sizeof(CodeRangeUnwindingRecord),
PAGE_EXECUTE_READ, &old_protect));
}
} else {
CodeRangeUnwindingRecord* record = new (start) CodeRangeUnwindingRecord();
InitUnwindingRecord(record, size_in_bytes);
CHECK(AddGrowableFunctionTable(
&record->dynamic_table, &record->runtime_function, 1, 1,
reinterpret_cast<DWORD64>(start),
reinterpret_cast<DWORD64>(reinterpret_cast<uint8_t*>(start) +
size_in_bytes)));
// Protect reserved page against modifications.
DWORD old_protect;
CHECK(VirtualProtect(start, sizeof(CodeRangeUnwindingRecord),
PAGE_EXECUTE_READ, &old_protect));
}
}
void UnregisterNonABICompliantCodeRange(void* start) {
DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());
if (RegisterUnwindInfoForExceptionHandlingOnly()) {
if (unhandled_exception_callback_g) {
ExceptionHandlerRecord* record =
reinterpret_cast<ExceptionHandlerRecord*>(start);
CHECK(::RtlDeleteFunctionTable(&record->runtime_function));
}
} else {
CodeRangeUnwindingRecord* record =
reinterpret_cast<CodeRangeUnwindingRecord*>(start);
if (record->dynamic_table) {
DeleteGrowableFunctionTable(record->dynamic_table);
}
}
}
void XdataEncoder::onPushRbp() {
current_push_rbp_offset_ = assembler_.pc_offset() - kPushRbpInstructionLength;
}
void XdataEncoder::onMovRbpRsp() {
if (current_push_rbp_offset_ >= 0 &&
current_push_rbp_offset_ == assembler_.pc_offset() - kRbpPrefixLength) {
fp_offsets_.push_back(current_push_rbp_offset_);
}
}
} // namespace win64_unwindinfo
} // namespace internal
} // namespace v8
#endif // defined(V8_OS_WIN_X64)