blob: 9eb5be6d49079b4126a14c115eb891bc0c8b8e25 [file] [log] [blame]
// 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:typed_data';
import 'dart:math';
String paddedHex(int value, [int bytes = 0]) {
return value.toRadixString(16).padLeft(2 * bytes, '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;
void set endian(Endian value) => _endian = value;
int get wordSize => _wordSize as int;
void 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.view(data.buffer, data.offsetInBytes, data.lengthInBytes);
Reader.fromFile(String path, {int? wordSize, Endian? endian})
: _wordSize = wordSize,
_endian = endian,
bdata = ByteData.sublistView(File(path).readAsBytesSync());
/// Returns a reader focused on a different portion of the underlying buffer.
Reader refocusedCopy(int pos, int size) {
assert(pos >= 0 && pos < bdata.buffer.lengthInBytes);
assert(size >= 0 && (pos + size) <= bdata.buffer.lengthInBytes);
return Reader.fromTypedData(ByteData.view(bdata.buffer, pos, size),
wordSize: _wordSize, endian: _endian);
}
int get start => bdata.offsetInBytes;
int get offset => _offset;
int get length => bdata.lengthInBytes;
bool get done => _offset >= length;
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 "
"${length - _offset} 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");
}
}
int readByte({bool signed = false}) => readBytes(1, signed: signed);
int readWord() => readBytes(wordSize);
String readNullTerminatedString() {
final start = bdata.offsetInBytes + _offset;
for (int i = 0; _offset + i < bdata.lengthInBytes; i++) {
if (bdata.getUint8(_offset + i) == 0) {
_offset += i + 1;
return String.fromCharCodes(bdata.buffer.asUint8List(start, i));
}
}
return String.fromCharCodes(
bdata.buffer.asUint8List(start, bdata.lengthInBytes - _offset));
}
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 (int i = startOffset; i < endOffset; i += bytesPerLine) {
buffer..write("0x")..write(paddedHex(i, 8))..write(" ");
for (int 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();
}
}
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 ?? 0))
..write(" (")
..write(start)
..writeln(")");
buffer
..write("Offset: 0x")
..write(paddedHex(offset, _wordSize ?? 0))
..write(" (")
..write(offset)
..writeln(")");
buffer
..write("Length: 0x")
..write(paddedHex(length, _wordSize ?? 0))
..write(" (")
..write(length)
..writeln(")");
buffer..writeln("Bytes around current position:");
writeCurrentReaderPosition(buffer, maxSize: 256);
return buffer.toString();
}
}