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.