blob: 71a8f83e5e81dfef4c82703e1558bbe132df09cf [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:math';
import 'dart:typed_data';
String paddedHex(int value, [int bytes = 0]) =>
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;
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());
/// Returns a reader focused on a different portion of the underlying buffer.
/// If size is not provided, then the new reader extends to the end of the
/// buffer.
Reader refocusedCopy(int pos, [int? size]) {
assert(pos >= 0 && pos < bdata.buffer.lengthInBytes);
if (size != null) {
assert(size >= 0 && (pos + size) <= bdata.buffer.lengthInBytes);
} else {
size = bdata.buffer.lengthInBytes - pos;
}
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;
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 '
'${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');
}
}
ByteData readRawBytes(int size) {
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;
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 ?? 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();
}
}