From bc021bbc0125b495f402faeb3fb8fe615ab9b253 Mon Sep 17 00:00:00 2001 From: Tess Strickland Date: Wed, 19 Nov 2025 07:45:52 -0800 Subject: [PATCH] [vm] Output accompaning relocatable object for Mach-O snapshots. In order to appropriately generate .dSYMs for a snapshot and allow the strip tool to be used on the snapshot afterwards, add a mode that not only outputs the snapshot (sans DWARF information) but also an associated relocatable object file that contains the program's DWARF information. That allows dsymutil to retrieve the DWARF information from the relocatable object when run on the snapshot prior to stripping. To specify that the relocatable object file should be output, use the new --macho-object command line argument to gen_snapshot to specify where the object file should be written. This CL also adds an additional command line argument, --macho-reduce-padding, which reduces the alignment used for segments and the text/const sections in Mach-O outputs from 16KB to 64 bytes. The larger padding is needed for some uses, like non-native loading of Mach-O objects, but can be elided for other uses like Flutter builds. TEST=vm/dart/use_dwarf_stack_traces_flag_test vm/dart/use_macho_reduce_padding_flag_test Change-Id: I2bf4bacb70c41299b8b6fdb7635c2374acf7a07d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/457420 Reviewed-by: Slava Egorov Commit-Queue: Tess Strickland --- pkg/native_stack_traces/lib/src/macho.dart | 27 +- runtime/bin/dart_api_win.c | 28 + runtime/bin/gen_snapshot.cc | 17 +- runtime/include/dart_api.h | 52 + runtime/platform/mach_o.h | 95 +- .../tests/vm/dart/exported_symbols_test.dart | 1 + .../use_dwarf_stack_traces_flag_test.dart | 55 + .../use_macho_reduce_padding_flag_test.dart | 180 +++ runtime/vm/dart_api_impl.cc | 116 +- runtime/vm/datastream.cc | 7 +- runtime/vm/image_snapshot.cc | 36 +- runtime/vm/image_snapshot.h | 58 +- runtime/vm/mach_o.cc | 1122 ++++++++++++++--- runtime/vm/mach_o.h | 12 +- runtime/vm/so_writer.cc | 91 +- runtime/vm/so_writer.h | 29 +- 16 files changed, 1602 insertions(+), 324 deletions(-) create mode 100644 runtime/tests/vm/dart/use_macho_reduce_padding_flag_test.dart diff --git a/pkg/native_stack_traces/lib/src/macho.dart b/pkg/native_stack_traces/lib/src/macho.dart index 1e0b38ed388..cb25baf9f9e 100644 --- a/pkg/native_stack_traces/lib/src/macho.dart +++ b/pkg/native_stack_traces/lib/src/macho.dart @@ -189,6 +189,7 @@ class SegmentCommand extends LoadCommand { final int initprot; final int nsects; final int flags; + final List
sectionsInOrder; final Map sections; SegmentCommand._( @@ -203,8 +204,10 @@ class SegmentCommand extends LoadCommand { this.initprot, this.nsects, this.flags, - this.sections) - : super._(); + this.sectionsInOrder) + : sections = Map.fromEntries( + sectionsInOrder.map((s) => MapEntry(s.sectname, s))), + super._(); static SegmentCommand fromReader(Reader reader, int cmd, int cmdsize) { final segname = reader.readFixedLengthNullTerminatedString(16); @@ -216,13 +219,13 @@ class SegmentCommand extends LoadCommand { final initprot = _readMachOUint32(reader); final nsects = _readMachOUint32(reader); final flags = _readMachOUint32(reader); - final sections = {}; + final sectionsInOrder =
[]; for (int i = 0; i < nsects; i++) { final section = Section.fromReader(reader); - sections[section.sectname] = section; + sectionsInOrder.add(section); } return SegmentCommand._(cmd, cmdsize, segname, vmaddr, vmsize, fileoff, - filesize, maxprot, initprot, nsects, flags, sections); + filesize, maxprot, initprot, nsects, flags, sectionsInOrder); } @override @@ -235,7 +238,7 @@ class SegmentCommand extends LoadCommand { ..write(' at offset 0x') ..writeln(fileoff.toRadixString(16)); buffer.writeln('Sections:'); - for (final section in sections.values) { + for (final section in sectionsInOrder) { section.writeToStringBuffer(buffer); buffer.writeln(); } @@ -698,14 +701,14 @@ class MachOHeader { } class MachO extends DwarfContainer { - final MachOHeader _header; + final MachOHeader header; final List _commands; final SymbolTable _symbolTable; final SegmentCommand? _dwarfSegment; final StringTable? _debugStringTable; final StringTable? _debugLineStringTable; - MachO._(this._header, this._commands, this._symbolTable, this._dwarfSegment, + MachO._(this.header, this._commands, this._symbolTable, this._dwarfSegment, this._debugStringTable, this._debugLineStringTable); static MachO? fromReader(Reader machOReader) { @@ -770,19 +773,19 @@ class MachO extends DwarfContainer { static MachO? fromFile(String fileName) => MachO.fromReader(Reader.fromFile(MachO.handleDSYM(fileName))); - bool get isDSYM => _header.isDSYM; + bool get isDSYM => header.isDSYM; bool get hasDwarf => _dwarfSegment != null; Reader applyWordSizeAndEndian(Reader reader) => Reader.fromTypedData(reader.bdata, - wordSize: _header.wordSize, endian: _header.endian); + wordSize: header.wordSize, endian: header.endian); Iterable get commands => _commands; Iterable commandsWhereType() => _commands.whereType(); @override - String? get architecture => CpuType.fromCode(_header.cputype)?.dartName; + String? get architecture => CpuType.fromCode(header.cputype)?.dartName; @override Reader? abbreviationsTableReader(Reader containerReader) => @@ -834,7 +837,7 @@ class MachO extends DwarfContainer { ..writeln(' Header') ..writeln('----------------------------------------') ..writeln(''); - _header.writeToStringBuffer(buffer); + header.writeToStringBuffer(buffer); buffer ..writeln('') ..writeln('') diff --git a/runtime/bin/dart_api_win.c b/runtime/bin/dart_api_win.c index 34a1e3b392e..58fc4544d40 100644 --- a/runtime/bin/dart_api_win.c +++ b/runtime/bin/dart_api_win.c @@ -445,6 +445,15 @@ typedef Dart_Handle (*Dart_CreateAppAOTSnapshotAsBinaryType)( void*, const char*, const char*); +typedef Dart_Handle (*Dart_CreateAppAOTSnapshotAndRelocatableObjectType)( + Dart_AotBinaryFormat, + Dart_StreamingWriteCallback, + void*, + void*, + bool, + void*, + const char*, + const char*); typedef Dart_Handle (*Dart_CreateVMAOTSnapshotAsAssemblyType)( Dart_StreamingWriteCallback, void*); @@ -756,6 +765,8 @@ static Dart_CreateAppAOTSnapshotAsElfsType Dart_CreateAppAOTSnapshotAsElfsFn = NULL; static Dart_CreateAppAOTSnapshotAsBinaryType Dart_CreateAppAOTSnapshotAsBinaryFn = NULL; +static Dart_CreateAppAOTSnapshotAndRelocatableObjectType + Dart_CreateAppAOTSnapshotAndRelocatableObjectFn = NULL; static Dart_CreateVMAOTSnapshotAsAssemblyType Dart_CreateVMAOTSnapshotAsAssemblyFn = NULL; static Dart_SortClassesType Dart_SortClassesFn = NULL; @@ -1346,6 +1357,9 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { Dart_CreateAppAOTSnapshotAsBinaryFn = (Dart_CreateAppAOTSnapshotAsBinaryType)GetProcAddress( process, "Dart_CreateAppAOTSnapshotAsBinary"); + Dart_CreateAppAOTSnapshotAndRelocatableObjectFn = + (Dart_CreateAppAOTSnapshotAndRelocatableObjectType)GetProcAddress( + process, "Dart_CreateAppAOTSnapshotAndRelocatableObject"); Dart_CreateVMAOTSnapshotAsAssemblyFn = (Dart_CreateVMAOTSnapshotAsAssemblyType)GetProcAddress( process, "Dart_CreateVMAOTSnapshotAsAssembly"); @@ -2657,6 +2671,20 @@ Dart_Handle Dart_CreateAppAOTSnapshotAsBinary( identifier, path); } +Dart_Handle Dart_CreateAppAOTSnapshotAndRelocatableObject( + Dart_AotBinaryFormat format, + Dart_StreamingWriteCallback callback, + void* snapshot_callback_data, + void* object_callback_data, + bool stripped, + void* debug_callback_data, + const char* identifier, + const char* path) { + return Dart_CreateAppAOTSnapshotAndRelocatableObjectFn( + format, callback, snapshot_callback_data, object_callback_data, stripped, + debug_callback_data, identifier, path); +} + Dart_Handle Dart_CreateVMAOTSnapshotAsAssembly( Dart_StreamingWriteCallback callback, void* callback_data) { diff --git a/runtime/bin/gen_snapshot.cc b/runtime/bin/gen_snapshot.cc index b59fb29429f..b4cafce7dd8 100644 --- a/runtime/bin/gen_snapshot.cc +++ b/runtime/bin/gen_snapshot.cc @@ -111,6 +111,7 @@ static const char* const kSnapshotKindNames[] = { V(assembly, assembly_filename) \ V(elf, elf_filename) \ V(macho, macho_filename) \ + V(macho_object, macho_object_filename) \ V(loading_unit_manifest, loading_unit_manifest_filename) \ V(save_debugging_info, debugging_info_filename) \ V(save_obfuscation_map, obfuscation_map_filename) @@ -640,6 +641,7 @@ static void CreateAndWritePrecompiledSnapshot() { Dart_AotBinaryFormat format; const char* kind_str = nullptr; const char* filename = nullptr; + const char* object_filename = nullptr; // Default to the assembly ones just to avoid having to type-specify here. auto* next_callback = NextAsmCallback; auto* create_multiple_callback = Dart_CreateAppAOTSnapshotAsAssemblies; @@ -659,6 +661,7 @@ static void CreateAndWritePrecompiledSnapshot() { case kAppAOTMachODylib: kind_str = "MachO dynamic library"; filename = macho_filename; + object_filename = macho_object_filename; format = Dart_AotBinaryFormat_MachO_Dylib; // Not currently implemented. next_callback = nullptr; @@ -691,9 +694,17 @@ static void CreateAndWritePrecompiledSnapshot() { if (debugging_info_filename != nullptr) { debug_file = OpenFile(debugging_info_filename); } - result = Dart_CreateAppAOTSnapshotAsBinary(format, StreamingWriteCallback, - file, strip, debug_file, - identifier, filename); + if (object_filename != nullptr) { + File* object_file = OpenFile(object_filename); + result = Dart_CreateAppAOTSnapshotAndRelocatableObject( + format, StreamingWriteCallback, file, object_file, strip, debug_file, + identifier, object_filename); + object_file->Release(); + } else { + result = Dart_CreateAppAOTSnapshotAsBinary(format, StreamingWriteCallback, + file, strip, debug_file, + identifier, filename); + } if (debug_file != nullptr) debug_file->Release(); if (identifier != nullptr) { free(identifier); diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h index be40c8a1fff..dad30ce21cd 100644 --- a/runtime/include/dart_api.h +++ b/runtime/include/dart_api.h @@ -4175,6 +4175,58 @@ Dart_CreateAppAOTSnapshotAsBinary(Dart_AotBinaryFormat format, const char* identifier, const char* path); +/** + * Creates a precompiled snapshot along with a relocatable object file. + * - A root library must have been loaded. + * - Dart_Precompile must have been called. + * + * Outputs both a snapshot and a relocatable object file in + * the specified binary format defining the symbols + * - _kDartVmSnapshotData + * - _kDartVmSnapshotInstructions + * - _kDartIsolateSnapshotData + * - _kDartIsolateSnapshotInstructions + * Whether or not the snapshot is stripped, the relocatable object file + * contains all debugging information. + * + * The shared library should be dynamically loaded by the embedder. + * Running this snapshot requires a VM compiled with DART_PRECOMPILED_RUNTIME. + * The kDartVmSnapshotData and kDartVmSnapshotInstructions should be passed to + * Dart_Initialize. The kDartIsolateSnapshotData and + * kDartIsolateSnapshotInstructions should be passed to Dart_CreateIsolate. + * + * The callback will be invoked one or more times to provide the binary output. + * + * If stripped is true, then the binary output will not include DWARF + * debugging sections. + * + * If debug_callback_data is provided, debug_callback_data will be used with + * the callback to provide separate debugging information. + * + * The identifier should be an appropriate string for identifying the resulting + * dynamic library. For example, the identifier is used in ID_DYLIB and + * CODE_SIGNATURE load commands for Mach-O dynamic libraries and for DW_AT_name + * in the Dart progam's root DWARF compilation unit. + * + * The path should be the full path of the resulting relocatable object file. + * Currently, it is only used in Mach-O relocatable object files and snapshots + * to create an appropriate N_OSO symbolic debugging variable + * so dsymutil can be used. Note that an external strip utility is needed to + * remove the N_OSO symbolic debugging variable after dsymutil usage. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_API_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAndRelocatableObject( + Dart_AotBinaryFormat format, + Dart_StreamingWriteCallback callback, + void* snapshot_callback_data, + void* object_callback_data, + bool stripped, + void* debug_callback_data, + const char* identifier, + const char* path); + /** * Like Dart_CreateAppAOTSnapshotAsAssembly, but only includes * kDartVmSnapshotData and kDartVmSnapshotInstructions. It also does diff --git a/runtime/platform/mach_o.h b/runtime/platform/mach_o.h index f104279b19a..07fa36fd7f0 100644 --- a/runtime/platform/mach_o.h +++ b/runtime/platform/mach_o.h @@ -86,7 +86,7 @@ static constexpr uint32_t MH_CIGAM_64 = 0xcffaedfe; // Filetypes for the Mach-O header. -// A relocatable object file (e.g., an executable). +// A relocatable object file that has all sections in a single unnamed segment. static constexpr uint32_t MH_OBJECT = 0x1; // A dynamically bound shared library. static constexpr uint32_t MH_DYLIB = 0x6; @@ -167,7 +167,7 @@ struct segment_command { uint32_t nsects; // uint32_t flags; - // section_command[] + // section[] }; // Contains the same fields as segment_command, but the starting memory @@ -184,7 +184,7 @@ struct segment_command_64 { vm_prot_t initprot; uint32_t nsects; uint32_t flags; - // section_command_64[] + // section_64[] }; struct section { @@ -271,6 +271,12 @@ static constexpr char SECT_DEBUG_ABBREV[] = "__debug_abbrev"; // table and code signature. static constexpr char SEG_LINKEDIT[] = "__LINKEDIT"; +// Segment/section names used for relocatable object files. +static constexpr char SEG_UNNAMED[] = ""; + +static constexpr char SEG_LD[] = "__LD"; +static constexpr char SECT_COMPACT_UNWIND[] = "__compact_unwind"; + struct symtab_command { uint32_t cmd; // LC_SYMTAB uint32_t cmdsize; @@ -654,6 +660,89 @@ struct unwind_info_header { // ... regular and compressed second level pages ... }; +// Relocation information in relocatable objects. The reloff field in +// the section and section_64 structs gives the starting file offset for +// the section's relocation information, and nreloc gives the number of +// structs found starting from that offset. +struct relocation_info { + // The "address" of the relocation entry is the offset into the + // corresponding section. + int32_t address; + // The metadata contains the following bit fields, from low to high: + // 0-23: The index of the section or symbol on which this relation entry + // is based. Note that, as with other parts of the Mach-O format, + // section indices are 1-based, while symbol indices are 0-based. + // 24: Whether or not this relocation is PC-relative. + // 25-26: log2(n), where n is the size of the relocation entry in bytes. + // 27: Whether or not this relocation is "external". An external + // relocation is based on a symbol in the symbol table. If false, + // then the relocation is based on a section instead. + // 28-31: The type of the relocation entry, see the RELOC_TYPE_* + // constants below. + uint32_t metadata; +}; + +// The number of low bits used to store the symbol or section index in +// the relocation entry. +static constexpr uint32_t RELOC_METADATA_INDEX_BITS = 24; + +// The size of the relocation in the section contents. +static constexpr uint32_t RELOC_SIZE_BYTE = 0 << 25; +static constexpr uint32_t RELOC_SIZE_2BYTES = 1 << 25; +static constexpr uint32_t RELOC_SIZE_4BYTES = 2 << 25; +static constexpr uint32_t RELOC_SIZE_8BYTES = 3 << 25; + +// This bit is set if the index in the payload is the index of a symbol +// in the symbol table. It is unset if the index in the payload is the +// (1-based) index of a section. +static constexpr uint32_t RELOC_EXTERN = 1 << 27; + +// For our purposes, the MachOWriter only emits two types of relocation entries: +// UNSIGNED and SUBTRACTOR. The numeric encoding of these types are +// platform dependent, but the semantics are the same for both X64 and ARM64: +// +// Consider a relocation comprised of the following parts: +// (Target + TOffset) - (Source + SOffset) +// at the offset ROffset in section Section with virtual address +// RAddress = Section.addr + ROffset +// in the relocatable object. +// +// An UNSIGNED relocation entry specifies Target. It refers either to a section +// or a symbol via the stored index. +// +// A SUBTRACTOR relocation entry specifies Source. It always refers to a symbol, +// never a section. Additionally, SUBTRACTOR relocation entries are always found +// immediately before the corresponding UNSIGNED relocation entries. +// +// The offsets are combined into a single addend, which is stored in the section +// contents at the offset of the relocation. If the relocation entries are +// symbol based, then +// addend = TOffset - SOffset +// and for section-based relocation entries (which have no Source/SOffset), +// addend = RAddress + TOffset +// That is, the virtual address of the relocation is included in the addend for +// section-based relocation entries. + +// X64-specific constants for relocation types. + +// A relocation specifying a section or symbol that is the target +// of the relocation. +static constexpr uint32_t RELOC_TYPE_X64_UNSIGNED = 0 << 28; +// A relocation specifying a symbol that is the source of the relocation. +// Note that in the list of the relocations, the source comes immediately +// _before_ the target (UNSIGNED) entry that it is subtracted from. +static constexpr uint32_t RELOC_TYPE_X64_SUBTRACTOR = 5 << 28; + +// ARM64-specific constants for relocation types. + +// A relocation specifying a section or symbol that is the target +// of the relocation. +static constexpr uint32_t RELOC_TYPE_ARM64_UNSIGNED = 0 << 28; +// A relocation specifying a symbol that is the source of the relocation. +// Note that in the list of the relocations, the source comes immediately +// _before_ the target (UNSIGNED) entry that it is subtracted from. +static constexpr uint32_t RELOC_TYPE_ARM64_SUBTRACTOR = 1 << 28; + #pragma pack(pop) } // namespace mach_o diff --git a/runtime/tests/vm/dart/exported_symbols_test.dart b/runtime/tests/vm/dart/exported_symbols_test.dart index c27f5250725..b1d8efd7838 100644 --- a/runtime/tests/vm/dart/exported_symbols_test.dart +++ b/runtime/tests/vm/dart/exported_symbols_test.dart @@ -70,6 +70,7 @@ main() { "Dart_CompileAll", "Dart_CompileToKernel", "Dart_CopyUTF8EncodingOfString", + "Dart_CreateAppAOTSnapshotAndRelocatableObject", "Dart_CreateAppAOTSnapshotAsAssemblies", "Dart_CreateAppAOTSnapshotAsAssembly", "Dart_CreateAppAOTSnapshotAsElf", diff --git a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart index 9ec337015e3..253b72011a5 100644 --- a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart +++ b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart @@ -33,6 +33,7 @@ Future main() async { [ runElf, runMachODylib, + runMachODsym, // Don't run assembly on Windows since DLLs don't contain DWARF. if (!Platform.isWindows) runAssembly, ], @@ -311,6 +312,60 @@ Future runMachODylib(String tempDir, String scriptDill) async { ); } +class DwarfDsymState extends DwarfState { + DwarfDsymState( + super.output, + super.outputWithOppositeFlag, + super.snapshot, + super.debugInfo, + ); + + @override + String get description => 'dSYM'; + + @override + Future check(Trace trace, Dwarf dwarf) => + compareTraces(trace, output, outputWithOppositeFlag, dwarf); +} + +Future runMachODsym(String tempDir, String scriptDill) async { + final dsymutil = llvmTool('dsymutil', verbose: true)!; + + print("Generating Mach-O snapshots"); + final snapshotPath = path.join(tempDir, 'dwarf_dsym.dylib'); + final objectPath = path.join(tempDir, 'dwarf_dsym.o'); + final debugInfoPath = path.join(tempDir, 'debug_info_dsym.so'); + await run(genSnapshot, [ + '--dwarf-stack-traces-mode', + '--save-debugging-info=$debugInfoPath', + '--snapshot-kind=app-aot-macho-dylib', + '--macho=$snapshotPath', + '--macho-object=$objectPath', + scriptDill, + ]); + + print("Generating dSYM"); + final dsymPath = path.join(tempDir, 'dwarf_dsym.dSYM'); + await run(dsymutil, ['-o', dsymPath, snapshotPath]); + + final dsym = Dwarf.fromFile(dsymPath)!; + final debugInfo = Dwarf.fromFile(debugInfoPath)!; + + // Run the resulting Dwarf-AOT compiled script. + print("Generating Mach-O snapshot outputs"); + final output = await runTestProgram(dartPrecompiledRuntime, [ + '--dwarf-stack-traces-mode', + snapshotPath, + scriptDill, + ]); + final outputWithOppositeFlag = await runTestProgram( + dartPrecompiledRuntime, + ['--no-dwarf-stack-traces-mode', snapshotPath, scriptDill], + ); + + return DwarfDsymState(output, outputWithOppositeFlag, dsym, debugInfo); +} + Future compareTraces( List nonDwarfTrace, DwarfTestOutput output1, diff --git a/runtime/tests/vm/dart/use_macho_reduce_padding_flag_test.dart b/runtime/tests/vm/dart/use_macho_reduce_padding_flag_test.dart new file mode 100644 index 00000000000..f1cc91a75a3 --- /dev/null +++ b/runtime/tests/vm/dart/use_macho_reduce_padding_flag_test.dart @@ -0,0 +1,180 @@ +// 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 ensures that using the --macho-reduce-padding flag actually +// reduces the padding used for segments and text/const sections in Mach-O +// outputs. + +import "dart:async"; +import "dart:io"; + +import 'package:native_stack_traces/src/macho.dart' show MachO, SegmentCommand; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +import 'use_flag_test_helper.dart'; + +Future main() async { + if (!isAOTRuntime) { + return; // Running in JIT: AOT binaries not available. + } + + if (Platform.isAndroid) { + return; // SDK tree and dart_bootstrap not available on the test device. + } + + // 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('macho-reduce-padding', (String tempDir) async { + // We have to use the program in its original location so it can use + // the dart:_internal library (as opposed to adding it as an OtherResources + // option to the test). + final scriptPath = path.join( + sdkDir, + 'runtime', + 'tests', + 'vm', + 'dart', + 'use_dwarf_stack_traces_flag_program.dart', + ); + final scriptDill = path.join(tempDir, 'flag_program.dill'); + + // Compile script to Kernel IR. + await run(genKernel, [ + '--aot', + '--platform=$platformDill', + '-o', + scriptDill, + scriptPath, + ]); + + final defaultPaddingTestCase = await createTestCase( + tempDir, + scriptDill, + 'default_padding', + 14, + const [], + ); + + final reducedPaddingTestCase = await createTestCase( + tempDir, + scriptDill, + 'reduced_padding', + 6, + const ['--macho-reduce-padding'], + ); + + test( + "Testing default MachO padding", + checkTestCase(defaultPaddingTestCase), + ); + + test( + "Testing reduced MachO padding", + checkTestCase(reducedPaddingTestCase), + ); + }); +} + +class TestCase { + int segmentAlignment; + MachO snapshot; + MachO debugInfo; + MachO relocatableObject; + + TestCase( + this.segmentAlignment, + this.snapshot, + this.debugInfo, + this.relocatableObject, + ); +} + +Future createTestCase( + String tempDir, + String scriptDill, + String prefix, + int alignment, + List extraArgs, +) async { + final dsymutil = llvmTool('dsymutil', verbose: true)!; + + print("Generating Mach-O snapshots with segment alignment ${1 << alignment}"); + final snapshotPath = path.join(tempDir, '$prefix.dylib'); + final objectPath = path.join(tempDir, '$prefix.o'); + final debugInfoPath = path.join(tempDir, 'debug_info_$prefix.so'); + await run(genSnapshot, [ + '--dwarf-stack-traces-mode', + '--save-debugging-info=$debugInfoPath', + '--snapshot-kind=app-aot-macho-dylib', + '--macho=$snapshotPath', + '--macho-object=$objectPath', + ...extraArgs, + scriptDill, + ]); + + // Make sure dsymutil doesn't have any issue with the snapshot or + // relocatable object. + print("Generating dSYM for segment alignment ${1 << alignment}"); + final dsymPath = path.join(tempDir, '$prefix.dSYM'); + await run(dsymutil, ['-o', dsymPath, snapshotPath]); + + return TestCase( + alignment, + MachO.fromFile(snapshotPath)!, + MachO.fromFile(debugInfoPath)!, + MachO.fromFile(objectPath)!, + ); +} + +int align(int n, int alignLog2) { + final alignment = 1 << alignLog2; + final extra = n % alignment; + final padding = (extra == 0) ? 0 : (alignment - extra); + return n + padding; +} + +void Function() checkTestCase(TestCase testCase) => () { + for (final macho in [ + testCase.snapshot, + testCase.debugInfo, + testCase.relocatableObject, + ]) { + for (final segment in macho.commandsWhereType()) { + print(segment); + // Skip the linkedit segment, which isn't padded to a specific alignment. + if (segment.segname == '__LINKEDIT') continue; + int contentsSize = 0; + if (segment.segname == '__TEXT') { + // The header and load commands are contained within the initial (text) + // segment for non-relocatable objects. + contentsSize += macho.header.size + macho.header.sizeofcmds; + } + for (final section in segment.sectionsInOrder) { + expect( + section.align, + lessThanOrEqualTo(testCase.segmentAlignment), + reason: + 'Section "${section.segname}", "${section.sectname}" has ' + 'an alignment of ${section.align} > ${testCase.segmentAlignment}', + ); + contentsSize = align(contentsSize, section.align); + contentsSize += section.size; + } + expect( + segment.filesize, + lessThanOrEqualTo(align(contentsSize, testCase.segmentAlignment)), + ); + } + } +}; diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc index 33ab56e141b..cea93b4d8e9 100644 --- a/runtime/vm/dart_api_impl.cc +++ b/runtime/vm/dart_api_impl.cc @@ -6616,7 +6616,7 @@ static constexpr intptr_t kAssemblyInitialSize = 512 * KB; static constexpr intptr_t kInitialSize = 2 * MB; static constexpr intptr_t kInitialDebugSize = 1 * MB; -static void CreateAppAOTSnapshot( +static void CreateAppAOTSnapshotHelper( Dart_StreamingWriteCallback callback, void* callback_data, bool strip, @@ -6626,7 +6626,8 @@ static void CreateAppAOTSnapshot( LoadingUnitSerializationData* unit, uint32_t program_hash, const char* identifier, - const char* path) { + const char* path, + void* object_callback_data) { Thread* T = Thread::Current(); NOT_IN_PRODUCT(TimelineBeginEndScope tbes2(T, Timeline::GetIsolateStream(), @@ -6671,6 +6672,14 @@ static void CreateAppAOTSnapshot( : kInitialSize, callback, callback_data); + // Should not be directly used below as writing to it when + // [object_callback_data] is null causes a crash. Instead, use + // [object_stream], which is appropriately nullptr in this case. + StreamingWriteStream object_stream_value(kInitialSize, callback, + object_callback_data); + StreamingWriteStream* object_stream = + object_callback_data != nullptr ? &object_stream_value : nullptr; + auto const use_output_writer = [&](ImageWriter* image_writer) { FullSnapshotWriter writer(Snapshot::kFullAOT, &vm_snapshot_data, &isolate_snapshot_data, image_writer, @@ -6693,9 +6702,15 @@ static void CreateAppAOTSnapshot( so = new (Z) ElfWriter(Z, &output_stream, SharedObjectWriter::Type::Snapshot, dwarf); } else if (format == Dart_AotBinaryFormat_MachO_Dylib) { + MachOWriter* object_writer = nullptr; + if (object_stream != nullptr) { + object_writer = new (Z) + MachOWriter(Z, object_stream, SharedObjectWriter::Type::Object, + identifier, path, dwarf); + } so = new (Z) MachOWriter(Z, &output_stream, SharedObjectWriter::Type::Snapshot, - identifier, path, dwarf); + identifier, path, dwarf, object_writer); } if (format == Dart_AotBinaryFormat_Assembly) { @@ -6704,13 +6719,44 @@ static void CreateAppAOTSnapshot( strip, debug_so); use_output_writer(&assembly_writer); } else { - BlobImageWriter blob_writer(T, &vm_snapshot_instructions, - &isolate_snapshot_instructions, - deobfuscation_trie, debug_so, so); + BlobImageWriter blob_writer( + T, &vm_snapshot_instructions, &isolate_snapshot_instructions, + deobfuscation_trie, debug_so, so, + /*needs_unique_names=*/object_callback_data != nullptr); use_output_writer(&blob_writer); } } +static void CreateAppAOTProgramSnapshot(Dart_StreamingWriteCallback callback, + void* callback_data, + bool strip, + Dart_AotBinaryFormat format, + void* debug_callback_data, + const char* identifier = nullptr, + const char* path = nullptr, + void* object_callback_data = nullptr) { + CreateAppAOTSnapshotHelper( + callback, callback_data, strip, format, debug_callback_data, + /*units=*/nullptr, + /*unit=*/nullptr, + /*program_hash=*/0, identifier, path, object_callback_data); +} + +static void CreateAppAOTUnitSnapshot( + Dart_StreamingWriteCallback callback, + void* callback_data, + bool strip, + Dart_AotBinaryFormat format, + void* debug_callback_data, + GrowableArray* units, + LoadingUnitSerializationData* unit, + uint32_t program_hash) { + CreateAppAOTSnapshotHelper(callback, callback_data, strip, format, + debug_callback_data, units, unit, program_hash, + /*identifier=*/nullptr, /*path=*/nullptr, + /*object_callback_data=*/nullptr); +} + static void Split(Dart_CreateLoadingUnitCallback next_callback, void* next_callback_data, bool strip, @@ -6746,10 +6792,9 @@ static void Split(Dart_CreateLoadingUnitCallback next_callback, next_callback(next_callback_data, id, &write_callback_data, &write_debug_callback_data); } - CreateAppAOTSnapshot(write_callback, write_callback_data, strip, format, - write_debug_callback_data, &data, data[id], - program_hash, /*identifier=*/nullptr, - /*path=*/nullptr); + CreateAppAOTUnitSnapshot(write_callback, write_callback_data, strip, format, + write_debug_callback_data, &data, data[id], + program_hash); { TransitionVMToNative transition(T); close_callback(write_callback_data); @@ -6779,10 +6824,9 @@ Dart_CreateAppAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback, // Mark as not split. T->isolate_group()->object_store()->set_loading_units(Object::null_array()); - CreateAppAOTSnapshot(callback, callback_data, strip, - Dart_AotBinaryFormat_Assembly, debug_callback_data, - nullptr, nullptr, 0, /*identifier=*/nullptr, - /*path=*/nullptr); + CreateAppAOTProgramSnapshot(callback, callback_data, strip, + Dart_AotBinaryFormat_Assembly, + debug_callback_data); return Api::Success(); #endif @@ -6858,9 +6902,8 @@ Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback, // Mark as not split. T->isolate_group()->object_store()->set_loading_units(Object::null_array()); - CreateAppAOTSnapshot(callback, callback_data, strip, Dart_AotBinaryFormat_Elf, - debug_callback_data, nullptr, nullptr, 0, - /*identifier=*/nullptr, /*path=*/nullptr); + CreateAppAOTProgramSnapshot(callback, callback_data, strip, + Dart_AotBinaryFormat_Elf, debug_callback_data); return Api::Success(); #endif @@ -6912,9 +6955,42 @@ Dart_CreateAppAOTSnapshotAsBinary(Dart_AotBinaryFormat format, // Mark as not split. T->isolate_group()->object_store()->set_loading_units(Object::null_array()); - CreateAppAOTSnapshot(callback, callback_data, strip, format, - debug_callback_data, nullptr, nullptr, 0, identifier, - path); + CreateAppAOTProgramSnapshot(callback, callback_data, strip, format, + debug_callback_data, identifier, path); + + return Api::Success(); +#endif +} + +DART_EXPORT Dart_Handle Dart_CreateAppAOTSnapshotAndRelocatableObject( + Dart_AotBinaryFormat format, + Dart_StreamingWriteCallback callback, + void* snapshot_callback_data, + void* object_callback_data, + bool strip, + void* debug_callback_data, + const char* identifier, + const char* path) { +#if defined(TARGET_ARCH_IA32) + return Api::NewError("AOT compilation is not supported on IA32."); +#elif !defined(DART_PRECOMPILER) + return Api::NewError( + "This VM was built without support for AOT compilation."); +#else + if (format != Dart_AotBinaryFormat_MachO_Dylib) { + return Api::NewError( + "Relocatable objects are currently only supported for Mach-O output."); + } + DARTSCOPE(Thread::Current()); + API_TIMELINE_DURATION(T); + CHECK_NULL(callback); + + // Mark as not split. + T->isolate_group()->object_store()->set_loading_units(Object::null_array()); + + CreateAppAOTProgramSnapshot(callback, snapshot_callback_data, strip, format, + debug_callback_data, identifier, path, + object_callback_data); return Api::Success(); #endif diff --git a/runtime/vm/datastream.cc b/runtime/vm/datastream.cc index 2dde0cfa6c0..3af99d45506 100644 --- a/runtime/vm/datastream.cc +++ b/runtime/vm/datastream.cc @@ -33,7 +33,12 @@ void ZoneWriteStream::Realloc(intptr_t new_size) { } StreamingWriteStream::~StreamingWriteStream() { - Flush(); + // Allow a StreamingWriteStream to be created for nullptr callback + // data as long as no data is ever written. + ASSERT(callback_data_ != nullptr || Position() == 0); + if (BaseWriteStream::Position() != 0) { + Flush(); + } free(buffer_); } diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc index 989015b3ccb..a7efdef18dd 100644 --- a/runtime/vm/image_snapshot.cc +++ b/runtime/vm/image_snapshot.cc @@ -222,9 +222,12 @@ bool ObjectOffsetTrait::IsKeyEqual(Pair pair, Key key) { #if defined(DART_PRECOMPILER) ImageWriter::ImageWriter(Thread* t, bool generates_assembly, + bool needs_unique_names, const Trie* deobfuscation_trie) #else -ImageWriter::ImageWriter(Thread* t, bool generates_assembly) +ImageWriter::ImageWriter(Thread* t, + bool generates_assembly, + bool needs_unique_names) #endif : thread_(ASSERT_NOTNULL(t)), zone_(t->zone()), @@ -235,7 +238,8 @@ ImageWriter::ImageWriter(Thread* t, bool generates_assembly) #if defined(DART_PRECOMPILER) namer_(t->zone(), deobfuscation_trie, - /*for_assembly=*/generates_assembly), + /*for_assembly=*/generates_assembly, + /*create_unique_names=*/needs_unique_names), #endif image_type_(TagObjectTypeAsReadOnly(zone_, "Image")), instructions_section_type_( @@ -1225,7 +1229,10 @@ AssemblyImageWriter::AssemblyImageWriter( const Trie* deobfuscation_trie, bool strip, SharedObjectWriter* debug_so) - : ImageWriter(thread, /*generates_assembly=*/true, deobfuscation_trie), + : ImageWriter(thread, + /*generates_assembly=*/true, + /*needs_unique_names=*/true, + deobfuscation_trie), assembly_stream_(stream), assembly_dwarf_( AddDwarfIfUnstripped(zone_, strip, debug_so, deobfuscation_trie)), @@ -1380,6 +1387,10 @@ void ImageWriter::SnapshotTextObjectNamer::ModifyForAssembly( buffer->Clear(); buffer->AddString(result); } +} + +void ImageWriter::SnapshotTextObjectNamer::EnsureUniqueName( + BaseTextBuffer* buffer) { auto* const pair = usage_count_.Lookup(buffer->buffer()); if (pair == nullptr) { usage_count_.Insert({buffer->buffer(), 1}); @@ -1401,6 +1412,9 @@ const char* ImageWriter::SnapshotTextObjectNamer::SnapshotNameFor( if (for_assembly_) { ModifyForAssembly(&printer); } + if (create_unique_names_) { + EnsureUniqueName(&printer); + } return printer.buffer(); } @@ -1430,6 +1444,9 @@ const char* ImageWriter::SnapshotTextObjectNamer::SnapshotNameFor( if (for_assembly_) { ModifyForAssembly(&printer); } + if (create_unique_names_) { + EnsureUniqueName(&printer); + } return printer.buffer(); } @@ -1861,15 +1878,20 @@ BlobImageWriter::BlobImageWriter(Thread* thread, NonStreamingWriteStream* isolate_instructions, const Trie* deobfuscation_trie, SharedObjectWriter* debug_so, - SharedObjectWriter* so) - : ImageWriter(thread, /*generates_assembly=*/false, deobfuscation_trie), + SharedObjectWriter* so, + bool needs_unique_names) + : ImageWriter(thread, + /*generates_assembly=*/false, + needs_unique_names, + deobfuscation_trie), #else BlobImageWriter::BlobImageWriter(Thread* thread, NonStreamingWriteStream* vm_instructions, NonStreamingWriteStream* isolate_instructions, SharedObjectWriter* debug_so, - SharedObjectWriter* so) - : ImageWriter(thread, /*generates_assembly=*/false), + SharedObjectWriter* so, + bool needs_unique_names) + : ImageWriter(thread, /*generates_assembly=*/false, needs_unique_names), #endif vm_instructions_(vm_instructions), isolate_instructions_(isolate_instructions), diff --git a/runtime/vm/image_snapshot.h b/runtime/vm/image_snapshot.h index 10d1c965542..60a1c4d93cd 100644 --- a/runtime/vm/image_snapshot.h +++ b/runtime/vm/image_snapshot.h @@ -95,6 +95,13 @@ class Image : ValueObject { // Only valid for instructions images from precompiled snapshots. bool compiled_to_macho() const; + // Constants used to denote special values for the offsets in the Image + // object header and the fields of the InstructionsSection object. + static constexpr intptr_t kNoInstructionsSection = 0; + static constexpr intptr_t kNoBssSection = 0; + static constexpr intptr_t kNoRelocatedAddress = 0; + static constexpr intptr_t kNoBuildId = 0; + private: // For snapshots directly compiled to a shared object, returns a pointer to // the beginning of the build id container. Otherwise returns nullptr; @@ -122,13 +129,6 @@ class Image : ValueObject { raw_memory)[static_cast(field)]; } - // Constants used to denote special values for the offsets in the Image - // object header and the fields of the InstructionsSection object. - static constexpr intptr_t kNoInstructionsSection = 0; - static constexpr intptr_t kNoBssSection = 0; - static constexpr intptr_t kNoRelocatedAddress = 0; - static constexpr intptr_t kNoBuildId = 0; - // The size of the Image object header. // // Note: Image::kHeaderSize is _not_ an architecture-dependent constant, @@ -385,9 +385,10 @@ class ImageWriter : public ValueObject { #if defined(DART_PRECOMPILER) ImageWriter(Thread* thread, bool generates_assembly, + bool needs_unique_names, const Trie* deobfuscation_trie = nullptr); #else - ImageWriter(Thread* thread, bool generates_assembly); + ImageWriter(Thread* thread, bool generates_assembly, bool needs_unique_names); #endif virtual ~ImageWriter() {} @@ -476,10 +477,22 @@ class ImageWriter : public ValueObject { // (if vm is true) or application isolate (otherwise) section. Some sections // are shared by both. static constexpr intptr_t SectionLabel(ProgramSection section, bool vm) { - // Both vm and isolate share the build id section. - const bool shared = section == ProgramSection::BuildId; - // The initial 1 is to ensure the result is positive. - return 1 + 2 * static_cast(section) + ((shared || vm) ? 0 : 1); + switch (section) { + case ProgramSection::Text: + return vm ? SharedObjectWriter::kVmInstructionsLabel + : SharedObjectWriter::kIsolateInstructionsLabel; + case ProgramSection::Data: + return vm ? SharedObjectWriter::kVmDataLabel + : SharedObjectWriter::kIsolateDataLabel; + case ProgramSection::Bss: + return vm ? SharedObjectWriter::kVmBssLabel + : SharedObjectWriter::kIsolateBssLabel; + case ProgramSection::BuildId: + // Both vm and isolate share the build id section. + return SharedObjectWriter::kBuildIdLabel; + } + UNREACHABLE(); + return 0; } static Trie* CreateReverseObfuscationTrie(Thread* thread); @@ -675,7 +688,8 @@ class ImageWriter : public ValueObject { public: explicit SnapshotTextObjectNamer(Zone* zone, const Trie* deobfuscation_trie, - bool for_assembly) + bool for_assembly, + bool create_unique_names) : zone_(ASSERT_NOTNULL(zone)), deobfuscation_trie_(deobfuscation_trie), lib_(Library::Handle(zone)), @@ -686,6 +700,7 @@ class ImageWriter : public ValueObject { insns_(Instructions::Handle(zone)), store_(IsolateGroup::Current()->object_store()), for_assembly_(for_assembly), + create_unique_names_(create_unique_names), usage_count_(zone) {} const char* StubNameForType(const AbstractType& type) const; @@ -708,6 +723,8 @@ class ImageWriter : public ValueObject { void AddNonUniqueNameFor(BaseTextBuffer* buffer, const Object& object); // Modifies the symbol name in the buffer as needed for assembly use. void ModifyForAssembly(BaseTextBuffer* buffer); + // Ensures the final symbol name is unique. + void EnsureUniqueName(BaseTextBuffer* buffer); Zone* const zone_; const Trie* const deobfuscation_trie_; @@ -718,8 +735,10 @@ class ImageWriter : public ValueObject { String& string_; Instructions& insns_; ObjectStore* const store_; - // Used to decide whether we need to add a uniqueness suffix. + // Avoids naming conventions that have meaning to the assembler. bool for_assembly_; + // Used to decide whether we need to add a uniqueness suffix. + bool create_unique_names_; CStringIntMap usage_count_; DISALLOW_COPY_AND_ASSIGN(SnapshotTextObjectNamer); @@ -727,9 +746,8 @@ class ImageWriter : public ValueObject { SnapshotTextObjectNamer namer_; - // Reserve two positive labels for each of the ProgramSection values (one for - // vm, one for isolate). - intptr_t next_label_ = 1 + 2 * kNumProgramSections; + intptr_t next_label_ = SharedObjectWriter::kLastReservedLabel + 1; + #endif IdSpace offset_space_ = IdSpace::kSnapshot; @@ -874,13 +892,15 @@ class BlobImageWriter : public ImageWriter { NonStreamingWriteStream* isolate_instructions, const Trie* deobfuscation_trie = nullptr, SharedObjectWriter* debug_so = nullptr, - SharedObjectWriter* so = nullptr); + SharedObjectWriter* so = nullptr, + bool needs_unique_names = false); #else BlobImageWriter(Thread* thread, NonStreamingWriteStream* vm_instructions, NonStreamingWriteStream* isolate_instructions, SharedObjectWriter* debug_so = nullptr, - SharedObjectWriter* so = nullptr); + SharedObjectWriter* so = nullptr, + bool needs_unique_names = false); #endif virtual void Finalize(); diff --git a/runtime/vm/mach_o.cc b/runtime/vm/mach_o.cc index 8ece79e14fb..1da2872df9b 100644 --- a/runtime/vm/mach_o.cc +++ b/runtime/vm/mach_o.cc @@ -34,6 +34,12 @@ DEFINE_FLAG(charp, "The install name to be used for the dynamic library. " "The output filename is used if not provided."); +DEFINE_FLAG(bool, + macho_reduce_padding, + false, + "Whether to use a smaller alignment size for segments and the " + "text/const sections in Mach-O outputs.") + #if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) DEFINE_FLAG(charp, macho_min_os_version, @@ -103,6 +109,9 @@ FOR_EACH_CHECKABLE_MACHO_CONTENTS_TYPE(DECLARE_CONTENTS_TYPE_CLASS) FOR_EACH_CONCRETE_MACHO_CONTENTS_TYPE(DECLARE_CONTENTS_TYPE_CLASS) #undef DECLARE_CONTENTS_TYPE_CLASS +using MachORelocationsArray = ZoneGrowableArray; +using MachORelocationAddendsArray = ZoneGrowableArray; + // The interface for a SharedObjectWriter::WriteStream with MachO-specific // utility methods. // @@ -111,10 +120,11 @@ FOR_EACH_CONCRETE_MACHO_CONTENTS_TYPE(DECLARE_CONTENTS_TYPE_CLASS) class MachOWriteStream : public SharedObjectWriter::WriteStream { template using only_if_unsigned = typename std::enable_if_t, S>; + using Relocation = SharedObjectWriter::Relocation; public: explicit MachOWriteStream(const MachOWriter& macho) - : SharedObjectWriter::WriteStream(), macho_(macho) {} + : SharedObjectWriter::WriteStream(macho.type()), macho_(macho) {} const MachOSegment& TextSegment() const; @@ -179,8 +189,40 @@ class MachOWriteStream : public SharedObjectWriter::WriteStream { // Call once all content that should be hashed has been written to the stream. virtual void FinalizeHashedContent() = 0; + void set_current_relocation_addends( + const MachORelocationAddendsArray* array) { + current_relocation_addends_ = array; + } + protected: + void WriteRelocatableValue(intptr_t address, + const Relocation& reloc, + intptr_t reloc_index) override { + if (type() != SharedObjectWriter::Type::Object) { + // Use the super implementation. + return SharedObjectWriter::WriteStream::WriteRelocatableValue( + address, reloc, reloc_index); + } + // Relocatable objects do not resolve relocations eagerly unless + // the source and target are the same, in which case the eagerly + // computed value has already been calculated as the "addend". + intptr_t to_write = 0; +#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64) + // For X64 and ARM64, the addend is stored in the relocated location + // as the MachOWriter only uses UNSIGNED/SUBTRACTOR relocation entries. + RELEASE_ASSERT(current_relocation_addends_ != nullptr); + to_write = current_relocation_addends_->At(reloc_index); +#else + // Relocatable objects aren't handled for this architecture. + UNREACHABLE(); +#endif + ASSERT(Utils::IsInt(reloc.size_in_bytes * kBitsPerByte, to_write)); + WriteBytes(reinterpret_cast(&to_write), + reloc.size_in_bytes); + } + const MachOWriter& macho_; + const MachORelocationAddendsArray* current_relocation_addends_ = nullptr; private: DISALLOW_COPY_AND_ASSIGN(MachOWriteStream); @@ -574,17 +616,20 @@ class MachOSection : public MachOContents { public: MachOSection(Zone* zone, const char* name, + const char* segname, + intptr_t alignment, intptr_t type = mach_o::S_REGULAR, intptr_t attributes = mach_o::S_NO_ATTRIBUTES, - bool has_contents = true, - intptr_t alignment = MachOWriter::kPageSize) + bool has_contents = true) : MachOContents(/*needs_offset=*/has_contents, /*in_segment=*/true), name_(name), + segname_(segname), flags_(mach_o::SectionFlags(type, attributes)), alignment_(alignment), portions_(zone, 0) { ASSERT(strlen(name) <= sizeof(SectionType::sectname)); + ASSERT(strlen(segname) <= sizeof(SectionType::segname)); ASSERT(Utils::IsPowerOfTwo(alignment)); ASSERT_EQUAL(type & mach_o::SECTION_TYPE, static_cast(type)); ASSERT_EQUAL(attributes & mach_o::SECTION_ATTRIBUTES, @@ -599,8 +644,12 @@ class MachOSection : public MachOContents { intptr_t Alignment() const override { return alignment_; } const char* name() const { return name_; } + const char* segname() const { return segname_; } bool HasName(const char* name) const { return strcmp(name_, name) == 0; } + bool HasSegname(const char* segname) const { + return strcmp(segname_, segname) == 0; + } intptr_t index() const { // The getter should not be called until after an initial index is assigned. @@ -672,14 +721,20 @@ class MachOSection : public MachOContents { return last.offset + last.size; } + // The first section in relocated objects will have a memory offset of 0, so + // don't use the superclass's implementation as all sections are allocated. + bool IsAllocated() const override { return true; } + void WriteSelf(MachOWriteStream* stream) const override { if (!HasContents()) return; + stream->set_current_relocation_addends(relocation_addends_); for (const auto& portion : portions_) { // Each portion is aligned within the section. stream->Align(Alignment()); ASSERT_EQUAL(stream->Position(), file_offset() + portion.offset); portion.Write(stream, memory_address()); } + stream->set_current_relocation_addends(nullptr); } const Portion* FindPortion(const char* symbol_name) const { @@ -700,20 +755,35 @@ class MachOSection : public MachOContents { void Accept(Visitor* visitor) override { visitor->VisitMachOSection(this); } + const MachORelocationsArray* relocations() const { return relocations_; } + void set_relocations(const MachORelocationsArray* relocations) { + relocations_ = relocations; + } + intptr_t num_relocations() const { + return relocations_ == nullptr ? 0 : relocations_->length(); + } + + const MachORelocationAddendsArray* relocation_addends() const { + return relocation_addends_; + } + void set_relocation_addends(const MachORelocationAddendsArray* array) { + relocation_addends_ = array; + } + private: uint32_t HeaderInfoSize() const { return sizeof(SectionType); } // Called during MachOSegment::WriteLoadCommand. - void WriteHeaderInfo(MachOWriteStream* stream, const char* segname) const { + void WriteHeaderInfo(MachOWriteStream* stream) const { auto const start = stream->Position(); stream->WriteFixedLengthCString(name_, sizeof(SectionType::sectname)); - stream->WriteFixedLengthCString(segname, sizeof(SectionType::segname)); + stream->WriteFixedLengthCString(segname_, sizeof(SectionType::segname)); // While stream->WriteWord(memory_address()); stream->WriteWord(MemorySize()); stream->Write32(file_offset()); stream->Write32(Utils::ShiftForPowerOfTwo(Alignment())); - stream->WriteOffsetCount(0, 0); // No relocation entries. + stream->WriteOffsetCount(relocations_file_offset(), num_relocations()); stream->Write32(flags_); // All reserved fields are 0 for our purposes. stream->Write32(0); // reserved1 @@ -726,10 +796,26 @@ class MachOSection : public MachOContents { } const char* const name_; + const char* const segname_; const decltype(SectionType::flags) flags_ = 0; const intptr_t alignment_; intptr_t index_ = mach_o::NO_SECT; GrowableArray portions_; + // The array of relocation_info structs that should be output for this + // section iff the output format is a relocatable object. + const MachORelocationsArray* relocations_ = nullptr; + // A list of relocation addends for relocatable objects. + const MachORelocationAddendsArray* relocation_addends_ = nullptr; + +#define FOR_EACH_CONTENTS_LINEAR_FIELD(M) M(relocations_file_offset) + + public: + FOR_EACH_CONTENTS_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS); + + private: + FOR_EACH_CONTENTS_LINEAR_FIELD(DEFINE_LINEAR_FIELD); + +#undef FOR_EACH_CONTENTS_LINEAR_FIELD friend class MachOSegment; @@ -782,7 +868,12 @@ class MachOSegment : public MachOCommand { return (initial_vm_protection_ & mach_o::VM_PROT_EXECUTE) != 0; } - intptr_t Alignment() const override { return MachOWriter::kPageSize; } + intptr_t Alignment() const override { + // TODO(dartbug.com/61973): Use the reduced padding size as the default + // for native (macOS/iOS) snapshots once the loading issue is resolved, or + // document why we can't use it for native snapshots loaded by the Dart VM. + return FLAG_macho_reduce_padding ? 64 : MachOWriter::kPageSize; + } // The text segment has a file and memory offset of 0, so the superclass's // implementations give false negatives after ComputeOffsets. @@ -894,14 +985,25 @@ class MachOSegment : public MachOCommand { // sections instead of these being in separate load commands. for (auto* const c : contents_) { if (!c->IsMachOSection()) continue; - c->AsMachOSection()->WriteHeaderInfo(stream, name_); + c->AsMachOSection()->WriteHeaderInfo(stream); } } - MachOSection* FindSection(const char* name) const { + MachOSection* FindSection(const char* name, const char* segname) const { + // Unless this is the unnamed segment in a relocatable object file, there + // should be no need to check the segment name of the section. + const bool unnamed = HasName(mach_o::SEG_UNNAMED); + if (!unnamed && !HasName(segname)) { + return nullptr; + } for (auto* const c : contents_) { if (auto* const s = c->AsMachOSection()) { - if (s->HasName(name)) return s; + if (s->HasName(name)) { + ASSERT(unnamed || s->HasSegname(name_)); + if (!unnamed || s->HasSegname(segname)) { + return s; + } + } } } return nullptr; @@ -1244,8 +1346,8 @@ class MachOSymbolTable : public MachOCommand { public: static constexpr uint32_t kCommandCode = mach_o::LC_SYMTAB; - explicit MachOSymbolTable(Zone* zone) - : MachOCommand(kCommandCode), + MachOSymbolTable(Zone* zone, bool in_segment) + : MachOCommand(kCommandCode, /*needs_offset=*/true, in_segment), zone_(zone), strings_(zone), symbols_(zone, 0), @@ -1378,21 +1480,35 @@ class MachOSymbolTable : public MachOCommand { const Symbol* FindLabel(intptr_t label) const { ASSERT(label > 0); // The stored index is 1-based. - const intptr_t symbols_index = by_label_index_.Lookup(label) - 1; + const intptr_t symbols_index = IndexForLabel(label); if (symbols_index < 0) return nullptr; // Not found. return &symbols_[symbols_index]; } - void Initialize(const char* path, + intptr_t IndexForLabel(intptr_t label) const { + ASSERT(label > 0); + // The stored index is 1-based. + return by_label_index_.Lookup(label) - 1; + } + + void Initialize(SharedObjectWriter::Type type, + const char* path, const GrowableArray& sections, bool is_stripped); uint32_t cmdsize() const override { return sizeof(mach_o::symtab_command); } intptr_t SelfMemorySize() const override { + if (!IsAllocated()) return 0; + return SelfFileSize(); + } + + intptr_t SelfFileSize() const override { return SymbolsSize() + strings_.FileSize(); } + intptr_t FileSize() const override { return SelfFileSize(); } + intptr_t Alignment() const override { return compiler::target::kWordSize; } void WriteLoadCommand(MachOWriteStream* stream) const override { @@ -1441,8 +1557,9 @@ class MachODynamicSymbolTable : public MachOCommand { public: static constexpr uint32_t kCommandCode = mach_o::LC_DYSYMTAB; - explicit MachODynamicSymbolTable(const MachOSymbolTable& table) - : MachOCommand(kCommandCode), table_(table) {} + MachODynamicSymbolTable(const MachOSymbolTable& table, bool in_segment) + : MachOCommand(kCommandCode, /*needs_offset=*/true, in_segment), + table_(table) {} uint32_t cmdsize() const override { return sizeof(mach_o::dysymtab_command); } @@ -1467,6 +1584,8 @@ class MachODynamicSymbolTable : public MachOCommand { // Currently no contents are written to the linkedit segment, as the // only non-zero fields are indexes/counts into the symbol table. intptr_t SelfMemorySize() const override { return 0; } + intptr_t SelfFileSize() const override { return 0; } + intptr_t FileSize() const override { return SelfFileSize(); } void Accept(Visitor* visitor) override { visitor->VisitMachODynamicSymbolTable(this); @@ -1633,18 +1752,21 @@ class MachOHeader : public MachOContents { MachOHeader(Zone* zone, SnapshotType type, bool is_stripped, + bool has_separate_object, const char* identifier, const char* path, Dwarf* dwarf) - : MachOContents(), + : MachOContents(/*needs_offset=*/true, + /*in_segment=*/type != SnapshotType::Object), zone_(zone), type_(type), is_stripped_(is_stripped), + has_separate_object_(has_separate_object), identifier_(identifier != nullptr ? identifier : ""), path_(path), dwarf_(dwarf), commands_(zone, 0), - full_symtab_(zone) { + full_symtab_(zone, /*in_segment=*/type != SnapshotType::Object) { #if defined(DART_TARGET_OS_MACOS) // A non-nullptr identifier must be provided for MacOS targets. ASSERT(identifier != nullptr); @@ -1666,6 +1788,7 @@ class MachOHeader : public MachOContents { ASSERT(text_segment_ != nullptr); return *text_segment_; } + SharedObjectWriter::Type type() const { return type_; } intptr_t NumSections() const { intptr_t num_sections = 0; @@ -1680,7 +1803,7 @@ class MachOHeader : public MachOContents { // The contents of the header is always at offset/address 0, so the // superclass's check returns a false negative here after ComputeOffsets. bool HasContents() const override { return true; } - bool IsAllocated() const override { return true; } + bool IsAllocated() const override { return type_ != SnapshotType::Object; } intptr_t Alignment() const override { return compiler::target::kWordSize; } // The header uses the default MemorySize() implementation, because @@ -1696,6 +1819,11 @@ class MachOHeader : public MachOContents { } intptr_t SelfMemorySize() const override { + if (!IsAllocated()) return 0; + return SelfFileSize(); + } + + intptr_t SelfFileSize() const override { intptr_t size = SizeWithoutLoadCommands(); for (auto* const command : commands_) { size += command->cmdsize(); @@ -1703,12 +1831,20 @@ class MachOHeader : public MachOContents { return size; } + intptr_t FileSize() const override { return SelfFileSize(); } + uint32_t filetype() const { - if (type_ == SnapshotType::Snapshot) { - return mach_o::MH_DYLIB; + switch (type_) { + case SnapshotType::Snapshot: + return mach_o::MH_DYLIB; + case SnapshotType::DebugInfo: + return mach_o::MH_DSYM; + case SnapshotType::Object: + return mach_o::MH_OBJECT; + default: + UNREACHABLE(); + return 0; } - ASSERT(type_ == SnapshotType::DebugInfo); - return mach_o::MH_DSYM; } uint32_t flags() const { @@ -1716,7 +1852,7 @@ class MachOHeader : public MachOContents { return mach_o::MH_NOUNDEFS | mach_o::MH_DYLDLINK | mach_o::MH_NO_REEXPORTED_DYLIBS; } - ASSERT(type_ == SnapshotType::DebugInfo); + ASSERT(type_ == SnapshotType::DebugInfo || type_ == SnapshotType::Object); return 0; } @@ -1830,18 +1966,27 @@ class MachOHeader : public MachOContents { // Returns the section with name [sectname] in segment [segname] // or nullptr if there is none. MachOSection* FindSection(const char* segname, const char* sectname) const { - auto* const s = FindSegment(segname); - if (s == nullptr) return nullptr; - return s->FindSection(sectname); + // All sections are in the unnamed segment for object files. + auto* const segment = + type_ == SnapshotType::Object ? text_segment_ : FindSegment(segname); + if (segment == nullptr) return nullptr; + return segment->FindSection(sectname, segname); } MachOSegment* EnsureTextSegment() { if (text_segment_ == nullptr) { + // For relocatable objects, all sections are put into a single unnamed + // segment. + auto* const name = type_ == SnapshotType::Object ? mach_o::SEG_UNNAMED + : mach_o::SEG_TEXT; // Make sure it didn't get added outside this method. - ASSERT(FindSegment(mach_o::SEG_TEXT) == nullptr); - auto const vm_protection = mach_o::VM_PROT_READ | mach_o::VM_PROT_EXECUTE; - text_segment_ = new (zone()) - MachOSegment(zone(), mach_o::SEG_TEXT, vm_protection, vm_protection); + ASSERT(FindSegment(name) == nullptr); + auto const vm_protection = + type_ == SnapshotType::Object + ? mach_o::VM_PROT_ALL + : mach_o::VM_PROT_READ | mach_o::VM_PROT_EXECUTE; + text_segment_ = + new (zone()) MachOSegment(zone(), name, vm_protection, vm_protection); commands_.Add(text_segment_); } return text_segment_; @@ -1851,18 +1996,34 @@ class MachOHeader : public MachOContents { void Accept(Visitor* visitor) override { visitor->VisitMachOHeader(this); } - // Since the header is in the initial segment, visiting the load commands - // here and also visiting the header in MachOSegment::VisitChildren() would - // cause a cycle if, say, Default() is overridden to be recursive. - // Thus, the default VisitChildren implementation here does no recursion, + // Since the header is in the initial segment for most snapshot types, + // visiting the load commands here and also visiting the header in + // MachOSegment::VisitChildren() would cause a cycle if, say, Default() + // is overridden to be recursive. Thus, the default VisitChildren + // implementation here does no recursion. void VisitChildren(Visitor* visitor) override {} - void VisitSegments(Visitor* visitor) { + void VisitContents(Visitor* visitor) { + if (type_ == SnapshotType::Object) { + // The header is visited during the initial segment for other types. + Accept(visitor); + } for (auto* const c : commands_) { - if (!c->IsMachOSegment()) continue; + if (type_ != SnapshotType::Object) { + // All commands with non-header content should be part of a segment. + if (!c->IsMachOSegment()) continue; + } c->Accept(visitor); } } + // Returns the symbol table that is included in the output, which + // may or may not be the full symbol table. + // + // Returns nullptr if called before symbol table initialization. + const MachOSymbolTable* IncludedSymbolTable() const { + return const_cast(this)->IncludedSymbolTable(); + } + private: void GenerateUuid(); void CreateBSS(); @@ -1896,6 +2057,9 @@ class MachOHeader : public MachOContents { // Used to determine whether to include non-global symbols in the // symbol table written to disk. bool const is_stripped_; + // Whether this is a snapshot that has an associated relocatable object + // emitted. + bool const has_separate_object_; // The identifier, used in the LC_ID_DYLIB command and the code signature. const char* const identifier_; // The absolute path, used to create an N_OSO symbolic debugging variable @@ -1905,6 +2069,8 @@ class MachOHeader : public MachOContents { GrowableArray commands_; // Contains all symbols for relocation calculations. MachOSymbolTable full_symtab_; + // For relocatable objects, the "text" segment is the unnamed segment that + // holds all sections. Otherwise, it is the text segment as expected. MachOSegment* text_segment_ = nullptr; DISALLOW_COPY_AND_ASSIGN(MachOHeader); @@ -1914,6 +2080,8 @@ void MachOSegment::AddContents(MachOContents* c) { ASSERT(c != nullptr); // Segment contents are always allocated. ASSERT(c->IsAllocated()); + // Only sections should be added to the unnamed segment. + ASSERT(!HasName(mach_o::SEG_UNNAMED) || c->IsMachOSection()); // The order of segment contents is as follows: // 1) The header (if this is the initial segment). // 2) Content-containing sections and commands (in the linkedit segment). @@ -1959,15 +2127,21 @@ MachOWriter::MachOWriter(Zone* zone, Type type, const char* id, const char* path, - Dwarf* dwarf) + Dwarf* dwarf, + MachOWriter* object_writer) : SharedObjectWriter(zone, stream, type, dwarf), + object_writer_(object_writer), header_(*new (zone) MachOHeader( zone, type, IsStripped(dwarf), + type == SharedObjectWriter::Type::Snapshot && + object_writer != nullptr, FLAG_macho_install_name != nullptr ? FLAG_macho_install_name : id, path, - dwarf)) {} + dwarf)) { + ASSERT(type == Type::Snapshot || object_writer == nullptr); +} void MachOWriter::AddText(const char* name, intptr_t label, @@ -1976,16 +2150,21 @@ void MachOWriter::AddText(const char* name, const ZoneGrowableArray* relocations, const ZoneGrowableArray* symbols) { auto* const text_segment = header_.EnsureTextSegment(); - auto* text_section = text_segment->FindSection(mach_o::SECT_TEXT); + auto* text_section = + text_segment->FindSection(mach_o::SECT_TEXT, mach_o::SEG_TEXT); if (text_section == nullptr) { - const bool has_contents = type_ == Type::Snapshot; + const bool has_contents = type_ != Type::DebugInfo; const intptr_t attributes = mach_o::S_ATTR_PURE_INSTRUCTIONS | mach_o::S_ATTR_SOME_INSTRUCTIONS; text_section = new (zone()) MachOSection( - zone(), mach_o::SECT_TEXT, mach_o::S_REGULAR, attributes, has_contents); + zone(), mach_o::SECT_TEXT, mach_o::SEG_TEXT, text_segment->Alignment(), + mach_o::S_REGULAR, attributes, has_contents); text_segment->AddContents(text_section); } text_section->AddPortion(bytes, size, relocations, symbols, name, label); + if (object_writer_ != nullptr) { + object_writer_->AddText(name, label, bytes, size, relocations, symbols); + } } void MachOWriter::AddROData(const char* name, @@ -1996,15 +2175,19 @@ void MachOWriter::AddROData(const char* name, const ZoneGrowableArray* symbols) { // Const data goes in the text segment, not the data one. auto* const text_segment = header_.EnsureTextSegment(); - auto* const_section = text_segment->FindSection(mach_o::SECT_CONST); + auto* const_section = + text_segment->FindSection(mach_o::SECT_CONST, mach_o::SEG_TEXT); if (const_section == nullptr) { - const bool has_contents = type_ == Type::Snapshot; - const_section = - new (zone()) MachOSection(zone(), mach_o::SECT_CONST, mach_o::S_REGULAR, - mach_o::S_NO_ATTRIBUTES, has_contents); + const bool has_contents = type_ != Type::DebugInfo; + const_section = new (zone()) MachOSection( + zone(), mach_o::SECT_CONST, mach_o::SEG_TEXT, text_segment->Alignment(), + mach_o::S_REGULAR, mach_o::S_NO_ATTRIBUTES, has_contents); text_segment->AddContents(const_section); } const_section->AddPortion(bytes, size, relocations, symbols, name, label); + if (object_writer_ != nullptr) { + object_writer_->AddROData(name, label, bytes, size, relocations, symbols); + } } class WriteVisitor : public MachOContents::Visitor { @@ -2033,16 +2216,71 @@ class WriteVisitor : public MachOContents::Visitor { DISALLOW_COPY_AND_ASSIGN(WriteVisitor); }; +class WriteRelocationsVisitor : public MachOContents::Visitor { + public: + explicit WriteRelocationsVisitor(MachOWriteStream* stream) + : stream_(stream) {} + + void Default(MachOContents* contents) override {} + + void VisitMachOSegment(MachOSegment* segment) override { + segment->VisitChildren(this); + } + + void VisitMachOSection(MachOSection* section) override { + if (auto* const relocations = section->relocations()) { + ASSERT_EQUAL(stream_->Position(), section->relocations_file_offset()); + for (const auto& reloc : *relocations) { + stream_->Write32(reloc.address); + stream_->Write32(reloc.metadata); + } + } else { + ASSERT_EQUAL(section->relocations_file_offset(), 0); + } + } + + private: + MachOWriteStream* stream_; + DISALLOW_COPY_AND_ASSIGN(WriteRelocationsVisitor); +}; + void MachOWriter::Finalize() { header_.Finalize(); if (header_.HasCommand(MachOCodeSignature::kCommandCode)) { HashingMachOWriteStream wrapped(zone_, unwrapped_stream_, *this); WriteVisitor visitor(&wrapped); - header_.VisitSegments(&visitor); + header_.VisitContents(&visitor); + // Relocatable objects aren't signed, so no relocations to write. } else { NonHashingMachOWriteStream wrapped(unwrapped_stream_, *this); WriteVisitor visitor(&wrapped); - header_.VisitSegments(&visitor); + header_.VisitContents(&visitor); + if (type_ == SharedObjectWriter::Type::Object) { + WriteRelocationsVisitor reloc_visitor(&wrapped); + header_.VisitContents(&reloc_visitor); + } + } + if (object_writer_ != nullptr) { + object_writer_->Finalize(); + } +} + +void MachOWriter::AssertConsistency(const SharedObjectWriter* debug) const { + if (FLAG_macho_reduce_padding) { + // TODO(sstrickl): This currently fails because the reduced padding + // and difference in header sizes means the virtual addresses won't + // align (though symbolicizing the symbol+offset (PCOffset) information + // in traces still gives appropriately matching information). + // + // However, the only usecase for this reduced padding creates a .dSYM + // for symbolization instead of using the separate debug info, so + // ignore this mismatch for now. + return; + } + if (auto* const debug_macho = debug->AsMachOWriter()) { + AssertConsistency(this, debug_macho); + } else { + FATAL("Expected both snapshot and debug to be MachO"); } } @@ -2125,14 +2363,16 @@ static uint32_t HashPortion(const MachOSection::Portion& portion) { // Any component of the build ID which does not have an associated section // in the output is kept as 0. void MachOHeader::GenerateUuid() { + // Don't create a UUID for a relocatable object. + if (type_ == SnapshotType::Object) return; // Not idempotent. ASSERT(!HasCommand(MachOUuid::kCommandCode)); // Currently, we construct the UUID out of data from two different // sections in the text segment: the text section and the const section. - auto* const text_segment = FindSegment(mach_o::SEG_TEXT); - if (text_segment == nullptr) return; + if (text_segment_ == nullptr) return; - auto* const text_section = text_segment->FindSection(mach_o::SECT_TEXT); + auto* const text_section = + text_segment_->FindSection(mach_o::SECT_TEXT, mach_o::SEG_TEXT); // If there is no text section, then a UUID is not needed, as it is only // used to symbolicize non-symbolic stack traces. if (text_section == nullptr) return; @@ -2144,7 +2384,8 @@ void MachOHeader::GenerateUuid() { // All MachO snapshots have at least one of the two instruction sections. ASSERT(vm_instructions != nullptr || isolate_instructions != nullptr); - auto* const data_section = text_segment->FindSection(mach_o::SECT_CONST); + auto* const data_section = + text_segment_->FindSection(mach_o::SECT_CONST, mach_o::SEG_TEXT); auto* const vm_data = data_section == nullptr ? nullptr @@ -2170,18 +2411,28 @@ void MachOHeader::CreateBSS() { auto* const text_section = FindSection(mach_o::SEG_TEXT, mach_o::SECT_TEXT); ASSERT(text_section != nullptr); - // Not idempotent. Currently the data segment only contains BSS data, so it - // shouldn't already exist. - ASSERT(FindSegment(mach_o::SEG_DATA) == nullptr); - auto const vm_protection = mach_o::VM_PROT_READ | mach_o::VM_PROT_WRITE; - auto* const data_segment = new (zone()) - MachOSegment(zone(), mach_o::SEG_DATA, vm_protection, vm_protection); - commands_.Add(data_segment); + // Not idempotent. + ASSERT(FindSection(mach_o::SECT_BSS, mach_o::SEG_DATA) == nullptr); + MachOSegment* data_segment = nullptr; + if (type_ == SnapshotType::Object) { + // The "text" segment in a relocatable object is the unnamed segment + // that contains all sections. + data_segment = EnsureTextSegment(); + ASSERT(data_segment->HasName(mach_o::SEG_UNNAMED)); + } else { + // Currently the data segment only contains BSS data, so it + // shouldn't already exist. + ASSERT(FindSegment(mach_o::SEG_DATA) == nullptr); + auto const vm_protection = mach_o::VM_PROT_READ | mach_o::VM_PROT_WRITE; + data_segment = new (zone()) + MachOSegment(zone(), mach_o::SEG_DATA, vm_protection, vm_protection); + commands_.Add(data_segment); + } - auto* const bss_section = - new (zone()) MachOSection(zone(), mach_o::SECT_BSS, mach_o::S_ZEROFILL, - mach_o::S_NO_ATTRIBUTES, /*has_contents=*/false, - /*alignment=*/compiler::target::kWordSize); + auto* const bss_section = new (zone()) MachOSection( + zone(), mach_o::SECT_BSS, mach_o::SEG_DATA, + /*alignment=*/compiler::target::kWordSize, mach_o::S_ZEROFILL, + mach_o::S_NO_ATTRIBUTES, /*has_contents=*/false); data_segment->AddContents(bss_section); for (const auto& portion : text_section->portions()) { @@ -2218,6 +2469,37 @@ void MachOHeader::CreateBSS() { void MachOHeader::GenerateCompactUnwindingInformation( DwarfSharedObjectStream& stream, const GrowableArray& fdes) { + // Each instructions image starts with the Image header and the + // InstructionsSection header. + const intptr_t header_size = + Image::kHeaderSize + compiler::target::InstructionsSection::HeaderSize(); + + if (type_ == SnapshotType::Object) { + // In relocatable objects, the compact unwind information is written + // differently. In this case, it's just a flat table with entries of + // the following format: + // start (word-sized) + // length (32 bits) + // encoding (32 bits) + // personality-function (word-sized, 0 if none) + // ldsa (word-sized, 0 if none) + for (intptr_t i = 0, n = fdes.length(); i < n; i++) { + const auto& fde = fdes[i]; + // The payload of the InstructionsSection. + stream.OffsetFromSymbol(fde.label, header_size); + stream.u4(fde.size); + stream.u4(mach_o::UNWIND_INFO_ENCODING_ARM64_MODE_FRAME); +#if defined(TARGET_ARCH_IS_32_BIT) + stream.u4(0); // Personality function + stream.u4(0); // LDSA +#else + stream.u8(0); // Personality function + stream.u8(0); // LDSA +#endif + } + return; + } + // 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. @@ -2319,10 +2601,6 @@ void MachOHeader::GenerateCompactUnwindingInformation( 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); @@ -2344,7 +2622,8 @@ void MachOHeader::GenerateCompactUnwindingInformation( void MachOHeader::GenerateUnwindingInformation() { #if !defined(TARGET_ARCH_IA32) - // Unwinding information is added to the text segment in Mach-O files. + // Unwinding information is added to the text segment in Mach-O files + // (except for relocatable object files, where the __LD segment name is used). // Thus, we need the size of the unwinding information even for debugging // information, since adding the unwinding information changes the memory size // of the initial text segment and thus changes the values for symbols @@ -2355,25 +2634,27 @@ void MachOHeader::GenerateUnwindingInformation() { // just use an appropriate zerofill section for it. const bool use_zerofill = type_ == SnapshotType::DebugInfo; const intptr_t alignment = compiler::target::kWordSize; - auto add_unwind_section = - [&](MachOSegment* segment, const char* sectname, + auto create_unwind_section = + [&](const char* segname, 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); - }; + const SharedObjectWriter::RelocationArray* relocations = nullptr, + const SharedObjectWriter::SymbolDataArray* symbols = + nullptr) -> MachOSection* { + // Not idempotent. + ASSERT(FindSection(sectname, segname) == nullptr); + auto* const section = new (zone()) + MachOSection(zone(), sectname, segname, alignment, + use_zerofill ? mach_o::S_ZEROFILL : mach_o::S_REGULAR, + mach_o::S_NO_ATTRIBUTES, !use_zerofill); + section->AddPortion(use_zerofill ? nullptr : stream.buffer(), + stream.bytes_written(), + use_zerofill ? nullptr : relocations, symbols); + return section; + }; ASSERT(text_segment_ != nullptr); if (auto* const text_section = - text_segment_->FindSection(mach_o::SECT_TEXT)) { + text_segment_->FindSection(mach_o::SECT_TEXT, mach_o::SEG_TEXT)) { // Generate the DWARF FDEs even for MacOS, because the same information // is used to create the compact unwinding info. GrowableArray fdes(zone_, 0); @@ -2387,16 +2668,32 @@ void MachOHeader::GenerateUnwindingInformation() { ZoneWriteStream stream(zone(), DwarfSharedObjectStream::kInitialBufferSize); DwarfSharedObjectStream dwarf_stream(zone(), &stream); + SharedObjectWriter::SymbolDataArray* symbols = nullptr; #if defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64) GenerateCompactUnwindingInformation(dwarf_stream, fdes); - auto* const sectname = mach_o::SECT_UNWIND_INFO; + auto* const sectname = type_ == SnapshotType::Object + ? mach_o::SECT_COMPACT_UNWIND + : mach_o::SECT_UNWIND_INFO; #else Dwarf::WriteCallFrameInformationRecords(&dwarf_stream, fdes); auto* const sectname = mach_o::SECT_EH_FRAME; + if (type_ == SnapshotType::Object) { + // To add appropriate relocations for the EH_FRAME section, a local symbol + // must be added since this section includes relocations with + // kSelfRelative source labels. + const size_t size = stream.bytes_written(); + symbols = new (zone_) SharedObjectWriter::SymbolDataArray(zone_, 1); + symbols->Add({"_kDartMachOEhFrameSection", + SharedObjectWriter::SymbolData::Type::Section, 0, size, + SharedObjectWriter::kMachOEhFrameLabel}); + } #endif - add_unwind_section(text_segment_, sectname, stream, - dwarf_stream.relocations()); + auto* const segname = + type_ == SnapshotType::Object ? mach_o::SEG_LD : mach_o::SEG_TEXT; + auto* const section = create_unwind_section( + segname, sectname, stream, dwarf_stream.relocations(), symbols); + text_segment_->AddContents(section); } #if defined(UNWINDING_RECORDS_WINDOWS_PRECOMPILER) @@ -2418,7 +2715,9 @@ void MachOHeader::GenerateUnwindingInformation() { section_start, unwinding_instructions), records_size); ASSERT_EQUAL(records_size, stream.Position()); - add_unwind_section(segment, mach_o::SECT_UNWIND_INFO, stream); + auto* const section = create_unwind_section( + segment->name(), mach_o::SECT_UNWIND_INFO, stream); + segment->AddContents(section); } } } @@ -2432,10 +2731,8 @@ void MachOHeader::GenerateMiscellaneousCommands() { ASSERT(!HasCommand(MachOIdDylib::kCommandCode)); commands_.Add(new (zone_) MachOIdDylib(identifier_)); #if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) - ASSERT(!HasCommand(MachOBuildVersion::kCommandCode)); ASSERT(!HasCommand(MachOLoadDylib::kCommandCode)); ASSERT(!HasCommand(MachORunPath::kCommandCode)); - commands_.Add(new (zone_) MachOBuildVersion()); commands_.Add(MachOLoadDylib::CreateLoadSystemDylib(zone_)); if (FLAG_macho_rpath != nullptr) { const char* current = FLAG_macho_rpath; @@ -2449,6 +2746,12 @@ void MachOHeader::GenerateMiscellaneousCommands() { } #endif } +#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) + if (type_ == SnapshotType::Snapshot || type_ == SnapshotType::Object) { + ASSERT(!HasCommand(MachOBuildVersion::kCommandCode)); + commands_.Add(new (zone_) MachOBuildVersion()); + } +#endif } void MachOHeader::InitializeSymbolTables() { @@ -2474,19 +2777,21 @@ void MachOHeader::InitializeSymbolTables() { // This symbol table is for the MachOWriter's internal use. All symbols // should be added to it so the writer can resolve relocations. - full_symtab_.Initialize(path_, sections, /*is_stripped=*/false); + full_symtab_.Initialize(type_, path_, sections, /*is_stripped=*/false); auto* table = &full_symtab_; if (is_stripped_) { // Create a separate symbol table that is actually written to the output. // This one will only contain what's needed for the dynamic symbol table. - auto* const table = new (zone()) MachOSymbolTable(zone()); - table->Initialize(path_, sections, is_stripped_); + auto* const table = new (zone()) + MachOSymbolTable(zone(), /*in_segment=*/type_ != SnapshotType::Object); + table->Initialize(type_, path_, sections, is_stripped_); } commands_.Add(table); - // For snapshots, include a dynamic symbol table as well. - if (type_ == SnapshotType::Snapshot) { - auto* const dynamic_symtab = new (zone()) MachODynamicSymbolTable(*table); + // For non-debugging information, include a dynamic symbol table as well. + if (type_ != SnapshotType::DebugInfo) { + auto* const dynamic_symtab = new (zone()) MachODynamicSymbolTable( + *table, /*in_segment=*/type_ != SnapshotType::Object); commands_.Add(dynamic_symtab); } } @@ -2494,28 +2799,43 @@ void MachOHeader::InitializeSymbolTables() { void MachOHeader::FinalizeDwarfSections() { if (dwarf_ == nullptr) return; + if (has_separate_object_) { + // If there is an associated relocatable object, do not emit the DWARF + // sections; they'll be emitted in the relocatable object instead. + ASSERT(type_ == SnapshotType::Snapshot); + return; + } + // Currently we only output DWARF information involving code. #if defined(DEBUG) - auto* const text_segment = FindSegment(mach_o::SEG_TEXT); - ASSERT(text_segment != nullptr); - ASSERT(text_segment->FindSection(mach_o::SECT_TEXT) != nullptr); + ASSERT(text_segment_ != nullptr); + ASSERT(text_segment_->FindSection(mach_o::SECT_TEXT, mach_o::SEG_TEXT) != + nullptr); #endif - // Create the DWARF segment, which should not already exist. - ASSERT(FindSegment(mach_o::SEG_DWARF) == nullptr); - auto const init_vm_protection = mach_o::VM_PROT_READ | mach_o::VM_PROT_WRITE; - auto const max_vm_protection = init_vm_protection | mach_o::VM_PROT_EXECUTE; - auto* const dwarf_segment = new (zone()) MachOSegment( - zone(), mach_o::SEG_DWARF, init_vm_protection, max_vm_protection); - commands_.Add(dwarf_segment); + MachOSegment* dwarf_segment = nullptr; + if (type_ == SnapshotType::Object) { + // All sections are put into the unnamed segment. + dwarf_segment = text_segment_; + ASSERT(dwarf_segment->HasName(mach_o::SEG_UNNAMED)); + } else { + // Create the DWARF segment, which should not already exist. + ASSERT(FindSegment(mach_o::SEG_DWARF) == nullptr); + auto const init_vm_protection = + mach_o::VM_PROT_READ | mach_o::VM_PROT_WRITE; + auto const max_vm_protection = init_vm_protection | mach_o::VM_PROT_EXECUTE; + dwarf_segment = new (zone()) MachOSegment( + zone(), mach_o::SEG_DWARF, init_vm_protection, max_vm_protection); + commands_.Add(dwarf_segment); + } const intptr_t alignment = 1; // No extra padding. auto add_debug = [&](const char* name, const DwarfSharedObjectStream& stream) { - ASSERT(!dwarf_segment->FindSection(name)); - auto* const section = new (zone()) - MachOSection(zone(), name, mach_o::S_REGULAR, mach_o::S_ATTR_DEBUG, - /*has_contents=*/true, alignment); + ASSERT(!dwarf_segment->FindSection(name, mach_o::SEG_DWARF)); + auto* const section = + new (zone()) MachOSection(zone(), name, mach_o::SEG_DWARF, alignment, + mach_o::S_REGULAR, mach_o::S_ATTR_DEBUG); section->AddPortion(stream.buffer(), stream.bytes_written(), stream.relocations()); dwarf_segment->AddContents(section); @@ -2579,25 +2899,30 @@ void MachOHeader::FinalizeCommands() { GrowableArray other_segments(zone_, 0); // Next comes any non-segment load commands that have allocated content - // outside of the header like the symbol table. A linkedit segment - // is created later to contain the non-header contents of these commands. + // outside of the header like the symbol table. For relocatable objects, + // the contents of these sections are written after the unnamed segment, + // otherwise a linkedit segment is created later to contain the non-header + // contents of these commands. GrowableArray linkedit_commands(zone_, 0); - for (auto* const command : commands_) { // Check that we're not reordering after offsets have been computed. ASSERT(!command->HasContents() || !command->file_offset_is_set()); if (auto* const s = command->AsMachOSegment()) { - if (s->HasName(mach_o::SEG_TEXT)) { + if (s->HasName(mach_o::SEG_TEXT) || s->HasName(mach_o::SEG_UNNAMED)) { + ASSERT_EQUAL(type_ == SnapshotType::Object, + s->HasName(mach_o::SEG_UNNAMED)); ASSERT(text_segment == s); } else if (s->ContainsSymbols()) { symbol_segments.Add(s); } else { other_segments.Add(s); } - } else if (!command->HasContents()) { - header_only_commands.Add(command); - } else { + } else if (type_ == SnapshotType::Object || command->HasContents()) { + // Stick every non-segment into linkedit_commands so that the segment + // load command is first in a relocatable object. linkedit_commands.Add(command); + } else { + header_only_commands.Add(command); } } @@ -2605,19 +2930,21 @@ void MachOHeader::FinalizeCommands() { // it only contains global exported symbols, which means there should // be a linkedit segment. ASSERT(!linkedit_commands.is_empty()); - auto* const linkedit_segment = - new (zone_) MachOSegment(zone_, mach_o::SEG_LINKEDIT); - num_commands += 1; - for (auto* const c : linkedit_commands) { - linkedit_segment->AddContents(c); - } - if (type_ == SnapshotType::Snapshot && FLAG_macho_linker_signature) { - // Also include an embedded ad-hoc linker signed code signature as the - // last contents of the linkedit segment (which is the last segment). - auto* const signature = new (zone_) MachOCodeSignature(identifier_); - linkedit_segment->AddContents(signature); - linkedit_commands.Add(signature); + MachOSegment* linkedit_segment = nullptr; + if (type_ != SnapshotType::Object) { + linkedit_segment = new (zone_) MachOSegment(zone_, mach_o::SEG_LINKEDIT); num_commands += 1; + for (auto* const c : linkedit_commands) { + linkedit_segment->AddContents(c); + } + if (type_ == SnapshotType::Snapshot && FLAG_macho_linker_signature) { + // Also include an embedded ad-hoc linker signed code signature as the + // last contents of the linkedit segment (which is the last segment). + auto* const signature = new (zone_) MachOCodeSignature(identifier_); + linkedit_segment->AddContents(signature); + linkedit_commands.Add(signature); + num_commands += 1; + } } GrowableArray segments( @@ -2626,12 +2953,16 @@ void MachOHeader::FinalizeCommands() { segments.Add(text_segment); segments.AddArray(symbol_segments); segments.AddArray(other_segments); - segments.Add(linkedit_segment); + if (type_ != SnapshotType::Object) { + segments.Add(linkedit_segment); + } - // The initial segment in the file should have the header as its initial - // contents. Since the header is not a section, this won't change the - // section numbering. - segments[0]->AddContents(this); + if (type_ != SnapshotType::Object) { + // The initial segment in the file should have the header as its initial + // contents. Since the header is not a section, this won't change the + // section numbering. + segments[0]->AddContents(this); + } // Now populate reordered_commands. reordered_commands.AddArray(header_only_commands); @@ -2662,7 +2993,10 @@ struct ContentOffsetsVisitor : public MachOContents::Visitor { void Default(MachOContents* contents) { ASSERT_EQUAL(contents->IsMachOHeader(), file_offset == 0); - ASSERT_EQUAL(contents->IsMachOHeader(), memory_address == 0); + // This can't be strictly equal, because the header is not in a segment + // in relocatable objects and thus is not allocated, so the first + // allocated contents (a section) will have a memory offset of 0. + ASSERT(!contents->IsMachOHeader() || memory_address == 0); // Increment the file and memory offsets by the appropriate amounts. if (contents->HasContents()) { file_offset = Utils::RoundUp(file_offset, contents->Alignment()); @@ -2686,7 +3020,8 @@ struct ContentOffsetsVisitor : public MachOContents::Visitor { void VisitMachOSegment(MachOSegment* segment) { ASSERT_EQUAL(segment->IsInitial(), file_offset == 0); - ASSERT_EQUAL(segment->IsInitial(), memory_address == 0); + ASSERT_EQUAL(segment->IsInitial() || segment->HasName(mach_o::SEG_UNNAMED), + memory_address == 0); // Segments are always allocated and we set the file offset even // when the segment doesn't actually write any contents. file_offset = Utils::RoundUp(file_offset, segment->Alignment()); @@ -2712,7 +3047,369 @@ struct ContentOffsetsVisitor : public MachOContents::Visitor { DISALLOW_COPY_AND_ASSIGN(ContentOffsetsVisitor); }; +class RelocationsConverter : public MachOContents::Visitor { + using SnapshotType = SharedObjectWriter::Type; + using Relocation = SharedObjectWriter::Relocation; + + public: + explicit RelocationsConverter(Zone* zone, + const MachOHeader& header, + intptr_t start) + : zone_(zone), + output_is_relocatable_object_(header.type() == SnapshotType::Object), + symbol_table_(*ASSERT_NOTNULL(header.IncludedSymbolTable())), + file_offset_(Utils::RoundUp(start, compiler::target::kWordSize)), + portion_and_section_by_label_(zone) { + for (auto* const command : header.commands()) { + if (auto* const segment = command->AsMachOSegment()) { + for (auto* const c : segment->contents()) { + if (auto* const s = c->AsMachOSection()) { + for (const auto& p : s->portions()) { + portion_and_section_by_label_.Insert({p.label, {s, &p}}); + } + } + } + } + } + } + + void Default(MachOContents* contents) override {} + + void VisitMachOSegment(MachOSegment* segment) override { + segment->VisitChildren(this); + } + + void VisitMachOSection(MachOSection* section) override { + current_relocations_ = nullptr; + current_relocation_addends_ = nullptr; + if (output_is_relocatable_object_) { + ASSERT(section->file_offset_is_set()); + current_section_ = section; + for (const auto& p : section->portions()) { + if (p.relocations == nullptr) continue; + if (p.symbols != nullptr && !p.symbols->is_empty()) { + // Local symbols are sorted in order of offset into the portion. + starting_index_for_self_ = + symbol_table_.IndexForLabel(p.symbols->At(0).label); + } + current_portion_ = &p; + for (const auto& reloc : *p.relocations) { + ConvertRelocation(reloc); + } + starting_index_for_self_ = -1; + current_portion_ = nullptr; + } + current_section_ = nullptr; + } + section->set_relocations(current_relocations_); + section->set_relocation_addends(current_relocation_addends_); + section->set_relocations_file_offset( + current_relocations_ != nullptr ? file_offset_ : 0); + file_offset_ += + section->num_relocations() * sizeof(mach_o::relocation_info); + } + + private: + static uint32_t RelocationSize(intptr_t size) { + switch (size) { + case 1: + return mach_o::RELOC_SIZE_BYTE; + case 2: + return mach_o::RELOC_SIZE_2BYTES; + case 4: + return mach_o::RELOC_SIZE_4BYTES; + case 8: + return mach_o::RELOC_SIZE_8BYTES; + default: + FATAL("Unexpected relocation size %" Pd "", size); + return mach_o::RELOC_SIZE_BYTE; + } + } + + // Finds the closest symbol to the offset in the given section portion. + // Starts the search of local symbols in the symbol table from starting_index + // and updates starting_index if the closest symbol is a local symbol with an + // appropriate place to start the next search for any portion offsets after + // the current one. + intptr_t FindClosestSymbolIndexTo(intptr_t portion_offset, + const MachOSection* section, + const MachOSection::Portion* portion, + intptr_t& starting_index) { + const uword address = + section->memory_address() + portion->offset + portion_offset; + MachOSymbolTable::Symbol* current = nullptr; + if (starting_index >= 0) { + current = &symbol_table_.symbols()[starting_index]; + if (current->value() == address) { + return starting_index; + } else if (current->value() < address) { + // Since index is the index of a local symbol, we're guaranteed + // that there's always at least one more symbol in the symbol table, + // as the global symbols come afterwards. + auto* next_symbol = &symbol_table_.symbols()[starting_index + 1]; + // Search until we run out of local symbols for this section. + while (next_symbol->section_index() == section->index()) { + // Stop if the current symbol is closer than the next. + if (Utils::Abs(next_symbol->value() - address) >= + Utils::Abs(address - current->value())) { + break; + } + ++starting_index; + current = next_symbol; + next_symbol = &symbol_table_.symbols()[starting_index + 1]; + } + return starting_index; + } + // Fall through to see if the closest global symbol preceding + // this address is closer. + } + intptr_t label = portion->label; + if (label == 0) { + // Search for a global symbol label in the portions preceding this one. + for (const auto& p : section->portions()) { + if (portion == &p) break; + if (p.label != 0) { + label = p.label; + } + } + } + if (current != nullptr) { + // Check to see if the found global symbol (if any) is closer than the + // local symbol following this address. + intptr_t global_index = symbol_table_.IndexForLabel(label); + if (global_index >= 0) { + const auto& global_symbol = symbol_table_.symbols()[global_index]; + if (Utils::Abs(address - global_symbol.value()) <= + Utils::Abs(current->value() - address)) { + return global_index; + } + } + return starting_index; + } + // IndexForLabel will return a negative value for label == 0. + return symbol_table_.IndexForLabel(label); + } + + MachORelocationsArray* EnsureCurrentRelocations() { + if (current_relocations_ == nullptr) { + current_relocations_ = new (zone_) MachORelocationsArray(zone_, 0); + } + return current_relocations_; + } + + MachORelocationAddendsArray* EnsureCurrentRelocationAddends() { + if (current_relocation_addends_ == nullptr) { + current_relocation_addends_ = + new (zone_) MachORelocationAddendsArray(zone_, 0); + } + return current_relocation_addends_; + } + + void ConvertRelocation(const Relocation& reloc) { + // In the Dart VM, we turn relocations into up to three different parts + // in a Mach-O relocatable object: + // + // - A SUBTRACTOR relocation entry for [reloc.source_label]. + // - An UNSIGNED relocation entry for [reloc.target_label]. + // - An addend stored at [reloc.section_offset] in the section contents. + // + // If the source and target labels are the same, then there are no + // relocation entries added and the addend is simply: + // [reloc.target_offset] - [reloc.source_offset] + // + // If there are distinct source and target labels, then a SUBTRACTOR + // relocation entry for [reloc.source_label] is emitted followed by the + // UNSIGNED relocation entry for [reloc.target_label]. Both relocation + // entries are based on symbols. The addend, like the previous case, is: + // [reloc.target_offset] - [reloc.source_offset] + // + // If [reloc.source_label] is kSnapshotRelative, then only an UNSIGNED + // relocation based on the section is emitted. Since a section-based + // relocation entry is used, the addend differs from the other two cases: + // [current_section_.memory_address()] + [reloc.section_offset] + + // [reloc.target_offset] - [reloc.source_offset] + // That is, the virtual address of the relocation is added to the addend. + if (!Utils::IsInt(kBitsPerInt32, + current_portion_->offset + reloc.section_offset)) { + FATAL("Offset into section for relocation is not a 32-bit integer."); + } + int32_t section_offset = current_portion_->offset + reloc.section_offset; + const intptr_t address = + current_section_->memory_address() + section_offset; + if (reloc.target_label == SharedObjectWriter::kBuildIdLabel) { + ASSERT_EQUAL(reloc.target_offset, 0); + ASSERT_EQUAL(reloc.source_offset, 0); + ASSERT_EQUAL(reloc.size_in_bytes, compiler::target::kWordSize); + // Build IDs are UUID load commands in Mach-O and so have no + // associated symbol to use for relocations. + EnsureCurrentRelocationAddends()->Add(Image::kNoBuildId); + return; + } + intptr_t addend = reloc.target_offset - reloc.source_offset; + // If there is an emitted subtrahend (the source), emit it before + // the minuend (the target). + bool emitted_subtrahend = false; + if (reloc.source_label == reloc.target_label) { + // The relocation can be computed eagerly as the source and target + // refer to the same object. + } else if (reloc.source_label == Relocation::kSnapshotRelative) { + ASSERT_EQUAL(reloc.source_offset, 0); + ASSERT_EQUAL(reloc.size_in_bytes, compiler::target::kWordSize); + if (auto* const kv = + portion_and_section_by_label_.Lookup(reloc.target_label)) { + const auto [section, portion] = kv->value; + if (section == current_section_) { + ASSERT(section->HasName(mach_o::SECT_TEXT) && + section->HasSegname(mach_o::SEG_TEXT)); + // This is considered an illegal text relocation by clang, so omit + // this relocation. + EnsureCurrentRelocationAddends()->Add(Image::kNoRelocatedAddress); + return; + } + } + } else { + // The subtrahend _must_ be a symbol. + intptr_t index = -1; + if (reloc.source_label == Relocation::kSelfRelative) { + index = FindClosestSymbolIndexTo(reloc.section_offset, current_section_, + current_portion_, + starting_index_for_self_); + if (index < 0) { + FATAL("Cannot find any symbol in section %" Pd "", + current_section_->index()); + } + const auto& closest_symbol = symbol_table_.symbols()[index]; + // Adjust the addend by subtracting the offset from the found symbol. + addend -= address - closest_symbol.value(); + } else { + index = symbol_table_.IndexForLabel(reloc.source_label); + RELEASE_ASSERT(index >= 0); + } + if (!Utils::IsUint(mach_o::RELOC_METADATA_INDEX_BITS, index)) { + FATAL("Symbol index cannot fit into metadata payload: %" Pd "", index); + } + uint32_t source_metadata = index | mach_o::RELOC_EXTERN; +#if defined(TARGET_ARCH_X64) + source_metadata |= mach_o::RELOC_TYPE_X64_SUBTRACTOR; +#elif defined(TARGET_ARCH_ARM64) + source_metadata |= mach_o::RELOC_TYPE_ARM64_SUBTRACTOR; +#else + // Relocatable objects aren't handled for this architecture. + UNREACHABLE(); +#endif + source_metadata |= RelocationSize(reloc.size_in_bytes); + EnsureCurrentRelocations()->Add({section_offset, source_metadata}); + emitted_subtrahend = true; + } + // Now calculate the relocation entry for the base or minuend (target). + // If there is no subtrahend, the base is emitted as a relocation using + // a section index. If the target and source are the same, then no entries + // are emitted and the relocatable value is computed eagerly and stored as + // the addend. + // + // Note that the base addend for section-based relocations is the virtual + // address of the relocation, which is added here after determining which + // kind of relocation to use. + // + // For symbol-based relocations, the base addend is 0. + if (reloc.target_label != reloc.source_label) { + uint32_t target_metadata = 0; + intptr_t index = symbol_table_.IndexForLabel(reloc.target_label); + if (index >= 0) { + if (emitted_subtrahend) { + // Both subtrahend and minuend must be emitted as symbols. + target_metadata |= mach_o::RELOC_EXTERN; + } else { + const auto& symbol = symbol_table_.symbols()[index]; + index = symbol.section_index(); + addend += symbol.value(); + } + } else if (reloc.target_label == Relocation::kSelfRelative) { + if (emitted_subtrahend) { + index = FindClosestSymbolIndexTo(reloc.section_offset, + current_section_, current_portion_, + starting_index_for_self_); + if (index < 0) { + FATAL("Cannot find any symbol in section %" Pd "", + current_section_->index()); + } + const auto& closest_symbol = symbol_table_.symbols()[index]; + // Adjust the addend by adding the offset from the found symbol. + addend += address - closest_symbol.value(); + } else { + index = current_section_->index(); + addend += address; + } + } else { + auto* const kv = + portion_and_section_by_label_.Lookup(reloc.target_label); + RELEASE_ASSERT(kv != nullptr); + const auto [section, portion] = kv->value; + const intptr_t target_address = + section->memory_address() + portion->offset; + if (emitted_subtrahend) { + intptr_t starting_index = symbol_table_.IndexForLabel(portion->label); + index = FindClosestSymbolIndexTo(0, section, portion, starting_index); + if (index < 0) { + FATAL("Cannot find any symbol in section %" Pd "", + section->index()); + } + const auto& closest_symbol = symbol_table_.symbols()[index]; + // Adjust the addend by adding the offset from the found symbol. + addend += target_address - closest_symbol.value(); + } else { + index = section->index(); + addend += target_address; + } + ASSERT(index != mach_o::NO_SECT); + } + if (!Utils::IsUint(mach_o::RELOC_METADATA_INDEX_BITS, index)) { + FATAL("Could not convert target label %" Pd + " of relocation, got %s index %" Pd " and addend %#" Px "", + reloc.target_label, + (target_metadata & mach_o::RELOC_EXTERN) != 0 ? "symbol" + : "section", + index, addend); + } + target_metadata |= index; +#if defined(TARGET_ARCH_X64) + target_metadata |= mach_o::RELOC_TYPE_X64_UNSIGNED; +#elif defined(TARGET_ARCH_ARM64) + target_metadata |= mach_o::RELOC_TYPE_ARM64_UNSIGNED; +#else + // Relocatable objects aren't handled for this architecture. + UNREACHABLE(); +#endif + target_metadata |= RelocationSize(reloc.size_in_bytes); + EnsureCurrentRelocations()->Add({section_offset, target_metadata}); + } + if (!Utils::IsInt(reloc.size_in_bytes * kBitsPerByte, addend)) { + FATAL("Calculated addend for relocation too large: %#" Px "", addend); + } + EnsureCurrentRelocationAddends()->Add(addend); + } + + Zone* const zone_; + const bool output_is_relocatable_object_; + const MachOSymbolTable& symbol_table_; + intptr_t file_offset_; + // A mapping of portion labels to the associated section and portion. + DirectChainedHashMap>> + portion_and_section_by_label_; + + // Internal state fields used by ConvertRelocation/ConvertRelocationLabel. + const MachOSection* current_section_ = nullptr; + const MachOSection::Portion* current_portion_ = nullptr; + MachORelocationsArray* current_relocations_ = nullptr; + MachORelocationAddendsArray* current_relocation_addends_ = nullptr; + intptr_t starting_index_for_self_ = -1; + + DISALLOW_COPY_AND_ASSIGN(RelocationsConverter); +}; + void MachOHeader::ComputeOffsets() { + // First, set the offsets of the load commands in the header. intptr_t header_offset = SizeWithoutLoadCommands(); for (auto* const c : commands_) { ASSERT( @@ -2721,22 +3418,36 @@ void MachOHeader::ComputeOffsets() { header_offset += c->cmdsize(); } + // Next, set the offsets of the contents of load commands with post-header + // content (segments, symbol tables, etc.). ContentOffsetsVisitor visitor(zone()); - // All commands with non-header content should be part of a segment. - // In addition, the header is visited during the initial segment. - VisitSegments(&visitor); + VisitContents(&visitor); + + // Finally, for relocatable objects, convert the relocations into appropriate + // relocation entries and addends and also set the offsets at which relocation + // information is stored for sections, as this information is written into + // the file after the other contents. + // + // For other types of output, there is no converted relocation information (as + // the relocatable values are fully computed), so this visitor sets the + // section fields for relocations to reflect this. + RelocationsConverter relocs_visitor(zone_, *this, visitor.file_offset); + VisitContents(&relocs_visitor); } -void MachOSymbolTable::Initialize(const char* path, +void MachOSymbolTable::Initialize(SharedObjectWriter::Type type, + const char* path, const GrowableArray& sections, bool is_stripped) { + using SnapshotType = SharedObjectWriter::Type; // Not idempotent. ASSERT(!num_local_symbols_is_set()); // If symbolic debugging symbols are emitted, then any section // symbols are marked as alternate entries in favor of the symbolic // debugging symbols. - const intptr_t desc = is_stripped ? 0 : mach_o::N_ALT_ENTRY; + const intptr_t desc = + (type == SnapshotType::Object || is_stripped) ? 0 : mach_o::N_ALT_ENTRY; // For unstripped symbol tables, we do two initial passes. In the first // pass, we add section symbols for local static symbols. @@ -2753,72 +3464,75 @@ void MachOSymbolTable::Initialize(const char* path, } } - // In the second pass, we add appropriate symbolic debugging symbols. - using Type = SharedObjectWriter::SymbolData::Type; - if (path != nullptr) { - // The value of the OSO symbolic debugging symbol is the mtime of the - // object file. However, clang may warn about a mismatch if this is not - // 0 and differs from the actual mtime of the object file, so just use 0. - AddSymbol(path, mach_o::N_OSO, /*section=*/nullptr, - /*description=*/1, /*section_offset_or_value=*/0); - } - auto add_symbolic_debugging_symbols = [&](const char* name, Type type, - const MachOSection* section, - intptr_t offset, intptr_t size, - bool is_global) { - switch (type) { - case Type::Function: { - AddSymbol("", mach_o::N_BNSYM, section, /*description=*/0, offset); - AddSymbol(name, mach_o::N_FUN, section, /*description=*/0, offset); - // The size is output as an unnamed N_FUN symbol with no section - // following the actual N_FUN symbol. - AddSymbol("", mach_o::N_FUN, /*section=*/nullptr, /*description=*/0, - size); - AddSymbol("", mach_o::N_ENSYM, section, /*description=*/0, - offset + size); - - break; - } - case Type::Section: - case Type::Object: { - if (is_global) { - AddSymbol(name, mach_o::N_GSYM, /*section=*/nullptr, - /*description=*/0, - /*section_offset_or_value=*/0); - } else { - AddSymbol(name, mach_o::N_STSYM, section, - /*description=*/0, offset); - } - break; - } + // The second pass adds appropriate symbolic debugging symbols. This pass + // is skipped for relocatable objects. + if (type != SnapshotType::Object) { + using Type = SharedObjectWriter::SymbolData::Type; + if (path != nullptr) { + // The value of the OSO symbolic debugging symbol is the mtime of the + // object file. However, clang may warn about a mismatch if this is not + // 0 and differs from the actual mtime of the object file. + AddSymbol(path, mach_o::N_OSO, /*section=*/nullptr, + /*description=*/1, /*section_offset_or_value=*/0); } - }; + auto add_symbolic_debugging_symbols = [&](const char* name, Type type, + const MachOSection* section, + intptr_t offset, intptr_t size, + bool is_global) { + switch (type) { + case Type::Function: { + AddSymbol("", mach_o::N_BNSYM, section, /*description=*/0, offset); + AddSymbol(name, mach_o::N_FUN, section, /*description=*/0, offset); + // The size is output as an unnamed N_FUN symbol with no section + // following the actual N_FUN symbol. + AddSymbol("", mach_o::N_FUN, /*section=*/nullptr, /*description=*/0, + size); + AddSymbol("", mach_o::N_ENSYM, section, /*description=*/0, + offset + size); - for (intptr_t i = 0, n = sections.length(); i < n; ++i) { - auto* const section = sections[i]; - // We handle global symbols for text sections slightly differently than - // those for other sections. - const bool is_text_section = section->HasName(mach_o::SECT_TEXT); - for (const auto& portion : section->portions()) { - if (portion.symbol_name != nullptr) { - // Matching the symbolic debugging symbols created for assembled - // snapshots. - auto const type = is_text_section ? Type::Function : Type::Section; - // The "size" of a function symbol created for start of a text portion - // is up to the first function symbol. - auto const size = is_text_section && portion.symbols != nullptr - ? portion.symbols->At(0).offset - : portion.size; - add_symbolic_debugging_symbols(portion.symbol_name, type, section, - portion.offset, size, - /*is_global=*/true); + break; + } + case Type::Section: + case Type::Object: { + if (is_global) { + AddSymbol(name, mach_o::N_GSYM, /*section=*/nullptr, + /*description=*/0, + /*section_offset_or_value=*/0); + } else { + AddSymbol(name, mach_o::N_STSYM, section, + /*description=*/0, offset); + } + break; + } } - if (portion.symbols != nullptr) { - for (const auto& symbol_data : *portion.symbols) { - add_symbolic_debugging_symbols( - symbol_data.name, symbol_data.type, section, - portion.offset + symbol_data.offset, symbol_data.size, - /*is_global=*/false); + }; + + for (intptr_t i = 0, n = sections.length(); i < n; ++i) { + auto* const section = sections[i]; + // We handle global symbols for text sections slightly differently than + // those for other sections. + const bool is_text_section = section->HasName(mach_o::SECT_TEXT); + for (const auto& portion : section->portions()) { + if (portion.symbol_name != nullptr) { + // Matching the symbolic debugging symbols created for assembled + // snapshots. + auto const type = is_text_section ? Type::Function : Type::Section; + // The "size" of a function symbol created for start of a text + // portion is up to the first function symbol. + auto const size = is_text_section && portion.symbols != nullptr + ? portion.symbols->At(0).offset + : portion.size; + add_symbolic_debugging_symbols(portion.symbol_name, type, section, + portion.offset, size, + /*is_global=*/true); + } + if (portion.symbols != nullptr) { + for (const auto& symbol_data : *portion.symbols) { + add_symbolic_debugging_symbols( + symbol_data.name, symbol_data.type, section, + portion.offset + symbol_data.offset, symbol_data.size, + /*is_global=*/false); + } } } } diff --git a/runtime/vm/mach_o.h b/runtime/vm/mach_o.h index 7e424452003..dac57e5b67e 100644 --- a/runtime/vm/mach_o.h +++ b/runtime/vm/mach_o.h @@ -29,7 +29,8 @@ class MachOWriter : public SharedObjectWriter { Type type, const char* id, const char* path = nullptr, - Dwarf* dwarf = nullptr); + Dwarf* dwarf = nullptr, + MachOWriter* object = nullptr); #if defined(TARGET_ARCH_ARM64) static constexpr intptr_t kPageSize = 16 * KB; @@ -56,13 +57,7 @@ class MachOWriter : public SharedObjectWriter { void Finalize() override; - void AssertConsistency(const SharedObjectWriter* debug) const override { - if (auto* const debug_macho = debug->AsMachOWriter()) { - AssertConsistency(this, debug_macho); - } else { - FATAL("Expected both snapshot and debug to be MachO"); - } - } + void AssertConsistency(const SharedObjectWriter* debug) const override; const MachOWriter* AsMachOWriter() const override { return this; } @@ -70,6 +65,7 @@ class MachOWriter : public SharedObjectWriter { static void AssertConsistency(const MachOWriter* snapshot, const MachOWriter* debug_info); + MachOWriter* const object_writer_; MachOHeader& header_; }; diff --git a/runtime/vm/so_writer.cc b/runtime/vm/so_writer.cc index baa216023ec..7a05808d2e8 100644 --- a/runtime/vm/so_writer.cc +++ b/runtime/vm/so_writer.cc @@ -10,6 +10,52 @@ namespace dart { +void SharedObjectWriter::WriteStream::WriteRelocatableValue( + intptr_t address, + const Relocation& reloc, + intptr_t reloc_index) { + intptr_t source_address = reloc.source_offset; + switch (reloc.source_label) { + case Relocation::kSelfRelative: + source_address += address; + break; + case Relocation::kSnapshotRelative: + // No change to source_address. + break; + default: + ASSERT(reloc.source_label > 0); + source_address += FindValueForLabel(reloc.source_label); + } + ASSERT(reloc.size_in_bytes <= kWordSize); + word to_write = reloc.target_offset - source_address; + switch (reloc.target_label) { + case Relocation::kSelfRelative: + to_write += address; + break; + case Relocation::kSnapshotRelative: + // No change to to_write. + break; + default: { + ASSERT(reloc.target_label > 0); + intptr_t value; + if (HasValueForLabel(reloc.target_label, &value)) { + to_write += value; + } else { + ASSERT_EQUAL(reloc.target_label, kBuildIdLabel); + ASSERT_EQUAL(reloc.target_offset, 0); + ASSERT_EQUAL(reloc.source_offset, 0); + ASSERT_EQUAL(reloc.size_in_bytes, compiler::target::kWordSize); + // TODO(dartbug.com/43516): Special case for snapshots with deferred + // sections that handles the build ID relocation in an + // InstructionsSection when there is no build ID. + to_write = Image::kNoRelocatedAddress; + } + } + } + ASSERT(Utils::IsInt(reloc.size_in_bytes * kBitsPerByte, to_write)); + WriteBytes(reinterpret_cast(&to_write), reloc.size_in_bytes); +} + void SharedObjectWriter::WriteStream::WriteBytesWithRelocations( const uint8_t* bytes, intptr_t size, @@ -17,7 +63,8 @@ void SharedObjectWriter::WriteStream::WriteBytesWithRelocations( const RelocationArray& relocations) { // Resolve relocations as we write. intptr_t current_pos = 0; - for (const auto& reloc : relocations) { + for (intptr_t i = 0; i < relocations.length(); i++) { + const auto& reloc = relocations[i]; // We assume here that the relocations are sorted in increasing order, // with unique section offsets. const intptr_t preceding = reloc.section_offset - current_pos; @@ -26,47 +73,7 @@ void SharedObjectWriter::WriteStream::WriteBytesWithRelocations( current_pos += preceding; } ASSERT_EQUAL(current_pos, reloc.section_offset); - intptr_t source_address = reloc.source_offset; - switch (reloc.source_label) { - case Relocation::kSelfRelative: - source_address += start_address + current_pos; - break; - case Relocation::kSnapshotRelative: - // No change to source_address. - break; - default: - ASSERT(reloc.source_label > 0); - source_address += FindValueForLabel(reloc.source_label); - } - ASSERT(reloc.size_in_bytes <= kWordSize); - word to_write = reloc.target_offset - source_address; - switch (reloc.target_label) { - case Relocation::kSelfRelative: - to_write += start_address + current_pos; - break; - case Relocation::kSnapshotRelative: - // No change to to_write. - break; - default: { - ASSERT(reloc.target_label > 0); - intptr_t value; - if (HasValueForLabel(reloc.target_label, &value)) { - to_write += value; - } else { - ASSERT_EQUAL(reloc.target_label, kBuildIdLabel); - ASSERT_EQUAL(reloc.target_offset, 0); - ASSERT_EQUAL(reloc.source_offset, 0); - ASSERT_EQUAL(reloc.size_in_bytes, compiler::target::kWordSize); - // TODO(dartbug.com/43516): Special case for snapshots with deferred - // sections that handles the build ID relocation in an - // InstructionsSection when there is no build ID. - to_write = Image::kNoRelocatedAddress; - } - } - } - ASSERT(Utils::IsInt(reloc.size_in_bytes * kBitsPerByte, to_write)); - WriteBytes(reinterpret_cast(&to_write), - reloc.size_in_bytes); + WriteRelocatableValue(start_address + current_pos, reloc, i); current_pos += reloc.size_in_bytes; } WriteBytes(bytes + current_pos, size - current_pos); diff --git a/runtime/vm/so_writer.h b/runtime/vm/so_writer.h index b2d9c43a4ab..b620e9eaeb9 100644 --- a/runtime/vm/so_writer.h +++ b/runtime/vm/so_writer.h @@ -28,6 +28,8 @@ class SharedObjectWriter : public ZoneAllocated { // Separately compiled debugging information that should not include // most segment contents. DebugInfo, + // A relocatable object file. + Object, }; enum class Output { @@ -55,6 +57,7 @@ class SharedObjectWriter : public ZoneAllocated { Zone* zone() const { return zone_; } Dwarf* dwarf() { return dwarf_; } + SharedObjectWriter::Type type() const { return type_; } // Stores the information needed to appropriately generate a // relocation from the target to the source at the given section offset. @@ -125,7 +128,9 @@ class SharedObjectWriter : public ZoneAllocated { using SymbolDataArray = ZoneGrowableArray; struct WriteStream : public AbstractWriteStream { - WriteStream() {} + explicit WriteStream(SharedObjectWriter::Type type) : type_(type) {} + + SharedObjectWriter::Type type() const { return type_; } void WriteBytesWithRelocations(const uint8_t* bytes, intptr_t size, @@ -142,7 +147,13 @@ class SharedObjectWriter : public ZoneAllocated { return value; } + protected: + virtual void WriteRelocatableValue(intptr_t address, + const Relocation& reloc, + intptr_t reloc_index); + private: + const SharedObjectWriter::Type type_; DISALLOW_COPY_AND_ASSIGN(WriteStream); }; @@ -150,7 +161,7 @@ class SharedObjectWriter : public ZoneAllocated { public: DelegatingWriteStream(BaseWriteStream* stream, const SharedObjectWriter& writer) - : WriteStream(), + : WriteStream(writer.type()), stream_(ASSERT_NOTNULL(stream)), start_(stream->Position()), page_size_(writer.page_size()) { @@ -184,9 +195,17 @@ class SharedObjectWriter : public ZoneAllocated { // Must be the same value as the values returned by ImageWriter::SectionLabel // for the appropriate section and vm values. - static constexpr intptr_t kVmBssLabel = 5; - static constexpr intptr_t kIsolateBssLabel = 6; - static constexpr intptr_t kBuildIdLabel = 7; + enum ReservedLabels : intptr_t { + kVmInstructionsLabel = 1, + kIsolateInstructionsLabel = 2, + kVmDataLabel = 3, + kIsolateDataLabel = 4, + kVmBssLabel = 5, + kIsolateBssLabel = 6, + kBuildIdLabel = 7, + kMachOEhFrameLabel = 8, + kLastReservedLabel = kMachOEhFrameLabel, + }; virtual void AddText(const char* name, intptr_t label,