[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:
Ryan Macnak 2023-11-21 20:39:29 +00:00 committed by Commit Queue
parent faf503cbc4
commit fc85488000
7 changed files with 229 additions and 24 deletions

View file

@ -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) {

View file

@ -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 {

View file

@ -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

View file

@ -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,
/*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());
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);
if (status != 0) {
FATAL("Failed to add growable function table: 0x%x\n", status);
}
}

View file

@ -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;

View file

@ -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 {

View file

@ -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(
/*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());
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());
if (status != 0) {
FATAL("Failed to add growable function table: 0x%x\n", status);
}
}