/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Collections.ObjectModel; using System.Text; namespace dnSpy.Contracts.Hex.Files.DotNet { /// /// A .NET heap /// public abstract class DotNetHeap { /// /// Constructor /// /// Heap span /// Heap kind protected DotNetHeap(HexBufferSpan span, DotNetHeapKind heapKind) { if (span.IsDefault) throw new ArgumentException(); Span = span; HeapKind = heapKind; } /// /// Gets the heap span /// public HexBufferSpan Span { get; } /// /// Gets the heap kind /// public DotNetHeapKind HeapKind { get; } /// /// Gets the metadata headers /// public abstract DotNetMetadataHeaders Metadata { get; } /// /// Gets a structure or null /// /// Position /// public virtual ComplexData? GetStructure(HexPosition position) => null; /// /// Checks whether is valid. Note that some heaps /// treat as an index, eg. . /// /// Offset (or index if #GUID heap) /// public virtual bool IsValidOffset(uint offset) => offset == 0 || offset < Span.Length; /// /// Reads a compressed and increments /// /// Position /// protected int? ReadCompressedUInt32(ref HexPosition position) { if (!Span.Contains(position)) return null; var res = Utils.ReadCompressedUInt32(Span.Buffer, ref position); if (position > Span.Span.End) return null; return (int)res; } } /// /// .NET tables (#~ or #-) heap /// public abstract class TablesHeap : DotNetHeap { /// /// Gets the metadata type /// public TablesHeapType TablesHeapType { get; } /// /// Gets all metadata table infos /// public abstract ReadOnlyCollection MDTables { get; } /// /// Span of header /// public abstract HexSpan HeaderSpan { get; } /// /// Span of all tables /// public abstract HexSpan TablesSpan { get; } /// /// Gets the header /// public abstract TablesHeaderData Header { get; } /// /// Gets the major version, this value is cached /// public abstract byte MajorVersion { get; } /// /// Gets the minor version, this value is cached /// public abstract byte MinorVersion { get; } /// /// Gets the flags, this value is cached /// public abstract MDStreamFlags Flags { get; } /// /// Gets the valid mask, this value is cached /// public abstract ulong ValidMask { get; } /// /// Gets the sorted mask, this value is cached /// public abstract ulong SortedMask { get; } /// /// Constructor /// /// Heap span /// Tables heap type protected TablesHeap(HexBufferSpan span, TablesHeapType tablesHeapType) : base(span, DotNetHeapKind.Tables) => TablesHeapType = tablesHeapType; /// /// Gets a record or null if is invalid /// /// Token /// public TableRecordData? GetRecord(uint token) => GetRecord(new MDToken(token)); /// /// Gets a record or null if is invalid /// /// Token /// public abstract TableRecordData? GetRecord(MDToken token); } /// /// .NET #Strings heap /// public abstract class StringsHeap : DotNetHeap { /// /// Constructor /// /// Heap span protected StringsHeap(HexBufferSpan span) : base(span, DotNetHeapKind.Strings) { } /// /// Gets the span of the string, not including the terminating zero byte. /// Returns an empty span if is invalid. /// /// Offset /// public HexBufferSpan GetStringSpan(uint offset) => GetStringSpan(offset, int.MaxValue); /// /// Gets the span of the string, not including the terminating zero byte. /// Returns an empty span if is invalid. /// /// Offset /// Maximum number of bytes to read /// public HexBufferSpan GetStringSpan(uint offset, int maxByteLength) { var start = Span.Span.Start + offset; var pos = start; var buffer = Span.Buffer; var end = HexPosition.Min(Span.Span.End, start + maxByteLength); while (pos < end && buffer.ReadByte(pos) != 0) pos++; if (start <= pos) return new HexBufferSpan(buffer, HexSpan.FromBounds(start, pos)); return new HexBufferSpan(buffer, Span.Span.Start, 0); } /// /// Reads string data which should be a UTF-8 encoded string. The array doesn't include the terminating zero. /// Returns an empty array if is invalid /// /// Offset /// public byte[] ReadBytes(uint offset) => ReadBytes(offset, int.MaxValue); /// /// Reads string data which should be a UTF-8 encoded string. The array doesn't include the terminating zero. /// Returns an empty array if is invalid /// /// Offset /// Maximum number of bytes to read /// public byte[] ReadBytes(uint offset, int maxByteLength) => GetStringSpan(offset, maxByteLength).GetData(); /// /// Reads a string. Returns an empty string if is invalid /// /// Offset /// public string Read(uint offset) => Read(offset, int.MaxValue); /// /// Reads a string. Returns an empty string if is invalid /// /// Offset /// Maximum number of bytes to read /// public string Read(uint offset, int maxByteLength) => Encoding.UTF8.GetString(ReadBytes(offset, maxByteLength)); } /// /// .NET #US heap /// public abstract class USHeap : DotNetHeap { /// /// Constructor /// /// Heap span protected USHeap(HexBufferSpan span) : base(span, DotNetHeapKind.US) { } /// /// Returns the span of the string data or an empty span if is 0 or invalid. /// /// Offset /// public HexBufferSpan GetStringSpan(uint offset) { // The CLR assumes offset 0 contains byte 0x00 if (offset == 0 || !IsValidOffset(offset)) return new HexBufferSpan(Span.Start, 0); var pos = Span.Start.Position + offset; int length = ReadCompressedUInt32(ref pos) ?? -1; if (length < 0) return new HexBufferSpan(Span.Start, 0); if (pos + length > Span.End.Position) return new HexBufferSpan(Span.Start, 0); return new HexBufferSpan(Span.Buffer, new HexSpan(pos, (ulong)length)); } /// /// Reads data at . Returns an empty array if is invalid. /// The returned data doesn't include the compressed data length at . /// /// Offset /// public byte[] ReadData(uint offset) => GetStringSpan(offset).GetData(); /// /// Reads the string at . Returns an empty string if /// is 0 or invalid. /// /// Offset /// public string Read(uint offset) => Encoding.Unicode.GetString(ReadData(offset)); } /// /// .NET #GUID heap /// public abstract class GUIDHeap : DotNetHeap { /// /// Constructor /// /// Heap span protected GUIDHeap(HexBufferSpan span) : base(span, DotNetHeapKind.GUID) { } /// /// Checks whether is valid. This method is identical to /// /// Index /// public bool IsValidIndex(uint index) => IsValidOffset(index); /// /// Checks whether is valid /// /// Index /// public override bool IsValidOffset(uint index) => index == 0 || (ulong)index * 16 <= Span.Length; /// /// Reads a . Returns null if is 0 or invalid /// /// Index /// public Guid? Read(uint index) { if (index == 0 || !IsValidIndex(index)) return null; return new Guid(Span.Buffer.ReadBytes(Span.Start.Position + (index - 1) * 16, 16)); } } /// /// .NET #Blob heap /// public abstract class BlobHeap : DotNetHeap { /// /// Constructor /// /// Heap span protected BlobHeap(HexBufferSpan span) : base(span, DotNetHeapKind.Blob) { } /// /// Gets the span of data in this heap. The span doesn't include the compressed data length /// at . An empty span is returned if /// is invalid or 0. /// /// Offset /// public HexBufferSpan GetDataSpan(uint offset) { // The CLR assumes offset 0 contains byte 0x00 if (offset == 0 || !IsValidOffset(offset)) return new HexBufferSpan(Span.Start, 0); var pos = Span.Start.Position + offset; int length = ReadCompressedUInt32(ref pos) ?? -1; if (length < 0) return new HexBufferSpan(Span.Start, 0); if (pos + length > Span.End.Position) return new HexBufferSpan(Span.Start, 0); return new HexBufferSpan(Span.Buffer, new HexSpan(pos, (ulong)length)); } /// /// Reads data at . Returns an empty array if is invalid. /// The returned data doesn't include the compressed data length at . /// /// Offset /// public byte[] Read(uint offset) => GetDataSpan(offset).GetData(); } /// /// .NET #! heap /// public abstract class HotHeap : DotNetHeap { /// /// Constructor /// /// Heap span protected HotHeap(HexBufferSpan span) : base(span, DotNetHeapKind.Hot) { } } /// /// .NET #Pdb heap /// public abstract class PdbHeap : DotNetHeap { /// /// Gets the header /// public abstract PdbStreamHeaderData Header { get; } /// /// Gets the PDB id, this value is cached /// public abstract ReadOnlyCollection PdbId { get; } /// /// Gets the entry point, this value is cached /// public abstract MDToken EntryPoint { get; } /// /// Gets a bit mask of all referenced type system tables, this value is cached /// public abstract ulong ReferencedTypeSystemTables { get; } /// /// Gets the rows, this value is cached /// public abstract ReadOnlyCollection TypeSystemTableRows { get; } /// /// Constructor /// /// Heap span protected PdbHeap(HexBufferSpan span) : base(span, DotNetHeapKind.Pdb) { } } /// /// Unknown .NET heap /// public abstract class UnknownHeap : DotNetHeap { /// /// Constructor /// /// Heap span protected UnknownHeap(HexBufferSpan span) : base(span, DotNetHeapKind.Unknown) { } } }