[vm] Add OS and architecture to non-symbolic stack traces.
Examples of the new line added to non-symbolic stack traces:
os: linux arch: x64 comp: yes sim: no
(Running on linux-x64c)
os: macos arch: arm64 comp: no sim: yes
(Running on mac-simarm64)
This CL also abstracts out the separate hardcoded strings across
the codebase for host and target OS and architecture into
definitions in platform/globals.h to ensure that they stay
in sync across different uses.
TEST=vm/dart{,_2}/use_dwarf_stack_traces_flag
Issue: https://github.com/flutter/flutter/pull/101586
Change-Id: Ifdfea5138dd1003f561da0174e89aebc165bf9b0
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-release-simarm-try,vm-kernel-precomp-linux-release-simarm64-try,vm-kernel-precomp-linux-release-simarm_x64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-mac-product-x64-try,vm-kernel-precomp-nnbd-linux-release-x64-try,vm-kernel-precomp-nnbd-linux-release-simarm_x64-try,vm-kernel-precomp-win-release-x64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try,vm-kernel-precomp-mac-release-simarm64-try,vm-ffi-android-release-arm-try,vm-ffi-android-release-arm64c-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/253283
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
committed by
Commit Bot
parent
5b489e6505
commit
24683da915
@@ -1,3 +1,8 @@
|
||||
## 0.5.1
|
||||
|
||||
- Parse new OS and architecture information from non-symbolic stack
|
||||
trace headers and store that information in PCOffsets when available.
|
||||
|
||||
## 0.5.0
|
||||
|
||||
- Require Dart >= 2.17 (enhanced enum support)
|
||||
|
||||
@@ -200,7 +200,8 @@ void find(ArgResults options) {
|
||||
}
|
||||
|
||||
PCOffset? convertAddress(StackTraceHeader header, String s) {
|
||||
final parsedOffset = tryParseSymbolOffset(s, forceHexadecimal);
|
||||
final parsedOffset =
|
||||
tryParseSymbolOffset(s, forceHexadecimal: forceHexadecimal);
|
||||
if (parsedOffset != null) return parsedOffset;
|
||||
|
||||
final address = tryParseIntAddress(s);
|
||||
@@ -236,7 +237,7 @@ void find(ArgResults options) {
|
||||
isolateStart = address;
|
||||
}
|
||||
|
||||
final header = StackTraceHeader(isolateStart, vmStart);
|
||||
final header = StackTraceHeader.fromStarts(isolateStart, vmStart);
|
||||
|
||||
final locations = <PCOffset>[];
|
||||
for (final String s in [
|
||||
|
||||
@@ -11,45 +11,99 @@ import 'dwarf.dart';
|
||||
String _stackTracePiece(CallInfo call, int depth) =>
|
||||
'#${depth.toString().padRight(6)} $call';
|
||||
|
||||
// A pattern matching the last line of the non-symbolic stack trace header.
|
||||
//
|
||||
// Currently, this happens to include the only pieces of information from the
|
||||
// stack trace header we need: the absolute addresses during program
|
||||
// execution of the start of the isolate and VM instructions.
|
||||
// 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 _headerEndRE = RegExp(r'isolate_instructions(?:=|: )([\da-f]+),? '
|
||||
r'vm_instructions(?:=|: )([\da-f]+)');
|
||||
final _osArchLineRE = RegExp(r'os(?:=|: )(\S+?),? '
|
||||
r'arch(?:=|: )(\S+?),? comp(?:=|: )(yes|no),? sim(?:=|: )(yes|no)');
|
||||
|
||||
// Parses instructions section information into a new [StackTraceHeader].
|
||||
// A pattern matching the last line of the non-symbolic stack trace header.
|
||||
//
|
||||
// Returns a new [StackTraceHeader] if [line] contains the needed header
|
||||
// information, otherwise returns `null`.
|
||||
StackTraceHeader? _parseInstructionsLine(String line) {
|
||||
final match = _headerEndRE.firstMatch(line);
|
||||
if (match == null) return null;
|
||||
final isolateAddr = int.parse(match[1]!, radix: 16);
|
||||
final vmAddr = int.parse(match[2]!, radix: 16);
|
||||
return StackTraceHeader(isolateAddr, vmAddr);
|
||||
}
|
||||
// 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 {
|
||||
final int _isolateStart;
|
||||
final int _vmStart;
|
||||
String? _os;
|
||||
String? _arch;
|
||||
bool? _compressed;
|
||||
bool? _simulated;
|
||||
int? _isolateStart;
|
||||
int? _vmStart;
|
||||
|
||||
StackTraceHeader(this._isolateStart, this._vmStart);
|
||||
String? get os => _os;
|
||||
String? get architecture => _arch;
|
||||
bool? get compressedPointers => _compressed;
|
||||
bool? get usingSimulator => _simulated;
|
||||
|
||||
static StackTraceHeader fromStarts(int isolateStart, int vmStart) =>
|
||||
StackTraceHeader()
|
||||
.._isolateStart = isolateStart
|
||||
.._vmStart = vmStart;
|
||||
|
||||
/// 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) {
|
||||
final isolateOffset = address - _isolateStart;
|
||||
var vmOffset = address - _vmStart;
|
||||
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);
|
||||
return PCOffset(vmOffset, InstructionsSection.vm,
|
||||
os: _os,
|
||||
architecture: _arch,
|
||||
compressedPointers: _compressed,
|
||||
usingSimulator: _simulated);
|
||||
} else {
|
||||
return PCOffset(isolateOffset, InstructionsSection.isolate);
|
||||
return PCOffset(isolateOffset, InstructionsSection.isolate,
|
||||
os: _os,
|
||||
architecture: _arch,
|
||||
compressedPointers: _compressed,
|
||||
usingSimulator: _simulated);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +135,8 @@ final _traceLineRE = RegExp(
|
||||
/// 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]) {
|
||||
PCOffset? tryParseSymbolOffset(String s,
|
||||
{bool forceHexadecimal = false, StackTraceHeader? header}) {
|
||||
final match = _symbolOffsetRE.firstMatch(s);
|
||||
if (match == null) return null;
|
||||
final symbolString = match.namedGroup('symbol')!;
|
||||
@@ -99,31 +154,38 @@ PCOffset? tryParseSymbolOffset(String s, [bool forceHexadecimal = false]) {
|
||||
if (offset == null) return null;
|
||||
switch (symbolString) {
|
||||
case constants.vmSymbolName:
|
||||
return PCOffset(offset, InstructionsSection.vm);
|
||||
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);
|
||||
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) {
|
||||
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);
|
||||
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.
|
||||
if (header != null) {
|
||||
final addressString = match.namedGroup('absolute')!;
|
||||
final address = int.parse(addressString, radix: 16);
|
||||
return header.offsetOf(address);
|
||||
}
|
||||
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
|
||||
@@ -131,18 +193,20 @@ PCOffset? _retrievePCOffset(StackTraceHeader? header, RegExpMatch? match) {
|
||||
final virtualString = match.namedGroup('virtual');
|
||||
if (virtualString != null) {
|
||||
final address = int.parse(virtualString, radix: 16);
|
||||
return PCOffset(address, InstructionsSection.none);
|
||||
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* {
|
||||
StackTraceHeader? header;
|
||||
final header = StackTraceHeader();
|
||||
for (var line in lines) {
|
||||
final parsedHeader = _parseInstructionsLine(line);
|
||||
if (parsedHeader != null) {
|
||||
header = parsedHeader;
|
||||
if (header.tryParseHeaderLine(line)) {
|
||||
continue;
|
||||
}
|
||||
final match = _traceLineRE.firstMatch(line);
|
||||
@@ -185,11 +249,10 @@ class DwarfStackTraceDecoder extends StreamTransformerBase<String, String> {
|
||||
@override
|
||||
Stream<String> bind(Stream<String> stream) async* {
|
||||
var depth = 0;
|
||||
StackTraceHeader? header;
|
||||
final header = StackTraceHeader();
|
||||
await for (final line in stream) {
|
||||
final parsedHeader = _parseInstructionsLine(line);
|
||||
if (parsedHeader != null) {
|
||||
header = parsedHeader;
|
||||
// If we successfully parse a header line, then we reset the depth to 0.
|
||||
if (header.tryParseHeaderLine(line)) {
|
||||
depth = 0;
|
||||
yield line;
|
||||
continue;
|
||||
|
||||
@@ -1451,12 +1451,34 @@ class StubCallInfo extends CallInfo {
|
||||
enum InstructionsSection { none, vm, isolate }
|
||||
|
||||
/// A program counter address viewed as an offset into the appropriate
|
||||
/// instructions section of a Dart snapshot.
|
||||
/// instructions section of a Dart snapshot. Includes other information
|
||||
/// parsed from the corresponding stack trace header when possible.
|
||||
class PCOffset {
|
||||
/// The offset into the corresponding instructions section.
|
||||
final int offset;
|
||||
|
||||
/// The instructions section into which this is an offset.
|
||||
final InstructionsSection section;
|
||||
|
||||
PCOffset(this.offset, this.section);
|
||||
/// The operating system on which the stack trace was generated, when
|
||||
/// available.
|
||||
final String? os;
|
||||
|
||||
/// The architecture on which the stack trace was generated, when
|
||||
/// available.
|
||||
final String? architecture;
|
||||
|
||||
/// Whether compressed pointers were enabled, when available.
|
||||
final bool? compressedPointers;
|
||||
|
||||
/// Whether the architecture was being simulated, when available.
|
||||
final bool? usingSimulator;
|
||||
|
||||
PCOffset(this.offset, this.section,
|
||||
{this.os,
|
||||
this.architecture,
|
||||
this.compressedPointers,
|
||||
this.usingSimulator});
|
||||
|
||||
/// The virtual address for this [PCOffset] in [dwarf].
|
||||
int virtualAddressIn(Dwarf dwarf) => dwarf.virtualAddressOf(this);
|
||||
@@ -1469,7 +1491,7 @@ class PCOffset {
|
||||
/// to user or library code is returned.
|
||||
Iterable<CallInfo>? callInfoFrom(Dwarf dwarf,
|
||||
{bool includeInternalFrames = false}) =>
|
||||
dwarf.callInfoFor(dwarf.virtualAddressOf(this),
|
||||
dwarf.callInfoForPCOffset(this,
|
||||
includeInternalFrames: includeInternalFrames);
|
||||
|
||||
@override
|
||||
@@ -1477,10 +1499,38 @@ class PCOffset {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is PCOffset && offset == other.offset && section == other.section;
|
||||
other is PCOffset &&
|
||||
offset == other.offset &&
|
||||
section == other.section &&
|
||||
os == other.os &&
|
||||
architecture == other.architecture &&
|
||||
compressedPointers == other.compressedPointers &&
|
||||
usingSimulator == other.usingSimulator;
|
||||
|
||||
@override
|
||||
String toString() => 'PCOffset($section, 0x${offset.toRadixString(16)})';
|
||||
String toString() {
|
||||
final buffer = StringBuffer();
|
||||
buffer
|
||||
..write('PCOffset(')
|
||||
..write(section)
|
||||
..write(', 0x')
|
||||
..write(offset.toRadixString(16));
|
||||
if (os != null) {
|
||||
buffer
|
||||
..write(', ')
|
||||
..write(os!);
|
||||
}
|
||||
if (architecture != null) {
|
||||
if (usingSimulator ?? false) {
|
||||
buffer.write('SIM');
|
||||
}
|
||||
buffer.write(architecture!.toUpperCase());
|
||||
if (compressedPointers ?? false) {
|
||||
buffer.write('C');
|
||||
}
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/// The DWARF debugging information for a Dart snapshot.
|
||||
@@ -1583,6 +1633,19 @@ class Dwarf {
|
||||
return calls;
|
||||
}
|
||||
|
||||
/// The call information for the given [PCOffset]. There may be multiple
|
||||
/// [CallInfo] objects returned for a single [PCOffset] when code has been
|
||||
/// inlined.
|
||||
///
|
||||
/// Returns null if the given address is invalid for the DWARF information.
|
||||
///
|
||||
/// If [includeInternalFrames] is false, then only information corresponding
|
||||
/// to user or library code is returned.
|
||||
Iterable<CallInfo>? callInfoForPCOffset(PCOffset pcOffset,
|
||||
{bool includeInternalFrames = false}) =>
|
||||
callInfoFor(virtualAddressOf(pcOffset),
|
||||
includeInternalFrames: includeInternalFrames);
|
||||
|
||||
/// The virtual address in this DWARF information for the given [PCOffset].
|
||||
int virtualAddressOf(PCOffset pcOffset) {
|
||||
switch (pcOffset.section) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: native_stack_traces
|
||||
version: 0.5.0
|
||||
version: 0.5.1
|
||||
description: Utilities for working with non-symbolic stack traces.
|
||||
repository: https://github.com/dart-lang/sdk/tree/main/pkg/native_stack_traces
|
||||
|
||||
|
||||
Reference in New Issue
Block a user