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