mirror of
https://github.com/dart-lang/sdk
synced 2024-09-18 20:01: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;
|
const dart::elf::Symbol* dynamic_symbol_table_ = nullptr;
|
||||||
uword dynamic_symbol_count_ = 0;
|
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.
|
// Dynamic table for looking up unwinding exceptions info.
|
||||||
// Initialized by LoadSegments as we load executable segment.
|
// Initialized by LoadSegments as we load executable segment.
|
||||||
MallocGrowableArray<void*> dynamic_runtime_function_tables_;
|
MallocGrowableArray<void*> dynamic_runtime_function_tables_;
|
||||||
|
@ -293,7 +294,8 @@ bool LoadedElf::Load() {
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadedElf::~LoadedElf() {
|
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++) {
|
for (intptr_t i = 0; i < dynamic_runtime_function_tables_.length(); i++) {
|
||||||
UnwindingRecordsPlatform::UnregisterDynamicTable(
|
UnwindingRecordsPlatform::UnregisterDynamicTable(
|
||||||
dynamic_runtime_function_tables_[i]);
|
dynamic_runtime_function_tables_[i]);
|
||||||
|
@ -463,7 +465,8 @@ bool LoadedElf::LoadSegments() {
|
||||||
CHECK_ERROR(memory != nullptr, "Could not map segment.");
|
CHECK_ERROR(memory != nullptr, "Could not map segment.");
|
||||||
CHECK_ERROR(memory->address() == memory_start,
|
CHECK_ERROR(memory->address() == memory_start,
|
||||||
"Mapping not at requested address.");
|
"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
|
// For executable pages register unwinding information that should be
|
||||||
// present on the page.
|
// present on the page.
|
||||||
if (map_type == File::kReadExecute) {
|
if (map_type == File::kReadExecute) {
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
#include "platform/unwinding_records.h"
|
#include "platform/unwinding_records.h"
|
||||||
#include "platform/globals.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 {
|
namespace dart {
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,127 @@ struct CodeRangeUnwindingRecord {
|
||||||
|
|
||||||
#pragma pack(pop)
|
#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)
|
#endif // defined(DART_HOST_OS_WINDOWS) && defined(TARGET_ARCH_X64)
|
||||||
|
|
||||||
} // namespace dart
|
} // namespace dart
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
#include "platform/assert.h"
|
#include "platform/assert.h"
|
||||||
#include "platform/globals.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 {
|
namespace dart {
|
||||||
|
|
||||||
|
@ -17,7 +18,12 @@ static decltype(&::RtlAddGrowableFunctionTable)
|
||||||
static decltype(&::RtlDeleteGrowableFunctionTable)
|
static decltype(&::RtlDeleteGrowableFunctionTable)
|
||||||
delete_growable_function_table_func_ = nullptr;
|
delete_growable_function_table_func_ = nullptr;
|
||||||
|
|
||||||
|
#if defined(TARGET_ARCH_X64)
|
||||||
const intptr_t kReservedUnwindingRecordsSizeBytes = 64;
|
const intptr_t kReservedUnwindingRecordsSizeBytes = 64;
|
||||||
|
#else
|
||||||
|
const intptr_t kReservedUnwindingRecordsSizeBytes = 4 * KB;
|
||||||
|
#endif
|
||||||
|
|
||||||
intptr_t UnwindingRecordsPlatform::SizeInBytes() {
|
intptr_t UnwindingRecordsPlatform::SizeInBytes() {
|
||||||
return kReservedUnwindingRecordsSizeBytes;
|
return kReservedUnwindingRecordsSizeBytes;
|
||||||
}
|
}
|
||||||
|
@ -64,13 +70,14 @@ void UnwindingRecordsPlatform::RegisterExecutableMemory(
|
||||||
reinterpret_cast<CodeRangeUnwindingRecord*>(record_ptr);
|
reinterpret_cast<CodeRangeUnwindingRecord*>(record_ptr);
|
||||||
uword start_num = reinterpret_cast<intptr_t>(start);
|
uword start_num = reinterpret_cast<intptr_t>(start);
|
||||||
uword end_num = start_num + size;
|
uword end_num = start_num + size;
|
||||||
if (func(pp_dynamic_table,
|
DWORD status = func(pp_dynamic_table,
|
||||||
/*FunctionTable=*/record->runtime_function,
|
/*FunctionTable=*/record->runtime_function,
|
||||||
/*EntryCount=*/record->runtime_function_count,
|
/*EntryCount=*/record->runtime_function_count,
|
||||||
/*MaximumEntryCount=*/record->runtime_function_count,
|
/*MaximumEntryCount=*/record->runtime_function_count,
|
||||||
/*RangeBase=*/start_num,
|
/*RangeBase=*/start_num,
|
||||||
/*RangeEnd=*/end_num) != 0) {
|
/*RangeEnd=*/end_num);
|
||||||
FATAL("Failed to add growable function table: %d\n", GetLastError());
|
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.
|
// No text section added means no .eh_frame.
|
||||||
if (text_section == nullptr) return;
|
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.
|
// Append Windows unwinding instructions to the end of .text section.
|
||||||
{
|
{
|
||||||
auto* const unwinding_instructions_frame = new (zone_) TextSection(type_);
|
auto* const unwinding_instructions_frame = new (zone_) TextSection(type_);
|
||||||
|
@ -1473,7 +1474,8 @@ void Elf::FinalizeEhFrame() {
|
||||||
|
|
||||||
// Emit an FDE covering each .text section.
|
// Emit an FDE covering each .text section.
|
||||||
for (const auto& portion : text_section->portions()) {
|
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) {
|
if (portion.label == 0) {
|
||||||
// Unwinding instructions sections doesn't have label, doesn't dwarf
|
// Unwinding instructions sections doesn't have label, doesn't dwarf
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
#include "vm/unwinding_records.h"
|
#include "vm/unwinding_records.h"
|
||||||
#include "vm/globals.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 {
|
namespace dart {
|
||||||
|
|
||||||
|
|
|
@ -8,19 +8,88 @@
|
||||||
|
|
||||||
#include "platform/unwinding_records.h"
|
#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 {
|
namespace dart {
|
||||||
|
|
||||||
static void InitUnwindingRecord(intptr_t offset,
|
static void InitUnwindingRecord(intptr_t offset,
|
||||||
CodeRangeUnwindingRecord* record,
|
CodeRangeUnwindingRecord* record,
|
||||||
size_t code_size_in_bytes) {
|
size_t code_size_in_bytes) {
|
||||||
|
#if defined(TARGET_ARCH_X64)
|
||||||
// All addresses are 32bit relative offsets to start.
|
// All addresses are 32bit relative offsets to start.
|
||||||
record->runtime_function[0].BeginAddress = 0;
|
record->runtime_function[0].BeginAddress = 0;
|
||||||
record->runtime_function[0].EndAddress = code_size_in_bytes;
|
record->runtime_function[0].EndAddress = code_size_in_bytes;
|
||||||
record->runtime_function[0].UnwindData =
|
record->runtime_function[0].UnwindData =
|
||||||
offset + offsetof(CodeRangeUnwindingRecord, unwind_info);
|
offset + offsetof(CodeRangeUnwindingRecord, unwind_info);
|
||||||
record->runtime_function_count = 1;
|
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,
|
const void* UnwindingRecords::GenerateRecordsInto(intptr_t offset,
|
||||||
|
@ -49,14 +118,15 @@ void UnwindingRecords::RegisterExecutablePage(Page* page) {
|
||||||
new (reinterpret_cast<uint8_t*>(page->memory_->start()) +
|
new (reinterpret_cast<uint8_t*>(page->memory_->start()) +
|
||||||
unwinding_record_offset) CodeRangeUnwindingRecord();
|
unwinding_record_offset) CodeRangeUnwindingRecord();
|
||||||
InitUnwindingRecord(unwinding_record_offset, record, page->memory_->size());
|
InitUnwindingRecord(unwinding_record_offset, record, page->memory_->size());
|
||||||
if (function(
|
DWORD status = function(
|
||||||
/*DynamicTable=*/&record->dynamic_table,
|
/*DynamicTable=*/&record->dynamic_table,
|
||||||
/*FunctionTable=*/record->runtime_function,
|
/*FunctionTable=*/record->runtime_function,
|
||||||
/*EntryCount=*/record->runtime_function_count,
|
/*EntryCount=*/record->runtime_function_count,
|
||||||
/*MaximumEntryCount=*/record->runtime_function_count,
|
/*MaximumEntryCount=*/record->runtime_function_count,
|
||||||
/*RangeBase=*/page->memory_->start(),
|
/*RangeBase=*/page->memory_->start(),
|
||||||
/*RangeEnd=*/page->memory_->end()) != 0) {
|
/*RangeEnd=*/page->memory_->end());
|
||||||
FATAL("Failed to add growable function table: %d\n", GetLastError());
|
if (status != 0) {
|
||||||
|
FATAL("Failed to add growable function table: 0x%x\n", status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue