[vm] Add compact unwinding info to ARM64 Mach-O snapshots.
For other architectures, a __eh_frame section is generated like for non-MacOS platforms. TEST=vm/dart/unwinding_information_test Issue: https://github.com/dart-lang/sdk/issues/60307 Change-Id: I46c6ff1357c222f73a129e1becd9b12b3fb6ef9c Cq-Include-Trybots: luci.dart.try:vm-aot-linux-debug-x64-try,vm-mac-release-arm64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-dwarf-linux-product-x64-try,vm-linux-debug-x64-try,vm-mac-debug-arm64-try,vm-fuchsia-release-x64-try,vm-fuchsia-release-arm64-try,vm-aot-linux-release-simarm_x64-try,vm-gcc-linux-try,vm-ubsan-linux-release-arm64-try,vm-aot-win-release-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/433020 Reviewed-by: Slava Egorov <vegorov@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Tess Strickland <sstrickl@google.com>
This commit is contained in:
committed by
Commit Queue
parent
a04c0d4221
commit
12994040e0
@@ -537,6 +537,112 @@ struct cs_code_directory {
|
||||
// 8-byte aligned (like blobs) and the hash data is 16-byte aligned.
|
||||
};
|
||||
|
||||
// Compact unwinding information constants for encodings.
|
||||
|
||||
// Architecture-independent constants for encodings.
|
||||
|
||||
// A shorthand for the zero-value encoding that denotes that the associated
|
||||
// memory space does not contain function instructions.
|
||||
static constexpr uint32_t UNWIND_INFO_ENCODING_NONE = 0;
|
||||
|
||||
static constexpr uint32_t UNWIND_INFO_ENCODING_IS_NOT_FUNCTION_START =
|
||||
0x80000000;
|
||||
static constexpr uint32_t UNWIND_INFO_ENCODING_HAS_LSDA = 0x40000000;
|
||||
static constexpr uint32_t UNWIND_INFO_ENCODING_PERSONALITY_MASK = 0x30000000;
|
||||
|
||||
// Currently compact unwinding information is only generated for ARM64, so only
|
||||
// the constants used by the MachO writer are included below.
|
||||
|
||||
// ARM64-specific constants for encodings.
|
||||
static constexpr uint32_t UNWIND_INFO_ENCODING_ARM64_MODE_MASK = 0x0F000000;
|
||||
|
||||
// A standard ARM64 prologue where FP/LR are immediately pushed on the
|
||||
// stack and then SP is copied to FP. If there are any non-volatile
|
||||
// registers saved, they are saved in pairs right below the FP/LR pair
|
||||
// in register number order.
|
||||
//
|
||||
// In the MachO writer, this is the only non-zero encoding used and
|
||||
// no non-volatile register pairs are recorded as being saved, so
|
||||
// the appropriate constants for each pair and for other encodings are elided.
|
||||
static constexpr uint32_t UNWIND_INFO_ENCODING_ARM64_MODE_FRAME = 0x04000000;
|
||||
|
||||
// Note that fields ending in section_offset are offsets into the unwind info
|
||||
// section as a whole, whereas fields ending in page_offset are offsets into
|
||||
// the specific second level page (e.g., the page header starts at offset 0).
|
||||
|
||||
struct unwind_info_lsda_index {
|
||||
uint32_t function_offset;
|
||||
uint32_t lsda_offset;
|
||||
};
|
||||
|
||||
struct unwind_info_first_level_page_index {
|
||||
uint32_t function_offset;
|
||||
uint32_t second_level_page_section_offset;
|
||||
uint32_t lsda_index_section_offset;
|
||||
};
|
||||
|
||||
// Second level pages have two formats: a compressed and a "regular" format.
|
||||
// As the Mach-O writer only creates a single second level page with four
|
||||
// entries currently, it uses the regular format as it is simpler.
|
||||
|
||||
static constexpr uint32_t UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE = 2;
|
||||
|
||||
struct unwind_info_regular_second_level_page_entry {
|
||||
uint32_t function_offset;
|
||||
uint32_t encoding;
|
||||
};
|
||||
|
||||
struct unwind_info_regular_second_level_page_header {
|
||||
uint32_t kind; // UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE
|
||||
uint16_t entry_page_offset;
|
||||
uint16_t entry_count;
|
||||
};
|
||||
|
||||
static const size_t UNWIND_INFO_SECOND_LEVEL_PAGE_MAX_SIZE = 4 * KB;
|
||||
|
||||
static constexpr uint32_t UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE_MAX_ENTRIES =
|
||||
(UNWIND_INFO_SECOND_LEVEL_PAGE_MAX_SIZE -
|
||||
sizeof(unwind_info_regular_second_level_page_header)) /
|
||||
sizeof(unwind_info_regular_second_level_page_entry);
|
||||
|
||||
constexpr size_t UnwindInfoRegularSecondLevelPageSize(intptr_t entries) {
|
||||
return sizeof(unwind_info_regular_second_level_page_header) +
|
||||
sizeof(unwind_info_regular_second_level_page_entry) * entries;
|
||||
}
|
||||
|
||||
static constexpr uint32_t UNWIND_INFO_VERSION = 1;
|
||||
|
||||
struct unwind_info_header {
|
||||
uint32_t version;
|
||||
uint32_t common_encodings_section_offset;
|
||||
uint32_t common_encodings_count;
|
||||
uint32_t personalities_section_offset;
|
||||
uint32_t personalities_count;
|
||||
uint32_t first_level_page_indices_section_offset;
|
||||
uint32_t first_level_page_indices_count;
|
||||
|
||||
// Note that the last first level page indices is a sentinel that contains
|
||||
// the end of the covered space as its function offset and the end
|
||||
// of the lsda array as its lsda_index_section_offset.
|
||||
//
|
||||
// sentinel_index = first_level_page_indices_count - 1
|
||||
//
|
||||
// lsda_size =
|
||||
// first_level_pages[sentinel_index].lsda_index_section_offset -
|
||||
// first_level_pages[0].lsda_index_section_offset
|
||||
//
|
||||
// lsda_count = lsda_size / sizeof(unwind_info_lsda_index)
|
||||
// second_level_page_count = sentinel_index;
|
||||
|
||||
// Variadic payload of unwind info section after unwind_info_header:
|
||||
// uint32_t common_encodings[common_encodings_count]
|
||||
// uint32_t personalities[personalities_count]
|
||||
// unwind_info_first_level_page_index
|
||||
// first_level_pages[first_level_page_indices_count]
|
||||
// unwind_info_lsda_index lsda[lsda_count]
|
||||
// ... regular and compressed second level pages ...
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
} // namespace mach_o
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
import "dart:io";
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:native_stack_traces/elf.dart';
|
||||
import 'package:native_stack_traces/src/dwarf_container.dart';
|
||||
import 'package:native_stack_traces/src/macho.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'use_flag_test_helper.dart';
|
||||
@@ -62,76 +60,6 @@ Future<void> main(List<String> args) async {
|
||||
});
|
||||
}
|
||||
|
||||
const commonGenSnapshotArgs = <String>[
|
||||
// Make sure that the runs are deterministic so we can depend on the same
|
||||
// snapshot being generated each time.
|
||||
'--deterministic',
|
||||
];
|
||||
|
||||
enum SnapshotType {
|
||||
elf,
|
||||
machoDylib,
|
||||
assembly;
|
||||
|
||||
String get kindString {
|
||||
switch (this) {
|
||||
case elf:
|
||||
return 'app-aot-elf';
|
||||
case machoDylib:
|
||||
return 'app-aot-macho-dylib';
|
||||
case assembly:
|
||||
return 'app-aot-assembly';
|
||||
}
|
||||
}
|
||||
|
||||
String get fileArgumentName {
|
||||
switch (this) {
|
||||
case elf:
|
||||
return 'elf';
|
||||
case machoDylib:
|
||||
return 'macho';
|
||||
case assembly:
|
||||
return 'assembly';
|
||||
}
|
||||
}
|
||||
|
||||
DwarfContainer? fromFile(String filename) {
|
||||
switch (this) {
|
||||
case elf:
|
||||
return Elf.fromFile(filename);
|
||||
case machoDylib:
|
||||
return MachO.fromFile(filename);
|
||||
case assembly:
|
||||
return Elf.fromFile(filename) ?? MachO.fromFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
|
||||
Future<void> createSnapshot(
|
||||
String scriptDill,
|
||||
SnapshotType snapshotType,
|
||||
String finalPath, [
|
||||
List<String> extraArgs = const [],
|
||||
]) async {
|
||||
String output = finalPath;
|
||||
if (snapshotType == SnapshotType.assembly) {
|
||||
output = path.withoutExtension(finalPath) + '.S';
|
||||
}
|
||||
await run(genSnapshot, <String>[
|
||||
...commonGenSnapshotArgs,
|
||||
...extraArgs,
|
||||
'--snapshot-kind=${snapshotType.kindString}',
|
||||
'--${snapshotType.fileArgumentName}=$output',
|
||||
scriptDill,
|
||||
]);
|
||||
if (snapshotType == SnapshotType.assembly) {
|
||||
await assembleSnapshot(output, finalPath);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<String>?> retrieveDebugMap(
|
||||
SnapshotType snapshotType,
|
||||
String snapshotPath,
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// This test checks that the compact unwinding information is appropriately
|
||||
// generated for Mac ARM64 snapshots.
|
||||
|
||||
// OtherResources=use_save_debugging_info_flag_program.dart
|
||||
|
||||
import "dart:io";
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:native_stack_traces/src/dwarf_container.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'use_flag_test_helper.dart';
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
if (!isAOTRuntime) {
|
||||
return; // Running in JIT: AOT binaries not available.
|
||||
}
|
||||
|
||||
// Currently, this test only checks compact unwinding information, which
|
||||
// is only generated on Mac ARM64 binaries.
|
||||
if (!Platform.isMacOS || !buildDir.endsWith('ARM64')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// These are the tools we need to be available to run on a given platform:
|
||||
if (!await testExecutable(genSnapshot)) {
|
||||
throw "Cannot run test as $genSnapshot not available";
|
||||
}
|
||||
if (!await testExecutable(dartPrecompiledRuntime)) {
|
||||
throw "Cannot run test as $dartPrecompiledRuntime not available";
|
||||
}
|
||||
if (!File(platformDill).existsSync()) {
|
||||
throw "Cannot run test as $platformDill does not exist";
|
||||
}
|
||||
|
||||
await withTempDir('unwinding-information', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
final script = path.join(
|
||||
cwDir,
|
||||
'use_save_debugging_info_flag_program.dart',
|
||||
);
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
await run(genKernel, <String>[
|
||||
'--aot',
|
||||
'--platform=$platformDill',
|
||||
'-o',
|
||||
scriptDill,
|
||||
script,
|
||||
]);
|
||||
|
||||
await checkMachO(tempDir, scriptDill);
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<String>> retrieveUnwindInfo(
|
||||
SnapshotType snapshotType,
|
||||
String snapshotPath,
|
||||
) async {
|
||||
if (snapshotType != SnapshotType.machoDylib) {
|
||||
throw ArgumentError("Unhandled snapshot type");
|
||||
}
|
||||
final objdump = llvmTool('llvm-objdump');
|
||||
if (objdump == null) {
|
||||
throw StateError('Expected llvm-objdump in buildutils');
|
||||
}
|
||||
return await runOutput(objdump, ['--macho', '-u', snapshotPath]);
|
||||
}
|
||||
|
||||
Future<void> checkSnapshotType(
|
||||
String tempDir,
|
||||
String scriptDill,
|
||||
SnapshotType snapshotType,
|
||||
) async {
|
||||
final scriptUnstrippedSnapshot = path.join(
|
||||
tempDir,
|
||||
'unstripped-$snapshotType.so',
|
||||
);
|
||||
await createSnapshot(scriptDill, snapshotType, scriptUnstrippedSnapshot);
|
||||
final unstrippedCase = TestCase(
|
||||
snapshotType,
|
||||
scriptUnstrippedSnapshot,
|
||||
snapshotType.fromFile(scriptUnstrippedSnapshot)!,
|
||||
await retrieveUnwindInfo(snapshotType, scriptUnstrippedSnapshot),
|
||||
);
|
||||
|
||||
final scriptStrippedSnapshot = path.join(
|
||||
tempDir,
|
||||
'stripped-$snapshotType.so',
|
||||
);
|
||||
await createSnapshot(scriptDill, snapshotType, scriptStrippedSnapshot, [
|
||||
'--strip',
|
||||
]);
|
||||
final strippedCase = TestCase(
|
||||
snapshotType,
|
||||
scriptStrippedSnapshot,
|
||||
snapshotType.fromFile(scriptStrippedSnapshot)!,
|
||||
await retrieveUnwindInfo(snapshotType, scriptStrippedSnapshot),
|
||||
);
|
||||
|
||||
checkCases(unstrippedCase, strippedCase);
|
||||
}
|
||||
|
||||
Future<void> checkMachO(String tempDir, String scriptDill) async {
|
||||
await checkSnapshotType(tempDir, scriptDill, SnapshotType.machoDylib);
|
||||
}
|
||||
|
||||
class TestCase {
|
||||
final SnapshotType snapshotType;
|
||||
final String snapshotPath;
|
||||
final DwarfContainer container;
|
||||
final List<String> unwindInfo;
|
||||
|
||||
TestCase(
|
||||
this.snapshotType,
|
||||
this.snapshotPath,
|
||||
this.container,
|
||||
this.unwindInfo,
|
||||
);
|
||||
}
|
||||
|
||||
void checkCases(TestCase unstripped, TestCase stripped) {
|
||||
Expect.isNotEmpty(unstripped.unwindInfo);
|
||||
Expect.deepEquals(unstripped.unwindInfo, stripped.unwindInfo);
|
||||
}
|
||||
@@ -8,6 +8,10 @@ import 'dart:ffi';
|
||||
|
||||
import 'package:expect/config.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:native_stack_traces/src/elf.dart' show Elf;
|
||||
import 'package:native_stack_traces/src/dwarf_container.dart'
|
||||
show DwarfContainer;
|
||||
import 'package:native_stack_traces/src/macho.dart' show MachO;
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
final isAOTRuntime = isVmAotConfiguration;
|
||||
@@ -286,3 +290,73 @@ Future<void> withTempDir(String name, Future<void> fun(String dir)) async {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum SnapshotType {
|
||||
elf,
|
||||
machoDylib,
|
||||
assembly;
|
||||
|
||||
String get kindString {
|
||||
switch (this) {
|
||||
case elf:
|
||||
return 'app-aot-elf';
|
||||
case machoDylib:
|
||||
return 'app-aot-macho-dylib';
|
||||
case assembly:
|
||||
return 'app-aot-assembly';
|
||||
}
|
||||
}
|
||||
|
||||
String get fileArgumentName {
|
||||
switch (this) {
|
||||
case elf:
|
||||
return 'elf';
|
||||
case machoDylib:
|
||||
return 'macho';
|
||||
case assembly:
|
||||
return 'assembly';
|
||||
}
|
||||
}
|
||||
|
||||
DwarfContainer? fromFile(String filename) {
|
||||
switch (this) {
|
||||
case elf:
|
||||
return Elf.fromFile(filename);
|
||||
case machoDylib:
|
||||
return MachO.fromFile(filename);
|
||||
case assembly:
|
||||
return Elf.fromFile(filename) ?? MachO.fromFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
|
||||
const _commonGenSnapshotArgs = <String>[
|
||||
// Make sure that the runs are deterministic so we can depend on the same
|
||||
// snapshot being generated each time.
|
||||
'--deterministic',
|
||||
];
|
||||
|
||||
Future<void> createSnapshot(
|
||||
String scriptDill,
|
||||
SnapshotType snapshotType,
|
||||
String finalPath, [
|
||||
List<String> extraArgs = const [],
|
||||
]) async {
|
||||
String output = finalPath;
|
||||
if (snapshotType == SnapshotType.assembly) {
|
||||
output = path.withoutExtension(finalPath) + '.S';
|
||||
}
|
||||
await run(genSnapshot, <String>[
|
||||
..._commonGenSnapshotArgs,
|
||||
...extraArgs,
|
||||
'--snapshot-kind=${snapshotType.kindString}',
|
||||
'--${snapshotType.fileArgumentName}=$output',
|
||||
scriptDill,
|
||||
]);
|
||||
if (snapshotType == SnapshotType.assembly) {
|
||||
await assembleSnapshot(output, finalPath);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1000,7 +1000,7 @@ void Dwarf::WriteCallFrameInformationRecords(
|
||||
// backwards: it will be subtracted from the current position.
|
||||
stream->u4(stream->Position() - cie_start);
|
||||
// Start address as a PC relative reference.
|
||||
stream->RelativeSymbolOffset<int32_t>(fde.label);
|
||||
stream->RelativeSymbolOffset(fde.label, kInt32Size);
|
||||
stream->u4(fde.size); // Size.
|
||||
stream->u1(0); // Augmentation Data length.
|
||||
|
||||
|
||||
+14
-1
@@ -144,12 +144,25 @@ class DwarfWriteStream : public ValueObject {
|
||||
virtual void WritePrefixedLength(const char* symbol_prefix,
|
||||
std::function<void()> body) = 0;
|
||||
|
||||
virtual void OffsetFromSymbol(intptr_t label, intptr_t offset) = 0;
|
||||
// Generates a relocated address from the given symbol label and offset.
|
||||
//
|
||||
// If no size is provided, the size of the relocated address in the stream
|
||||
// is the native word size.
|
||||
virtual void OffsetFromSymbol(intptr_t label,
|
||||
intptr_t offset,
|
||||
size_t size = kAddressSize) = 0;
|
||||
|
||||
virtual void InitializeAbstractOrigins(intptr_t size) = 0;
|
||||
virtual void RegisterAbstractOrigin(intptr_t index) = 0;
|
||||
virtual void AbstractOrigin(intptr_t index) = 0;
|
||||
|
||||
protected:
|
||||
#if defined(TARGET_ARCH_IS_32_BIT)
|
||||
static constexpr size_t kAddressSize = kInt32Size;
|
||||
#else
|
||||
static constexpr size_t kAddressSize = kInt64Size;
|
||||
#endif
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DwarfWriteStream);
|
||||
};
|
||||
|
||||
|
||||
@@ -28,18 +28,19 @@ class DwarfSharedObjectStream : public DwarfWriteStream {
|
||||
intptr_t bytes_written() const { return stream_->bytes_written(); }
|
||||
intptr_t Position() const { return stream_->Position(); }
|
||||
|
||||
void sleb128(intptr_t value) { stream_->WriteSLEB128(value); }
|
||||
void uleb128(uintptr_t value) { stream_->WriteLEB128(value); }
|
||||
void u1(uint8_t value) { stream_->WriteByte(value); }
|
||||
void u2(uint16_t value) { stream_->WriteFixed(value); }
|
||||
void u4(uint32_t value) { stream_->WriteFixed(value); }
|
||||
void u8(uint64_t value) { stream_->WriteFixed(value); }
|
||||
void string(const char* cstr) { // NOLINT
|
||||
void sleb128(intptr_t value) override { stream_->WriteSLEB128(value); }
|
||||
void uleb128(uintptr_t value) override { stream_->WriteLEB128(value); }
|
||||
void u1(uint8_t value) override { stream_->WriteByte(value); }
|
||||
void u2(uint16_t value) override { stream_->WriteFixed(value); }
|
||||
void u4(uint32_t value) override { stream_->WriteFixed(value); }
|
||||
void u8(uint64_t value) override { stream_->WriteFixed(value); }
|
||||
void string(const char* cstr) override { // NOLINT
|
||||
// Unlike stream_->WriteString(), we want the null terminator written.
|
||||
stream_->WriteBytes(cstr, strlen(cstr) + 1);
|
||||
}
|
||||
// The prefix is ignored for DwarfSharedObjectStreams.
|
||||
void WritePrefixedLength(const char* unused, std::function<void()> body) {
|
||||
void WritePrefixedLength(const char* unused,
|
||||
std::function<void()> body) override {
|
||||
const intptr_t fixup = stream_->Position();
|
||||
// We assume DWARF v2 currently, so all sizes are 32-bit.
|
||||
u4(0);
|
||||
@@ -57,49 +58,53 @@ class DwarfSharedObjectStream : public DwarfWriteStream {
|
||||
WritePrefixedLength(nullptr, body);
|
||||
}
|
||||
|
||||
void OffsetFromSymbol(intptr_t label, intptr_t offset) {
|
||||
relocations_->Add({kAddressSize, stream_->Position(),
|
||||
void OffsetFromSymbol(intptr_t label,
|
||||
intptr_t offset,
|
||||
size_t size = kAddressSize) override {
|
||||
ASSERT(size > 0);
|
||||
ASSERT(size <= static_cast<size_t>(kInt64Size));
|
||||
relocations_->Add({size, stream_->Position(),
|
||||
SharedObjectWriter::Relocation::kSnapshotRelative, 0,
|
||||
label, offset});
|
||||
addr(0); // Resolved later.
|
||||
const uint64_t placeholder = 0; // Resolved later.
|
||||
stream_->WriteBytes(&placeholder, size);
|
||||
}
|
||||
template <typename T>
|
||||
void RelativeSymbolOffset(intptr_t label) {
|
||||
relocations_->Add({sizeof(T), stream_->Position(),
|
||||
SharedObjectWriter::Relocation::kSelfRelative, 0, label,
|
||||
0});
|
||||
stream_->WriteFixed<T>(0); // Resolved later.
|
||||
}
|
||||
void InitializeAbstractOrigins(intptr_t size) {
|
||||
void InitializeAbstractOrigins(intptr_t size) override {
|
||||
abstract_origins_size_ = size;
|
||||
abstract_origins_ = zone_->Alloc<uint32_t>(abstract_origins_size_);
|
||||
}
|
||||
void RegisterAbstractOrigin(intptr_t index) {
|
||||
void RegisterAbstractOrigin(intptr_t index) override {
|
||||
ASSERT(abstract_origins_ != nullptr);
|
||||
ASSERT(index < abstract_origins_size_);
|
||||
abstract_origins_[index] = stream_->Position();
|
||||
}
|
||||
void AbstractOrigin(intptr_t index) { u4(abstract_origins_[index]); }
|
||||
void AbstractOrigin(intptr_t index) override { u4(abstract_origins_[index]); }
|
||||
|
||||
// Generates the offset of the virtual address corresponding to the given
|
||||
// symbol label from the current position in the output. That is, if
|
||||
// X = the virtual address of the current position
|
||||
// Y = the virtual address of the symbol
|
||||
// then the value at the current position in the output is Y - X.
|
||||
//
|
||||
// If no size is provided, the size of the offset in the stream is
|
||||
// the native word size.
|
||||
void RelativeSymbolOffset(intptr_t label, size_t size = kAddressSize) {
|
||||
relocations_->Add({size, stream_->Position(),
|
||||
SharedObjectWriter::Relocation::kSelfRelative, 0, label,
|
||||
0});
|
||||
const uint64_t placeholder = 0; // Resolved later.
|
||||
stream_->WriteBytes(&placeholder, size);
|
||||
}
|
||||
|
||||
intptr_t Align(intptr_t alignment, intptr_t offset = 0) {
|
||||
return stream_->Align(alignment, offset);
|
||||
}
|
||||
|
||||
const SharedObjectWriter::RelocationArray* relocations() const {
|
||||
return relocations_;
|
||||
}
|
||||
|
||||
protected:
|
||||
#if defined(TARGET_ARCH_IS_32_BIT)
|
||||
static constexpr intptr_t kAddressSize = kInt32Size;
|
||||
#else
|
||||
static constexpr intptr_t kAddressSize = kInt64Size;
|
||||
#endif
|
||||
|
||||
void addr(uword value) {
|
||||
#if defined(TARGET_ARCH_IS_32_BIT)
|
||||
u4(value);
|
||||
#else
|
||||
u8(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
Zone* const zone_;
|
||||
NonStreamingWriteStream* const stream_;
|
||||
SharedObjectWriter::RelocationArray* const relocations_ = nullptr;
|
||||
|
||||
@@ -1064,24 +1064,26 @@ class DwarfAssemblyStream : public DwarfWriteStream {
|
||||
stream_(ASSERT_NOTNULL(stream)),
|
||||
label_to_name_(label_to_name) {}
|
||||
|
||||
void sleb128(intptr_t value) { stream_->Printf(".sleb128 %" Pd "\n", value); }
|
||||
void uleb128(uintptr_t value) {
|
||||
void sleb128(intptr_t value) override {
|
||||
stream_->Printf(".sleb128 %" Pd "\n", value);
|
||||
}
|
||||
void uleb128(uintptr_t value) override {
|
||||
stream_->Printf(".uleb128 %" Pd "\n", value);
|
||||
}
|
||||
void u1(uint8_t value) {
|
||||
void u1(uint8_t value) override {
|
||||
stream_->Printf("%s %u\n", kSizeDirectives[kInt8SizeLog2], value);
|
||||
}
|
||||
void u2(uint16_t value) {
|
||||
void u2(uint16_t value) override {
|
||||
stream_->Printf("%s %u\n", kSizeDirectives[kInt16SizeLog2], value);
|
||||
}
|
||||
void u4(uint32_t value) {
|
||||
void u4(uint32_t value) override {
|
||||
stream_->Printf("%s %" Pu32 "\n", kSizeDirectives[kInt32SizeLog2], value);
|
||||
}
|
||||
void u8(uint64_t value) {
|
||||
void u8(uint64_t value) override {
|
||||
stream_->Printf("%s %" Pu64 "\n", kSizeDirectives[kInt64SizeLog2], value);
|
||||
}
|
||||
void string(const char* cstr) { // NOLINT
|
||||
stream_->WriteString(".string \""); // NOLINT
|
||||
void string(const char* cstr) override { // NOLINT
|
||||
stream_->WriteString(".string \""); // NOLINT
|
||||
while (char c = *cstr++) {
|
||||
if (c == '"') {
|
||||
stream_->WriteString("\\\"");
|
||||
@@ -1097,7 +1099,8 @@ class DwarfAssemblyStream : public DwarfWriteStream {
|
||||
}
|
||||
stream_->WriteString("\"\n");
|
||||
}
|
||||
void WritePrefixedLength(const char* prefix, std::function<void()> body) {
|
||||
void WritePrefixedLength(const char* prefix,
|
||||
std::function<void()> body) override {
|
||||
ASSERT(prefix != nullptr);
|
||||
const char* const length_prefix_symbol =
|
||||
OS::SCreate(zone_, ".L%s_length_prefix", prefix);
|
||||
@@ -1113,23 +1116,25 @@ class DwarfAssemblyStream : public DwarfWriteStream {
|
||||
body();
|
||||
stream_->Printf(".L%s_end:\n", prefix);
|
||||
}
|
||||
void OffsetFromSymbol(intptr_t label, intptr_t offset) {
|
||||
void OffsetFromSymbol(intptr_t label,
|
||||
intptr_t offset,
|
||||
size_t size = kAddressSize) override {
|
||||
const char* symbol = label_to_name_.Lookup(label);
|
||||
ASSERT(symbol != nullptr);
|
||||
if (offset == 0) {
|
||||
PrintNamedAddress(symbol);
|
||||
PrintNamedAddress(symbol, size);
|
||||
} else {
|
||||
PrintNamedAddressWithOffset(symbol, offset);
|
||||
PrintNamedAddressWithOffset(symbol, offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
// No-op, we'll be using labels.
|
||||
void InitializeAbstractOrigins(intptr_t size) {}
|
||||
void RegisterAbstractOrigin(intptr_t index) {
|
||||
void InitializeAbstractOrigins(intptr_t size) override {}
|
||||
void RegisterAbstractOrigin(intptr_t index) override {
|
||||
// Label for DW_AT_abstract_origin references
|
||||
stream_->Printf("Lfunc%" Pd " = .-%s\n", index, kDebugInfoLabel);
|
||||
}
|
||||
void AbstractOrigin(intptr_t index) {
|
||||
void AbstractOrigin(intptr_t index) override {
|
||||
stream_->Printf("%s Lfunc%" Pd "\n", kSizeDirectives[kInt32SizeLog2],
|
||||
index);
|
||||
}
|
||||
@@ -1171,11 +1176,15 @@ class DwarfAssemblyStream : public DwarfWriteStream {
|
||||
private:
|
||||
static constexpr const char* kDebugInfoLabel = ".Ldebug_info";
|
||||
|
||||
void PrintNamedAddress(const char* name) {
|
||||
stream_->Printf("%s \"%s\"\n", kWordDirective, name);
|
||||
void PrintNamedAddress(const char* name, size_t size) {
|
||||
auto* const directive = kSizeDirectives[Utils::ShiftForPowerOfTwo(size)];
|
||||
stream_->Printf("%s \"%s\"\n", directive, name);
|
||||
}
|
||||
void PrintNamedAddressWithOffset(const char* name, intptr_t offset) {
|
||||
stream_->Printf("%s \"%s\" + %" Pd "\n", kWordDirective, name, offset);
|
||||
void PrintNamedAddressWithOffset(const char* name,
|
||||
intptr_t offset,
|
||||
size_t size) {
|
||||
auto* const directive = kSizeDirectives[Utils::ShiftForPowerOfTwo(size)];
|
||||
stream_->Printf("%s \"%s\" + %" Pd "\n", directive, name, offset);
|
||||
}
|
||||
|
||||
Zone* const zone_;
|
||||
@@ -1759,6 +1768,8 @@ void AssemblyImageWriter::FrameUnwindPrologue() {
|
||||
assembly_stream_->WriteString(".cfi_offset rip, -8\n");
|
||||
assembly_stream_->WriteString(".cfi_offset rbp, -16\n");
|
||||
#elif defined(TARGET_ARCH_ARM64)
|
||||
// If this changes, then MachOHeader::GenerateUnwindingInformation must
|
||||
// also change to generate the appropriate compact unwinding information.
|
||||
COMPILE_ASSERT(R29 == FP);
|
||||
COMPILE_ASSERT(R30 == LINK_REGISTER);
|
||||
assembly_stream_->WriteString(".cfi_def_cfa x29, 16\n");
|
||||
|
||||
@@ -161,6 +161,7 @@ class Image : ValueObject {
|
||||
friend class BlobImageWriter;
|
||||
friend class ImageWriter;
|
||||
friend class SharedObjectWriter;
|
||||
friend class MachOHeader; // For kHeaderSize.
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Image);
|
||||
};
|
||||
|
||||
+188
-53
@@ -11,9 +11,11 @@
|
||||
#include "openssl/sha.h"
|
||||
#include "platform/mach_o.h"
|
||||
#include "platform/unwinding_records.h"
|
||||
#include "vm/compiler/runtime_api.h"
|
||||
#include "vm/dwarf.h"
|
||||
#include "vm/dwarf_so_writer.h"
|
||||
#include "vm/hash_map.h"
|
||||
#include "vm/image_snapshot.h"
|
||||
#include "vm/os.h"
|
||||
#include "vm/unwinding_records.h"
|
||||
#include "vm/zone_text_buffer.h"
|
||||
@@ -1758,6 +1760,12 @@ class MachOHeader : public MachOContents {
|
||||
void FinalizeCommands();
|
||||
void ComputeOffsets();
|
||||
|
||||
#if defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64)
|
||||
void GenerateCompactUnwindingInformation(
|
||||
DwarfSharedObjectStream& stream,
|
||||
const GrowableArray<Dwarf::FrameDescriptionEntry>& fdes);
|
||||
#endif
|
||||
|
||||
// Returns the symbol table that is included in the output, which
|
||||
// may or may not be the full symbol table.
|
||||
//
|
||||
@@ -1931,18 +1939,21 @@ void MachOHeader::Finalize() {
|
||||
// debugging information.
|
||||
CreateBSS();
|
||||
|
||||
FinalizeDwarfSections();
|
||||
|
||||
// Generate miscellenous load commands needed for the final output.
|
||||
GenerateMiscellaneousCommands();
|
||||
|
||||
// Generate appropriate unwinding information for the target platform,
|
||||
// for example, unwinding records on Windows.
|
||||
GenerateUnwindingInformation();
|
||||
|
||||
FinalizeDwarfSections();
|
||||
|
||||
// Create and initialize the dynamic and static symbol tables.
|
||||
// Initialize both the static and dynamic symbol tables. Calls to methods
|
||||
// that change section numbering (by either adding or reordering sections
|
||||
// and/or segments) after this point must update the section numbers on
|
||||
// section symbols to match.
|
||||
InitializeSymbolTables();
|
||||
|
||||
// Generate miscellenous load commands needed for the final output.
|
||||
GenerateMiscellaneousCommands();
|
||||
|
||||
// Reorders the added commands as well as adding segments and commands
|
||||
// that must appear at the end of the file.
|
||||
FinalizeCommands();
|
||||
@@ -2086,6 +2097,134 @@ void MachOHeader::CreateBSS() {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64)
|
||||
void MachOHeader::GenerateCompactUnwindingInformation(
|
||||
DwarfSharedObjectStream& stream,
|
||||
const GrowableArray<Dwarf::FrameDescriptionEntry>& fdes) {
|
||||
// Since we currently generate only regular second level pages, there's
|
||||
// no need for common encodings as those are only used by compressed
|
||||
// second level pages.
|
||||
const intptr_t common_encodings_offset = sizeof(mach_o::unwind_info_header);
|
||||
GrowableArray<uint32_t> common_encodings(zone(), 0);
|
||||
|
||||
const intptr_t personalities_offset =
|
||||
common_encodings_offset + common_encodings.length() * kInt32Size;
|
||||
GrowableArray<uint32_t> personalities(zone(), 0);
|
||||
|
||||
// For N FDEs, we generate 2N entries:
|
||||
// * One at the start of the text section with the none encoding.
|
||||
// * One at the start of each FDE's InstructionsSection payload with
|
||||
// the frame encoding.
|
||||
// * For all but the last FDE, one at the end of the InstructionsSection
|
||||
// payload with the none encoding.
|
||||
// No entry is needed for the end of the last FDE, since it is
|
||||
// already recorded as the end of the instructions in the first
|
||||
// page index sentinel entry.
|
||||
const intptr_t second_level_page_entry_count = 2 * fdes.length();
|
||||
const bool second_level_pages_count =
|
||||
(second_level_page_entry_count +
|
||||
(mach_o::UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE_MAX_ENTRIES - 1)) /
|
||||
mach_o::UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE_MAX_ENTRIES;
|
||||
|
||||
const intptr_t first_level_page_indices_offset =
|
||||
personalities_offset + personalities.length() * kInt32Size;
|
||||
// There is one first level page index per second level page, plus an
|
||||
// additional first level page index that serves as as a sentinel and
|
||||
// contains the ending offset of the LSDA entries.
|
||||
const intptr_t first_level_page_indices_count = second_level_pages_count + 1;
|
||||
|
||||
// Align the LSDA indices to the target word size, as the first level page
|
||||
// indices are 12 bytes long and so may not end on a word boundary
|
||||
// on 64-bit systems.
|
||||
const intptr_t lsda_indices_offset =
|
||||
Utils::RoundUp(first_level_page_indices_offset +
|
||||
first_level_page_indices_count *
|
||||
sizeof(mach_o::unwind_info_first_level_page_index),
|
||||
compiler::target::kWordSize);
|
||||
GrowableArray<mach_o::unwind_info_lsda_index> lsda_indices(zone(), 0);
|
||||
|
||||
const intptr_t second_level_pages_offset =
|
||||
lsda_indices_offset +
|
||||
lsda_indices.length() * sizeof(mach_o::unwind_info_lsda_index);
|
||||
// We should only generate at most 2 FDEs and thus 4 entries, so there
|
||||
// should only be one second level page that, if placed right after
|
||||
// the other content, is wholly contained in a 4 * KB page.
|
||||
ASSERT_EQUAL(1, second_level_pages_count);
|
||||
const intptr_t second_level_pages_size =
|
||||
mach_o::UnwindInfoRegularSecondLevelPageSize(
|
||||
second_level_page_entry_count);
|
||||
const intptr_t unwind_info_size =
|
||||
second_level_pages_offset + second_level_pages_size;
|
||||
ASSERT(static_cast<size_t>(unwind_info_size) <=
|
||||
mach_o::UNWIND_INFO_SECOND_LEVEL_PAGE_MAX_SIZE);
|
||||
|
||||
stream.u4(mach_o::UNWIND_INFO_VERSION);
|
||||
stream.u4(common_encodings_offset);
|
||||
stream.u4(common_encodings.length());
|
||||
stream.u4(personalities_offset);
|
||||
stream.u4(personalities.length());
|
||||
stream.u4(first_level_page_indices_offset);
|
||||
stream.u4(first_level_page_indices_count);
|
||||
|
||||
ASSERT_EQUAL(common_encodings_offset, stream.Position());
|
||||
for (const auto& encoding : common_encodings) {
|
||||
stream.u4(encoding);
|
||||
}
|
||||
|
||||
ASSERT_EQUAL(personalities_offset, stream.Position());
|
||||
for (const auto& personality : personalities) {
|
||||
stream.u4(personality);
|
||||
}
|
||||
|
||||
ASSERT_EQUAL(first_level_page_indices_offset, stream.Position());
|
||||
ASSERT_EQUAL(2, first_level_page_indices_count);
|
||||
const auto& first_fde = fdes[0];
|
||||
const auto& last_fde = fdes.Last();
|
||||
stream.OffsetFromSymbol(first_fde.label, 0, kInt32Size);
|
||||
stream.u4(second_level_pages_offset);
|
||||
stream.u4(lsda_indices_offset);
|
||||
// Sentinel that includes the end of the function space as the offset
|
||||
// and has an LSDA index offset at the end of the LSDA index array.
|
||||
stream.OffsetFromSymbol(last_fde.label, last_fde.size, kInt32Size);
|
||||
stream.u4(0); // No second level page.
|
||||
stream.u4(lsda_indices_offset +
|
||||
lsda_indices.length() * sizeof(mach_o::unwind_info_lsda_index));
|
||||
|
||||
stream.Align(compiler::target::kWordSize);
|
||||
ASSERT_EQUAL(lsda_indices_offset, stream.Position());
|
||||
for (const auto& lsda_index : lsda_indices) {
|
||||
stream.u4(lsda_index.function_offset);
|
||||
stream.u4(lsda_index.lsda_offset);
|
||||
}
|
||||
|
||||
ASSERT_EQUAL(second_level_pages_offset, stream.Position());
|
||||
ASSERT_EQUAL(1, second_level_pages_count);
|
||||
stream.u4(mach_o::UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE);
|
||||
stream.u2(sizeof(mach_o::unwind_info_regular_second_level_page_header));
|
||||
stream.u2(second_level_page_entry_count);
|
||||
// Each instructions image starts with the Image header and the
|
||||
// InstructionsSection header.
|
||||
const intptr_t header_size =
|
||||
Image::kHeaderSize + compiler::target::InstructionsSection::HeaderSize();
|
||||
// There are no instructions until the first InstructionsSection payload.
|
||||
stream.OffsetFromSymbol(fdes[0].label, 0, kInt32Size);
|
||||
stream.u4(mach_o::UNWIND_INFO_ENCODING_NONE);
|
||||
for (intptr_t i = 0, n = fdes.length(); i < n - 1; i++) {
|
||||
const auto& fde = fdes[i];
|
||||
// The payload of the InstructionsSection.
|
||||
stream.OffsetFromSymbol(fde.label, header_size, kInt32Size);
|
||||
stream.u4(mach_o::UNWIND_INFO_ENCODING_ARM64_MODE_FRAME);
|
||||
// The padding (if any) between this Image and the next.
|
||||
stream.OffsetFromSymbol(fde.label, fde.size, kInt32Size);
|
||||
stream.u4(mach_o::UNWIND_INFO_ENCODING_NONE);
|
||||
}
|
||||
// The payload of the last InstructionsSection.
|
||||
stream.OffsetFromSymbol(fdes.Last().label, header_size, kInt32Size);
|
||||
stream.u4(mach_o::UNWIND_INFO_ENCODING_ARM64_MODE_FRAME);
|
||||
ASSERT_EQUAL(unwind_info_size, stream.Position());
|
||||
}
|
||||
#endif
|
||||
|
||||
void MachOHeader::GenerateUnwindingInformation() {
|
||||
#if !defined(TARGET_ARCH_IA32)
|
||||
// Unwinding information is added to the text segment in Mach-O files.
|
||||
@@ -2098,42 +2237,50 @@ void MachOHeader::GenerateUnwindingInformation() {
|
||||
// the Mach-O loader, we don't actually need to generate the instructions,
|
||||
// just use an appropriate zerofill section for it.
|
||||
const bool use_zerofill = type_ == SnapshotType::DebugInfo;
|
||||
auto const section_type =
|
||||
use_zerofill ? mach_o::S_ZEROFILL : mach_o::S_REGULAR;
|
||||
const intptr_t alignment = compiler::target::kWordSize;
|
||||
auto add_unwind_section =
|
||||
[&](MachOSegment* segment, const char* sectname,
|
||||
const ZoneWriteStream& stream,
|
||||
const SharedObjectWriter::RelocationArray* relocations = nullptr) {
|
||||
// Not idempotent.
|
||||
ASSERT(segment->FindSection(sectname) == nullptr);
|
||||
auto* const section = new (zone())
|
||||
MachOSection(zone(), sectname,
|
||||
use_zerofill ? mach_o::S_ZEROFILL : mach_o::S_REGULAR,
|
||||
mach_o::S_NO_ATTRIBUTES, !use_zerofill, alignment);
|
||||
section->AddPortion(use_zerofill ? nullptr : stream.buffer(),
|
||||
stream.bytes_written(),
|
||||
use_zerofill ? nullptr : relocations);
|
||||
segment->AddContents(section);
|
||||
};
|
||||
|
||||
#if defined(DART_TARGET_OS_MACOS)
|
||||
// TODO(dartbug.com/60307): Add compact unwind information.
|
||||
USE(section_type);
|
||||
#else
|
||||
ASSERT(text_segment_ != nullptr);
|
||||
if (auto* const text_section =
|
||||
text_segment_->FindSection(mach_o::SECT_TEXT)) {
|
||||
ASSERT(use_zerofill || !text_segment_->HasZerofillSections());
|
||||
// Not idempotent.
|
||||
ASSERT(text_segment_->FindSection(mach_o::SECT_EH_FRAME) == nullptr);
|
||||
|
||||
// For the __eh_frame section, the easiest way to determine the size is to
|
||||
// generate the contents and just discard them if using zerofill.
|
||||
// Generate the DWARF FDEs even for MacOS, because the same information
|
||||
// is used to create the compact unwinding info.
|
||||
GrowableArray<Dwarf::FrameDescriptionEntry> fdes(zone_, 0);
|
||||
for (const auto& portion : text_section->portions()) {
|
||||
ASSERT(portion.label != 0);
|
||||
fdes.Add({portion.label, portion.size});
|
||||
}
|
||||
|
||||
// Even if the unwinding information is not written to the output, it is
|
||||
// generated so a zerofill section of the appropriate size can be created.
|
||||
ZoneWriteStream stream(zone(), DwarfSharedObjectStream::kInitialBufferSize);
|
||||
DwarfSharedObjectStream dwarf_stream(zone_, &stream);
|
||||
Dwarf::WriteCallFrameInformationRecords(&dwarf_stream, fdes);
|
||||
DwarfSharedObjectStream dwarf_stream(zone(), &stream);
|
||||
|
||||
auto* const eh_frame = new (zone())
|
||||
MachOSection(zone(), mach_o::SECT_EH_FRAME, section_type,
|
||||
mach_o::S_NO_ATTRIBUTES, /*has_contents=*/!use_zerofill,
|
||||
/*alignment=*/compiler::target::kWordSize);
|
||||
eh_frame->AddPortion(use_zerofill ? nullptr : dwarf_stream.buffer(),
|
||||
dwarf_stream.bytes_written(),
|
||||
use_zerofill ? nullptr : dwarf_stream.relocations());
|
||||
text_segment_->AddContents(eh_frame);
|
||||
#if defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64)
|
||||
GenerateCompactUnwindingInformation(dwarf_stream, fdes);
|
||||
auto* const sectname = mach_o::SECT_UNWIND_INFO;
|
||||
#else
|
||||
Dwarf::WriteCallFrameInformationRecords(&dwarf_stream, fdes);
|
||||
auto* const sectname = mach_o::SECT_EH_FRAME;
|
||||
#endif
|
||||
|
||||
add_unwind_section(text_segment_, sectname, stream,
|
||||
dwarf_stream.relocations());
|
||||
}
|
||||
#endif // defined(DART_TARGET_OS_MACOS)
|
||||
|
||||
#if defined(UNWINDING_RECORDS_WINDOWS_PRECOMPILER)
|
||||
// Append Windows unwinding instructions as a __unwind_info section at
|
||||
@@ -2141,32 +2288,20 @@ void MachOHeader::GenerateUnwindingInformation() {
|
||||
for (auto* const command : commands_) {
|
||||
if (auto* const segment = command->AsMachOSegment()) {
|
||||
if (segment->IsExecutable()) {
|
||||
// Only more zerofill sections can come after zerofill sections, and
|
||||
// the unwinding instructions cover the entire executable segment up
|
||||
// to the unwinding instructions including zerofill sections.
|
||||
ASSERT(use_zerofill || !segment->HasZerofillSections());
|
||||
// Not idempotent.
|
||||
ASSERT(segment->FindSection(mach_o::SECT_UNWIND_INFO) == nullptr);
|
||||
|
||||
auto* const unwinding_records = new (zone()) MachOSection(
|
||||
zone(), mach_o::SECT_UNWIND_INFO, section_type,
|
||||
mach_o::S_NO_ATTRIBUTES,
|
||||
/*has_contents=*/!use_zerofill, compiler::target::kWordSize);
|
||||
const intptr_t records_size = UnwindingRecordsPlatform::SizeInBytes();
|
||||
const intptr_t section_start = Utils::RoundUp(
|
||||
segment->UnpaddedMemorySize(), unwinding_records->Alignment());
|
||||
const uint8_t* bytes = nullptr;
|
||||
if (!use_zerofill) {
|
||||
ZoneWriteStream stream(zone(), /*initial_size=*/records_size);
|
||||
uint8_t* unwinding_instructions =
|
||||
zone()->Alloc<uint8_t>(records_size);
|
||||
stream.WriteBytes(UnwindingRecords::GenerateRecordsInto(
|
||||
section_start, unwinding_instructions),
|
||||
records_size);
|
||||
ASSERT_EQUAL(records_size, stream.Position());
|
||||
bytes = stream.buffer();
|
||||
}
|
||||
unwinding_records->AddPortion(bytes, records_size);
|
||||
segment->AddContents(unwinding_records);
|
||||
ASSERT_EQUAL(section_start + records_size,
|
||||
segment->UnpaddedMemorySize());
|
||||
ZoneWriteStream stream(zone(), /*initial_size=*/records_size);
|
||||
uint8_t* unwinding_instructions = zone()->Alloc<uint8_t>(records_size);
|
||||
const intptr_t section_start =
|
||||
Utils::RoundUp(segment->UnpaddedMemorySize(), alignment);
|
||||
stream.WriteBytes(UnwindingRecords::GenerateRecordsInto(
|
||||
section_start, unwinding_instructions),
|
||||
records_size);
|
||||
ASSERT_EQUAL(records_size, stream.Position());
|
||||
add_unwind_section(segment, mach_o::SECT_UNWIND_INFO, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user