Files
sdk/pkg/native_stack_traces/lib/src/reader.dart
T
Tess Strickland 0a4cb4d43e [pkg/native_stack_traces] Add support for MacOS universal binaries.
In addition to adding a parser for the universal binary format, this
also requires major reworks to handle files that contain different
DWARF information for different architectures, and how to pass the
architecture down to where it's needed.

Also fix dSYM handling: instead of assuming the name of the MachO file
corresponds exactly to the basename of the dSYM with the extension
stripped, just look for the single file within the
Contents/Resources/DWARF directory.

Also add `unrecognized` enum entries for DW_TAG, DW_AT, and DW_FORM
values that aren't handled.

Issue: https://github.com/flutter/flutter/pull/101586
Change-Id: Ief5edc275ccd1192669252140d128136cd2bed26
Cq-Include-Trybots: luci.dart.try:vm-kernel-nnbd-mac-release-arm64-try,vm-kernel-precomp-mac-product-x64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try,vm-kernel-nnbd-mac-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/252821
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
2022-08-25 13:27:24 +00:00

232 lines
7.4 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:io';
import 'dart:math';
import 'dart:typed_data';
String paddedHex(int value, [int? bytes]) =>
value.toRadixString(16).padLeft(2 * (bytes ?? 0), '0');
class Reader {
final ByteData bdata;
// These are mutable so we can update them, in case the endianness and
// wordSize are read using the reader (e.g., ELF files).
Endian? _endian;
int? _wordSize;
int _offset = 0;
Endian get endian => _endian as Endian;
set endian(Endian value) => _endian = value;
int get wordSize => _wordSize as int;
set wordSize(int value) => _wordSize = value;
/// Unless provided, [wordSize] and [endian] are initialized to values that
/// ensure no reads are made that depend on their value (e.g., readBytes).
Reader.fromTypedData(TypedData data, {int? wordSize, Endian? endian})
: _wordSize = wordSize,
_endian = endian,
bdata = ByteData.sublistView(data);
Reader.fromFile(String path, {int? wordSize, Endian? endian})
: _wordSize = wordSize,
_endian = endian,
bdata = ByteData.sublistView(File(path).readAsBytesSync());
// Similar to the sublistView constructor for classes in dart:typed-data.
// That is, it creates a new reader that views a subset of the underlying
// buffer currently viewed by [this]. Thus, [pos] and [size] are relative
// to the view of [this], not to the underlying buffer as a whole.
Reader shrink(int pos, [int? size]) {
assert(pos >= 0 && pos <= length);
if (size != null) {
assert(size >= 0 && (pos + size) <= length);
} else {
size = length - pos;
}
return Reader.fromTypedData(ByteData.sublistView(bdata, pos, pos + size),
wordSize: _wordSize, endian: _endian);
}
int get start => bdata.offsetInBytes;
int get offset => _offset;
int get length => bdata.lengthInBytes;
int get remaining => length - offset;
bool get done => _offset >= length;
Uint8List get bytes => Uint8List.sublistView(bdata);
void seek(int offset, {bool absolute = false}) {
final newOffset = (absolute ? 0 : _offset) + offset;
assert(newOffset >= 0 && newOffset <= bdata.lengthInBytes);
_offset = newOffset;
}
int readBytes(int size, {bool signed = false}) {
if (_offset + size > length) {
throw ArgumentError('attempt to read $size bytes with only '
'$remaining bytes remaining in the reader');
}
final start = _offset;
_offset += size;
switch (size) {
case 1:
return signed ? bdata.getInt8(start) : bdata.getUint8(start);
case 2:
return signed
? bdata.getInt16(start, endian)
: bdata.getUint16(start, endian);
case 4:
return signed
? bdata.getInt32(start, endian)
: bdata.getUint32(start, endian);
case 8:
return signed
? bdata.getInt64(start, endian)
: bdata.getUint64(start, endian);
default:
_offset -= size;
throw ArgumentError('invalid request to read $size bytes');
}
}
ByteData readRawBytes(int size) {
if (offset + size > length) {
throw ArgumentError('attempt to read $size bytes with only '
'$remaining bytes remaining in the reader');
}
final start = _offset;
_offset += size;
return ByteData.sublistView(bdata, start, start + size);
}
int readByte({bool signed = false}) => readBytes(1, signed: signed);
int readWord() => readBytes(wordSize);
String readNullTerminatedString({int? maxSize}) {
final start = _offset;
int end = maxSize != null ? _offset + maxSize : bdata.lengthInBytes;
for (; _offset < end; _offset++) {
if (bdata.getUint8(_offset) == 0) {
end = _offset;
_offset++; // Move reader past null terminator.
break;
}
}
return String.fromCharCodes(
bdata.buffer.asUint8List(bdata.offsetInBytes + start, end - start));
}
String readFixedLengthNullTerminatedString(int maxSize) {
final start = _offset;
final str = readNullTerminatedString(maxSize: maxSize);
// Ensure reader points past fixed space, not at end of string within it.
_offset = start + maxSize;
return str;
}
int readLEB128EncodedInteger({bool signed = false}) {
var ret = 0;
var shift = 0;
for (var byte = readByte(); !done; byte = readByte()) {
ret |= (byte & 0x7f) << shift;
shift += 7;
if (byte & 0x80 == 0) {
if (signed && byte & 0x40 != 0) {
ret |= -(1 << shift);
}
break;
}
}
return ret;
}
/// Repeatedly calls [callback] with this reader to retrieve items.
///
/// The key of the returned [MapEntry]s are the offsets of the items. If
/// absolute is false, the offsets are from the reader position when
/// [readRepeated] was invoked, otherwise they are absolute offsets from
/// the start of the reader.
///
/// Stops either when the reader is empty or when a null item is returned
/// from the callback.
Iterable<MapEntry<int, S>> readRepeatedWithOffsets<S>(
S? Function(Reader) callback,
{bool absolute = false}) sync* {
final start = offset;
while (!done) {
final itemStart = offset;
final item = callback(this);
if (item == null) break;
yield MapEntry(absolute ? itemStart : itemStart - start, item);
}
}
Iterable<S> readRepeated<S>(S? Function(Reader) callback) =>
readRepeatedWithOffsets(callback).map((kv) => kv.value);
void writeCurrentReaderPosition(StringBuffer buffer,
{int maxSize = 0, int bytesPerLine = 16}) {
var baseData = ByteData.view(bdata.buffer, 0, bdata.buffer.lengthInBytes);
var startOffset = 0;
var endOffset = baseData.lengthInBytes;
final currentOffset = start + _offset;
if (maxSize != 0 && maxSize < baseData.lengthInBytes) {
var lowerWindow = currentOffset - (maxSize >> 1);
// Adjust so that we always start at the beginning of a line.
lowerWindow -= lowerWindow % bytesPerLine;
final upperWindow = lowerWindow + maxSize;
startOffset = max(startOffset, lowerWindow);
endOffset = min(endOffset, upperWindow);
}
for (var i = startOffset; i < endOffset; i += bytesPerLine) {
buffer
..write('0x')
..write(paddedHex(i, 8))
..write(' ');
for (var j = 0; j < bytesPerLine && i + j < endOffset; j++) {
var byte = baseData.getUint8(i + j);
buffer
..write(i + j == currentOffset ? '|' : ' ')
..write(paddedHex(byte, 1));
}
buffer.writeln();
}
}
@override
String toString() {
final buffer = StringBuffer();
buffer
..write('Word size: ')
..write(_wordSize)
..writeln();
buffer
..write('Endianness: ')
..write(_endian)
..writeln();
buffer
..write('Start: 0x')
..write(paddedHex(start, _wordSize))
..write(' (')
..write(start)
..writeln(')');
buffer
..write('Offset: 0x')
..write(paddedHex(offset, _wordSize))
..write(' (')
..write(offset)
..writeln(')');
buffer
..write('Length: 0x')
..write(paddedHex(length, _wordSize))
..write(' (')
..write(length)
..writeln(')');
buffer.writeln('Bytes around current position:');
writeCurrentReaderPosition(buffer, maxSize: 256);
return buffer.toString();
}
}