From afed0f59e35dbb4bb53eb044ca15b1be2b5ad422 Mon Sep 17 00:00:00 2001 From: Teagan Strickland Date: Mon, 6 Apr 2020 13:11:41 +0000 Subject: [PATCH] [native_stack_traces] Allow static symbol offsets as locations. Static symbol offsets from Dart stack traces can now be used directly as locations for the find command. Change-Id: Ie2971099cbc33b871f8e0e213865854b5ee6890b Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-linux-product-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/142362 Commit-Queue: Tess Strickland Reviewed-by: Martin Kustermann --- pkg/native_stack_traces/CHANGELOG.md | 10 +- pkg/native_stack_traces/bin/decode.dart | 106 +++++++++++------- .../lib/native_stack_traces.dart | 6 +- pkg/native_stack_traces/lib/src/convert.dart | 70 ++++++++---- pkg/native_stack_traces/lib/src/dwarf.dart | 18 ++- pkg/native_stack_traces/pubspec.yaml | 2 +- 6 files changed, 138 insertions(+), 74 deletions(-) diff --git a/pkg/native_stack_traces/CHANGELOG.md b/pkg/native_stack_traces/CHANGELOG.md index 06b17020bb6..bb84d4363b0 100644 --- a/pkg/native_stack_traces/CHANGELOG.md +++ b/pkg/native_stack_traces/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.3.2 + +- The `find` command can now look up addresses given as offsets from static + symbols, not just hexadecimal virtual or absolute addresses. +- Integer inputs (addresses or offsets) without an '0x' prefix or hexadecimal + digits will now be parsed as decimal unless the `-x`/`--force_hexadecimal` + flag is used. + ## 0.3.1 - Uses dynamic symbol information embedded in stack frame lines when available. @@ -15,7 +23,7 @@ ## 0.2.1 -- Added static method Dwarf.fromBuffer. +- Added static method `Dwarf.fromBuffer`. ## 0.2.0 diff --git a/pkg/native_stack_traces/bin/decode.dart b/pkg/native_stack_traces/bin/decode.dart index 4b864c0d02a..22a5e7e6301 100644 --- a/pkg/native_stack_traces/bin/decode.dart +++ b/pkg/native_stack_traces/bin/decode.dart @@ -34,6 +34,10 @@ final ArgParser _findParser = _createBaseDebugParser(ArgParser(allowTrailingOptions: true)) ..addMultiOption('location', abbr: 'l', help: 'PC address to find', valueHelp: 'PC') + ..addFlag('force_hexadecimal', + abbr: 'x', + negatable: false, + help: 'Always parse integers as hexadecimal') ..addOption('vm_start', help: 'Absolute address for start of VM instructions', valueHelp: 'PC') @@ -88,19 +92,30 @@ final String _findUsage = ''' Usage: convert_stack_traces find [options] ... The find command looks up program counter (PC) addresses, either given as -arguments on the command line or via the -l option. For each PC address, -it outputs the file, function, and line number information if found. +arguments on the command line or via the -l/--location option. For each +successful PC lookup, it outputs the call information in one of two formats: -PC addresses are expected to be hexadecimal numbers with or without an initial -"0x" marker. +- If the location corresponds to a call site in Dart source code, the call + information includes the file, function, and line number information. +- If it corresponds to a Dart VM stub, the call information includes the dynamic + symbol name for the instructions payload and an offset into that payload. The -l option may be provided multiple times, or a single use of the -l option may be given multiple arguments separated by commas. -By default, PC addresses are assumed to be virtual addresses valid for the -given debugging information. To find absolute PC addresses, use both the ---vm_start and --isolate_start arguments tp provide the absolute addresses of -the VM and isolate instructions sections. +PC addresses can be provided in one of two formats: + +- An integer, e.g. 0x2a3f or 15049 +- A static symbol in the VM snapshot plus an integer offset, e.g., + _kDartIsolateSnapshotInstructions+1523 or _kDartVMSnapshotInstructions+0x403f + +Integers without an "0x" prefix that do not includes hexadecimal digits are +assumed to be decimal unless the -x/--force_hexadecimal flag is used. + +By default, integer PC addresses are assumed to be virtual addresses valid for +the given debugging information. Otherwise, use both the --vm_start and +--isolate_start arguments to provide the appropriate starting addresses of the +VM and isolate instructions sections. Options shared by all commands: ${_argParser.usage} @@ -159,67 +174,74 @@ Dwarf _loadFromFile(String original, Function(String) usageError) { } void find(ArgResults options) { + final bool verbose = options['verbose']; + final bool forceHexadecimal = options['force_hexadecimal']; + void usageError(String message) => errorWithUsage(message, command: 'find'); - int convertAddress(String s) => int.tryParse(s, radix: 16); + int parseIntAddress(String s) { + if (!forceHexadecimal && !s.startsWith("0x")) { + final decimal = int.tryParse(s); + if (decimal != null) return decimal; + } + return int.tryParse(s.startsWith("0x") ? s.substring(2) : s, radix: 16); + } + + PCOffset convertAddress(StackTraceHeader header, String s) { + final parsedOffset = tryParseSymbolOffset(s, forceHexadecimal); + if (parsedOffset != null) return parsedOffset; + + final address = parseIntAddress(s); + if (address != null) return header.offsetOf(address); + + return null; + } final dwarf = _loadFromFile(options['debug'], usageError); - if (dwarf == null) { - return; - } + if (dwarf == null) return; + if (options['dump_debug_file_contents']) { print(dwarf.dumpFileInfo()); } - final verbose = options['verbose']; - int vm_start; + if ((options['vm_start'] == null) != (options['isolate_start'] == null)) { + return usageError("need both VM start and isolate start"); + } + + int vmStart = dwarf.vmStartAddress; if (options['vm_start'] != null) { - vm_start = convertAddress(options['vm_start']); - if (vm_start == null) { + vmStart = parseIntAddress(options['vm_start']); + if (vmStart == null) { return usageError('could not parse VM start address ' '${options['vm_start']}'); } } - int isolate_start; + int isolateStart = dwarf.isolateStartAddress; if (options['isolate_start'] != null) { - isolate_start = convertAddress(options['isolate_start']); - if (isolate_start == null) { + isolateStart = parseIntAddress(options['isolate_start']); + if (isolateStart == null) { return usageError('could not parse isolate start address ' '${options['isolate_start']}'); } } - if ((vm_start == null) != (isolate_start == null)) { - return usageError("need both VM start and isolate start"); - } + final header = StackTraceHeader(isolateStart, vmStart); - final locations = []; - for (final s in options['location'] + options.rest) { - final location = convertAddress(s); - if (location == null) { - return usageError('could not parse PC address ${s}'); - } + final locations = []; + for (final String s in options['location'] + options.rest) { + final location = convertAddress(header, s); + if (location == null) return usageError('could not parse PC address ${s}'); locations.add(location); } if (locations.isEmpty) return usageError('no PC addresses to find'); - // Used to approximate how many hex digits we should have in the final output. - final maxDigits = options['location'] - .fold(0, ((acc, s) => s.startsWith('0x') ? s.length - 2 : s.length)); - - Iterable addresses = locations; - if (vm_start != null) { - final header = StackTraceHeader(isolate_start, vm_start); - addresses = - locations.map((l) => header.offsetOf(l).virtualAddressIn(dwarf)); - } - for (final addr in addresses) { + for (final offset in locations) { + final addr = dwarf.virtualAddressOf(offset); final frames = dwarf .callInfoFor(addr, includeInternalFrames: verbose) ?.map((CallInfo c) => " " + c.toString()); - final addrString = addr > 0 - ? "0x" + addr.toRadixString(16).padLeft(maxDigits, '0') - : addr.toString(); + final addrString = + addr > 0 ? "0x" + addr.toRadixString(16) : addr.toString(); print("For virtual address ${addrString}:"); if (frames == null) { print(" Invalid virtual address."); diff --git a/pkg/native_stack_traces/lib/native_stack_traces.dart b/pkg/native_stack_traces/lib/native_stack_traces.dart index bbb59f7d993..13e28fff1ea 100644 --- a/pkg/native_stack_traces/lib/native_stack_traces.dart +++ b/pkg/native_stack_traces/lib/native_stack_traces.dart @@ -3,6 +3,10 @@ // BSD-style license that can be found in the LICENSE file. export 'src/convert.dart' - show collectPCOffsets, DwarfStackTraceDecoder, StackTraceHeader; + show + collectPCOffsets, + tryParseSymbolOffset, + DwarfStackTraceDecoder, + StackTraceHeader; export 'src/dwarf.dart' show CallInfo, DartCallInfo, StubCallInfo, Dwarf, PCOffset; diff --git a/pkg/native_stack_traces/lib/src/convert.dart b/pkg/native_stack_traces/lib/src/convert.dart index df3260487c6..1575b3bd151 100644 --- a/pkg/native_stack_traces/lib/src/convert.dart +++ b/pkg/native_stack_traces/lib/src/convert.dart @@ -55,42 +55,66 @@ class StackTraceHeader { /// - The virtual address of the program counter, if the snapshot was /// loaded as a dynamic library, otherwise not present. /// - The location of the virtual address, which is one of the following: -/// - A dynamic symbol name, a plus sign, and a hexadecimal offset. +/// - A dynamic symbol name, a plus sign, and an integer offset. /// - The path to the snapshot, if it was loaded as a dynamic library, /// otherwise the string "". -const _symbolREString = r'(?:(?' + +const _symbolOffsetREString = r'(?' + constants.vmSymbolName + r'|' + constants.isolateSymbolName + - r')\+0x(?[\da-f]+))'; + r')\+(?(?:0x)?[\da-f]+)'; +final _symbolOffsetRE = RegExp(_symbolOffsetREString); final _traceLineRE = RegExp( - r' #(\d{2}) abs (?
[\da-f]+)(?: virt ([\da-f]+))? (?:' + - _symbolREString + - r'|.*)$'); + r' #(\d{2}) abs (?
[\da-f]+)(?: virt ([\da-f]+))? (?.*)$'); + +/// Parses strings of the format +, where +/// is one of the static symbols used for Dart instruction +/// sections. +/// +/// Unless forceHexadecimal is true, an integer offset without a "0x" prefix or +/// any hexdecimal digits will be parsed as decimal. +/// +/// Returns null if the string is not of the expected format. +PCOffset tryParseSymbolOffset(String s, [bool forceHexadecimal = false]) { + final match = _symbolOffsetRE.firstMatch(s); + if (match == null) return null; + final symbolString = match.namedGroup('symbol'); + final offsetString = match.namedGroup('offset'); + int offset; + if (!forceHexadecimal && !offsetString.startsWith("0x")) { + offset = int.tryParse(offsetString); + } + if (offset == null) { + final digits = offsetString.startsWith("0x") + ? offsetString.substring(2) + : offsetString; + offset = int.tryParse(digits, radix: 16); + } + if (offset == null) return null; + switch (symbolString) { + case constants.vmSymbolName: + return PCOffset(offset, InstructionsSection.vm); + case constants.isolateSymbolName: + return PCOffset(offset, InstructionsSection.isolate); + default: + break; + } + return null; +} PCOffset _retrievePCOffset(StackTraceHeader header, RegExpMatch match) { if (match == null) return null; - // Try using the symbol information first, since we don't need the header + final restString = match.namedGroup('rest'); + // Try checking for symbol information first, since we don't need the header // information to translate it. - final symbolString = match.namedGroup('symbol'); - final offsetString = match.namedGroup('offset'); - if (symbolString != null && offsetString != null) { - final offset = int.tryParse(offsetString, radix: 16); - if (offset != null) { - switch (symbolString) { - case constants.vmSymbolName: - return PCOffset(offset, InstructionsSection.vm); - case constants.isolateSymbolName: - return PCOffset(offset, InstructionsSection.isolate); - default: - break; - } - } + if (restString.isNotEmpty) { + final offset = tryParseSymbolOffset(restString); + if (offset != null) return offset; } // If we're parsing the absolute address, we can only convert it into // a PCOffset if we saw the instructions line of the stack trace header. - final addressString = match.namedGroup('address'); - if (addressString != null && header != null) { + if (header != null) { + final addressString = match.namedGroup('address'); final address = int.tryParse(addressString, radix: 16); return header.offsetOf(address); } diff --git a/pkg/native_stack_traces/lib/src/dwarf.dart b/pkg/native_stack_traces/lib/src/dwarf.dart index 88c75b1ccfa..8db0703ad46 100644 --- a/pkg/native_stack_traces/lib/src/dwarf.dart +++ b/pkg/native_stack_traces/lib/src/dwarf.dart @@ -1132,7 +1132,7 @@ class StubCallInfo extends CallInfo { } @override - String toString() => "${name} + ${offset}"; + String toString() => "${name}+0x${offset.toRadixString(16)}"; } /// The instructions section in which a program counter address is located. @@ -1175,11 +1175,17 @@ class Dwarf { final Map _abbreviationTables; final DebugInfo _debugInfo; final LineNumberInfo _lineNumberInfo; - final int _vmStartAddress; - final int _isolateStartAddress; + + /// Virtual address of the start of the VM instructions section in the DWARF + /// information. + final int vmStartAddress; + + /// Virtual address of the start of the isolate instructions section in the + /// DWARF information. + final int isolateStartAddress; Dwarf._(this._elf, this._abbreviationTables, this._debugInfo, - this._lineNumberInfo, this._vmStartAddress, this._isolateStartAddress); + this._lineNumberInfo, this.vmStartAddress, this.isolateStartAddress); /// Attempts to load the DWARF debugging information from the reader. /// @@ -1269,9 +1275,9 @@ class Dwarf { int virtualAddressOf(PCOffset pcOffset) { switch (pcOffset.section) { case InstructionsSection.vm: - return pcOffset.offset + _vmStartAddress; + return pcOffset.offset + vmStartAddress; case InstructionsSection.isolate: - return pcOffset.offset + _isolateStartAddress; + return pcOffset.offset + isolateStartAddress; default: throw "Unexpected value for instructions section"; } diff --git a/pkg/native_stack_traces/pubspec.yaml b/pkg/native_stack_traces/pubspec.yaml index ad15e3704eb..74048a7a0a0 100644 --- a/pkg/native_stack_traces/pubspec.yaml +++ b/pkg/native_stack_traces/pubspec.yaml @@ -1,6 +1,6 @@ name: native_stack_traces description: Utilities for working with non-symbolic stack traces. -version: 0.3.1 +version: 0.3.2 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/native_stack_traces