77978889eb
Closes https://github.com/dart-lang/sdk/pull/51143 GitOrigin-RevId: 9e21c99a222d588e4fc95980725a2f8c9784965c Change-Id: If0870e8936c7649935dce7e23cd783d62aa5610c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279916 Reviewed-by: Alexander Thomas <athom@google.com> Commit-Queue: Alexander Thomas <athom@google.com>
290 lines
11 KiB
Dart
290 lines
11 KiB
Dart
// 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 'constants.dart' as constants;
|
|
import 'dwarf.dart';
|
|
|
|
String _stackTracePiece(CallInfo call, int depth) =>
|
|
'#${depth.toString().padRight(6)} $call';
|
|
|
|
// The initial header line in a non-symbolic stack trace.
|
|
const _headerStartLine =
|
|
'*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***';
|
|
|
|
// A pattern matching the os/arch line of the non-symbolic stack trace header.
|
|
//
|
|
// This RegExp has been adjusted to parse the header line found in
|
|
// non-symbolic stack traces and the modified version in signal handler stack
|
|
// traces.
|
|
final _osArchLineRE = RegExp(r'os(?:=|: )(\S+?),? '
|
|
r'arch(?:=|: )(\S+?),? comp(?:=|: )(yes|no),? sim(?:=|: )(yes|no)');
|
|
|
|
// A pattern matching the last line of the non-symbolic stack trace header.
|
|
//
|
|
// This RegExp has been adjusted to parse the header line found in
|
|
// non-symbolic stack traces and the modified version in signal handler stack
|
|
// traces.
|
|
final _instructionsLineRE = RegExp(r'isolate_instructions(?:=|: )([\da-f]+),? '
|
|
r'vm_instructions(?:=|: )([\da-f]+)');
|
|
|
|
/// Header information for a non-symbolic Dart stack trace.
|
|
class StackTraceHeader {
|
|
String? _os;
|
|
String? _arch;
|
|
bool? _compressed;
|
|
bool? _simulated;
|
|
int? _isolateStart;
|
|
int? _vmStart;
|
|
|
|
String? get os => _os;
|
|
String? get architecture => _arch;
|
|
bool? get compressedPointers => _compressed;
|
|
bool? get usingSimulator => _simulated;
|
|
|
|
static StackTraceHeader fromStarts(int isolateStart, int vmStart,
|
|
{String? architecture}) =>
|
|
StackTraceHeader()
|
|
.._isolateStart = isolateStart
|
|
.._vmStart = vmStart
|
|
.._arch = architecture;
|
|
|
|
/// Try and parse the given line as one of the recognized lines in the
|
|
/// header of a non-symbolic stack trace.
|
|
///
|
|
/// Returns whether the line was recognized and parsed successfully.
|
|
bool tryParseHeaderLine(String line) {
|
|
if (line.contains(_headerStartLine)) {
|
|
// This is the start of a new non-symbolic stack trace, so reset all the
|
|
// stored information to be parsed anew.
|
|
_os = null;
|
|
_arch = null;
|
|
_compressed = null;
|
|
_simulated = null;
|
|
_isolateStart = null;
|
|
_vmStart = null;
|
|
return true;
|
|
}
|
|
RegExpMatch? match = _osArchLineRE.firstMatch(line);
|
|
if (match != null) {
|
|
_os = match[1]!;
|
|
_arch = match[2]!;
|
|
_compressed = match[3]! == "yes";
|
|
_simulated = match[4]! == "yes";
|
|
// The architecture line always proceeds the instructions section line,
|
|
// so reset these to null just in case we missed the header line.
|
|
_isolateStart = null;
|
|
_vmStart = null;
|
|
return true;
|
|
}
|
|
match = _instructionsLineRE.firstMatch(line);
|
|
if (match != null) {
|
|
_isolateStart = int.parse(match[1]!, radix: 16);
|
|
_vmStart = int.parse(match[2]!, radix: 16);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// The [PCOffset] for the given absolute program counter address.
|
|
PCOffset? offsetOf(int address) {
|
|
if (_isolateStart == null || _vmStart == null) return null;
|
|
final isolateOffset = address - _isolateStart!;
|
|
var vmOffset = address - _vmStart!;
|
|
if (vmOffset > 0 && vmOffset == min(vmOffset, isolateOffset)) {
|
|
return PCOffset(vmOffset, InstructionsSection.vm,
|
|
os: _os,
|
|
architecture: _arch,
|
|
compressedPointers: _compressed,
|
|
usingSimulator: _simulated);
|
|
} else {
|
|
return PCOffset(isolateOffset, InstructionsSection.isolate,
|
|
os: _os,
|
|
architecture: _arch,
|
|
compressedPointers: _compressed,
|
|
usingSimulator: _simulated);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A Dart DWARF stack trace contains up to four pieces of information:
|
|
/// - The zero-based frame index from the top of the stack.
|
|
/// - The absolute address of the program counter.
|
|
/// - 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 an integer offset.
|
|
/// - The path to the snapshot, if it was loaded as a dynamic library,
|
|
/// otherwise the string "<unknown>".
|
|
const _symbolOffsetREString = r'(?<symbol>' +
|
|
constants.vmSymbolName +
|
|
r'|' +
|
|
constants.isolateSymbolName +
|
|
r')\+(?<offset>(?:0x)?[\da-f]+)';
|
|
final _symbolOffsetRE = RegExp(_symbolOffsetREString);
|
|
final _traceLineRE =
|
|
RegExp(r'\s*#(\d+) abs (?<absolute>[\da-f]+)(?: virt (?<virtual>[\da-f]+))?'
|
|
r' (?<rest>.*)$');
|
|
|
|
/// Parses strings of the format <static symbol>+<integer offset>, where
|
|
/// <static symbol> is one of the static symbols used for Dart instruction
|
|
/// sections.
|
|
///
|
|
/// Unless forceHexadecimal is true, an integer offset without a "0x" prefix or
|
|
/// any hexadecimal digits will be parsed as decimal.
|
|
///
|
|
/// Returns null if the string is not of the expected format.
|
|
PCOffset? tryParseSymbolOffset(String s,
|
|
{bool forceHexadecimal = false, StackTraceHeader? header}) {
|
|
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,
|
|
os: header?.os,
|
|
architecture: header?.architecture,
|
|
compressedPointers: header?.compressedPointers,
|
|
usingSimulator: header?.usingSimulator);
|
|
case constants.isolateSymbolName:
|
|
return PCOffset(offset, InstructionsSection.isolate,
|
|
os: header?.os,
|
|
architecture: header?.architecture,
|
|
compressedPointers: header?.compressedPointers,
|
|
usingSimulator: header?.usingSimulator);
|
|
default:
|
|
break;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
PCOffset? _retrievePCOffset(StackTraceHeader header, RegExpMatch? match) {
|
|
if (match == null) return null;
|
|
final restString = match.namedGroup('rest')!;
|
|
// Try checking for symbol information first, since we don't need the header
|
|
// information to translate it.
|
|
if (restString.isNotEmpty) {
|
|
final offset = tryParseSymbolOffset(restString, header: header);
|
|
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('absolute')!;
|
|
final address = int.parse(addressString, radix: 16);
|
|
final pcOffset = header.offsetOf(address);
|
|
if (pcOffset != null) return pcOffset;
|
|
// If all other cases failed, check for a virtual address. Until this package
|
|
// depends on a version of Dart which only prints virtual addresses when the
|
|
// virtual addresses in the snapshot are the same as in separately saved
|
|
// debugging information, the other methods should be tried first.
|
|
final virtualString = match.namedGroup('virtual');
|
|
if (virtualString != null) {
|
|
final address = int.parse(virtualString, radix: 16);
|
|
return PCOffset(address, InstructionsSection.none,
|
|
os: header.os,
|
|
architecture: header.architecture,
|
|
compressedPointers: header.compressedPointers,
|
|
usingSimulator: header.usingSimulator);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// The [PCOffset]s for frames of the non-symbolic stack traces in [lines].
|
|
Iterable<PCOffset> collectPCOffsets(Iterable<String> lines) sync* {
|
|
final header = StackTraceHeader();
|
|
for (var line in lines) {
|
|
if (header.tryParseHeaderLine(line)) {
|
|
continue;
|
|
}
|
|
final match = _traceLineRE.firstMatch(line);
|
|
final offset = _retrievePCOffset(header, match);
|
|
if (offset != null) yield offset;
|
|
}
|
|
}
|
|
|
|
/// A [StreamTransformer] that scans lines for non-symbolic stack traces.
|
|
///
|
|
/// A [NativeStackTraceDecoder] scans a stream of lines for non-symbolic
|
|
/// stack traces containing only program counter address information. Such
|
|
/// stack traces are generated by the VM when executing a snapshot compiled
|
|
/// with `--dwarf-stack-traces`.
|
|
///
|
|
/// The transformer assumes that there may be text preceding the stack frames
|
|
/// on individual lines, like in log files, but that there is no trailing text.
|
|
/// 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, then the line
|
|
/// will be unchanged in the output stream.
|
|
///
|
|
/// If the located information corresponds to Dart internals and
|
|
/// [includeInternalFrames] is false, then the output stream contains no
|
|
/// entries for the line.
|
|
///
|
|
/// Otherwise, the output stream contains one or more lines with symbolic stack
|
|
/// frames for the given non-symbolic stack frame line. Multiple symbolic stack
|
|
/// frame lines are generated when the PC address corresponds to inlined code.
|
|
/// In the output stream, each symbolic stack frame is prefixed by the non-stack
|
|
/// frame portion of the original line.
|
|
class DwarfStackTraceDecoder extends StreamTransformerBase<String, String> {
|
|
final Dwarf _dwarf;
|
|
final bool _includeInternalFrames;
|
|
|
|
DwarfStackTraceDecoder(this._dwarf, {bool includeInternalFrames = false})
|
|
: _includeInternalFrames = includeInternalFrames;
|
|
|
|
@override
|
|
Stream<String> bind(Stream<String> stream) async* {
|
|
var depth = 0;
|
|
final header = StackTraceHeader();
|
|
await for (final line in stream) {
|
|
// If we successfully parse a header line, then we reset the depth to 0.
|
|
if (header.tryParseHeaderLine(line)) {
|
|
depth = 0;
|
|
yield line;
|
|
continue;
|
|
}
|
|
// If at any point we can't get appropriate information for the current
|
|
// line as a stack trace line, then just pass the line through unchanged.
|
|
final lineMatch = _traceLineRE.firstMatch(line);
|
|
final offset = _retrievePCOffset(header, lineMatch);
|
|
final callInfo = offset?.callInfoFrom(_dwarf,
|
|
includeInternalFrames: _includeInternalFrames);
|
|
if (callInfo == null) {
|
|
yield line;
|
|
continue;
|
|
}
|
|
// No lines to output (as this corresponds to Dart internals).
|
|
if (callInfo.isEmpty) continue;
|
|
// Output the lines for the symbolic frame with the prefix found on the
|
|
// original non-symbolic frame line, modulo all whitespace between the
|
|
// prefix and stack trace information converted to a single space.
|
|
//
|
|
// If there was no prefix, just swallow any initial whitespace, since
|
|
// symbolic Dart stacktrace lines have no initial whitespace.
|
|
String prefix = line.substring(0, lineMatch!.start);
|
|
if (prefix.isNotEmpty) {
|
|
prefix += ' ';
|
|
}
|
|
for (final call in callInfo) {
|
|
yield prefix + _stackTracePiece(call, depth++);
|
|
}
|
|
}
|
|
}
|
|
}
|