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,