From 089aeedf565f024a8fa93f8629a45fb9c8a2a38e Mon Sep 17 00:00:00 2001 From: Teagan Strickland Date: Wed, 18 Dec 2019 14:14:41 +0000 Subject: [PATCH] [vm/compiler] Reland "Add --save-debugging-info flag to gen_snapshot." Removes an unnecessary change to the names of type testing stubs that allowed name collisions to happen. Old commit message: The flag can be used when creating AOT snapshots. The resulting file can be used with package:vm/dwarf/convert.dart to convert DWARF-based stack traces to stack traces with function, file, and line number information. Currently the saved file will be an ELF file with DWARF debugging information, but this is subject to change in the future. To avoid being affected by any changes in format, read the DWARF information from the file using Dwarf.fromFile() in package:vm/dwarf/dwarf.dart. Also adds --dwarf-stack-traces to the VM global flag list, so its value at compilation will be read out of snapshots by the precompiled runtime. Fixes https://github.com/dart-lang/sdk/issues/39512, which was due to a missing compiler::target:: prefix for Instructions::HeaderSize() in BlobImageWriter::WriteText(). Exposes the information suggested in https://github.com/dart-lang/sdk/issues/39490 so that package:vm/dwarf can appropriately convert absolute PC addresses to the correct virtual address for a given DWARF line number program. Bug: https://github.com/dart-lang/sdk/issues/35851 Change-Id: I6723449dceb0b89c054a1f1e70e7acd7749baf14 Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-precomp-mac-release-simarm64-try,vm-kernel-precomp-win-release-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/128730 Reviewed-by: Martin Kustermann --- pkg/vm/bin/convert_stack_traces.dart | 4 +- pkg/vm/lib/dwarf/convert.dart | 179 ++++ pkg/vm/lib/{elf => dwarf}/dwarf.dart | 26 +- pkg/vm/lib/{elf => dwarf}/elf.dart | 40 +- pkg/vm/lib/{elf => dwarf}/reader.dart | 0 pkg/vm/lib/elf/convert.dart | 95 --- runtime/bin/gen_snapshot.cc | 54 +- runtime/include/dart_api.h | 6 +- runtime/platform/elf.h | 3 + .../vm/dart/product_aot_kernel_test.dart | 12 +- .../dart/use_bare_instructions_flag_test.dart | 43 +- .../use_dwarf_stack_traces_flag_program.dart | 19 + .../use_dwarf_stack_traces_flag_test.dart | 113 +++ .../tests/vm/dart/use_flag_test_helper.dart | 92 ++ .../use_save_debugging_info_flag_test.dart | 198 +++++ runtime/vm/dart_api_impl.cc | 30 +- runtime/vm/dwarf.cc | 18 +- runtime/vm/dwarf.h | 21 +- runtime/vm/elf.cc | 795 ++++++++++++------ runtime/vm/elf.h | 125 ++- runtime/vm/flag_list.h | 3 +- runtime/vm/image_snapshot.cc | 2 +- runtime/vm/object.cc | 21 +- .../standalone_2/dwarf_stack_trace_test.dart | 32 +- 24 files changed, 1405 insertions(+), 526 deletions(-) create mode 100644 pkg/vm/lib/dwarf/convert.dart rename pkg/vm/lib/{elf => dwarf}/dwarf.dart (97%) rename pkg/vm/lib/{elf => dwarf}/elf.dart (93%) rename pkg/vm/lib/{elf => dwarf}/reader.dart (100%) delete mode 100644 pkg/vm/lib/elf/convert.dart create mode 100644 runtime/tests/vm/dart/use_dwarf_stack_traces_flag_program.dart create mode 100644 runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart create mode 100644 runtime/tests/vm/dart/use_flag_test_helper.dart create mode 100644 runtime/tests/vm/dart/use_save_debugging_info_flag_test.dart diff --git a/pkg/vm/bin/convert_stack_traces.dart b/pkg/vm/bin/convert_stack_traces.dart index 1ca4db44243..6d0e92617ba 100644 --- a/pkg/vm/bin/convert_stack_traces.dart +++ b/pkg/vm/bin/convert_stack_traces.dart @@ -7,8 +7,8 @@ import "dart:convert"; import "dart:io" as io; import 'package:args/args.dart' show ArgParser, ArgResults; -import 'package:vm/elf/convert.dart'; -import 'package:vm/elf/dwarf.dart'; +import 'package:vm/dwarf/convert.dart'; +import 'package:vm/dwarf/dwarf.dart'; final ArgParser _argParser = new ArgParser(allowTrailingOptions: true) ..addOption('elf', diff --git a/pkg/vm/lib/dwarf/convert.dart b/pkg/vm/lib/dwarf/convert.dart new file mode 100644 index 00000000000..9bcdf508779 --- /dev/null +++ b/pkg/vm/lib/dwarf/convert.dart @@ -0,0 +1,179 @@ +// Copyright (c) 2019, 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. + +import "dart:async"; +import "dart:math"; + +import "dwarf.dart"; + +String _stackTracePiece(CallInfo call, int depth) => "#${depth}\t${call}"; + +final _traceStart = 'Warning: This VM has been configured to produce ' + 'stack traces that violate the Dart standard.'; +final _traceInstructionsStartRE = RegExp(r'isolate_instructions: ([0-9a-f]+) ' + r'vm_instructions: ([0-9a-f]+)$'); +final _traceLineRE = + RegExp(r' #(\d{2}) abs ([0-9a-f]+)(?: virt [0-9a-f]+)? (.*)$'); + +enum InstructionSection { vm, isolate } + +class PCOffset { + final int offset; + final InstructionSection section; + + PCOffset(this.offset, this.section); + + int virtualAddress(Dwarf dwarf) { + switch (section) { + case InstructionSection.vm: + return dwarf.convertToVMVirtualAddress(offset); + case InstructionSection.isolate: + return dwarf.convertToIsolateVirtualAddress(offset); + } + } + + int get hashCode => offset.hashCode; + + bool operator ==(Object other) { + return other is PCOffset && + offset == other.offset && + section == other.section; + } +} + +class StackTraceHeader { + final int _isolateStart; + final int _vmStart; + + StackTraceHeader(this._isolateStart, this._vmStart); + + factory StackTraceHeader.fromMatch(Match match) { + if (match == null) { + return null; + } + final isolateAddr = int.parse("0x" + match[1]); + final vmAddr = int.parse("0x" + match[2]); + return StackTraceHeader(isolateAddr, vmAddr); + } + + PCOffset convertAbsoluteAddress(int address) { + int isolateOffset = address - _isolateStart; + int vmOffset = address - _vmStart; + if (vmOffset > 0 && vmOffset == min(vmOffset, isolateOffset)) { + return PCOffset(vmOffset, InstructionSection.vm); + } else { + return PCOffset(isolateOffset, InstructionSection.isolate); + } + } +} + +PCOffset retrievePCOffset(StackTraceHeader header, Match match) { + assert(header != null && match != null); + final address = int.parse("0x" + match[2]); + return header.convertAbsoluteAddress(address); +} + +// Returns the [PCOffset] for each frame's absolute PC address if [lines] +// contains one or more DWARF stack traces. +Iterable collectPCOffsets(Iterable lines) { + final ret = []; + StackTraceHeader header = null; + for (var line in lines) { + if (line.endsWith(_traceStart)) { + header = null; + } + final startMatch = _traceInstructionsStartRE.firstMatch(line); + if (startMatch != null) { + header = StackTraceHeader.fromMatch(startMatch); + continue; + } + final lineMatch = _traceLineRE.firstMatch(line); + if (lineMatch != null) { + ret.add(retrievePCOffset(header, lineMatch)); + } + } + return ret; +} + +// Scans a stream of lines for Dart DWARF-based stack traces (i.e., Dart stack +// traces where the frame entries include PC addresses). For each stack frame +// found, the transformer attempts to locate a function name, file name and line +// number using the provided DWARF information. +// +// If no information is found, or the line is not a stack frame, the line is +// output to the sink unchanged. +// +// If the located information corresponds to Dart internals, the frame will be +// dropped. +// +// Otherwise, at least one altered stack frame is generated and replaces the +// stack frame portion of the original line. If the PC address corresponds to +// inlined code, then multiple stack frames may be generated. When multiple +// stack frames are generated, only the first replaces the stack frame portion +// of the original line, and the remaining frames are separately output. +class DwarfStackTraceDecoder extends StreamTransformerBase { + final Dwarf _dwarf; + final bool includeInternalFrames; + + DwarfStackTraceDecoder(this._dwarf, {this.includeInternalFrames = false}); + + Stream bind(Stream stream) => Stream.eventTransformed( + stream, + (sink) => _DwarfStackTraceEventSink(sink, _dwarf, + includeInternalFrames: includeInternalFrames)); +} + +class _DwarfStackTraceEventSink implements EventSink { + final EventSink _sink; + final Dwarf _dwarf; + final bool includeInternalFrames; + int _cachedDepth = 0; + StackTraceHeader _cachedHeader = null; + + _DwarfStackTraceEventSink(this._sink, this._dwarf, + {this.includeInternalFrames = false}); + + void close() => _sink.close(); + void addError(Object e, [StackTrace st]) => _sink.addError(e, st); + Future addStream(Stream stream) => stream.forEach(add); + + void add(String line) { + // Reset any stack-related state when we see the start of a new + // stacktrace. + if (line.endsWith(_traceStart)) { + _cachedDepth = 0; + _cachedHeader = null; + } + final startMatch = _traceInstructionsStartRE.firstMatch(line); + if (startMatch != null) { + _cachedHeader = StackTraceHeader.fromMatch(startMatch); + _sink.add(line); + return; + } + final lineMatch = _traceLineRE.firstMatch(line); + if (lineMatch == null) { + _sink.add(line); + return; + } + final location = + retrievePCOffset(_cachedHeader, lineMatch).virtualAddress(_dwarf); + final callInfo = _dwarf + .callInfo(location, includeInternalFrames: includeInternalFrames) + ?.toList(); + if (callInfo == null) { + // If we can't get appropriate information for the stack trace line, + // then just return the line unchanged. + _sink.add(line); + return; + } else if (callInfo.isEmpty) { + // No lines to output (as this corresponds to Dart internals). + return; + } + _sink.add(line.substring(0, lineMatch.start) + + _stackTracePiece(callInfo.first, _cachedDepth++)); + for (int i = 1; i < callInfo.length; i++) { + _sink.add(_stackTracePiece(callInfo[i], _cachedDepth++)); + } + } +} diff --git a/pkg/vm/lib/elf/dwarf.dart b/pkg/vm/lib/dwarf/dwarf.dart similarity index 97% rename from pkg/vm/lib/elf/dwarf.dart rename to pkg/vm/lib/dwarf/dwarf.dart index 012246dbce0..204e747f08e 100644 --- a/pkg/vm/lib/elf/dwarf.dart +++ b/pkg/vm/lib/dwarf/dwarf.dart @@ -969,6 +969,8 @@ class Dwarf { Map abbreviationTables; DebugInfo debugInfo; LineNumberInfo lineNumberInfo; + int vmStartAddress; + int isolateStartAddress; Dwarf.fromElf(Elf this.elf) { _loadSections(); @@ -980,7 +982,7 @@ class Dwarf { } void _loadSections() { - final abbrevSection = elf.namedSection(".debug_abbrev"); + final abbrevSection = elf.namedSection(".debug_abbrev").first; abbreviationTables = {}; var abbreviationOffset = 0; while (abbreviationOffset < abbrevSection.reader.length) { @@ -991,11 +993,21 @@ class Dwarf { } assert(abbreviationOffset == abbrevSection.reader.length); - final lineNumberSection = elf.namedSection(".debug_line"); + final lineNumberSection = elf.namedSection(".debug_line").first; lineNumberInfo = LineNumberInfo.fromReader(lineNumberSection.reader); - final infoSection = elf.namedSection(".debug_info"); + final infoSection = elf.namedSection(".debug_info").first; debugInfo = DebugInfo.fromReader(infoSection.reader, this); + + final textSegments = elf.namedSection(".text"); + if (textSegments.length != 2) { + throw FormatException( + "Expected two text segments for VM and isolate instructions"); + } + + final textAddresses = textSegments.map((s) => s.headerEntry.addr).toList(); + vmStartAddress = textAddresses[0]; + isolateStartAddress = textAddresses[1]; } Iterable callInfo(int address, @@ -1007,6 +1019,14 @@ class Dwarf { return calls; } + int convertToVMVirtualAddress(int textOffset) { + return textOffset + vmStartAddress; + } + + int convertToIsolateVirtualAddress(int textOffset) { + return textOffset + isolateStartAddress; + } + String toString() => "DWARF debugging information:\n\n" + abbreviationTables diff --git a/pkg/vm/lib/elf/elf.dart b/pkg/vm/lib/dwarf/elf.dart similarity index 93% rename from pkg/vm/lib/elf/elf.dart rename to pkg/vm/lib/dwarf/elf.dart index 0cf5672cc5f..0f0845b181a 100644 --- a/pkg/vm/lib/elf/elf.dart +++ b/pkg/vm/lib/dwarf/elf.dart @@ -415,7 +415,7 @@ class SectionHeader { // for the other section header entries. final nameTableEntry = _readSectionHeaderEntry(stringsIndex); assert(nameTableEntry.type == SectionHeaderEntry._SHT_STRTAB); - nameTable = StringTable.fromReader( + nameTable = StringTable(nameTableEntry, reader.refocus(nameTableEntry.offset, nameTableEntry.size)); nameTableEntry.setName(nameTable); @@ -444,19 +444,16 @@ class SectionHeader { class Section { final Reader reader; + final SectionHeaderEntry headerEntry; - Section.fromReader(Reader this.reader); + Section(this.headerEntry, this.reader); factory Section.fromEntryAndReader(SectionHeaderEntry entry, Reader reader) { switch (entry.type) { - case SectionHeaderEntry._SHT_NULL: - return NullSection.fromReader(reader.refocus(entry.offset, 0)); - case SectionHeaderEntry._SHT_NOBITS: - return NoBits.fromReader(reader.refocus(entry.offset, 0)); case SectionHeaderEntry._SHT_STRTAB: - return StringTable.fromReader(reader.refocus(entry.offset, entry.size)); + return StringTable(entry, reader); default: - return Section.fromReader(reader.refocus(entry.offset, entry.size)); + return Section(entry, reader); } } @@ -464,22 +461,10 @@ class Section { String toString() => "an unparsed section of ${length} bytes\n"; } -class NullSection extends Section { - NullSection.fromReader(Reader reader) : super.fromReader(reader); - - String toString() => "a null section\n"; -} - -class NoBits extends Section { - NoBits.fromReader(Reader reader) : super.fromReader(reader); - - String toString() => "a section with no bits in file\n"; -} - class StringTable extends Section { final _entries = Map(); - StringTable.fromReader(Reader reader) : super.fromReader(reader) { + StringTable(SectionHeaderEntry entry, Reader reader) : super(entry, reader) { while (!reader.done) { _entries[reader.offset] = reader.readNullTerminatedString(); } @@ -524,13 +509,17 @@ class Elf { return ret; } - Section namedSection(String name) { + Iterable
namedSection(String name) { + final ret =
[]; for (var entry in sections.keys) { if (entry.name == name) { - return sections[entry]; + ret.add(sections[entry]); } } - throw FormatException("No section named $name found in ELF file"); + if (ret.isEmpty) { + throw FormatException("No section named $name found in ELF file"); + } + return ret; } void _read() { @@ -554,7 +543,8 @@ class Elf { if (i == header.sectionHeaderStringsIndex) { sections[entry] = sectionHeader.nameTable; } else { - sections[entry] = Section.fromEntryAndReader(entry, reader.copy()); + sections[entry] = Section.fromEntryAndReader( + entry, reader.refocus(entry.offset, entry.size)); } } } diff --git a/pkg/vm/lib/elf/reader.dart b/pkg/vm/lib/dwarf/reader.dart similarity index 100% rename from pkg/vm/lib/elf/reader.dart rename to pkg/vm/lib/dwarf/reader.dart diff --git a/pkg/vm/lib/elf/convert.dart b/pkg/vm/lib/elf/convert.dart deleted file mode 100644 index cce90d545a1..00000000000 --- a/pkg/vm/lib/elf/convert.dart +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2019, 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. - -import "dart:async"; - -import "dwarf.dart"; - -String _stackTracePiece(CallInfo call, int depth) => "#${depth}\t${call}"; - -final _traceLineRE = RegExp(r' #(\d{2}) pc ([0-9a-f]+) (.*)$'); - -Iterable collectPCAddresses(Iterable lines) { - final ret = []; - for (var line in lines) { - final match = _traceLineRE.firstMatch(line); - if (match == null) continue; - ret.add(int.parse("0x" + match[2])); - } - return ret; -} - -// Scans a stream of lines for Dart DWARF-based stack traces (i.e., Dart stack -// traces where the frame entries include PC addresses). For each stack frame -// found, the transformer attempts to locate a function name, file name and line -// number using the provided DWARF information. -// -// If no information is found, or the line is not a stack frame, the line is -// output to the sink unchanged. -// -// If the located information corresponds to Dart internals, the frame will be -// dropped. -// -// Otherwise, at least one altered stack frame is generated and replaces the -// stack frame portion of the original line. If the PC address corresponds to -// inlined code, then multiple stack frames may be generated. When multiple -// stack frames are generated, only the first replaces the stack frame portion -// of the original line, and the remaining frames are separately output. -class DwarfStackTraceDecoder extends StreamTransformerBase { - final Dwarf _dwarf; - final bool includeInternalFrames; - - DwarfStackTraceDecoder(this._dwarf, {this.includeInternalFrames = false}); - - Stream bind(Stream stream) => Stream.eventTransformed( - stream, - (sink) => _DwarfStackTraceEventSink(sink, _dwarf, - includeInternalFrames: includeInternalFrames)); -} - -class _DwarfStackTraceEventSink implements EventSink { - final EventSink _sink; - final Dwarf _dwarf; - final bool includeInternalFrames; - int _cachedDepth = 0; - - _DwarfStackTraceEventSink(this._sink, this._dwarf, - {this.includeInternalFrames = false}); - - void close() => _sink.close(); - void addError(Object e, [StackTrace st]) => _sink.addError(e, st); - Future addStream(Stream stream) => stream.forEach(add); - - void add(String line) { - final match = _traceLineRE.firstMatch(line); - if (match == null) { - _sink.add(line); - return; - } - // We don't use the original frame depths because we may elide frames. - // If we match a stack frame with a depth of 0, then we're starting a - // new stack frame. - if (int.parse(match[1]) == 0) { - _cachedDepth = 0; - } - final location = int.parse("0x" + match[2]); - final callInfo = _dwarf - .callInfo(location, includeInternalFrames: includeInternalFrames) - ?.toList(); - if (callInfo == null) { - // If we can't get appropriate information for the stack trace line, - // then just return the line unchanged. - _sink.add(line); - return; - } else if (callInfo.isEmpty) { - // No lines to output (as this corresponds to Dart internals). - return; - } - _sink.add(line.substring(0, match.start) + - _stackTracePiece(callInfo.first, _cachedDepth++)); - for (int i = 1; i < callInfo.length; i++) { - _sink.add(_stackTracePiece(callInfo[i], _cachedDepth++)); - } - } -} diff --git a/runtime/bin/gen_snapshot.cc b/runtime/bin/gen_snapshot.cc index f6df0b91b8f..4dc0eadd38c 100644 --- a/runtime/bin/gen_snapshot.cc +++ b/runtime/bin/gen_snapshot.cc @@ -118,6 +118,7 @@ static const char* kSnapshotKindNames[] = { V(elf, elf_filename) \ V(load_compilation_trace, load_compilation_trace_filename) \ V(load_type_feedback, load_type_feedback_filename) \ + V(save_debugging_info, debugging_info_filename) \ V(save_obfuscation_map, obfuscation_map_filename) #define BOOL_OPTIONS_LIST(V) \ @@ -171,6 +172,7 @@ static void PrintUsage() { "as a static or dynamic library: \n" "--snapshot_kind=app-aot-assembly \n" "--assembly= \n" +"[--save-debugging-info=] \n" "[--obfuscate] \n" "[--save-obfuscation-map=] \n" " \n" @@ -180,6 +182,7 @@ static void PrintUsage() { "--elf= \n" "[--strip] \n" "[--obfuscate] \n" +"[--save-debugging-info=] \n" "[--save-obfuscation-map=] \n" " \n" " \n" @@ -333,8 +336,8 @@ static int ParseArguments(int argc, if (!obfuscate && obfuscation_map_filename != NULL) { Syslog::PrintErr( - "--obfuscation_map=<...> should only be specified when obfuscation is " - "enabled by --obfuscate flag.\n\n"); + "--save-obfuscation_map=<...> should only be specified when " + "obfuscation is enabled by the --obfuscate flag.\n\n"); return -1; } @@ -344,6 +347,20 @@ static int ParseArguments(int argc, return -1; } + if (debugging_info_filename != nullptr && + !IsSnapshottingForPrecompilation()) { + Syslog::PrintErr( + "--save-debugging-info=<...> can only be enabled when building an AOT " + "snapshot.\n\n"); + return -1; + } + + if (strip && snapshot_kind != kAppAOTElf) { + Syslog::PrintErr( + "Stripping can only be enabled when building an ELF AOT snapshot.\n\n"); + return -1; + } + return 0; } @@ -613,15 +630,23 @@ static void CreateAndWritePrecompiledSnapshot() { result = Dart_CreateAppAOTSnapshotAsAssembly(StreamingWriteCallback, file); CHECK_RESULT(result); } else if (snapshot_kind == kAppAOTElf) { - if (strip) { - Syslog::PrintErr( - "Warning: Generating ELF library without DWARF debugging" - " information.\n"); - } File* file = OpenFile(elf_filename); RefCntReleaseScope rs(file); - result = - Dart_CreateAppAOTSnapshotAsElf(StreamingWriteCallback, file, strip); + if (debugging_info_filename != nullptr) { + File* debug_file = OpenFile(debugging_info_filename); + RefCntReleaseScope rsd(debug_file); + result = Dart_CreateAppAOTSnapshotAsElf(StreamingWriteCallback, file, + strip, debug_file); + } else { + if (strip) { + Syslog::PrintErr( + "Warning: Generating ELF library without DWARF debugging" + " information.\n"); + } + result = + Dart_CreateAppAOTSnapshotAsElf(StreamingWriteCallback, file, strip, + /*debug_callback_data=*/nullptr); + } CHECK_RESULT(result); } else if (snapshot_kind == kAppAOTBlobs) { Syslog::PrintErr( @@ -678,6 +703,17 @@ static void CreateAndWritePrecompiledSnapshot() { CHECK_RESULT(result); WriteFile(obfuscation_map_filename, buffer, size); } + + // Output separate debugging information if not generating ELF (otherwise we + // have already generated it as part of that process). + if (debugging_info_filename != nullptr && snapshot_kind != kAppAOTElf) { + File* debug_file = OpenFile(debugging_info_filename); + RefCntReleaseScope rsd(debug_file); + result = Dart_CreateAppAOTSnapshotAsElf(StreamingWriteCallback, + /*callback_data=*/nullptr, + /*strip=*/false, debug_file); + CHECK_RESULT(result); + } } static Dart_QualifiedFunctionName no_entry_points[] = { diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h index c71c13f1c64..7af5c178dc1 100644 --- a/runtime/include/dart_api.h +++ b/runtime/include/dart_api.h @@ -3419,12 +3419,16 @@ Dart_CreateAppAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback, * * The callback will be invoked one or more times to provide the binary output. * + * If debug_callback_data is provided, debug_callback_data will be used with + * the callback to provide separate debugging information. + * * \return A valid handle if no error occurs during the operation. */ DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback, void* callback_data, - bool stripped); + bool stripped, + void* debug_callback_data); /** * Like Dart_CreateAppAOTSnapshotAsAssembly, but only includes diff --git a/runtime/platform/elf.h b/runtime/platform/elf.h index 939225ee12a..fac5ab498bb 100644 --- a/runtime/platform/elf.h +++ b/runtime/platform/elf.h @@ -138,6 +138,7 @@ static const intptr_t SHT_PROGBITS = 1; static const intptr_t SHT_SYMTAB = 2; static const intptr_t SHT_STRTAB = 3; static const intptr_t SHT_HASH = 5; +static const intptr_t SHT_NOBITS = 8; static const intptr_t SHT_DYNAMIC = 6; static const intptr_t SHT_DYNSYM = 11; @@ -153,10 +154,12 @@ static const intptr_t PT_LOAD = 1; static const intptr_t PT_DYNAMIC = 2; static const intptr_t PT_PHDR = 6; +static const intptr_t STB_LOCAL = 0; static const intptr_t STB_GLOBAL = 1; static const intptr_t STT_OBJECT = 1; // I.e., data. static const intptr_t STT_FUNC = 2; +static const intptr_t STT_SECTION = 3; static const intptr_t DT_NULL = 0; static const intptr_t DT_HASH = 4; diff --git a/runtime/tests/vm/dart/product_aot_kernel_test.dart b/runtime/tests/vm/dart/product_aot_kernel_test.dart index 85369a5c172..5bc2f13fd8c 100644 --- a/runtime/tests/vm/dart/product_aot_kernel_test.dart +++ b/runtime/tests/vm/dart/product_aot_kernel_test.dart @@ -15,9 +15,7 @@ import 'package:kernel/kernel.dart'; import 'package:path/path.dart' as path; import 'package:vm/metadata/bytecode.dart' show BytecodeMetadataRepository; -import 'use_bare_instructions_flag_test.dart' show run, withTempDir; - -const platformFilename = 'vm_platform_strong.dill'; +import 'use_flag_test_helper.dart'; Future main(List args) async { final buildDir = path.dirname(Platform.resolvedExecutable); @@ -28,20 +26,16 @@ Future main(List args) async { } if (Platform.isAndroid) { - print('Skipping test due missing "$platformFilename".'); + print('Skipping test due to missing "${path.basename(platformDill)}".'); return; } - final platformDill = path.join(buildDir, platformFilename); - await withTempDir((String tempDir) async { + await withTempDir('product-aot-kernel-test', (String tempDir) async { final helloFile = path.join(tempDir, 'hello.dart'); final helloDillFile = path.join(tempDir, 'hello.dart.dill'); // Compile script to Kernel IR. await File(helloFile).writeAsString('main() => print("Hello");'); - final genKernel = Platform.isWindows - ? "pkg\\vm\\tool\\gen_kernel.bat" - : 'pkg/vm/tool/gen_kernel'; await run(genKernel, [ '--aot', '--platform=$platformDill', diff --git a/runtime/tests/vm/dart/use_bare_instructions_flag_test.dart b/runtime/tests/vm/dart/use_bare_instructions_flag_test.dart index aaf6db81e18..eead3b6339f 100644 --- a/runtime/tests/vm/dart/use_bare_instructions_flag_test.dart +++ b/runtime/tests/vm/dart/use_bare_instructions_flag_test.dart @@ -12,8 +12,10 @@ import "dart:io"; import 'package:expect/expect.dart'; import 'package:path/path.dart' as path; +import 'use_flag_test_helper.dart'; + main(List args) async { - if (!Platform.executable.endsWith("dart_precompiled_runtime")) { + if (!isAOTRuntime) { return; // Running in JIT: AOT binaries not available. } @@ -21,18 +23,12 @@ main(List args) async { return; // SDK tree and dart_bootstrap not available on the test device. } - final buildDir = path.dirname(Platform.executable); - final sdkDir = path.dirname(path.dirname(buildDir)); - final platformDill = path.join(buildDir, 'vm_platform_strong.dill'); - final genSnapshot = path.join(buildDir, 'gen_snapshot'); - final aotRuntime = path.join(buildDir, 'dart_precompiled_runtime'); - - await withTempDir((String tempDir) async { + await withTempDir('bare-flag-test', (String tempDir) async { final script = path.join(sdkDir, 'pkg/kernel/bin/dump.dart'); final scriptDill = path.join(tempDir, 'kernel_dump.dill'); // Compile script to Kernel IR. - await run('pkg/vm/tool/gen_kernel', [ + await run(genKernel, [ '--aot', '--platform=$platformDill', '-o', @@ -105,32 +101,3 @@ main(List args) async { Future readFile(String file) { return new File(file).readAsString(); } - -Future run(String executable, List args) async { - print('Running $executable ${args.join(' ')}'); - - final result = await Process.run(executable, args); - final String stdout = result.stdout; - final String stderr = result.stderr; - if (stdout.isNotEmpty) { - print('stdout:'); - print(stdout); - } - if (stderr.isNotEmpty) { - print('stderr:'); - print(stderr); - } - - if (result.exitCode != 0) { - throw 'Command failed with non-zero exit code (was ${result.exitCode})'; - } -} - -withTempDir(Future fun(String dir)) async { - final tempDir = Directory.systemTemp.createTempSync('bare-flag-test'); - try { - await fun(tempDir.path); - } finally { - tempDir.deleteSync(recursive: true); - } -} diff --git a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_program.dart b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_program.dart new file mode 100644 index 00000000000..c422f18a08f --- /dev/null +++ b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_program.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2019, 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. +// Test that the full stacktrace in an error object matches the stacktrace +// handed to the catch clause. + +import "package:expect/expect.dart"; + +class C { + // operator*(o) is missing to trigger a noSuchMethodError when a C object + // is used in the multiplication below. +} + +bar(c) => c * 4; +foo(c) => bar(c); + +main() { + var a = foo(new C()); +} 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 new file mode 100644 index 00000000000..c1c84620c82 --- /dev/null +++ b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart @@ -0,0 +1,113 @@ +// Copyright (c) 2019, 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 the flag for --dwarf-stack-traces given at AOT +// compile-time will be used at runtime (irrespective if other values were +// passed to the runtime). + +// OtherResources=use_dwarf_stack_traces_flag_program.dart + +import "dart:async"; +import "dart:io"; + +import 'package:expect/expect.dart'; +import 'package:path/path.dart' as path; +import 'package:vm/dwarf/convert.dart'; + +import 'use_flag_test_helper.dart'; + +main(List args) 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(aotRuntime)) { + throw "Cannot run test as $aotRuntime not available"; + } + if (!File(platformDill).existsSync()) { + throw "Cannot run test as $platformDill does not exist"; + } + + await withTempDir('dwarf-flag-test', (String tempDir) async { + final cwDir = path.dirname(Platform.script.toFilePath()); + final script = path.join(cwDir, '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, + script, + ]); + + // Run the AOT compiler with/without Dwarf stack traces. + final scriptDwarfSnapshot = path.join(tempDir, 'dwarf.so'); + final scriptNonDwarfSnapshot = path.join(tempDir, 'non_dwarf.so'); + await Future.wait([ + run(genSnapshot, [ + '--dwarf-stack-traces', + '--snapshot-kind=app-aot-elf', + '--elf=$scriptDwarfSnapshot', + scriptDill, + ]), + run(genSnapshot, [ + '--no-dwarf-stack-traces', + '--snapshot-kind=app-aot-elf', + '--elf=$scriptNonDwarfSnapshot', + scriptDill, + ]), + ]); + + // Run the resulting Dwarf-AOT compiled script. + final dwarfOut1 = await runError(aotRuntime, [ + '--dwarf-stack-traces', + scriptDwarfSnapshot, + scriptDill, + ]); + final dwarfTrace1 = cleanStacktrace(dwarfOut1); + final dwarfOut2 = await runError(aotRuntime, [ + '--no-dwarf-stack-traces', + scriptDwarfSnapshot, + scriptDill, + ]); + final dwarfTrace2 = cleanStacktrace(dwarfOut2); + + // Run the resulting non-Dwarf-AOT compiled script. + final nonDwarfTrace1 = await runError(aotRuntime, [ + '--dwarf-stack-traces', + scriptNonDwarfSnapshot, + scriptDill, + ]); + final nonDwarfTrace2 = await runError(aotRuntime, [ + '--no-dwarf-stack-traces', + scriptNonDwarfSnapshot, + scriptDill, + ]); + + // Ensure the result is based off the flag passed to gen_snapshot, not + // the one passed to the runtime. + Expect.deepEquals(nonDwarfTrace1, nonDwarfTrace2); + + // For DWARF stack traces, we can't guarantee that the stack traces are + // textually equal on all platforms, but if we retrieve the PC offsets + // out of the stack trace, those should be equal. + Expect.deepEquals( + collectPCOffsets(dwarfTrace1), collectPCOffsets(dwarfTrace2)); + }); +} + +Iterable cleanStacktrace(Iterable lines) { + // For DWARF stack traces, the pid/tid, if output, will vary over runs. + return lines.where((line) => !line.startsWith('pid')); +} diff --git a/runtime/tests/vm/dart/use_flag_test_helper.dart b/runtime/tests/vm/dart/use_flag_test_helper.dart new file mode 100644 index 00000000000..685fdb58327 --- /dev/null +++ b/runtime/tests/vm/dart/use_flag_test_helper.dart @@ -0,0 +1,92 @@ +// Copyright (c) 2019, 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. + +import 'dart:io'; + +import 'package:expect/expect.dart'; +import 'package:path/path.dart' as path; + +final isAOTRuntime = path.basenameWithoutExtension(Platform.executable) == + 'dart_precompiled_runtime'; +final buildDir = path.dirname(Platform.executable); +final sdkDir = path.dirname(path.dirname(buildDir)); +final platformDill = path.join(buildDir, 'vm_platform_strong.dill'); +final genKernel = path.join(sdkDir, 'pkg', 'vm', 'tool', + 'gen_kernel' + (Platform.isWindows ? '.bat' : '')); +final _genSnapshotBase = 'gen_snapshot' + (Platform.isWindows ? '.exe' : ''); +// Slight hack to work around issue that gen_snapshot for simarm_x64 is not +// in the same subdirectory as dart_precompiled_runtime (${MODE}SIMARM), but +// instead it's in ${MODE}SIMARM_X64. +final genSnapshot = File(path.join(buildDir, _genSnapshotBase)).existsSync() + ? path.join(buildDir, _genSnapshotBase) + : path.join(buildDir + '_X64', _genSnapshotBase); +final aotRuntime = path.join( + buildDir, 'dart_precompiled_runtime' + (Platform.isWindows ? '.exe' : '')); + +Future runHelper(String executable, List args) async { + print('Running $executable ${args.join(' ')}'); + + final result = await Process.run(executable, args); + if (result.stdout.isNotEmpty) { + print('Subcommand stdout:'); + print(result.stdout); + } + if (result.stderr.isNotEmpty) { + print('Subcommand stderr:'); + print(result.stderr); + } + + return result; +} + +Future testExecutable(String executable) async { + try { + final result = await runHelper(executable, ['--version']); + return result.exitCode == 0; + } on ProcessException catch (e) { + print('Got process exception: $e'); + return false; + } +} + +Future run(String executable, List args) async { + final result = await runHelper(executable, args); + + if (result.exitCode != 0) { + throw 'Command failed with unexpected exit code (was ${result.exitCode})'; + } +} + +Future> runOutput(String executable, List args) async { + final result = await runHelper(executable, args); + + if (result.exitCode != 0) { + throw 'Command failed with unexpected exit code (was ${result.exitCode})'; + } + Expect.isTrue(result.stdout.isNotEmpty); + Expect.isTrue(result.stderr.isEmpty); + + return result.stdout.split(RegExp(r'[\r\n]')); +} + +Future> runError(String executable, List args) async { + final result = await runHelper(executable, args); + + if (result.exitCode == 0) { + throw 'Command did not fail with non-zero exit code'; + } + Expect.isTrue(result.stdout.isEmpty); + Expect.isTrue(result.stderr.isNotEmpty); + + return result.stderr.split(RegExp(r'[\r\n]')); +} + +Future withTempDir(String name, Future fun(String dir)) async { + final tempDir = Directory.systemTemp.createTempSync(name); + try { + await fun(tempDir.path); + } finally { + tempDir.deleteSync(recursive: true); + } +} diff --git a/runtime/tests/vm/dart/use_save_debugging_info_flag_test.dart b/runtime/tests/vm/dart/use_save_debugging_info_flag_test.dart new file mode 100644 index 00000000000..965ff5098fd --- /dev/null +++ b/runtime/tests/vm/dart/use_save_debugging_info_flag_test.dart @@ -0,0 +1,198 @@ +// Copyright (c) 2019, 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 the AOT compiler can generate debugging information +// for stripped ELF output, and that using the debugging information to look +// up stripped stack trace information matches the non-stripped version. + +// OtherResources=use_dwarf_stack_traces_flag_program.dart + +import "dart:io"; +import "dart:math"; +import "dart:typed_data"; + +import 'package:expect/expect.dart'; +import 'package:path/path.dart' as path; +import 'package:vm/dwarf/convert.dart'; +import 'package:vm/dwarf/dwarf.dart'; + +import 'use_flag_test_helper.dart'; + +main(List args) 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(aotRuntime)) { + throw "Cannot run test as $aotRuntime not available"; + } + if (!File(platformDill).existsSync()) { + throw "Cannot run test as $platformDill does not exist"; + } + + await withTempDir('save-debug-info-flag-test', (String tempDir) async { + final cwDir = path.dirname(Platform.script.toFilePath()); + // We can just reuse the program for the use_dwarf_stack_traces test. + final script = path.join(cwDir, '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, + script, + ]); + + // Run the AOT compiler with Dwarf stack traces, once without stripping, + // once with stripping, and once with stripping and saving debugging + // information. + final scriptWholeSnapshot = path.join(tempDir, 'whole.so'); + await run(genSnapshot, [ + '--dwarf-stack-traces', + '--snapshot-kind=app-aot-elf', + '--elf=$scriptWholeSnapshot', + scriptDill, + ]); + + final scriptStrippedOnlySnapshot = path.join(tempDir, 'stripped_only.so'); + await run(genSnapshot, [ + '--dwarf-stack-traces', + '--snapshot-kind=app-aot-elf', + '--elf=$scriptStrippedOnlySnapshot', + '--strip', + scriptDill, + ]); + + final scriptStrippedSnapshot = path.join(tempDir, 'stripped.so'); + final scriptDebuggingInfo = path.join(tempDir, 'debug.so'); + await run(genSnapshot, [ + '--dwarf-stack-traces', + '--snapshot-kind=app-aot-elf', + '--elf=$scriptStrippedSnapshot', + '--strip', + '--save-debugging-info=$scriptDebuggingInfo', + scriptDill, + ]); + + // Run the resulting scripts, saving the stack traces. + final wholeTrace = await runError(aotRuntime, [ + scriptWholeSnapshot, + scriptDill, + ]); + final wholeOffsets = collectPCOffsets(wholeTrace); + + final strippedOnlyTrace = await runError(aotRuntime, [ + scriptStrippedOnlySnapshot, + scriptDill, + ]); + final strippedOnlyOffsets = collectPCOffsets(strippedOnlyTrace); + + final strippedTrace = await runError(aotRuntime, [ + scriptStrippedSnapshot, + scriptDill, + ]); + final strippedOffsets = collectPCOffsets(strippedTrace); + + if (Platform.isWindows) { + // TODO(dartbug.com/35274): After this point, we make sure that we get + // the same offsets from the DWARF stack traces. On Windows, we currently + // aren't guaranteed to get the same offset in DWARF stack traces from + // different runs because we aren't using the native loader for dynamic + // libraries (as the Windows one does not understand ELF). Instead, we + // fall back onto our own ELF loader, and the DWARF stack trace output + // only prints relative PC addresses for dynamically loaded libraries. + return; + } + + // The retrieved offsets should be the same for all runs. + Expect.deepEquals(wholeOffsets, strippedOffsets); + Expect.deepEquals(strippedOnlyOffsets, strippedOffsets); + + // Stripped output should not change when --save-debugging-info is used. + compareSnapshots(scriptStrippedOnlySnapshot, scriptStrippedSnapshot); + + final stackTraceWithTerminators = strippedTrace.map((String s) => s + "\n"); + print("\nOriginal stack trace:"); + print(stackTraceWithTerminators.join()); + + final debugDwarf = Dwarf.fromFile(scriptDebuggingInfo); + final wholeDwarf = Dwarf.fromFile(scriptWholeSnapshot); + + final fromDebug = await Stream.fromIterable(stackTraceWithTerminators) + .transform(DwarfStackTraceDecoder(debugDwarf)) + .toList(); + print("\nStack trace converted using separate debugging info:"); + print(fromDebug.join()); + + final fromWhole = await Stream.fromIterable(stackTraceWithTerminators) + .transform(DwarfStackTraceDecoder(wholeDwarf)) + .toList(); + print("\nStack trace converted using unstripped ELF file:"); + print(fromWhole.join()); + + Expect.deepEquals(fromDebug, fromWhole); + }); +} + +void compareSnapshots(String file1, String file2) { + final bytes1 = File(file1).readAsBytesSync(); + final bytes2 = File(file2).readAsBytesSync(); + final diff = diffBinary(bytes1, bytes2); + if (diff.isNotEmpty) { + print("\nFound differences between $file1 and $file2:"); + printDiff(diff); + } + Expect.equals(bytes1.length, bytes2.length); + Expect.equals(0, diff.length); +} + +Map> diffBinary(Uint8List bytes1, Uint8List bytes2) { + final ret = Map>(); + final len = min(bytes1.length, bytes2.length); + for (var i = 0; i < len; i++) { + if (bytes1[i] != bytes2[i]) { + ret[i] = [bytes1[i], bytes2[i]]; + } + } + if (bytes1.length > len) { + for (var i = len; i < bytes1.length; i++) { + ret[i] = [bytes1[i], -1]; + } + } else if (bytes2.length > len) { + for (var i = len; i < bytes2.length; i++) { + ret[i] = [-1, bytes2[i]]; + } + } + return ret; +} + +void printDiff(Map> map, [int maxOutput = 100]) { + int lines = 0; + for (var index in map.keys) { + final pair = map[index]; + if (pair[0] == -1) { + print('$index: <>, ${pair[1]}'); + lines++; + } else if (pair[1] == -1) { + print('$index: ${pair[0]}, <>'); + lines++; + } else { + print('$index: ${pair[0]}, ${pair[1]}'); + lines++; + } + if (lines >= maxOutput) { + return; + } + } +} diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc index 0ebcea1b06c..7363f619096 100644 --- a/runtime/vm/dart_api_impl.cc +++ b/runtime/vm/dart_api_impl.cc @@ -6177,7 +6177,8 @@ Dart_CreateVMAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback, DART_EXPORT Dart_Handle Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback, void* callback_data, - bool strip) { + bool strip, + void* debug_callback_data) { #if defined(TARGET_ARCH_IA32) return Api::NewError("AOT compilation is not supported on IA32."); #elif !defined(DART_PRECOMPILER) @@ -6195,11 +6196,20 @@ Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback, uint8_t* isolate_snapshot_data_buffer = nullptr; uint8_t* isolate_snapshot_instructions_buffer = nullptr; - StreamingWriteStream elf_stream(2 * MB, callback, callback_data); + const bool generate_elf = callback_data != nullptr; + const bool generate_debug = debug_callback_data != nullptr; - Elf* elf = new (Z) Elf(Z, &elf_stream); + const intptr_t kInitialSize = 2 * MB; + StreamingWriteStream elf_stream(generate_elf ? kInitialSize : 0, callback, + callback_data); + const intptr_t kInitialDebugSize = generate_debug ? 1 * MB : 0; + StreamingWriteStream debug_stream(kInitialDebugSize, callback, + debug_callback_data); + + Elf* elf = new (Z) Elf(Z, generate_elf ? &elf_stream : nullptr, strip, + generate_debug ? &debug_stream : nullptr); Dwarf* dwarf = nullptr; - if (!strip) { + if (!strip || generate_debug) { dwarf = new (Z) Dwarf(Z, nullptr, elf); } @@ -6210,11 +6220,11 @@ Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback, elf->AddBSSData("_kDartBSSData", sizeof(compiler::target::uword)); BlobImageWriter vm_image_writer(T, &vm_snapshot_instructions_buffer, - ApiReallocate, /* initial_size= */ 2 * MB, - bss_base, elf, dwarf); - BlobImageWriter isolate_image_writer( - T, &isolate_snapshot_instructions_buffer, ApiReallocate, - /* initial_size= */ 2 * MB, bss_base, elf, dwarf); + ApiReallocate, kInitialSize, bss_base, elf, + dwarf); + BlobImageWriter isolate_image_writer(T, &isolate_snapshot_instructions_buffer, + ApiReallocate, kInitialSize, bss_base, + elf, dwarf); FullSnapshotWriter writer(Snapshot::kFullAOT, &vm_snapshot_data_buffer, &isolate_snapshot_data_buffer, ApiReallocate, &vm_image_writer, &isolate_image_writer); @@ -6224,7 +6234,7 @@ Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback, writer.VmIsolateSnapshotSize()); elf->AddROData("_kDartIsolateSnapshotData", isolate_snapshot_data_buffer, writer.IsolateSnapshotSize()); - if (!strip) { + if (dwarf != nullptr) { // TODO(rmacnak): Generate .debug_frame / .eh_frame / .arm.exidx to // provide unwinding information. dwarf->Write(); diff --git a/runtime/vm/dwarf.cc b/runtime/vm/dwarf.cc index a54f96a30d6..ec5024bc509 100644 --- a/runtime/vm/dwarf.cc +++ b/runtime/vm/dwarf.cc @@ -19,6 +19,8 @@ namespace dart { #define FORM_ADDR ".8byte" #endif +static const intptr_t kTargetWordSize = sizeof(compiler::target::kWordSize); + class InliningNode : public ZoneAllocated { public: InliningNode(const Function& function, @@ -285,9 +287,9 @@ void Dwarf::WriteCompilationUnit() { cu_start = position(); } - u2(2); // DWARF version 2 - u4(0); // debug_abbrev_offset - u1(sizeof(void*)); // address_size + u2(2); // DWARF version 2 + u4(0); // debug_abbrev_offset + u1(kTargetWordSize); // address_size // Compilation Unit DIE. We describe the entire Dart program as a single // compilation unit. Note we write attributes in the same order we declared @@ -537,9 +539,9 @@ void Dwarf::WriteInliningNode(InliningNode* node, const char* asm_name = namer->AssemblyNameFor(root_code_index, *codes_[root_code_index]); // DW_AT_low_pc - Print(FORM_ADDR " %s + %d\n", asm_name, node->start_pc_offset); + Print(FORM_ADDR " %s + %" Pd32 "\n", asm_name, node->start_pc_offset); // DW_AT_high_pc - Print(FORM_ADDR " %s + %d\n", asm_name, node->end_pc_offset); + Print(FORM_ADDR " %s + %" Pd32 "\n", asm_name, node->end_pc_offset); } else { // DW_AT_low_pc addr(root_code_offset + node->start_pc_offset); @@ -732,11 +734,11 @@ void Dwarf::WriteLines() { // 4. Update LNP pc. if (previous_code_offset == -1) { // This variant is relocatable. - u1(0); // This is an extended opcode - u1(1 + sizeof(void*)); // that is 5 or 9 bytes long + u1(0); // This is an extended opcode + u1(1 + kTargetWordSize); // that is 5 or 9 bytes long u1(DW_LNE_set_address); if (asm_stream_) { - Print(FORM_ADDR " %s + %d\n", asm_name, current_pc_offset); + Print(FORM_ADDR " %s + %" Pd32 "\n", asm_name, current_pc_offset); } else { addr(current_code_offset + current_pc_offset); } diff --git a/runtime/vm/dwarf.h b/runtime/vm/dwarf.h index ddbc287e699..b1cfc168555 100644 --- a/runtime/vm/dwarf.h +++ b/runtime/vm/dwarf.h @@ -223,7 +223,7 @@ class Dwarf : public ZoneAllocated { } void u1(uint8_t value) { if (asm_stream_) { - Print(".byte %d\n", value); + Print(".byte %u\n", value); } else { bin_stream_->WriteBytes(reinterpret_cast(&value), sizeof(value)); @@ -231,7 +231,7 @@ class Dwarf : public ZoneAllocated { } void u2(uint16_t value) { if (asm_stream_) { - Print(".2byte %d\n", value); + Print(".2byte %u\n", value); } else { bin_stream_->WriteBytes(reinterpret_cast(&value), sizeof(value)); @@ -239,7 +239,7 @@ class Dwarf : public ZoneAllocated { } intptr_t u4(uint32_t value) { if (asm_stream_) { - Print(".4byte %d\n", value); + Print(".4byte %" Pu32 "\n", value); return -1; } else { intptr_t fixup = position(); @@ -255,12 +255,23 @@ class Dwarf : public ZoneAllocated { memmove(bin_stream_->buffer() + position, &value, sizeof(value)); } } + void u8(uint64_t value) { + if (asm_stream_) { + Print(".8byte %" Pu64 "\n", value); + } else { + bin_stream_->WriteBytes(reinterpret_cast(&value), + sizeof(value)); + } + } void addr(uword value) { if (asm_stream_) { UNREACHABLE(); } else { - bin_stream_->WriteBytes(reinterpret_cast(&value), - sizeof(value)); +#if defined(TARGET_ARCH_IS_32_BIT) + u4(value); +#else + u8(value); +#endif } } void string(const char* cstr) { // NOLINT diff --git a/runtime/vm/elf.cc b/runtime/vm/elf.cc index fd1fc591ab7..dbf3db6a8fb 100644 --- a/runtime/vm/elf.cc +++ b/runtime/vm/elf.cc @@ -34,10 +34,10 @@ class Section : public ZoneAllocated { Section() {} virtual ~Section() {} - virtual void Write(Elf* stream) = 0; + virtual void Write(StreamingWriteStream* stream) = 0; // Linker view. - intptr_t section_name = 0; // Index into string table. + intptr_t section_name = -1; // Index into shstrtab_. intptr_t section_type = 0; intptr_t section_flags = 0; intptr_t section_index = -1; @@ -54,6 +54,17 @@ class Section : public ZoneAllocated { intptr_t segment_flags = 0; intptr_t memory_size = 0; intptr_t memory_offset = -1; + + enum OutputType { + kMainOutput, + kDebugOutput, + kAllOutput, + }; + + // When this section should be output, if we are stripping and/or splitting + // debugging information. Only a few sections are not part of the main + // (non-debugging) output, so we use kMainOutput as the default value. + OutputType output_type = kMainOutput; }; class ProgramBits : public Section { @@ -83,9 +94,9 @@ class ProgramBits : public Section { memory_size = memsz; } - void Write(Elf* stream) { + void Write(StreamingWriteStream* stream) { if (bytes_ != nullptr) { - stream->WriteBytes(bytes_, file_size); + Elf::WriteBytes(stream, bytes_, file_size); } } @@ -94,30 +105,45 @@ class ProgramBits : public Section { class StringTable : public Section { public: - explicit StringTable(bool allocate) : text_(128) { + explicit StringTable(bool dynamic) : text_(128), text_indices_() { section_type = elf::SHT_STRTAB; - section_flags = allocate ? elf::SHF_ALLOC : 0; - segment_type = elf::PT_LOAD; - segment_flags = elf::PF_R; + if (dynamic) { + section_flags = elf::SHF_ALLOC; + segment_type = elf::PT_LOAD; + segment_flags = elf::PF_R; + } else { + section_flags = 0; + memory_offset = 0; // No segments for static tables. + } text_.AddChar('\0'); + text_indices_.Insert({"", 1}); memory_size = file_size = text_.length(); } intptr_t AddString(const char* str) { + if (auto const kv = text_indices_.Lookup(str)) return kv->value - 1; intptr_t offset = text_.length(); text_.AddString(str); text_.AddChar('\0'); + text_indices_.Insert({str, offset + 1}); memory_size = file_size = text_.length(); return offset; } - void Write(Elf* stream) { - stream->WriteBytes(reinterpret_cast(text_.buf()), - text_.length()); + const char* GetString(intptr_t index) { + ASSERT(index >= 0 && index < text_.length()); + return text_.buf() + index; + } + + void Write(StreamingWriteStream* stream) { + Elf::WriteBytes(stream, reinterpret_cast(text_.buf()), + text_.length()); } TextBuffer text_; + // To avoid kNoValue for intptr_t (0), we store an index n as n + 1. + CStringMap text_indices_; }; class Symbol : public ZoneAllocated { @@ -142,39 +168,45 @@ class SymbolTable : public Section { // No need to load the static symbol table at runtime since it's ignored // by the dynamic linker. section_type = elf::SHT_SYMTAB; - memory_offset = 0; section_flags = 0; + memory_offset = 0; // No segments for static tables. + alignment = compiler::target::kWordSize; } section_entry_size = kElfSymbolTableEntrySize; - AddSymbol(NULL); - section_info = 1; // One "local" symbol, the reserved first entry. + section_info = 0; + AddSymbol(nullptr); } void AddSymbol(Symbol* symbol) { + // Adjust section_info to contain the count of local symbols, including the + // reserved first entry (represented by the nullptr value). + if (symbol == nullptr || ((symbol->info >> 4) == elf::STB_LOCAL)) { + section_info += 1; + } symbols_.Add(symbol); memory_size += kElfSymbolTableEntrySize; file_size += kElfSymbolTableEntrySize; } - void Write(Elf* stream) { + void Write(StreamingWriteStream* stream) { // The first symbol table entry is reserved and must be all zeros. { const intptr_t start = stream->position(); #if defined(TARGET_ARCH_IS_32_BIT) - stream->WriteWord(0); - stream->WriteAddr(0); - stream->WriteWord(0); - stream->WriteByte(0); - stream->WriteByte(0); - stream->WriteHalf(0); + Elf::WriteWord(stream, 0); + Elf::WriteAddr(stream, 0); + Elf::WriteWord(stream, 0); + Elf::WriteByte(stream, 0); + Elf::WriteByte(stream, 0); + Elf::WriteHalf(stream, 0); #else - stream->WriteWord(0); - stream->WriteByte(0); - stream->WriteByte(0); - stream->WriteHalf(0); - stream->WriteAddr(0); - stream->WriteXWord(0); + Elf::WriteWord(stream, 0); + Elf::WriteByte(stream, 0); + Elf::WriteByte(stream, 0); + Elf::WriteHalf(stream, 0); + Elf::WriteAddr(stream, 0); + Elf::WriteXWord(stream, 0); #endif const intptr_t end = stream->position(); ASSERT((end - start) == kElfSymbolTableEntrySize); @@ -184,19 +216,19 @@ class SymbolTable : public Section { Symbol* symbol = symbols_[i]; const intptr_t start = stream->position(); #if defined(TARGET_ARCH_IS_32_BIT) - stream->WriteWord(symbol->name); - stream->WriteAddr(symbol->offset); - stream->WriteWord(symbol->size); - stream->WriteByte(symbol->info); - stream->WriteByte(0); - stream->WriteHalf(symbol->section); + Elf::WriteWord(stream, symbol->name); + Elf::WriteAddr(stream, symbol->offset); + Elf::WriteWord(stream, symbol->size); + Elf::WriteByte(stream, symbol->info); + Elf::WriteByte(stream, 0); + Elf::WriteHalf(stream, symbol->section); #else - stream->WriteWord(symbol->name); - stream->WriteByte(symbol->info); - stream->WriteByte(0); - stream->WriteHalf(symbol->section); - stream->WriteAddr(symbol->offset); - stream->WriteXWord(symbol->size); + Elf::WriteWord(stream, symbol->name); + Elf::WriteByte(stream, symbol->info); + Elf::WriteByte(stream, 0); + Elf::WriteHalf(stream, symbol->section); + Elf::WriteAddr(stream, symbol->offset); + Elf::WriteXWord(stream, symbol->size); #endif const intptr_t end = stream->position(); ASSERT((end - start) == kElfSymbolTableEntrySize); @@ -254,14 +286,14 @@ class SymbolHashTable : public Section { memory_size = file_size = 4 * (nbucket_ + nchain_ + 2); } - void Write(Elf* stream) { - stream->WriteWord(nbucket_); - stream->WriteWord(nchain_); + void Write(StreamingWriteStream* stream) { + Elf::WriteWord(stream, nbucket_); + Elf::WriteWord(stream, nchain_); for (intptr_t i = 0; i < nbucket_; i++) { - stream->WriteWord(bucket_[i]); + Elf::WriteWord(stream, bucket_[i]); } for (intptr_t i = 0; i < nchain_; i++) { - stream->WriteWord(chain_[i]); + Elf::WriteWord(stream, chain_[i]); } } @@ -293,15 +325,15 @@ class DynamicTable : public Section { AddEntry(elf::DT_NULL, 0); } - void Write(Elf* stream) { + void Write(StreamingWriteStream* stream) { for (intptr_t i = 0; i < entries_.length(); i++) { const intptr_t start = stream->position(); #if defined(TARGET_ARCH_IS_32_BIT) - stream->WriteWord(entries_[i]->tag); - stream->WriteAddr(entries_[i]->value); + Elf::WriteWord(stream, entries_[i]->tag); + Elf::WriteAddr(stream, entries_[i]->value); #else - stream->WriteXWord(entries_[i]->tag); - stream->WriteAddr(entries_[i]->value); + Elf::WriteXWord(stream, entries_[i]->tag); + Elf::WriteAddr(stream, entries_[i]->value); #endif const intptr_t end = stream->position(); ASSERT((end - start) == kElfDynamicTableEntrySize); @@ -337,34 +369,52 @@ static const intptr_t kNumImplicitSegments = 3; static const intptr_t kProgramTableSegmentSize = Elf::kPageSize; -Elf::Elf(Zone* zone, StreamingWriteStream* stream) - : zone_(zone), stream_(stream), memory_offset_(0) { +Elf::Elf(Zone* zone, + StreamingWriteStream* stream, + bool strip, + StreamingWriteStream* debug_stream) + : zone_(ASSERT_NOTNULL(zone)), + strip_(strip), + stream_(stream), + debug_stream_(debug_stream), + sections_(zone, 2), + segments_(zone, 2), + active_sections_(zone, 2), + output_sections_(zone, 2), + adjusted_indices_(zone), + file_sizes_(zone, 2), + section_names_(zone, 2), + section_types_(zone, 2) { + // We should be outputting at least one file. + ASSERT(stream_ != nullptr || debug_stream_ != nullptr); + // Stripping the main output only makes sense if it'll be output. + ASSERT(!strip || stream_ != nullptr); + // Assumed by various offset logic in this file. - ASSERT(stream_->position() == 0); + ASSERT(stream_ == nullptr || stream_->position() == 0); + ASSERT(debug_stream_ == nullptr || debug_stream_->position() == 0); // All our strings would fit in a single page. However, we use separate // .shstrtab and .dynstr to work around a bug in Android's strip utility. - shstrtab_ = new (zone_) StringTable(/* allocate= */ false); - shstrtab_->section_name = shstrtab_->AddString(".shstrtab"); - - dynstrtab_ = new (zone_) StringTable(/* allocate= */ true); - dynstrtab_->section_name = shstrtab_->AddString(".dynstr"); + shstrtab_ = new (zone_) StringTable(/*dynamic=*/false); + shstrtab_->output_type = Section::kAllOutput; + dynstrtab_ = new (zone_) StringTable(/*dynamic=*/true); dynsym_ = new (zone_) SymbolTable(/*dynamic=*/true); - dynsym_->section_name = shstrtab_->AddString(".dynsym"); - - strtab_ = new (zone_) StringTable(/* allocate= */ false); - strtab_->section_name = shstrtab_->AddString(".strtab"); + // The (non-section header) static tables are not needed in stripped output. + strtab_ = new (zone_) StringTable(/*dynamic=*/false); + strtab_->output_type = Section::kDebugOutput; symtab_ = new (zone_) SymbolTable(/*dynamic=*/false); - symtab_->section_name = shstrtab_->AddString(".symtab"); + symtab_->output_type = Section::kDebugOutput; // Allocate regular segments after the program table. memory_offset_ = kProgramTableSegmentSize; } -void Elf::AddSection(Section* section) { - section->section_index = sections_.length() + kNumInvalidSections; +void Elf::AddSection(Section* section, const char* name) { + section->section_index = NextSectionIndex(); + section->section_name = shstrtab_->AddString(name); sections_.Add(section); } @@ -380,18 +430,13 @@ void Elf::AddSegment(Section* section) { memory_offset_ = Utils::RoundUp(memory_offset_, kPageSize); } -intptr_t Elf::NextMemoryOffset() const { - return memory_offset_; -} - intptr_t Elf::NextSectionIndex() const { return sections_.length() + kNumInvalidSections; } intptr_t Elf::AddText(const char* name, const uint8_t* bytes, intptr_t size) { ProgramBits* image = new (zone_) ProgramBits(true, true, false, bytes, size); - image->section_name = shstrtab_->AddString(".text"); - AddSection(image); + AddSection(image, ".text"); AddSegment(image); Symbol* symbol = new (zone_) Symbol(); @@ -434,8 +479,7 @@ intptr_t Elf::AddBSSData(const char* name, intptr_t size) { ProgramBits* const image = new (zone_) ProgramBits(true, false, true, bytes, /*filesz=*/size, /*memsz=*/size); - image->section_name = shstrtab_->AddString(".bss"); - AddSection(image); + AddSection(image, ".bss"); AddSegment(image); Symbol* symbol = new (zone_) Symbol(); @@ -454,8 +498,7 @@ intptr_t Elf::AddBSSData(const char* name, intptr_t size) { intptr_t Elf::AddROData(const char* name, const uint8_t* bytes, intptr_t size) { ProgramBits* image = new (zone_) ProgramBits(true, false, false, bytes, size); - image->section_name = shstrtab_->AddString(".rodata"); - AddSection(image); + AddSection(image, ".rodata"); AddSegment(image); Symbol* symbol = new (zone_) Symbol(); @@ -475,26 +518,19 @@ intptr_t Elf::AddROData(const char* name, const uint8_t* bytes, intptr_t size) { void Elf::AddDebug(const char* name, const uint8_t* bytes, intptr_t size) { ProgramBits* image = new (zone_) ProgramBits(false, false, false, bytes, size); - image->section_name = shstrtab_->AddString(name); - AddSection(image); + image->output_type = Section::kDebugOutput; + AddSection(image, name); } void Elf::Finalize() { SymbolHashTable* hash = new (zone_) SymbolHashTable(dynstrtab_, dynsym_); - hash->section_name = shstrtab_->AddString(".hash"); - AddSection(hash); - AddSection(dynsym_); - AddSection(dynstrtab_); - AddSection(strtab_); - AddSection(symtab_); + AddSection(hash, ".hash"); + AddSection(dynsym_, ".dynsym"); + AddSection(dynstrtab_, ".dynstr"); dynsym_->section_link = dynstrtab_->section_index; hash->section_link = dynsym_->section_index; - symtab_->section_link = strtab_->section_index; - - // Before finalizing the string table's memory size: - intptr_t name_dynamic = shstrtab_->AddString(".dynamic"); // Finalizes memory size of string and symbol tables. AddSegment(hash); @@ -502,44 +538,268 @@ void Elf::Finalize() { AddSegment(dynstrtab_); dynamic_ = new (zone_) DynamicTable(dynstrtab_, dynsym_, hash); - dynamic_->section_name = name_dynamic; - AddSection(dynamic_); + AddSection(dynamic_, ".dynamic"); AddSegment(dynamic_); - AddSection(shstrtab_); - shstrtab_->memory_offset = 0; // No segment. + // We only output the static symbol and string tables if they are non-empty. + // We only need to check symtab_, since entries are added to strtab_ whenever + // we add symbols. Here, an "empty" static symbol table only has one entry + // (a nullptr value for the initial reserved entry). + if (symtab_->symbols_.length() > 1) { + AddSection(symtab_, ".symtab"); + AddSection(strtab_, ".strtab"); + symtab_->section_link = strtab_->section_index; + } - ComputeFileOffsets(); + // The section header string table should come last. + AddSection(shstrtab_, ".shstrtab"); - WriteHeader(); - WriteProgramTable(); - WriteSections(); - WriteSectionTable(); + if (debug_stream_ != nullptr) { + PrepareDebugOutputInfo(); + WriteHeader(debug_stream_); + WriteProgramTable(debug_stream_); + WriteSections(debug_stream_); + WriteSectionTable(debug_stream_); + } + + if (stream_ != nullptr) { + PrepareMainOutputInfo(); + WriteHeader(stream_); + WriteProgramTable(stream_); + WriteSections(stream_); + WriteSectionTable(stream_); + } } -void Elf::ComputeFileOffsets() { +void Elf::ClearOutputInfo() { + active_sections_.Clear(); + output_sections_.Clear(); + adjusted_indices_.Clear(); + file_sizes_.Clear(); + section_names_.Clear(); + section_types_.Clear(); + + // These don't need to be cleared normally, but doing so in DEBUG mode + // may help us catch issues. +#if defined(DEBUG) + section_table_entry_count_ = -1; + section_table_file_offset_ = -1; + program_table_entry_count_ = -1; + program_table_file_offset_ = -1; + for (auto section : sections_) { + section->file_offset = -1; + } +#endif +} + +intptr_t Elf::ActiveSectionsIndex(intptr_t section_index) const { + // This assumes all invalid sections come first in the table. + ASSERT(section_index >= kNumInvalidSections); + return SectionTableIndex(section_index) - kNumInvalidSections; +} + +intptr_t Elf::SectionTableIndex(intptr_t section_index) const { + // This assumes all invalid sections come first in the table. + if (section_index < kNumInvalidSections) return section_index; + ASSERT(adjusted_indices_.HasKey(section_index)); + return adjusted_indices_.LookupValue(section_index); +} + +intptr_t Elf::ProgramTableSize() const { + ASSERT(program_table_entry_count_ >= 0); + return program_table_entry_count_ * kElfProgramTableEntrySize; +} + +intptr_t Elf::SectionTableSize() const { + ASSERT(section_table_entry_count_ >= 0); + return section_table_entry_count_ * kElfSectionTableEntrySize; +} + +void Elf::VerifyOutputInfo() const { +#if defined(DEBUG) + // The section header string table should always be the last section. We can't + // check for shstrtab_ because we recreate it to trim and reorder entries. + ASSERT(active_sections_.Last()->section_type == elf::SHT_STRTAB); + ASSERT(active_sections_.Last()->section_flags == 0); + auto const shstrtab = reinterpret_cast(active_sections_.Last()); + + ASSERT(file_sizes_.length() == active_sections_.length()); + ASSERT(section_names_.length() == active_sections_.length()); + ASSERT(section_types_.length() == active_sections_.length()); + // Need this to output the section header. + ASSERT(adjusted_indices_.HasKey(shstrtab->section_index)); + // Need this to output the dynamic section of the program table. + ASSERT(adjusted_indices_.HasKey(dynamic_->section_index)); + + // Perform extra checks on the Section GrowableArrays used in output + // (segments_, active_sections_, and output_sections_), including that + // they appear in the same order as in sections_. + intptr_t last_index = 0; + for (auto section : segments_) { + ASSERT(section->file_offset != -1); + ASSERT(section->section_index > last_index); + last_index = section->section_index; + auto const index = ActiveSectionsIndex(section->section_index); + ASSERT(index >= 0 && index < active_sections_.length()); + ASSERT(file_sizes_.At(index) == 0 || + file_sizes_.At(index) == section->file_size); + } + + last_index = 0; + for (auto section : active_sections_) { + ASSERT(section->file_offset != -1); + ASSERT(section->section_index > last_index); + last_index = section->section_index; + auto const index = ActiveSectionsIndex(section->section_index); + ASSERT(section_types_.At(index) == section->section_type || + section_types_.At(index) == elf::SHT_NOBITS); + auto const link_index = SectionTableIndex(section->section_link); + ASSERT(link_index >= 0 && link_index < section_table_entry_count_); + auto const name_index = section_names_.At(index); + ASSERT(name_index >= 0 && name_index < shstrtab->text_.length()); + // All (non-reserved) section names start with '.'. + ASSERT(shstrtab->GetString(name_index)[0] == '.'); + } + + // Here, we primarily check that output_sections_ is a subset of + // active_sections_, and thus all output sections are in the section table, + // and that all the sections are continguous in the file modulo alignment. + intptr_t file_offset = program_table_file_offset_ + ProgramTableSize(); + for (auto section : output_sections_) { + auto const index = ActiveSectionsIndex(section->section_index); + file_offset = Utils::RoundUp(file_offset, section->alignment); + ASSERT(section->file_offset == file_offset); + file_offset += section->file_size; + ASSERT(index >= 0 && index < active_sections_.length()); + } + ASSERT(Utils::RoundUp(file_offset, kElfSectionTableAlignment) == + section_table_file_offset_); +#endif +} + +intptr_t Elf::PrepareDebugSection(Section* section, + intptr_t file_offset, + bool use_fake_info) { + // All sections are output in the section table, even for debugging. + active_sections_.Add(section); + adjusted_indices_.Insert(section->section_index, section->section_index); + if (use_fake_info) { + // The fake offset of this section will be the aligned offset immediately + // after the program table. + auto const fake_offset = program_table_file_offset_ + ProgramTableSize(); + // No actual data will be output for these sections. + section_types_.Add(elf::SHT_NOBITS); + file_sizes_.Add(0); + section->file_offset = Utils::RoundUp(fake_offset, section->alignment); + return file_offset; + } + output_sections_.Add(section); + section_types_.Add(section->section_type); + file_sizes_.Add(section->file_size); + section->file_offset = Utils::RoundUp(file_offset, section->alignment); + return section->file_offset + section->file_size; +} + +intptr_t Elf::PrepareMainSection(Section* section, + intptr_t file_offset, + intptr_t skipped_sections) { + active_sections_.Add(section); + output_sections_.Add(section); + file_sizes_.Add(section->file_size); + section_types_.Add(section->section_type); + adjusted_indices_.Insert(section->section_index, + section->section_index - skipped_sections); + section->file_offset = Utils::RoundUp(file_offset, section->alignment); + return section->file_offset + section->file_size; +} + +StringTable* Elf::CreateSectionHeaderStringTable() { + // If there are no dropped sections prior to adding the section header string + // table, we can just use the current name indices and shstrtab_. + if (active_sections_.length() == (sections_.length() - 1)) { + for (auto section : active_sections_) { + section_names_.Add(section->section_name); + } + section_names_.Add(shstrtab_->section_name); + return shstrtab_; + } + + auto ret = new (zone_) StringTable(/*allocate=*/false); + // Fill fields set outside of methods in Section and its subclasses. + ret->section_name = shstrtab_->section_name; + ret->section_index = shstrtab_->section_index; + ret->output_type = Section::kAllOutput; + + for (auto section : active_sections_) { + auto const cstr = shstrtab_->GetString(section->section_name); + section_names_.Add(ret->AddString(cstr)); + } + // Now add the name for the section header string table itself. + section_names_.Add(ret->AddString(shstrtab_->GetString(ret->section_name))); + return ret; +} + +Section* Elf::AdjustForActiveSections(Section* section) { + // Possibly trim shstrtab_ to remove names for dropped sections. + if (section == shstrtab_) return CreateSectionHeaderStringTable(); + // No other section currently needs adjustment. + return section; +} + +void Elf::PrepareDebugOutputInfo() { + ClearOutputInfo(); + intptr_t file_offset = kElfHeaderSize; + // This is the same for both the debugging and stripped output. program_table_file_offset_ = file_offset; - program_table_file_size_ = - (segments_.length() + kNumImplicitSegments) * kElfProgramTableEntrySize; - file_offset += program_table_file_size_; + program_table_entry_count_ = segments_.length() + kNumImplicitSegments; + file_offset += ProgramTableSize(); - for (intptr_t i = 0; i < sections_.length(); i++) { - Section* section = sections_[i]; - file_offset = Utils::RoundUp(file_offset, section->alignment); - section->file_offset = file_offset; - file_offset += section->file_size; + for (auto section : sections_) { + // When splitting out debugging information, we only output the contents + // of debug sections and the section header string table, so change the + // section header information appropriately for other sections. + auto const use_fake_info = section->output_type == Section::kMainOutput; + section = AdjustForActiveSections(section); + file_offset = PrepareDebugSection(section, file_offset, use_fake_info); } file_offset = Utils::RoundUp(file_offset, kElfSectionTableAlignment); section_table_file_offset_ = file_offset; - section_table_file_size_ = - (sections_.length() + kNumInvalidSections) * kElfSectionTableEntrySize; - file_offset += section_table_file_size_; + section_table_entry_count_ = active_sections_.length() + kNumInvalidSections; + file_offset += SectionTableSize(); + + VerifyOutputInfo(); } -void Elf::WriteHeader() { +void Elf::PrepareMainOutputInfo() { + ClearOutputInfo(); + intptr_t file_offset = kElfHeaderSize; + + program_table_file_offset_ = file_offset; + program_table_entry_count_ = segments_.length() + kNumImplicitSegments; + file_offset += ProgramTableSize(); + + intptr_t skipped_sections = 0; + for (auto section : sections_) { + if (strip_ && section->output_type == Section::kDebugOutput) { + skipped_sections += 1; + continue; + } + section = AdjustForActiveSections(section); + file_offset = PrepareMainSection(section, file_offset, skipped_sections); + } + + file_offset = Utils::RoundUp(file_offset, kElfSectionTableAlignment); + section_table_file_offset_ = file_offset; + section_table_entry_count_ = active_sections_.length() + kNumInvalidSections; + file_offset += SectionTableSize(); + + VerifyOutputInfo(); +} + +void Elf::WriteHeader(StreamingWriteStream* stream) { #if defined(TARGET_ARCH_IS_32_BIT) uint8_t size = elf::ELFCLASS32; #else @@ -561,26 +821,26 @@ void Elf::WriteHeader() { 0, 0, 0}; - stream_->WriteBytes(e_ident, 16); + WriteBytes(stream, e_ident, 16); - WriteHalf(elf::ET_DYN); // Shared library. + WriteHalf(stream, elf::ET_DYN); // Shared library. #if defined(TARGET_ARCH_IA32) - WriteHalf(elf::EM_386); + WriteHalf(stream, elf::EM_386); #elif defined(TARGET_ARCH_X64) - WriteHalf(elf::EM_X86_64); + WriteHalf(stream, elf::EM_X86_64); #elif defined(TARGET_ARCH_ARM) - WriteHalf(elf::EM_ARM); + WriteHalf(stream, elf::EM_ARM); #elif defined(TARGET_ARCH_ARM64) - WriteHalf(elf::EM_AARCH64); + WriteHalf(stream, elf::EM_AARCH64); #else FATAL("Unknown ELF architecture"); #endif - WriteWord(elf::EV_CURRENT); // Version - WriteAddr(0); // "Entry point" - WriteOff(program_table_file_offset_); - WriteOff(section_table_file_offset_); + WriteWord(stream, elf::EV_CURRENT); // Version + WriteAddr(stream, 0); // "Entry point" + WriteOff(stream, program_table_file_offset_); + WriteOff(stream, section_table_file_offset_); #if defined(TARGET_ARCH_ARM) uword flags = elf::EF_ARM_ABI | (TargetCPUFeatures::hardfp_supported() @@ -589,46 +849,48 @@ void Elf::WriteHeader() { #else uword flags = 0; #endif - WriteWord(flags); + WriteWord(stream, flags); - WriteHalf(kElfHeaderSize); - WriteHalf(kElfProgramTableEntrySize); - WriteHalf(segments_.length() + kNumImplicitSegments); - WriteHalf(kElfSectionTableEntrySize); - WriteHalf(sections_.length() + kNumInvalidSections); - WriteHalf(shstrtab_->section_index); + WriteHalf(stream, kElfHeaderSize); + WriteHalf(stream, kElfProgramTableEntrySize); + WriteHalf(stream, program_table_entry_count_); + WriteHalf(stream, kElfSectionTableEntrySize); + WriteHalf(stream, section_table_entry_count_); + // The section header string table is always last in the active sections. + WriteHalf(stream, SectionTableIndex(active_sections_.Last()->section_index)); - ASSERT(stream_->position() == kElfHeaderSize); + ASSERT(stream->position() == kElfHeaderSize); } -void Elf::WriteProgramTable() { - ASSERT(stream_->position() == program_table_file_offset_); +void Elf::WriteProgramTable(StreamingWriteStream* stream) { + ASSERT(stream->position() == program_table_file_offset_); + auto const program_table_file_size = ProgramTableSize(); // Self-reference to program header table. Required by Android but not by // Linux. Must appear before any PT_LOAD entries. { ASSERT(kNumImplicitSegments == 3); - const intptr_t start = stream_->position(); + const intptr_t start = stream->position(); #if defined(TARGET_ARCH_IS_32_BIT) - WriteWord(elf::PT_PHDR); - WriteOff(program_table_file_offset_); // File offset. - WriteAddr(program_table_file_offset_); // Virtual address. - WriteAddr(program_table_file_offset_); // Physical address, not used. - WriteWord(program_table_file_size_); - WriteWord(program_table_file_size_); - WriteWord(elf::PF_R); - WriteWord(kPageSize); + WriteWord(stream, elf::PT_PHDR); + WriteOff(stream, program_table_file_offset_); // File offset. + WriteAddr(stream, program_table_file_offset_); // Virtual address. + WriteAddr(stream, program_table_file_offset_); // Physical address, unused. + WriteWord(stream, program_table_file_size); + WriteWord(stream, program_table_file_size); + WriteWord(stream, elf::PF_R); + WriteWord(stream, kPageSize); #else - WriteWord(elf::PT_PHDR); - WriteWord(elf::PF_R); - WriteOff(program_table_file_offset_); // File offset. - WriteAddr(program_table_file_offset_); // Virtual address. - WriteAddr(program_table_file_offset_); // Physical address, not used. - WriteXWord(program_table_file_size_); - WriteXWord(program_table_file_size_); - WriteXWord(kPageSize); + WriteWord(stream, elf::PT_PHDR); + WriteWord(stream, elf::PF_R); + WriteOff(stream, program_table_file_offset_); // File offset. + WriteAddr(stream, program_table_file_offset_); // Virtual address. + WriteAddr(stream, program_table_file_offset_); // Physical address, unused. + WriteXWord(stream, program_table_file_size); + WriteXWord(stream, program_table_file_size); + WriteXWord(stream, kPageSize); #endif - const intptr_t end = stream_->position(); + const intptr_t end = stream->position(); ASSERT((end - start) == kElfProgramTableEntrySize); } // Load for self-reference to program header table. Required by Android but @@ -639,11 +901,11 @@ void Elf::WriteProgramTable() { // fixed num of segments based on the four pieces of a snapshot, but if we // use more in the future we'll likely need to do something more compilated // to generate DWARF without knowing a piece's virtual address in advance. - RELEASE_ASSERT((program_table_file_offset_ + program_table_file_size_) < + RELEASE_ASSERT((program_table_file_offset_ + program_table_file_size) < kProgramTableSegmentSize); ASSERT(kNumImplicitSegments == 3); - const intptr_t start = stream_->position(); + const intptr_t start = stream->position(); // The Android dynamic linker in Jelly Bean incorrectly assumes that all // non-writable segments are continguous. We put BSS first, so we must make @@ -652,51 +914,58 @@ void Elf::WriteProgramTable() { // The bug is here: // https://github.com/aosp-mirror/platform_bionic/blob/94963af28e445384e19775a838a29e6a71708179/linker/linker.c#L1991-L2001 #if defined(TARGET_ARCH_IS_32_BIT) - WriteWord(elf::PT_LOAD); - WriteOff(0); // File offset. - WriteAddr(0); // Virtual address. - WriteAddr(0); // Physical address, not used. - WriteWord(program_table_file_offset_ + program_table_file_size_); - WriteWord(program_table_file_offset_ + program_table_file_size_); - WriteWord(elf::PF_R | elf::PF_W); - WriteWord(kPageSize); + WriteWord(stream, elf::PT_LOAD); + WriteOff(stream, 0); // File offset. + WriteAddr(stream, 0); // Virtual address. + WriteAddr(stream, 0); // Physical address, not used. + WriteWord(stream, program_table_file_offset_ + program_table_file_size); + WriteWord(stream, program_table_file_offset_ + program_table_file_size); + WriteWord(stream, elf::PF_R | elf::PF_W); + WriteWord(stream, kPageSize); #else - WriteWord(elf::PT_LOAD); - WriteWord(elf::PF_R | elf::PF_W); - WriteOff(0); // File offset. - WriteAddr(0); // Virtual address. - WriteAddr(0); // Physical address, not used. - WriteXWord(program_table_file_offset_ + program_table_file_size_); - WriteXWord(program_table_file_offset_ + program_table_file_size_); - WriteXWord(kPageSize); + WriteWord(stream, elf::PT_LOAD); + WriteWord(stream, elf::PF_R | elf::PF_W); + WriteOff(stream, 0); // File offset. + WriteAddr(stream, 0); // Virtual address. + WriteAddr(stream, 0); // Physical address, not used. + WriteXWord(stream, program_table_file_offset_ + program_table_file_size); + WriteXWord(stream, program_table_file_offset_ + program_table_file_size); + WriteXWord(stream, kPageSize); #endif - const intptr_t end = stream_->position(); + const intptr_t end = stream->position(); ASSERT((end - start) == kElfProgramTableEntrySize); } - for (intptr_t i = 0; i < segments_.length(); i++) { - Section* section = segments_[i]; - const intptr_t start = stream_->position(); + // We need to write out the segment headers even in the debugging info, + // even though there won't be any contents of those segments here and + // so we should report sizes of 0. + for (const auto section : segments_) { + const intptr_t start = stream->position(); + // file_sizes_ corresponds to active_sections_, so we first need to + // find the offset of this section in there. + auto const active_sections_index = + ActiveSectionsIndex(section->section_index); + auto const file_size = file_sizes_.At(active_sections_index); #if defined(TARGET_ARCH_IS_32_BIT) - WriteWord(section->segment_type); - WriteOff(section->file_offset); - WriteAddr(section->memory_offset); // Virtual address. - WriteAddr(section->memory_offset); // Physical address, not used. - WriteWord(section->file_size); - WriteWord(section->memory_size); - WriteWord(section->segment_flags); - WriteWord(section->alignment); + WriteWord(stream, section->segment_type); + WriteOff(stream, section->file_offset); + WriteAddr(stream, section->memory_offset); // Virtual address. + WriteAddr(stream, section->memory_offset); // Physical address, not used. + WriteWord(stream, file_size); + WriteWord(stream, section->memory_size); + WriteWord(stream, section->segment_flags); + WriteWord(stream, section->alignment); #else - WriteWord(section->segment_type); - WriteWord(section->segment_flags); - WriteOff(section->file_offset); - WriteAddr(section->memory_offset); // Virtual address. - WriteAddr(section->memory_offset); // Physical address, not used. - WriteXWord(section->file_size); - WriteXWord(section->memory_size); - WriteXWord(section->alignment); + WriteWord(stream, section->segment_type); + WriteWord(stream, section->segment_flags); + WriteOff(stream, section->file_offset); + WriteAddr(stream, section->memory_offset); // Virtual address. + WriteAddr(stream, section->memory_offset); // Physical address, not used. + WriteXWord(stream, file_size); + WriteXWord(stream, section->memory_size); + WriteXWord(stream, section->alignment); #endif - const intptr_t end = stream_->position(); + const intptr_t end = stream->position(); ASSERT((end - start) == kElfProgramTableEntrySize); } @@ -704,105 +973,111 @@ void Elf::WriteProgramTable() { // header table entries. { ASSERT(kNumImplicitSegments == 3); - const intptr_t start = stream_->position(); + const intptr_t start = stream->position(); + auto const active_sections_index = + ActiveSectionsIndex(dynamic_->section_index); + auto const file_size = file_sizes_.At(active_sections_index); #if defined(TARGET_ARCH_IS_32_BIT) - WriteWord(elf::PT_DYNAMIC); - WriteOff(dynamic_->file_offset); - WriteAddr(dynamic_->memory_offset); // Virtual address. - WriteAddr(dynamic_->memory_offset); // Physical address, not used. - WriteWord(dynamic_->file_size); - WriteWord(dynamic_->memory_size); - WriteWord(dynamic_->segment_flags); - WriteWord(dynamic_->alignment); + WriteWord(stream, elf::PT_DYNAMIC); + WriteOff(stream, dynamic_->file_offset); + WriteAddr(stream, dynamic_->memory_offset); // Virtual address. + WriteAddr(stream, dynamic_->memory_offset); // Physical address, not used. + WriteWord(stream, file_size); + WriteWord(stream, dynamic_->memory_size); + WriteWord(stream, dynamic_->segment_flags); + WriteWord(stream, dynamic_->alignment); #else - WriteWord(elf::PT_DYNAMIC); - WriteWord(dynamic_->segment_flags); - WriteOff(dynamic_->file_offset); - WriteAddr(dynamic_->memory_offset); // Virtual address. - WriteAddr(dynamic_->memory_offset); // Physical address, not used. - WriteXWord(dynamic_->file_size); - WriteXWord(dynamic_->memory_size); - WriteXWord(dynamic_->alignment); + WriteWord(stream, elf::PT_DYNAMIC); + WriteWord(stream, dynamic_->segment_flags); + WriteOff(stream, dynamic_->file_offset); + WriteAddr(stream, dynamic_->memory_offset); // Virtual address. + WriteAddr(stream, dynamic_->memory_offset); // Physical address, not used. + WriteXWord(stream, file_size); + WriteXWord(stream, dynamic_->memory_size); + WriteXWord(stream, dynamic_->alignment); #endif - const intptr_t end = stream_->position(); + const intptr_t end = stream->position(); ASSERT((end - start) == kElfProgramTableEntrySize); } } -void Elf::WriteSectionTable() { - stream_->Align(kElfSectionTableAlignment); - - ASSERT(stream_->position() == section_table_file_offset_); +void Elf::WriteSectionTable(StreamingWriteStream* stream) { + stream->Align(kElfSectionTableAlignment); + ASSERT(stream->position() == section_table_file_offset_); { // The first entry in the section table is reserved and must be all zeros. ASSERT(kNumInvalidSections == 1); - const intptr_t start = stream_->position(); + const intptr_t start = stream->position(); #if defined(TARGET_ARCH_IS_32_BIT) - WriteWord(0); - WriteWord(0); - WriteWord(0); - WriteAddr(0); - WriteOff(0); - WriteWord(0); - WriteWord(0); - WriteWord(0); - WriteWord(0); - WriteWord(0); + WriteWord(stream, 0); + WriteWord(stream, 0); + WriteWord(stream, 0); + WriteAddr(stream, 0); + WriteOff(stream, 0); + WriteWord(stream, 0); + WriteWord(stream, 0); + WriteWord(stream, 0); + WriteWord(stream, 0); + WriteWord(stream, 0); #else - WriteWord(0); - WriteWord(0); - WriteXWord(0); - WriteAddr(0); - WriteOff(0); - WriteXWord(0); - WriteWord(0); - WriteWord(0); - WriteXWord(0); - WriteXWord(0); + WriteWord(stream, 0); + WriteWord(stream, 0); + WriteXWord(stream, 0); + WriteAddr(stream, 0); + WriteOff(stream, 0); + WriteXWord(stream, 0); + WriteWord(stream, 0); + WriteWord(stream, 0); + WriteXWord(stream, 0); + WriteXWord(stream, 0); #endif - const intptr_t end = stream_->position(); + const intptr_t end = stream->position(); ASSERT((end - start) == kElfSectionTableEntrySize); } - for (intptr_t i = 0; i < sections_.length(); i++) { - Section* section = sections_[i]; - const intptr_t start = stream_->position(); + for (intptr_t i = 0; i < active_sections_.length(); i++) { + Section* section = active_sections_[i]; + auto const name = section_names_.At(i); + auto const type = section_types_.At(i); + auto const file_size = file_sizes_.At(i); + auto const link = SectionTableIndex(section->section_link); + + const intptr_t start = stream->position(); #if defined(TARGET_ARCH_IS_32_BIT) - WriteWord(section->section_name); - WriteWord(section->section_type); - WriteWord(section->section_flags); - WriteAddr(section->memory_offset); - WriteOff(section->file_offset); - WriteWord(section->file_size); // Has different meaning for BSS. - WriteWord(section->section_link); - WriteWord(section->section_info); - WriteWord(section->alignment); - WriteWord(section->section_entry_size); + WriteWord(stream, name); + WriteWord(stream, type); + WriteWord(stream, section->section_flags); + WriteAddr(stream, section->memory_offset); + WriteOff(stream, section->file_offset); + WriteWord(stream, file_size); // Has different meaning for BSS. + WriteWord(stream, link); + WriteWord(stream, section->section_info); + WriteWord(stream, section->alignment); + WriteWord(stream, section->section_entry_size); #else - WriteWord(section->section_name); - WriteWord(section->section_type); - WriteXWord(section->section_flags); - WriteAddr(section->memory_offset); - WriteOff(section->file_offset); - WriteXWord(section->file_size); // Has different meaning for BSS. - WriteWord(section->section_link); - WriteWord(section->section_info); - WriteXWord(section->alignment); - WriteXWord(section->section_entry_size); + WriteWord(stream, name); + WriteWord(stream, type); + WriteXWord(stream, section->section_flags); + WriteAddr(stream, section->memory_offset); + WriteOff(stream, section->file_offset); + WriteXWord(stream, file_size); // Has different meaning for BSS. + WriteWord(stream, link); + WriteWord(stream, section->section_info); + WriteXWord(stream, section->alignment); + WriteXWord(stream, section->section_entry_size); #endif - const intptr_t end = stream_->position(); + const intptr_t end = stream->position(); ASSERT((end - start) == kElfSectionTableEntrySize); } } -void Elf::WriteSections() { - for (intptr_t i = 0; i < sections_.length(); i++) { - Section* section = sections_[i]; - stream_->Align(section->alignment); - ASSERT(stream_->position() == section->file_offset); - section->Write(this); - ASSERT(stream_->position() == section->file_offset + section->file_size); +void Elf::WriteSections(StreamingWriteStream* stream) { + for (auto section : output_sections_) { + stream->Align(section->alignment); + ASSERT(stream->position() == section->file_offset); + section->Write(stream); + ASSERT(stream->position() == section->file_offset + section->file_size); } } diff --git a/runtime/vm/elf.h b/runtime/vm/elf.h index b328e0f2873..f6ac23f4ecd 100644 --- a/runtime/vm/elf.h +++ b/runtime/vm/elf.h @@ -9,6 +9,7 @@ #include "vm/compiler/runtime_api.h" #include "vm/datastream.h" #include "vm/growable_array.h" +#include "vm/hash_map.h" #include "vm/zone.h" namespace dart { @@ -21,11 +22,14 @@ class SymbolTable; class Elf : public ZoneAllocated { public: - Elf(Zone* zone, StreamingWriteStream* stream); + Elf(Zone* zone, + StreamingWriteStream* stream, + bool strip, + StreamingWriteStream* debug_stream = nullptr); static const intptr_t kPageSize = 4096; - intptr_t NextMemoryOffset() const; + intptr_t NextMemoryOffset() const { return memory_offset_; } intptr_t NextSectionIndex() const; intptr_t AddText(const char* name, const uint8_t* bytes, intptr_t size); intptr_t AddROData(const char* name, const uint8_t* bytes, intptr_t size); @@ -37,57 +41,108 @@ class Elf : public ZoneAllocated { void Finalize(); - intptr_t position() const { return stream_->position(); } - void WriteBytes(const uint8_t* b, intptr_t size) { - stream_->WriteBytes(b, size); + static void WriteBytes(StreamingWriteStream* stream, + const uint8_t* bytes, + intptr_t size) { + stream->WriteBytes(bytes, size); } - void WriteByte(uint8_t value) { - stream_->WriteBytes(reinterpret_cast(&value), sizeof(value)); + static void WriteByte(StreamingWriteStream* stream, uint8_t value) { + stream->WriteBytes(reinterpret_cast(&value), sizeof(value)); } - void WriteHalf(uint16_t value) { - stream_->WriteBytes(reinterpret_cast(&value), sizeof(value)); + static void WriteHalf(StreamingWriteStream* stream, uint16_t value) { + stream->WriteBytes(reinterpret_cast(&value), sizeof(value)); } - void WriteWord(uint32_t value) { - stream_->WriteBytes(reinterpret_cast(&value), sizeof(value)); + static void WriteWord(StreamingWriteStream* stream, uint32_t value) { + stream->WriteBytes(reinterpret_cast(&value), sizeof(value)); } - void WriteAddr(compiler::target::uword value) { - stream_->WriteBytes(reinterpret_cast(&value), sizeof(value)); + static void WriteAddr(StreamingWriteStream* stream, + compiler::target::uword value) { + stream->WriteBytes(reinterpret_cast(&value), sizeof(value)); } - void WriteOff(compiler::target::uword value) { - stream_->WriteBytes(reinterpret_cast(&value), sizeof(value)); + static void WriteOff(StreamingWriteStream* stream, + compiler::target::uword value) { + stream->WriteBytes(reinterpret_cast(&value), sizeof(value)); } #if defined(TARGET_ARCH_IS_64_BIT) - void WriteXWord(uint64_t value) { - stream_->WriteBytes(reinterpret_cast(&value), sizeof(value)); + static void WriteXWord(StreamingWriteStream* stream, uint64_t value) { + stream->WriteBytes(reinterpret_cast(&value), sizeof(value)); } #endif private: - void AddSection(Section* section); + void AddSection(Section* section, const char* name); void AddSegment(Section* section); - void ComputeFileOffsets(); - void WriteHeader(); - void WriteSectionTable(); - void WriteProgramTable(); - void WriteSections(); + intptr_t ActiveSectionsIndex(intptr_t section_index) const; + intptr_t SectionTableIndex(intptr_t section_index) const; + intptr_t ProgramTableSize() const; + intptr_t SectionTableSize() const; + + void ClearOutputInfo(); + // Checks that the output information used by the writing methods has been + // properly constructed. + void VerifyOutputInfo() const; + + // Creates a new version of shstrtab_ that only copies over names of active + // sections. Sets the contents of section_names_ to indices in the new table. + StringTable* CreateSectionHeaderStringTable(); + // Either returns the original section or a section like the old one that + // also accounts for what sections are currently active. + Section* AdjustForActiveSections(Section* section); + + // These methods return the new file offset taking into consideration the + // alignment and size of the section. + intptr_t PrepareDebugSection(Section* section, + intptr_t start_offset, + bool use_fake_info); + intptr_t PrepareMainSection(Section* section, + intptr_t start_offset, + intptr_t skipped_sections); + + // These methods set up: + // * Various information about file offsets + // * The number of entries in the program and section tables + // * An array of the active sections (i.e., those in the section table). + // * An array of the sections that will be fully output. + // * Some arrays of information used instead of the values of their + // corresponding Section fields when creating the section table. + void PrepareDebugOutputInfo(); + void PrepareMainOutputInfo(); + + void WriteHeader(StreamingWriteStream* s); + void WriteProgramTable(StreamingWriteStream* s); + void WriteSectionTable(StreamingWriteStream* s); + void WriteSections(StreamingWriteStream* s); Zone* const zone_; - StreamingWriteStream* stream_; + const bool strip_; + StreamingWriteStream* const stream_; + StreamingWriteStream* const debug_stream_; GrowableArray sections_; GrowableArray segments_; - intptr_t memory_offset_; - intptr_t section_table_file_offset_; - intptr_t section_table_file_size_; - intptr_t program_table_file_offset_; - intptr_t program_table_file_size_; - StringTable* shstrtab_; - StringTable* dynstrtab_; - SymbolTable* dynsym_; - StringTable* strtab_; - SymbolTable* symtab_; - DynamicTable* dynamic_; + intptr_t memory_offset_ = 0; + StringTable* shstrtab_ = nullptr; + StringTable* dynstrtab_ = nullptr; + SymbolTable* dynsym_ = nullptr; + StringTable* strtab_ = nullptr; + SymbolTable* symtab_ = nullptr; + DynamicTable* dynamic_ = nullptr; + + // Filled out during the Prepare*OutputInfo methods and used by the Write* + // instance methods, as these values will differ between stripped and + // debugging outputs. + GrowableArray active_sections_; + GrowableArray output_sections_; + IntMap adjusted_indices_; + // These should all contain entries for the sections in active_sections_. + GrowableArray file_sizes_; + GrowableArray section_names_; + GrowableArray section_types_; + intptr_t section_table_file_offset_ = -1; + intptr_t section_table_entry_count_ = -1; + intptr_t program_table_file_offset_ = -1; + intptr_t program_table_entry_count_ = -1; }; } // namespace dart diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h index d4134b29b10..534d88df845 100644 --- a/runtime/vm/flag_list.h +++ b/runtime/vm/flag_list.h @@ -244,11 +244,12 @@ constexpr bool kDartUseBackgroundCompilation = true; // List of VM-global (i.e. non-isolate specific) flags. // // The value used for those flags at snapshot generation time needs to be the -// same as during runtime. +// same as during runtime. Currently only boolean flags are supported. // // Usage: // V(name, command-line-flag-name) #define VM_GLOBAL_FLAG_LIST(V) \ + V(dwarf_stack_traces, FLAG_dwarf_stack_traces) \ V(use_bare_instructions, FLAG_use_bare_instructions) #endif // RUNTIME_VM_FLAG_LIST_H_ diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc index fbf67126a7c..862513657f0 100644 --- a/runtime/vm/image_snapshot.cc +++ b/runtime/vm/image_snapshot.cc @@ -924,7 +924,7 @@ void BlobImageWriter::WriteText(WriteStream* clustered_stream, bool vm) { const Code& code = *instructions_[i].code_; if ((elf_ != nullptr) && (dwarf_ != nullptr) && !code.IsNull()) { intptr_t segment_offset = instructions_blob_stream_.bytes_written() + - Instructions::HeaderSize(); + compiler::target::Instructions::HeaderSize(); dwarf_->AddCode(code, segment_base + segment_offset); } #endif diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index dd5a28f7784..b54b2843317 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -22411,9 +22411,10 @@ const char* StackTrace::ToDartCString(const StackTrace& stack_trace_in) { const char* StackTrace::ToDwarfCString(const StackTrace& stack_trace_in) { #if defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME) - Zone* zone = Thread::Current()->zone(); - StackTrace& stack_trace = StackTrace::Handle(zone, stack_trace_in.raw()); - Object& code = Object::Handle(zone); + auto const T = Thread::Current(); + auto const zone = T->zone(); + auto& stack_trace = StackTrace::Handle(zone, stack_trace_in.raw()); + auto& code = Object::Handle(zone); ZoneTextBuffer buffer(zone, 1024); // The Dart standard requires the output of StackTrace.toString to include @@ -22429,6 +22430,14 @@ const char* StackTrace::ToDwarfCString(const StackTrace& stack_trace_in) { OSThread* thread = OSThread::Current(); buffer.Printf("pid: %" Pd ", tid: %" Pd ", name %s\n", OS::ProcessId(), OSThread::ThreadIdToIntPtr(thread->id()), thread->name()); + auto const isolate_instructions = + T->isolate_group()->source()->snapshot_instructions; + auto const vm_instructions = + Dart::vm_isolate()->group()->source()->snapshot_instructions; + buffer.Printf("isolate_instructions: %" Px "", + reinterpret_cast(isolate_instructions)); + buffer.Printf(" vm_instructions: %" Px "\n", + reinterpret_cast(vm_instructions)); intptr_t frame_index = 0; uint32_t frame_skip = 0; do { @@ -22463,11 +22472,11 @@ const char* StackTrace::ToDwarfCString(const StackTrace& stack_trace_in) { if (NativeSymbolResolver::LookupSharedObject(call_addr, &dso_base, &dso_name)) { uword dso_offset = call_addr - dso_base; - buffer.Printf(" #%02" Pd " pc %" Pp " %s\n", frame_index, - dso_offset, dso_name); + buffer.Printf(" #%02" Pd " abs %" Pp " virt %" Pp " %s\n", + frame_index, call_addr, dso_offset, dso_name); NativeSymbolResolver::FreeSymbolName(dso_name); } else { - buffer.Printf(" #%02" Pd " pc %" Pp " \n", frame_index, + buffer.Printf(" #%02" Pd " abs %" Pp " \n", frame_index, call_addr); } frame_index++; diff --git a/tests/standalone_2/dwarf_stack_trace_test.dart b/tests/standalone_2/dwarf_stack_trace_test.dart index d32008a5d05..91c7d7f16cd 100644 --- a/tests/standalone_2/dwarf_stack_trace_test.dart +++ b/tests/standalone_2/dwarf_stack_trace_test.dart @@ -2,15 +2,14 @@ // 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. -/// VMOptions=--dwarf-stack-traces +/// VMOptions=--dwarf-stack-traces --save-debugging-info=dwarf.so import 'dart:convert'; import 'dart:io'; import 'package:unittest/unittest.dart'; -import 'package:vm/elf/convert.dart'; -import 'package:vm/elf/dwarf.dart'; -import 'package:vm/elf/elf.dart'; +import 'package:vm/dwarf/convert.dart'; +import 'package:vm/dwarf/dwarf.dart'; import 'package:path/path.dart' as path; @pragma("vm:prefer-inline") @@ -40,24 +39,21 @@ Future main() async { print("Raw stack trace:"); print(rawStack); - if (Platform.isWindows) { - // TODO(dartbug.com/39490): Remove this when we can retrieve or calculate - // virtual addresses from DWARF stack traces on Windows. - print("Skipping test because we are running on Windows."); - return; + if (!Platform.executable.endsWith("dart_precompiled_runtime")) { + return; // Not running from an AOT compiled snapshot. } - if (!Elf.startsWithMagicNumber(Platform.script.toFilePath())) { - print("Skipping test because we are not running from ELF."); - return; + if (Platform.isAndroid) { + return; // Generated dwarf.so not available on the test device. } - final dwarf = Dwarf.fromFile(Platform.script.toFilePath()); + final dwarf = Dwarf.fromFile("dwarf.so"); - var rawLines = + final rawLines = await Stream.value(rawStack).transform(const LineSplitter()).toList(); - final pcAddresses = collectPCAddresses(rawLines).toList(); + final pcAddresses = + collectPCOffsets(rawLines).map((pc) => pc.virtualAddress(dwarf)).toList(); // We should have at least enough PC addresses to cover the frames we'll be // checking. @@ -142,20 +138,20 @@ final expectedExternalCallInfo = >[ CallInfo( function: "bar", filename: "dwarf_stack_trace_test.dart", - line: 19, + line: 18, inlined: true), // The second frame corresponds to call to foo in main. CallInfo( function: "foo", filename: "dwarf_stack_trace_test.dart", - line: 25, + line: 24, inlined: false) ], [ CallInfo( function: "main", filename: "dwarf_stack_trace_test.dart", - line: 31, + line: 30, inlined: false) ], // No call information for the main tearoff.