private static string DecodeUtf8Prefixed(byte* bytes, int byteCount, byte[] prefix, MetadataStringDecoder utf8Decoder) { Debug.Assert(utf8Decoder != null); int prefixedByteCount = byteCount + prefix.Length; if (prefixedByteCount == 0) { return String.Empty; } byte[] buffer = AcquireBuffer(prefixedByteCount); prefix.CopyTo(buffer, 0); Marshal.Copy((IntPtr)bytes, buffer, prefix.Length, byteCount); string result; fixed (byte* prefixedBytes = buffer) { result = utf8Decoder.GetString(prefixedBytes, prefixedByteCount); } ReleaseBuffer(buffer); return result; }
public static PdbSymbolReader TryOpen(string pdbFilename, MetadataStringDecoder stringDecoder) { MemoryMappedViewAccessor mappedViewAccessor; MetadataReader reader = TryOpenMetadataFile(pdbFilename, stringDecoder, out mappedViewAccessor); if (reader == null) return null; return new PortablePdbSymbolReader(reader, mappedViewAccessor); }
/// <summary> /// Gets a <see cref="MetadataReader"/> from a <see cref="PEReader"/>. /// </summary> /// <remarks> /// The caller must keep the <see cref="PEReader"/> alive and undisposed throughout the lifetime of the metadata reader. /// </remarks> /// <exception cref="ArgumentNullException"><paramref name="peReader"/> is null</exception> /// <exception cref="ArgumentException">The encoding of <paramref name="utf8Decoder"/> is not <see cref="UTF8Encoding"/>.</exception> /// <exception cref="PlatformNotSupportedException">The current platform is big-endian.</exception> /// <exception cref="IOException">IO error while reading from the underlying stream.</exception> public static unsafe MetadataReader GetMetadataReader(this PEReader peReader, MetadataReaderOptions options, MetadataStringDecoder utf8Decoder) { if (peReader == null) { throw new ArgumentNullException(nameof(peReader)); } var metadata = peReader.GetMetadata(); return new MetadataReader(metadata.Pointer, metadata.Length, options, utf8Decoder); }
public static string DecodeUtf8(byte* bytes, int byteCount, byte[] prefix, MetadataStringDecoder utf8Decoder) { Debug.Assert(utf8Decoder != null); if (prefix != null) { return DecodeUtf8Prefixed(bytes, byteCount, prefix, utf8Decoder); } if (byteCount == 0) { return String.Empty; } return utf8Decoder.GetString(bytes, byteCount); }
private static unsafe MetadataReader TryOpenMetadataFile(string filePath, MetadataStringDecoder stringDecoder, out MemoryMappedViewAccessor mappedViewAccessor) { FileStream fileStream = null; MemoryMappedFile mappedFile = null; MemoryMappedViewAccessor accessor = null; try { // Create stream because CreateFromFile(string, ...) uses FileShare.None which is too strict fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false); mappedFile = MemoryMappedFile.CreateFromFile( fileStream, null, fileStream.Length, MemoryMappedFileAccess.Read, HandleInheritability.None, true); accessor = mappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); var safeBuffer = accessor.SafeMemoryMappedViewHandle; // Check whether this is a real metadata file to avoid thrown and caught exceptions // for non-portable .pdbs if (safeBuffer.Read<byte>(0) != 'B' || // COR20MetadataSignature safeBuffer.Read<byte>(1) != 'S' || safeBuffer.Read<byte>(2) != 'J' || safeBuffer.Read<byte>(3) != 'B') { mappedViewAccessor = null; return null; } var metadataReader = new MetadataReader((byte*)safeBuffer.DangerousGetHandle(), (int)safeBuffer.ByteLength, MetadataReaderOptions.Default, stringDecoder); // MemoryMappedFile does not need to be kept around. MemoryMappedViewAccessor is enough. mappedViewAccessor = accessor; accessor = null; return metadataReader; } finally { if (accessor != null) accessor.Dispose(); if (mappedFile != null) mappedFile.Dispose(); if (fileStream != null) fileStream.Dispose(); } }
/// <summary> /// Gets a <see cref="MetadataReader"/> from a <see cref="PEReader"/>. /// </summary> /// <remarks> /// The caller must keep the <see cref="PEReader"/> alive and undisposed throughout the lifetime of the metadata reader. /// </remarks> /// <exception cref="ArgumentNullException"><paramref name="peReader"/> is null</exception> /// <exception cref="ArgumentException">The encoding of <paramref name="utf8Decoder"/> is not <see cref="UTF8Encoding"/>.</exception> /// <exception cref="PlatformNotSupportedException">The current platform is big-endian.</exception> /// <exception cref="IOException">IO error while reading from the underlying stream.</exception> public static unsafe MetadataReader GetMetadataReader(this PEReader peReader, MetadataReaderOptions options, MetadataStringDecoder utf8Decoder) { if (peReader == null) { throw new ArgumentNullException(nameof(peReader)); } var metadata = peReader.GetMetadata(); return(new MetadataReader(metadata.Pointer, metadata.Length, options, utf8Decoder)); }
// comparison stops at null terminator, terminator parameter, or end-of-block -- whichever comes first. internal bool Utf8NullTerminatedStartsWith(int offset, string text, MetadataStringDecoder utf8Decoder, char terminator = '\0') { FastComparisonResult result = Utf8NullTerminatedFastCompare(offset, text, terminator); switch (result) { case FastComparisonResult.Equal: case FastComparisonResult.IsPrefix: return true; case FastComparisonResult.Unequal: return false; default: Debug.Assert(result == FastComparisonResult.Inconclusive); int bytesRead; string decoded = PeekUtf8NullTerminated(offset, null, utf8Decoder, out bytesRead, terminator); return decoded.StartsWith(text, StringComparison.Ordinal); } }
/// <summary> /// Gets a <see cref="MetadataReader"/> from a <see cref="MetadataReaderProvider"/>. /// </summary> /// <remarks> /// The caller must keep the <see cref="MetadataReaderProvider"/> alive and undisposed throughout the lifetime of the metadata reader. /// </remarks> /// <exception cref="ArgumentException">The encoding of <paramref name="utf8Decoder"/> is not <see cref="UTF8Encoding"/>.</exception> /// <exception cref="PlatformNotSupportedException">The current platform is big-endian.</exception> /// <exception cref="IOException">IO error while reading from the underlying stream.</exception> public unsafe MetadataReader GetMetadataReader(MetadataReaderOptions options = MetadataReaderOptions.Default, MetadataStringDecoder utf8Decoder = null) { AbstractMemoryBlock metadata = GetMetadataBlock(); return new MetadataReader(metadata.Pointer, metadata.Size, options, utf8Decoder); }
/// <summary> /// Creates a metadata reader from the metadata stored at the given memory location. /// </summary> /// <remarks> /// The memory is owned by the caller and it must be kept memory alive and unmodified throughout the lifetime of the <see cref="MetadataReader"/>. /// Use <see cref="PEReaderExtensions.GetMetadataReader(PortableExecutable.PEReader, MetadataReaderOptions, MetadataStringDecoder)"/> to obtain /// metadata from a PE image. /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is not positive.</exception> /// <exception cref="ArgumentNullException"><paramref name="metadata"/> is null.</exception> /// <exception cref="ArgumentException">The encoding of <paramref name="utf8Decoder"/> is not <see cref="UTF8Encoding"/>.</exception> /// <exception cref="PlatformNotSupportedException">The current platform is big-endian.</exception> public unsafe MetadataReader(byte* metadata, int length, MetadataReaderOptions options, MetadataStringDecoder utf8Decoder) { // Do not throw here when length is 0. We'll throw BadImageFormatException later on, so that the caller doesn't need to // worry about the image (stream) being empty and can handle all image errors by catching BadImageFormatException. if (length < 0) { throw new ArgumentOutOfRangeException(nameof(length)); } if (metadata == null) { throw new ArgumentNullException(nameof(metadata)); } if (utf8Decoder == null) { utf8Decoder = MetadataStringDecoder.DefaultUTF8; } if (!(utf8Decoder.Encoding is UTF8Encoding)) { throw new ArgumentException(SR.MetadataStringDecoderEncodingMustBeUtf8, nameof(utf8Decoder)); } if (!BitConverter.IsLittleEndian) { Throw.LitteEndianArchitectureRequired(); } this.Block = new MemoryBlock(metadata, length); _options = options; this.UTF8Decoder = utf8Decoder; var headerReader = new BlobReader(this.Block); this.ReadMetadataHeader(ref headerReader, out _versionString); _metadataKind = GetMetadataKind(_versionString); var streamHeaders = this.ReadStreamHeaders(ref headerReader); // storage header and stream headers: MemoryBlock metadataTableStream; MemoryBlock standalonePdbStream; this.InitializeStreamReaders(ref this.Block, streamHeaders, out _metadataStreamKind, out metadataTableStream, out standalonePdbStream); int[] externalTableRowCountsOpt; if (standalonePdbStream.Length > 0) { ReadStandalonePortablePdbStream(standalonePdbStream, out _debugMetadataHeader, out externalTableRowCountsOpt); } else { externalTableRowCountsOpt = null; } var tableReader = new BlobReader(metadataTableStream); HeapSizes heapSizes; int[] metadataTableRowCounts; this.ReadMetadataTableHeader(ref tableReader, out heapSizes, out metadataTableRowCounts, out _sortedTables); this.InitializeTableReaders(tableReader.GetMemoryBlockAt(0, tableReader.RemainingBytes), heapSizes, metadataTableRowCounts, externalTableRowCountsOpt); // This previously could occur in obfuscated assemblies but a check was added to prevent // it getting to this point Debug.Assert(this.AssemblyTable.NumberOfRows <= 1); // Although the specification states that the module table will have exactly one row, // the native metadata reader would successfully read files containing more than one row. // Such files exist in the wild and may be produced by obfuscators. if (standalonePdbStream.Length == 0 && this.ModuleTable.NumberOfRows < 1) { throw new BadImageFormatException(SR.Format(SR.ModuleTableInvalidNumberOfRows, this.ModuleTable.NumberOfRows)); } // read this.NamespaceCache = new NamespaceCache(this); if (_metadataKind != MetadataKind.Ecma335) { this.WinMDMscorlibRef = FindMscorlibAssemblyRefNoProjection(); } }
private static bool CanReuseReader(MetadataReader reader, MetadataReaderOptions options, MetadataStringDecoder utf8DecoderOpt) { return(reader != null && reader.Options == options && ReferenceEquals(reader.UTF8Decoder, utf8DecoderOpt ?? MetadataStringDecoder.DefaultUTF8)); }
private static bool CanReuseReader(MetadataReader reader, MetadataReaderOptions options, MetadataStringDecoder utf8DecoderOpt) { return reader != null && reader.Options == options && ReferenceEquals(reader.Utf8Decoder, utf8DecoderOpt ?? MetadataStringDecoder.DefaultUTF8); }
// comparison stops at null terminator, terminator parameter, or end-of-block -- whichever comes first. internal bool Utf8NullTerminatedStartsWith(int offset, string text, MetadataStringDecoder utf8Decoder, char terminator, bool ignoreCase) { int endIndex; FastComparisonResult result = Utf8NullTerminatedFastCompare(offset, text, 0, out endIndex, terminator, ignoreCase); switch (result) { case FastComparisonResult.Equal: case FastComparisonResult.BytesStartWithText: return true; case FastComparisonResult.Unequal: case FastComparisonResult.TextStartsWithBytes: return false; default: Debug.Assert(result == FastComparisonResult.Inconclusive); int bytesRead; string decoded = PeekUtf8NullTerminated(offset, null, utf8Decoder, out bytesRead, terminator); return decoded.StartsWith(text, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); } }
/// <summary> /// Read UTF8 at the given offset up to the given terminator, null terminator, or end-of-block. /// </summary> /// <param name="offset">Offset in to the block where the UTF8 bytes start.</param> /// <param name="prefix">UTF8 encoded prefix to prepend to the bytes at the offset before decoding.</param> /// <param name="utf8Decoder">The UTF8 decoder to use that allows user to adjust fallback and/or reuse existing strings without allocating a new one.</param> /// <param name="numberOfBytesRead">The number of bytes read, which includes the terminator if we did not hit the end of the block.</param> /// <param name="terminator">A character in the ASCII range that marks the end of the string. /// If a value other than '\0' is passed we still stop at the null terminator if encountered first.</param> /// <returns>The decoded string.</returns> internal string PeekUtf8NullTerminated(int offset, byte[] prefix, MetadataStringDecoder utf8Decoder, out int numberOfBytesRead, char terminator = '\0') { Debug.Assert(terminator <= 0x7F); CheckBounds(offset, 0); int length = GetUtf8NullTerminatedLength(offset, out numberOfBytesRead, terminator); return EncodingHelper.DecodeUtf8(Pointer + offset, length, prefix, utf8Decoder); }
/// <summary> /// Gets a <see cref="MetadataReader"/> from a <see cref="PEReader"/>. /// </summary> /// <remarks> /// The caller must keep the <see cref="PEReader"/> alive and undisposed throughout the lifetime of the metadata reader. /// </remarks> public static unsafe MetadataReader GetMetadataReader(this PEReader peReader, MetadataReaderOptions options, MetadataStringDecoder utf8Decoder) { var metadata = peReader.GetMetadata(); return(new MetadataReader(metadata.Pointer, metadata.Length, options, utf8Decoder)); }
/// <summary> /// Gets a <see cref="MetadataReader"/> from a <see cref="MetadataReaderProvider"/>. /// </summary> /// <remarks> /// The caller must keep the <see cref="MetadataReaderProvider"/> alive and undisposed throughout the lifetime of the metadata reader. /// </remarks> /// <exception cref="ArgumentException">The encoding of <paramref name="utf8Decoder"/> is not <see cref="UTF8Encoding"/>.</exception> /// <exception cref="PlatformNotSupportedException">The current platform is big-endian.</exception> /// <exception cref="IOException">IO error while reading from the underlying stream.</exception> public unsafe MetadataReader GetMetadataReader(MetadataReaderOptions options = MetadataReaderOptions.Default, MetadataStringDecoder utf8Decoder = null) { AbstractMemoryBlock metadata = GetMetadataBlock(); return(new MetadataReader(metadata.Pointer, metadata.Size, options, utf8Decoder)); }
/// <summary> /// Gets a <see cref="MetadataReader"/> from a <see cref="PEReader"/>. /// </summary> /// <remarks> /// The caller must keep the <see cref="PEReader"/> alive and undisposed throughout the lifetime of the metadata reader. /// </remarks> public static unsafe MetadataReader GetMetadataReader(this PEReader peReader, MetadataReaderOptions options, MetadataStringDecoder utf8Decoder) { var metadata = peReader.GetMetadata(); return new MetadataReader(metadata.Pointer, metadata.Length, options, utf8Decoder); }
MetadataStringDecoder IMetadataStringDecoderProvider.GetMetadataStringDecoder() { if (_metadataStringDecoder == null) _metadataStringDecoder = new CachingMetadataStringDecoder(0x10000); // TODO: Tune the size return _metadataStringDecoder; }
// comparison stops at null terminator, terminator parameter, or end-of-block -- whichever comes first. internal bool Utf8NullTerminatedEquals(int offset, string text, MetadataStringDecoder utf8Decoder, char terminator, bool ignoreCase) { int firstDifference; FastComparisonResult result = Utf8NullTerminatedFastCompare(offset, text, 0, out firstDifference, terminator, ignoreCase); if (result == FastComparisonResult.Inconclusive) { int bytesRead; string decoded = PeekUtf8NullTerminated(offset, null, utf8Decoder, out bytesRead, terminator); return decoded.Equals(text, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); } return result == FastComparisonResult.Equal; }
/// <summary> /// Gets a <see cref="MetadataReader"/> from a <see cref="MetadataReaderProvider"/>. /// </summary> /// <remarks> /// The caller must keep the <see cref="MetadataReaderProvider"/> undisposed throughout the lifetime of the metadata reader. /// /// If this method is called multiple times each call with arguments equal to the arguments passed to the previous successful call /// returns the same instance of <see cref="MetadataReader"/> as the previous call. /// </remarks> /// <exception cref="ArgumentException">The encoding of <paramref name="utf8Decoder"/> is not <see cref="UTF8Encoding"/>.</exception> /// <exception cref="PlatformNotSupportedException">The current platform is big-endian.</exception> /// <exception cref="IOException">IO error while reading from the underlying stream.</exception> /// <exception cref="ObjectDisposedException">Provider has been disposed.</exception> public unsafe MetadataReader GetMetadataReader(MetadataReaderOptions options = MetadataReaderOptions.Default, MetadataStringDecoder utf8Decoder = null) { var cachedReader = _lazyMetadataReader; if (CanReuseReader(cachedReader, options, utf8Decoder)) { return(cachedReader); } // If multiple threads attempt to open a metadata reader with the same options and decoder // it's cheaper to wait for the other thread to finish initializing the reader than to open // two readers and discard one. // Note that it's rare to reader the same metadata using different options. lock (_metadataReaderGuard) { cachedReader = _lazyMetadataReader; if (CanReuseReader(cachedReader, options, utf8Decoder)) { return(cachedReader); } AbstractMemoryBlock metadata = GetMetadataBlock(); var newReader = new MetadataReader(metadata.Pointer, metadata.Size, options, utf8Decoder, memoryOwner: this); _lazyMetadataReader = newReader; return(newReader); } }
/// <summary> /// Creates a metadata reader from the metadata stored at the given memory location. /// </summary> /// <remarks> /// The memory is owned by the caller and it must be kept memory alive and unmodified throughout the lifetime of the <see cref="MetadataReader"/>. /// Use <see cref="PEReaderExtensions.GetMetadataReader(PortableExecutable.PEReader, MetadataReaderOptions, MetadataStringDecoder)"/> to obtain /// metadata from a PE image. /// </remarks> public unsafe MetadataReader(byte* metadata, int length, MetadataReaderOptions options, MetadataStringDecoder utf8Decoder) { if (length <= 0) { throw new ArgumentOutOfRangeException("length"); } if (metadata == null) { throw new ArgumentNullException("metadata"); } if (utf8Decoder == null) { utf8Decoder = MetadataStringDecoder.DefaultUTF8; } if (!(utf8Decoder.Encoding is UTF8Encoding)) { throw new ArgumentException(MetadataResources.MetadataStringDecoderEncodingMustBeUtf8, "utf8Decoder"); } if (!BitConverter.IsLittleEndian) { throw new PlatformNotSupportedException(MetadataResources.LitteEndianArchitectureRequired); } this.Block = new MemoryBlock(metadata, length); this.options = options; this.utf8Decoder = utf8Decoder; BlobReader memReader = new BlobReader(this.Block); this.ReadMetadataHeader(ref memReader); // storage header and stream headers: MemoryBlock metadataTableStream; var streamHeaders = this.ReadStreamHeaders(ref memReader); this.InitializeStreamReaders(ref this.Block, streamHeaders, out metadataTableStream); memReader = new BlobReader(metadataTableStream); uint[] metadataTableRowCounts; this.ReadMetadataTableHeader(ref memReader, out metadataTableRowCounts); this.InitializeTableReaders(memReader.GetMemoryBlockAt(0, memReader.RemainingBytes), metadataTableRowCounts); // This previously could occur in obfuscated assemblies but a check was added to prevent // it getting to this point Debug.Assert(this.AssemblyTable.NumberOfRows <= 1); // Although the specification states that the module table will have exactly one row, // the native metadata reader would successfully read files containing more than one row. // Such files exist in the wild and may be produced by obfuscators. if (this.ModuleTable.NumberOfRows < 1) { throw new BadImageFormatException(string.Format(MetadataResources.ModuleTableInvalidNumberOfRows, this.ModuleTable.NumberOfRows)); } // read this.namespaceCache = new NamespaceCache(this); if (this.metadataKind != MetadataKind.Ecma335) { this.WinMDMscorlibRef = FindMscorlibAssemblyRefNoProjection(); } }
/// <summary> /// Gets a <see cref="MetadataReader"/> from a <see cref="MetadataReaderProvider"/>. /// </summary> /// <remarks> /// The caller must keep the <see cref="MetadataReaderProvider"/> alive and undisposed throughout the lifetime of the metadata reader. /// /// If this method is called multiple times each call with arguments equal to the arguments passed to the previous successful call /// returns the same instance of <see cref="MetadataReader"/> as the previous call. /// </remarks> /// <exception cref="ArgumentException">The encoding of <paramref name="utf8Decoder"/> is not <see cref="UTF8Encoding"/>.</exception> /// <exception cref="PlatformNotSupportedException">The current platform is big-endian.</exception> /// <exception cref="IOException">IO error while reading from the underlying stream.</exception> /// <exception cref="ObjectDisposedException">Provider has been disposed.</exception> public unsafe MetadataReader GetMetadataReader(MetadataReaderOptions options = MetadataReaderOptions.Default, MetadataStringDecoder utf8Decoder = null) { var cachedReader = _lazyMetadataReader; if (CanReuseReader(cachedReader, options, utf8Decoder)) { return cachedReader; } // If multiple threads attempt to open a metadata reader with the same options and decoder // it's cheaper to wait for the other thread to finish initializing the reader than to open // two readers and discard one. // Note that it's rare to reader the same metadata using different options. lock (_metadataReaderGuard) { cachedReader = _lazyMetadataReader; if (CanReuseReader(cachedReader, options, utf8Decoder)) { return cachedReader; } AbstractMemoryBlock metadata = GetMetadataBlock(); var newReader = new MetadataReader(metadata.Pointer, metadata.Size, options, utf8Decoder); _lazyMetadataReader = newReader; return newReader; } }