[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:
Tess Strickland
2025-06-17 06:22:15 -07:00
committed by Commit Queue
parent a04c0d4221
commit 12994040e0
10 changed files with 584 additions and 181 deletions
+106
View File
@@ -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
View File
@@ -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
View File
@@ -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);
};
+40 -35
View File
@@ -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;
+30 -19
View File
@@ -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");
+1
View File
@@ -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
View File
@@ -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);
}
}
}