mirror of
https://github.com/dart-lang/sdk
synced 2024-09-18 20:11:19 +00:00
[vm] Unwind records for Windows ARM64.
TEST=ffi_induce_a_crash_test Bug: https://github.com/dart-lang/sdk/issues/51726 Change-Id: I7df3b56a150434d4c7b0cfbadda4fd9d57606eef Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/336203 Reviewed-by: Alexander Aprelev <aam@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
parent
faf503cbc4
commit
fc85488000
|
@ -241,7 +241,8 @@ class LoadedElf {
|
|||
const dart::elf::Symbol* dynamic_symbol_table_ = nullptr;
|
||||
uword dynamic_symbol_count_ = 0;
|
||||
|
||||
#if defined(DART_HOST_OS_WINDOWS) && defined(HOST_ARCH_X64)
|
||||
#if defined(DART_HOST_OS_WINDOWS) && \
|
||||
(defined(HOST_ARCH_X64) || defined(HOST_ARCH_ARM64))
|
||||
// Dynamic table for looking up unwinding exceptions info.
|
||||
// Initialized by LoadSegments as we load executable segment.
|
||||
MallocGrowableArray<void*> dynamic_runtime_function_tables_;
|
||||
|
@ -293,7 +294,8 @@ bool LoadedElf::Load() {
|
|||
}
|
||||
|
||||
LoadedElf::~LoadedElf() {
|
||||
#if defined(DART_HOST_OS_WINDOWS) && defined(HOST_ARCH_X64)
|
||||
#if defined(DART_HOST_OS_WINDOWS) && \
|
||||
(defined(HOST_ARCH_X64) || defined(HOST_ARCH_ARM64))
|
||||
for (intptr_t i = 0; i < dynamic_runtime_function_tables_.length(); i++) {
|
||||
UnwindingRecordsPlatform::UnregisterDynamicTable(
|
||||
dynamic_runtime_function_tables_[i]);
|
||||
|
@ -463,7 +465,8 @@ bool LoadedElf::LoadSegments() {
|
|||
CHECK_ERROR(memory != nullptr, "Could not map segment.");
|
||||
CHECK_ERROR(memory->address() == memory_start,
|
||||
"Mapping not at requested address.");
|
||||
#if defined(DART_HOST_OS_WINDOWS) && defined(HOST_ARCH_X64)
|
||||
#if defined(DART_HOST_OS_WINDOWS) && \
|
||||
(defined(HOST_ARCH_X64) || defined(HOST_ARCH_ARM64))
|
||||
// For executable pages register unwinding information that should be
|
||||
// present on the page.
|
||||
if (map_type == File::kReadExecute) {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
#include "platform/unwinding_records.h"
|
||||
#include "platform/globals.h"
|
||||
|
||||
#if !defined(DART_HOST_OS_WINDOWS) || !defined(TARGET_ARCH_X64)
|
||||
#if !defined(DART_HOST_OS_WINDOWS) || \
|
||||
(!defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_ARM64))
|
||||
|
||||
namespace dart {
|
||||
|
||||
|
|
|
@ -87,6 +87,127 @@ struct CodeRangeUnwindingRecord {
|
|||
|
||||
#pragma pack(pop)
|
||||
|
||||
#elif defined(DART_HOST_OS_WINDOWS) && defined(TARGET_ARCH_ARM64)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// ARM64 unwind codes are defined in below doc.
|
||||
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
|
||||
enum UnwindOp8Bit {
|
||||
OpNop = 0xE3,
|
||||
OpAllocS = 0x00,
|
||||
OpSaveFpLr = 0x40,
|
||||
OpSaveFpLrX = 0x80,
|
||||
OpSetFp = 0xE1,
|
||||
OpAddFp = 0xE2,
|
||||
OpEnd = 0xE4,
|
||||
};
|
||||
|
||||
typedef uint32_t UNWIND_CODE;
|
||||
|
||||
constexpr UNWIND_CODE Combine8BitUnwindCodes(uint8_t code0 = OpNop,
|
||||
uint8_t code1 = OpNop,
|
||||
uint8_t code2 = OpNop,
|
||||
uint8_t code3 = OpNop) {
|
||||
return static_cast<uint32_t>(code0) | (static_cast<uint32_t>(code1) << 8) |
|
||||
(static_cast<uint32_t>(code2) << 16) |
|
||||
(static_cast<uint32_t>(code3) << 24);
|
||||
}
|
||||
|
||||
// UNWIND_INFO defines the static part (first 32-bit) of the .xdata record in
|
||||
// below doc.
|
||||
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#xdata-records
|
||||
struct UNWIND_INFO {
|
||||
uint32_t FunctionLength : 18;
|
||||
uint32_t Version : 2;
|
||||
uint32_t X : 1;
|
||||
uint32_t E : 1;
|
||||
uint32_t EpilogCount : 5;
|
||||
uint32_t CodeWords : 5;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base on below doc, unwind record has 18 bits (unsigned) to encode function
|
||||
* length, besides 2 LSB which are always 0.
|
||||
* https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#xdata-records
|
||||
*/
|
||||
static const int kMaxFunctionLength = ((1 << 18) - 1) << 2;
|
||||
|
||||
static constexpr int kDefaultNumberOfUnwindCodeWords = 1;
|
||||
static constexpr int kMaxExceptionThunkSize = 16;
|
||||
static constexpr int kFunctionLengthShiftSize = 2;
|
||||
static constexpr int kFunctionLengthMask = (1 << kFunctionLengthShiftSize) - 1;
|
||||
|
||||
// Generate an unwind code for "stp fp, lr, [sp, #pre_index_offset]!".
|
||||
inline uint8_t MakeOpSaveFpLrX(int pre_index_offset) {
|
||||
// See unwind code save_fplr_x in
|
||||
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
|
||||
ASSERT(pre_index_offset <= -8);
|
||||
ASSERT(pre_index_offset >= -512);
|
||||
constexpr int kShiftSize = 3;
|
||||
constexpr int kShiftMask = (1 << kShiftSize) - 1;
|
||||
ASSERT((pre_index_offset & kShiftMask) == 0);
|
||||
USE(kShiftMask);
|
||||
// Solve for Z where -(Z+1)*8 = pre_index_offset.
|
||||
int encoded_value = (-pre_index_offset >> kShiftSize) - 1;
|
||||
return OpSaveFpLrX | encoded_value;
|
||||
}
|
||||
|
||||
template <int kNumberOfUnwindCodeWords = kDefaultNumberOfUnwindCodeWords>
|
||||
struct UnwindData {
|
||||
UNWIND_INFO unwind_info;
|
||||
UNWIND_CODE unwind_codes[kNumberOfUnwindCodeWords];
|
||||
|
||||
UnwindData() {
|
||||
memset(&unwind_info, 0, sizeof(UNWIND_INFO));
|
||||
unwind_info.X = 0; // no exception handler
|
||||
unwind_info.CodeWords = kNumberOfUnwindCodeWords;
|
||||
|
||||
// Generate unwind codes for the following prolog:
|
||||
//
|
||||
// stp fp, lr, [sp, #-kCallerSPOffset]!
|
||||
// mov fp, sp
|
||||
//
|
||||
// This is a very rough approximation of the actual function prologs used in
|
||||
// V8. In particular, we often push other data before the (fp, lr) pair,
|
||||
// meaning the stack pointer computed for the caller frame is wrong. That
|
||||
// error is acceptable when the unwinding info for the caller frame also
|
||||
// depends on fp rather than sp, as is the case for V8 builtins and runtime-
|
||||
// generated code.
|
||||
static_assert(kNumberOfUnwindCodeWords >= 1);
|
||||
uword kCallerSPOffset = -16;
|
||||
unwind_codes[0] = Combine8BitUnwindCodes(
|
||||
OpSetFp, MakeOpSaveFpLrX(kCallerSPOffset), OpEnd);
|
||||
|
||||
// Fill the rest with nops.
|
||||
for (int i = 1; i < kNumberOfUnwindCodeWords; ++i) {
|
||||
unwind_codes[i] = Combine8BitUnwindCodes();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const uint32_t kDefaultRuntimeFunctionCount = 1;
|
||||
|
||||
struct CodeRangeUnwindingRecord {
|
||||
void* dynamic_table;
|
||||
uint32_t runtime_function_count;
|
||||
UnwindData<> unwind_info;
|
||||
uint32_t exception_handler;
|
||||
|
||||
// For Windows ARM64 unwinding, register 2 unwind_info for each code range,
|
||||
// unwind_info for all full size ranges (1MB - 4 bytes) and unwind_info1 for
|
||||
// the remaining non full size range. There is at most 1 range which is less
|
||||
// than full size.
|
||||
UnwindData<> unwind_info1;
|
||||
|
||||
// More RUNTIME_FUNCTION structs could follow below array because the number
|
||||
// of RUNTIME_FUNCTION needed to cover given code range is computed at
|
||||
// runtime.
|
||||
RUNTIME_FUNCTION runtime_function[kDefaultRuntimeFunctionCount];
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif // defined(DART_HOST_OS_WINDOWS) && defined(TARGET_ARCH_X64)
|
||||
|
||||
} // namespace dart
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
#include "platform/assert.h"
|
||||
#include "platform/globals.h"
|
||||
|
||||
#if defined(DART_HOST_OS_WINDOWS) && defined(TARGET_ARCH_X64)
|
||||
#if defined(DART_HOST_OS_WINDOWS) && \
|
||||
(defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64))
|
||||
|
||||
namespace dart {
|
||||
|
||||
|
@ -17,7 +18,12 @@ static decltype(&::RtlAddGrowableFunctionTable)
|
|||
static decltype(&::RtlDeleteGrowableFunctionTable)
|
||||
delete_growable_function_table_func_ = nullptr;
|
||||
|
||||
#if defined(TARGET_ARCH_X64)
|
||||
const intptr_t kReservedUnwindingRecordsSizeBytes = 64;
|
||||
#else
|
||||
const intptr_t kReservedUnwindingRecordsSizeBytes = 4 * KB;
|
||||
#endif
|
||||
|
||||
intptr_t UnwindingRecordsPlatform::SizeInBytes() {
|
||||
return kReservedUnwindingRecordsSizeBytes;
|
||||
}
|
||||
|
@ -64,13 +70,14 @@ void UnwindingRecordsPlatform::RegisterExecutableMemory(
|
|||
reinterpret_cast<CodeRangeUnwindingRecord*>(record_ptr);
|
||||
uword start_num = reinterpret_cast<intptr_t>(start);
|
||||
uword end_num = start_num + size;
|
||||
if (func(pp_dynamic_table,
|
||||
DWORD status = func(pp_dynamic_table,
|
||||
/*FunctionTable=*/record->runtime_function,
|
||||
/*EntryCount=*/record->runtime_function_count,
|
||||
/*MaximumEntryCount=*/record->runtime_function_count,
|
||||
/*RangeBase=*/start_num,
|
||||
/*RangeEnd=*/end_num) != 0) {
|
||||
FATAL("Failed to add growable function table: %d\n", GetLastError());
|
||||
/*RangeEnd=*/end_num);
|
||||
if (status != 0) {
|
||||
FATAL("Failed to add growable function table: 0x%x\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1411,7 +1411,8 @@ void Elf::FinalizeEhFrame() {
|
|||
// No text section added means no .eh_frame.
|
||||
if (text_section == nullptr) return;
|
||||
|
||||
#if defined(DART_TARGET_OS_WINDOWS) && defined(TARGET_ARCH_X64)
|
||||
#if defined(DART_TARGET_OS_WINDOWS) && \
|
||||
(defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64))
|
||||
// Append Windows unwinding instructions to the end of .text section.
|
||||
{
|
||||
auto* const unwinding_instructions_frame = new (zone_) TextSection(type_);
|
||||
|
@ -1473,7 +1474,8 @@ void Elf::FinalizeEhFrame() {
|
|||
|
||||
// Emit an FDE covering each .text section.
|
||||
for (const auto& portion : text_section->portions()) {
|
||||
#if defined(DART_TARGET_OS_WINDOWS) && defined(TARGET_ARCH_X64)
|
||||
#if defined(DART_TARGET_OS_WINDOWS) && \
|
||||
(defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64))
|
||||
if (portion.label == 0) {
|
||||
// Unwinding instructions sections doesn't have label, doesn't dwarf
|
||||
continue;
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
#include "vm/unwinding_records.h"
|
||||
#include "vm/globals.h"
|
||||
|
||||
#if !defined(DART_HOST_OS_WINDOWS) || !defined(TARGET_ARCH_X64)
|
||||
#if !defined(DART_HOST_OS_WINDOWS) || \
|
||||
(!defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_ARM64))
|
||||
|
||||
namespace dart {
|
||||
|
||||
|
|
|
@ -8,19 +8,88 @@
|
|||
|
||||
#include "platform/unwinding_records.h"
|
||||
|
||||
#if defined(DART_HOST_OS_WINDOWS) && defined(TARGET_ARCH_X64)
|
||||
#if defined(DART_HOST_OS_WINDOWS) && \
|
||||
(defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64))
|
||||
|
||||
namespace dart {
|
||||
|
||||
static void InitUnwindingRecord(intptr_t offset,
|
||||
CodeRangeUnwindingRecord* record,
|
||||
size_t code_size_in_bytes) {
|
||||
#if defined(TARGET_ARCH_X64)
|
||||
// All addresses are 32bit relative offsets to start.
|
||||
record->runtime_function[0].BeginAddress = 0;
|
||||
record->runtime_function[0].EndAddress = code_size_in_bytes;
|
||||
record->runtime_function[0].UnwindData =
|
||||
offset + offsetof(CodeRangeUnwindingRecord, unwind_info);
|
||||
record->runtime_function_count = 1;
|
||||
#elif defined(TARGET_ARCH_ARM64)
|
||||
|
||||
const intptr_t kInstrSize = 4;
|
||||
|
||||
// We assume that the first page of the code range is executable and
|
||||
// committed and reserved to contain multiple PDATA/XDATA to cover the whole
|
||||
// range. All addresses are 32bit relative offsets to start.
|
||||
|
||||
// Maximum RUNTIME_FUNCTION count available in reserved memory, this includes
|
||||
// static part in Record as kDefaultRuntimeFunctionCount plus dynamic part in
|
||||
// the remaining reserved memory.
|
||||
const uint32_t max_runtime_function_count =
|
||||
static_cast<uint32_t>((UnwindingRecordsPlatform::SizeInBytes() -
|
||||
sizeof(CodeRangeUnwindingRecord)) /
|
||||
sizeof(RUNTIME_FUNCTION) +
|
||||
kDefaultRuntimeFunctionCount);
|
||||
|
||||
uint32_t runtime_function_index = 0;
|
||||
uint32_t current_unwind_start_address = 0;
|
||||
int64_t remaining_size_in_bytes = static_cast<int64_t>(code_size_in_bytes);
|
||||
|
||||
// Divide the code range into chunks in size kMaxFunctionLength and create a
|
||||
// RUNTIME_FUNCTION for each of them. All the chunks in the same size can
|
||||
// share 1 unwind_info struct, but a separate unwind_info is needed for the
|
||||
// last chunk if it is smaller than kMaxFunctionLength, because unlike X64,
|
||||
// unwind_info encodes the function/chunk length.
|
||||
while (remaining_size_in_bytes >= kMaxFunctionLength &&
|
||||
runtime_function_index < max_runtime_function_count) {
|
||||
record->runtime_function[runtime_function_index].BeginAddress =
|
||||
current_unwind_start_address;
|
||||
record->runtime_function[runtime_function_index].UnwindData =
|
||||
offset +
|
||||
static_cast<DWORD>(offsetof(CodeRangeUnwindingRecord, unwind_info));
|
||||
|
||||
runtime_function_index++;
|
||||
current_unwind_start_address += kMaxFunctionLength;
|
||||
remaining_size_in_bytes -= kMaxFunctionLength;
|
||||
}
|
||||
// FunctionLength is ensured to be aligned at instruction size and Windows
|
||||
// ARM64 doesn't encoding 2 LSB.
|
||||
record->unwind_info.unwind_info.FunctionLength = kMaxFunctionLength >> 2;
|
||||
|
||||
if (remaining_size_in_bytes > 0 &&
|
||||
runtime_function_index < max_runtime_function_count) {
|
||||
ASSERT((remaining_size_in_bytes % kInstrSize) == 0);
|
||||
|
||||
record->unwind_info1.unwind_info.FunctionLength = static_cast<uint32_t>(
|
||||
remaining_size_in_bytes >> kFunctionLengthShiftSize);
|
||||
record->runtime_function[runtime_function_index].BeginAddress =
|
||||
current_unwind_start_address;
|
||||
record->runtime_function[runtime_function_index].UnwindData =
|
||||
offset +
|
||||
static_cast<DWORD>(offsetof(CodeRangeUnwindingRecord, unwind_info1));
|
||||
|
||||
remaining_size_in_bytes -= kMaxFunctionLength;
|
||||
record->runtime_function_count = runtime_function_index + 1;
|
||||
} else {
|
||||
record->runtime_function_count = runtime_function_index;
|
||||
}
|
||||
|
||||
// 1 page can cover kMaximalCodeRangeSize for ARM64 (128MB). If
|
||||
// kMaximalCodeRangeSize is changed for ARM64 and makes 1 page insufficient to
|
||||
// cover it, more pages will need to reserved for unwind data.
|
||||
ASSERT(remaining_size_in_bytes <= 0);
|
||||
#else
|
||||
#error What architecture?
|
||||
#endif
|
||||
}
|
||||
|
||||
const void* UnwindingRecords::GenerateRecordsInto(intptr_t offset,
|
||||
|
@ -49,14 +118,15 @@ void UnwindingRecords::RegisterExecutablePage(Page* page) {
|
|||
new (reinterpret_cast<uint8_t*>(page->memory_->start()) +
|
||||
unwinding_record_offset) CodeRangeUnwindingRecord();
|
||||
InitUnwindingRecord(unwinding_record_offset, record, page->memory_->size());
|
||||
if (function(
|
||||
DWORD status = function(
|
||||
/*DynamicTable=*/&record->dynamic_table,
|
||||
/*FunctionTable=*/record->runtime_function,
|
||||
/*EntryCount=*/record->runtime_function_count,
|
||||
/*MaximumEntryCount=*/record->runtime_function_count,
|
||||
/*RangeBase=*/page->memory_->start(),
|
||||
/*RangeEnd=*/page->memory_->end()) != 0) {
|
||||
FATAL("Failed to add growable function table: %d\n", GetLastError());
|
||||
/*RangeEnd=*/page->memory_->end());
|
||||
if (status != 0) {
|
||||
FATAL("Failed to add growable function table: 0x%x\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue