| // 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.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. |
| /// 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; |
| |
| 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({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(); |
| } |
| } |