diff --git a/src/TraceEvent/TraceEvent.Tests/Utilities/PEFileTests.cs b/src/TraceEvent/TraceEvent.Tests/Utilities/PEFileTests.cs
new file mode 100644
index 000000000..b1edf07fa
--- /dev/null
+++ b/src/TraceEvent/TraceEvent.Tests/Utilities/PEFileTests.cs
@@ -0,0 +1,2006 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using PEFile;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace TraceEventTests
+{
+ public class PEFileTests
+ {
+ private readonly ITestOutputHelper _output;
+
+ public PEFileTests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ ///
+ /// Comprehensive comparison test between original and new PEFile implementations for managed assemblies
+ ///
+ [Fact]
+ public void PEFile_NewImplementationMatchesOriginal_ManagedAssembly()
+ {
+ string assemblyPath = typeof(PEFileTests).Assembly.Location;
+ _output.WriteLine($"Testing managed assembly: {assemblyPath}");
+ CompareImplementations(assemblyPath, expectManaged: true);
+ }
+
+ ///
+ /// Comprehensive comparison test between original and new PEFile implementations for native binaries
+ ///
+ [Fact]
+ public void PEFile_NewImplementationMatchesOriginal_NativeBinary()
+ {
+ string winDir = Environment.GetEnvironmentVariable("WINDIR");
+ Assert.False(string.IsNullOrEmpty(winDir), "WINDIR environment variable not set");
+
+ string kernel32Path = Path.Combine(winDir, "System32", "kernel32.dll");
+ Assert.True(File.Exists(kernel32Path), $"kernel32.dll not found at {kernel32Path}");
+
+ _output.WriteLine($"Testing native binary: {kernel32Path}");
+ CompareImplementations(kernel32Path, expectManaged: false);
+ }
+
+ ///
+ /// Helper method to compare old and new PEFile implementations
+ ///
+ private void CompareImplementations(string filePath, bool expectManaged)
+ {
+ using (var newPEFile = new PEFile.PEFile(filePath))
+ using (var oldPEFile = new OriginalPEFile.PEFile(filePath))
+ {
+ var newHeader = newPEFile.Header;
+ var oldHeader = oldPEFile.Header;
+
+ // Compare all basic properties
+ Assert.Equal(oldHeader.PEHeaderSize, newHeader.PEHeaderSize);
+ _output.WriteLine($"PEHeaderSize: {newHeader.PEHeaderSize} (matches: {oldHeader.PEHeaderSize == newHeader.PEHeaderSize})");
+
+ Assert.Equal(oldHeader.Signature, newHeader.Signature);
+ _output.WriteLine($"Signature: 0x{newHeader.Signature:X} (matches: {oldHeader.Signature == newHeader.Signature})");
+
+ Assert.Equal((int)oldHeader.Machine, (int)newHeader.Machine);
+ _output.WriteLine($"Machine: {newHeader.Machine} (matches: {(int)oldHeader.Machine == (int)newHeader.Machine})");
+
+ Assert.Equal(oldHeader.NumberOfSections, newHeader.NumberOfSections);
+ _output.WriteLine($"NumberOfSections: {newHeader.NumberOfSections} (matches: {oldHeader.NumberOfSections == newHeader.NumberOfSections})");
+
+ Assert.Equal(oldHeader.TimeDateStampSec, newHeader.TimeDateStampSec);
+ _output.WriteLine($"TimeDateStampSec: {newHeader.TimeDateStampSec} (matches: {oldHeader.TimeDateStampSec == newHeader.TimeDateStampSec})");
+
+ Assert.Equal(oldHeader.PointerToSymbolTable, newHeader.PointerToSymbolTable);
+ Assert.Equal(oldHeader.NumberOfSymbols, newHeader.NumberOfSymbols);
+ Assert.Equal(oldHeader.SizeOfOptionalHeader, newHeader.SizeOfOptionalHeader);
+ Assert.Equal(oldHeader.Characteristics, newHeader.Characteristics);
+
+ // Compare optional header properties
+ Assert.Equal(oldHeader.Magic, newHeader.Magic);
+ Assert.Equal(oldHeader.MajorLinkerVersion, newHeader.MajorLinkerVersion);
+ Assert.Equal(oldHeader.MinorLinkerVersion, newHeader.MinorLinkerVersion);
+ Assert.Equal(oldHeader.SizeOfCode, newHeader.SizeOfCode);
+ Assert.Equal(oldHeader.SizeOfInitializedData, newHeader.SizeOfInitializedData);
+ Assert.Equal(oldHeader.SizeOfUninitializedData, newHeader.SizeOfUninitializedData);
+ Assert.Equal(oldHeader.AddressOfEntryPoint, newHeader.AddressOfEntryPoint);
+ Assert.Equal(oldHeader.BaseOfCode, newHeader.BaseOfCode);
+
+ Assert.Equal(oldHeader.ImageBase, newHeader.ImageBase);
+ Assert.Equal(oldHeader.SectionAlignment, newHeader.SectionAlignment);
+ Assert.Equal(oldHeader.FileAlignment, newHeader.FileAlignment);
+ Assert.Equal(oldHeader.SizeOfImage, newHeader.SizeOfImage);
+ Assert.Equal(oldHeader.SizeOfHeaders, newHeader.SizeOfHeaders);
+ Assert.Equal(oldHeader.CheckSum, newHeader.CheckSum);
+ Assert.Equal(oldHeader.Subsystem, newHeader.Subsystem);
+ Assert.Equal(oldHeader.DllCharacteristics, newHeader.DllCharacteristics);
+
+ Assert.Equal(oldHeader.IsPE64, newHeader.IsPE64);
+ _output.WriteLine($"IsPE64: {newHeader.IsPE64} (matches: {oldHeader.IsPE64 == newHeader.IsPE64})");
+
+ Assert.Equal(oldHeader.IsManaged, newHeader.IsManaged);
+ _output.WriteLine($"IsManaged: {newHeader.IsManaged} (matches: {oldHeader.IsManaged == newHeader.IsManaged})");
+
+ // Verify expectation
+ Assert.Equal(expectManaged, newHeader.IsManaged);
+
+ // Compare data directories
+ var oldExportDir = oldHeader.ExportDirectory;
+ var newExportDir = newHeader.ExportDirectory;
+ Assert.Equal(oldExportDir.VirtualAddress, newExportDir.VirtualAddress);
+ Assert.Equal(oldExportDir.Size, newExportDir.Size);
+ _output.WriteLine($"ExportDirectory RVA: 0x{newExportDir.VirtualAddress:X}, Size: {newExportDir.Size}");
+
+ var oldImportDir = oldHeader.ImportDirectory;
+ var newImportDir = newHeader.ImportDirectory;
+ Assert.Equal(oldImportDir.VirtualAddress, newImportDir.VirtualAddress);
+ Assert.Equal(oldImportDir.Size, newImportDir.Size);
+ _output.WriteLine($"ImportDirectory RVA: 0x{newImportDir.VirtualAddress:X}, Size: {newImportDir.Size}");
+
+ var oldComDescriptor = oldHeader.ComDescriptorDirectory;
+ var newComDescriptor = newHeader.ComDescriptorDirectory;
+ Assert.Equal(oldComDescriptor.VirtualAddress, newComDescriptor.VirtualAddress);
+ Assert.Equal(oldComDescriptor.Size, newComDescriptor.Size);
+ _output.WriteLine($"ComDescriptorDirectory RVA: 0x{newComDescriptor.VirtualAddress:X}, Size: {newComDescriptor.Size}");
+
+ // Test RvaToFileOffset with the entry point
+ if (newHeader.AddressOfEntryPoint > 0)
+ {
+ int oldOffset = oldHeader.RvaToFileOffset((int)newHeader.AddressOfEntryPoint);
+ int newOffset = newHeader.RvaToFileOffset((int)newHeader.AddressOfEntryPoint);
+ Assert.Equal(oldOffset, newOffset);
+ _output.WriteLine($"RvaToFileOffset(EntryPoint) Old: 0x{oldOffset:X}, New: 0x{newOffset:X} (matches: {oldOffset == newOffset})");
+ }
+
+ _output.WriteLine("\n✅ All comparisons passed - new implementation matches original!");
+ }
+ }
+
+ ///
+ /// Test that we can successfully read a PE file and access basic properties
+ ///
+ [Fact]
+ public void PEFile_CanReadManagedAssembly()
+ {
+ // Use the currently executing assembly as a test PE file
+ string assemblyPath = typeof(PEFileTests).Assembly.Location;
+ _output.WriteLine($"Testing with assembly: {assemblyPath}");
+
+ using (var peFile = new PEFile.PEFile(assemblyPath))
+ {
+ Assert.NotNull(peFile.Header);
+
+ // Verify basic PE header properties
+ Assert.True(peFile.Header.PEHeaderSize > 0);
+ _output.WriteLine($"PE Header Size: {peFile.Header.PEHeaderSize}");
+
+ Assert.True(peFile.Header.NumberOfSections > 0);
+ _output.WriteLine($"Number of Sections: {peFile.Header.NumberOfSections}");
+
+ // Check that it's a managed assembly
+ Assert.True(peFile.Header.IsManaged);
+ _output.WriteLine($"Is Managed: {peFile.Header.IsManaged}");
+ }
+ }
+
+ ///
+ /// Test that machine type is correctly identified
+ ///
+ [Fact]
+ public void PEFile_ReadsCorrectMachineType()
+ {
+ string assemblyPath = typeof(PEFileTests).Assembly.Location;
+
+ using (var peFile = new PEFile.PEFile(assemblyPath))
+ {
+ var machineType = peFile.Header.Machine;
+ _output.WriteLine($"Machine Type: {machineType}");
+
+ // Should be one of the known machine types
+ Assert.True(
+ machineType == MachineType.X86 ||
+ machineType == MachineType.Amd64 ||
+ machineType == MachineType.ARM ||
+ machineType == MachineType.ia64,
+ $"Unexpected machine type: {machineType}");
+ }
+ }
+
+ ///
+ /// Test that PE64 detection works correctly
+ ///
+ [Fact]
+ public void PEFile_DetectsPE64Correctly()
+ {
+ string assemblyPath = typeof(PEFileTests).Assembly.Location;
+
+ using (var peFile = new PEFile.PEFile(assemblyPath))
+ {
+ bool isPE64 = peFile.Header.IsPE64;
+ _output.WriteLine($"Is PE64: {isPE64}");
+
+ // The IsPE64 flag should match the machine type
+ if (peFile.Header.Machine == MachineType.Amd64 || peFile.Header.Machine == MachineType.ia64)
+ {
+ Assert.True(isPE64, "64-bit machine type should report IsPE64 = true");
+ }
+ else if (peFile.Header.Machine == MachineType.X86)
+ {
+ Assert.False(isPE64, "32-bit machine type should report IsPE64 = false");
+ }
+ }
+ }
+
+ ///
+ /// Test that various PE header properties are accessible without throwing
+ ///
+ [Fact]
+ public void PEFile_AllPropertiesAccessible()
+ {
+ string assemblyPath = typeof(PEFileTests).Assembly.Location;
+
+ using (var peFile = new PEFile.PEFile(assemblyPath))
+ {
+ var header = peFile.Header;
+
+ // Access all major properties to ensure they don't throw
+ var signature = header.Signature;
+ var machine = header.Machine;
+ var numberOfSections = header.NumberOfSections;
+ var sizeOfOptionalHeader = header.SizeOfOptionalHeader;
+ var characteristics = header.Characteristics;
+ var magic = header.Magic;
+ var majorLinkerVersion = header.MajorLinkerVersion;
+ var minorLinkerVersion = header.MinorLinkerVersion;
+ var sizeOfCode = header.SizeOfCode;
+ var sizeOfInitializedData = header.SizeOfInitializedData;
+ var sizeOfUninitializedData = header.SizeOfUninitializedData;
+ var addressOfEntryPoint = header.AddressOfEntryPoint;
+ var baseOfCode = header.BaseOfCode;
+ var imageBase = header.ImageBase;
+ var sectionAlignment = header.SectionAlignment;
+ var fileAlignment = header.FileAlignment;
+ var sizeOfImage = header.SizeOfImage;
+ var sizeOfHeaders = header.SizeOfHeaders;
+ var checkSum = header.CheckSum;
+ var subsystem = header.Subsystem;
+ var dllCharacteristics = header.DllCharacteristics;
+
+ _output.WriteLine($"Signature: 0x{signature:X}");
+ _output.WriteLine($"Machine: {machine}");
+ _output.WriteLine($"Sections: {numberOfSections}");
+ _output.WriteLine($"Magic: 0x{magic:X}");
+ _output.WriteLine($"Entry Point: 0x{addressOfEntryPoint:X}");
+ _output.WriteLine($"Image Base: 0x{imageBase:X}");
+ _output.WriteLine($"Size of Image: 0x{sizeOfImage:X}");
+
+ // Verify PE signature is correct
+ Assert.Equal(0x4550u, signature); // "PE\0\0"
+ }
+ }
+
+ ///
+ /// Test that data directories are accessible
+ ///
+ [Fact]
+ public void PEFile_DataDirectoriesAccessible()
+ {
+ string assemblyPath = typeof(PEFileTests).Assembly.Location;
+
+ using (var peFile = new PEFile.PEFile(assemblyPath))
+ {
+ var header = peFile.Header;
+
+ // Access various data directories
+ var exportDir = header.ExportDirectory;
+ var importDir = header.ImportDirectory;
+ var resourceDir = header.ResourceDirectory;
+ var exceptionDir = header.ExceptionDirectory;
+ var securityDir = header.CertificatesDirectory;
+ var relocDir = header.BaseRelocationDirectory;
+ var debugDir = header.DebugDirectory;
+ var comDescriptorDir = header.ComDescriptorDirectory;
+
+ _output.WriteLine($"Export Directory RVA: 0x{exportDir.VirtualAddress:X}");
+ _output.WriteLine($"Import Directory RVA: 0x{importDir.VirtualAddress:X}");
+ _output.WriteLine($"Resource Directory RVA: 0x{resourceDir.VirtualAddress:X}");
+ _output.WriteLine($"COM Descriptor Directory RVA: 0x{comDescriptorDir.VirtualAddress:X}");
+
+ // Managed assemblies should have a COM descriptor
+ if (header.IsManaged)
+ {
+ Assert.True(comDescriptorDir.VirtualAddress > 0, "Managed assembly should have COM descriptor");
+ }
+ }
+ }
+
+ ///
+ /// Test that RvaToFileOffset works correctly
+ ///
+ [Fact]
+ public void PEFile_RvaToFileOffsetWorks()
+ {
+ string assemblyPath = typeof(PEFileTests).Assembly.Location;
+
+ using (var peFile = new PEFile.PEFile(assemblyPath))
+ {
+ var header = peFile.Header;
+
+ // Get a valid RVA from the entry point
+ uint entryPointRva = header.AddressOfEntryPoint;
+
+ if (entryPointRva > 0)
+ {
+ // Convert RVA to file offset
+ int fileOffset = header.RvaToFileOffset((int)entryPointRva);
+
+ _output.WriteLine($"Entry Point RVA: 0x{entryPointRva:X}");
+ _output.WriteLine($"Entry Point File Offset: 0x{fileOffset:X}");
+
+ // File offset should be positive and reasonable
+ Assert.True(fileOffset > 0, "File offset should be positive");
+ Assert.True(fileOffset < new FileInfo(assemblyPath).Length, "File offset should be within file size");
+ }
+ }
+ }
+
+ ///
+ /// Test that bounds checking works - accessing beyond buffer should be caught
+ ///
+ [Fact]
+ public void PEFile_BoundsCheckingWorks()
+ {
+ string assemblyPath = typeof(PEFileTests).Assembly.Location;
+
+ using (var peFile = new PEFile.PEFile(assemblyPath))
+ {
+ var header = peFile.Header;
+
+ // Try to access a directory index that's out of range
+ // This should return an empty directory structure rather than throwing
+ var invalidDir = header.Directory(99);
+
+ Assert.Equal(0u, (uint)invalidDir.VirtualAddress);
+ Assert.Equal(0u, (uint)invalidDir.Size);
+ }
+ }
+
+ ///
+ /// Test multiple sequential reads from the same file
+ ///
+ [Fact]
+ public void PEFile_MultipleReadsWork()
+ {
+ string assemblyPath = typeof(PEFileTests).Assembly.Location;
+
+ using (var peFile = new PEFile.PEFile(assemblyPath))
+ {
+ var header = peFile.Header;
+
+ // Read the same property multiple times
+ var machine1 = header.Machine;
+ var machine2 = header.Machine;
+ var machine3 = header.Machine;
+
+ Assert.Equal(machine1, machine2);
+ Assert.Equal(machine2, machine3);
+
+ // Read different properties
+ var numberOfSections1 = header.NumberOfSections;
+ var isPE64 = header.IsPE64;
+ var numberOfSections2 = header.NumberOfSections;
+
+ Assert.Equal(numberOfSections1, numberOfSections2);
+ }
+ }
+ }
+}
+namespace OriginalPEFile
+{
+ ///
+ /// PEFile is a reader for the information in a Portable Exectable (PE) FILE. This is what EXEs and DLLs are.
+ ///
+ /// It can read both 32 and 64 bit PE files.
+ ///
+#if PEFILE_PUBLIC
+ public
+#endif
+ sealed unsafe class PEFile : IDisposable
+ {
+ ///
+ /// Create a new PEFile header reader that inspects the
+ ///
+ public PEFile(string filePath)
+ {
+ m_stream = File.OpenRead(filePath);
+ m_headerBuff = new PEBuffer(m_stream);
+
+ byte* ptr = m_headerBuff.Fetch(0, 1024);
+ if (m_headerBuff.Length < 512)
+ {
+ goto ThrowBadHeader;
+ }
+
+ Header = new PEHeader(ptr);
+
+ if (Header.PEHeaderSize > 1024 * 64) // prevent insane numbers;
+ {
+ goto ThrowBadHeader;
+ }
+
+ // We did not read in the complete header, Try again using the right sized buffer.
+ if (Header.PEHeaderSize > m_headerBuff.Length)
+ {
+ ptr = m_headerBuff.Fetch(0, Header.PEHeaderSize);
+ if (m_headerBuff.Length < Header.PEHeaderSize)
+ {
+ goto ThrowBadHeader;
+ }
+
+ Header = new PEHeader(ptr);
+ }
+ return;
+ ThrowBadHeader:
+ throw new InvalidOperationException("Bad PE Header in " + filePath);
+ }
+ ///
+ /// The Header for the PE file. This contains the infor in a link /dump /headers
+ ///
+ public PEHeader Header { get; private set; }
+
+ ///
+ /// Looks up the debug signature information in the EXE. Returns true and sets the parameters if it is found.
+ ///
+ /// If 'first' is true then the first entry is returned, otherwise (by default) the last entry is used
+ /// (this is what debuggers do today). Thus NGEN images put the IL PDB last (which means debuggers
+ /// pick up that one), but we can set it to 'first' if we want the NGEN PDB.
+ ///
+ public bool GetPdbSignature(out string pdbName, out Guid pdbGuid, out int pdbAge, bool first = false)
+ {
+ pdbName = null;
+ pdbGuid = Guid.Empty;
+ pdbAge = 0;
+ bool ret = false;
+
+ if (Header.DebugDirectory.VirtualAddress != 0)
+ {
+ var buff = AllocBuff();
+ var debugEntries = (IMAGE_DEBUG_DIRECTORY*)FetchRVA(Header.DebugDirectory.VirtualAddress, Header.DebugDirectory.Size, buff);
+ Debug.Assert(Header.DebugDirectory.Size % sizeof(IMAGE_DEBUG_DIRECTORY) == 0);
+ int debugCount = Header.DebugDirectory.Size / sizeof(IMAGE_DEBUG_DIRECTORY);
+ for (int i = 0; i < debugCount; i++)
+ {
+ if (debugEntries[i].Type == IMAGE_DEBUG_TYPE.CODEVIEW)
+ {
+ var stringBuff = AllocBuff();
+ var info = (CV_INFO_PDB70*)stringBuff.Fetch((int)debugEntries[i].PointerToRawData, debugEntries[i].SizeOfData);
+ if (info->CvSignature == CV_INFO_PDB70.PDB70CvSignature)
+ {
+ // If there are several this picks the last one.
+ pdbGuid = info->Signature;
+ pdbAge = info->Age;
+ pdbName = info->PdbFileName;
+ ret = true;
+ if (first)
+ {
+ break;
+ }
+ }
+ FreeBuff(stringBuff);
+ }
+ }
+ FreeBuff(buff);
+ }
+ return ret;
+ }
+ ///
+ /// Gets the File Version Information that is stored as a resource in the PE file. (This is what the
+ /// version tab a file's property page is populated with).
+ ///
+ public FileVersionInfo GetFileVersionInfo()
+ {
+ var resources = GetResources();
+ var versionNode = ResourceNode.GetChild(ResourceNode.GetChild(resources, "Version"), "1");
+ if (versionNode == null)
+ {
+ return null;
+ }
+
+ if (!versionNode.IsLeaf && versionNode.Children.Count == 1)
+ {
+ versionNode = versionNode.Children[0];
+ }
+
+ var buff = AllocBuff();
+ byte* bytes = versionNode.FetchData(0, versionNode.DataLength, buff);
+ var ret = new FileVersionInfo(bytes, versionNode.DataLength);
+
+ FreeBuff(buff);
+ return ret;
+ }
+ ///
+ /// For side by side dlls, the manifest that describes the binding information is stored as the RT_MANIFEST resource, and it
+ /// is an XML string. This routine returns this.
+ ///
+ ///
+ public string GetSxSManfest()
+ {
+ var resources = GetResources();
+ var manifest = ResourceNode.GetChild(ResourceNode.GetChild(resources, "RT_MANIFEST"), "1");
+ if (manifest == null)
+ {
+ return null;
+ }
+
+ if (!manifest.IsLeaf && manifest.Children.Count == 1)
+ {
+ manifest = manifest.Children[0];
+ }
+
+ var buff = AllocBuff();
+ byte* bytes = manifest.FetchData(0, manifest.DataLength, buff);
+ string ret = null;
+ using (var textReader = new StreamReader(new UnmanagedMemoryStream(bytes, manifest.DataLength)))
+ {
+ ret = textReader.ReadToEnd();
+ }
+
+ FreeBuff(buff);
+ return ret;
+ }
+
+ ///
+ /// Returns true if this is and NGEN or Ready-to-Run image (it has precompiled native code)
+ ///
+ public bool HasPrecompiledManagedCode
+ {
+ get
+ {
+ if (!getNativeInfoCalled)
+ {
+ GetNativeInfo();
+ }
+
+ return hasPrecomiledManagedCode;
+ }
+ }
+
+ ///
+ /// Returns true if file has a managed ready-to-run image.
+ ///
+ public bool IsManagedReadyToRun
+ {
+ get
+ {
+ if (!getNativeInfoCalled)
+ {
+ GetNativeInfo();
+ }
+
+ return isManagedReadyToRun;
+ }
+ }
+
+ ///
+ /// Gets the major and minor ready-to-run version. returns true if ready-to-run.
+ ///
+ public bool ReadyToRunVersion(out short major, out short minor)
+ {
+ if (!getNativeInfoCalled)
+ {
+ GetNativeInfo();
+ }
+
+ major = readyToRunMajor;
+ minor = readyToRunMinor;
+ return isManagedReadyToRun;
+ }
+
+ ///
+ /// Closes any file handles and cleans up resources.
+ ///
+ public void Dispose()
+ {
+ // This method can only be called once on a given object.
+ m_stream.Dispose();
+ m_headerBuff.Dispose();
+ if (m_freeBuff != null)
+ {
+ m_freeBuff.Dispose();
+ }
+ }
+
+ // TODO make public?
+ internal ResourceNode GetResources()
+ {
+ if (Header.ResourceDirectory.VirtualAddress == 0 || Header.ResourceDirectory.Size < sizeof(IMAGE_RESOURCE_DIRECTORY))
+ {
+ return null;
+ }
+
+ var ret = new ResourceNode("", Header.FileOffsetOfResources, this, false, true);
+ return ret;
+ }
+
+ #region private
+ private bool getNativeInfoCalled;
+ private bool hasPrecomiledManagedCode;
+ private bool isManagedReadyToRun;
+ private short readyToRunMajor;
+ private short readyToRunMinor;
+
+ private struct IMAGE_COR20_HEADER
+ {
+ // Header versioning
+ public int cb;
+ public short MajorRuntimeVersion;
+ public short MinorRuntimeVersion;
+
+ // Symbol table and startup information
+ public IMAGE_DATA_DIRECTORY MetaData;
+ public int Flags;
+
+ public int EntryPointToken;
+ public IMAGE_DATA_DIRECTORY Resources;
+ public IMAGE_DATA_DIRECTORY StrongNameSignature;
+
+ public IMAGE_DATA_DIRECTORY CodeManagerTable;
+ public IMAGE_DATA_DIRECTORY VTableFixups;
+ public IMAGE_DATA_DIRECTORY ExportAddressTableJumps;
+
+ // Precompiled image info (internal use only - set to zero)
+ public IMAGE_DATA_DIRECTORY ManagedNativeHeader;
+ }
+
+ private const int READYTORUN_SIGNATURE = 0x00525452; // 'RTR'
+
+ private struct READYTORUN_HEADER
+ {
+ public int Signature; // READYTORUN_SIGNATURE
+ public short MajorVersion; // READYTORUN_VERSION_XXX
+ public short MinorVersion;
+
+ public int Flags; // READYTORUN_FLAG_XXX
+
+ public int NumberOfSections;
+
+ // Array of sections follows. The array entries are sorted by Type
+ // READYTORUN_SECTION Sections[];
+ };
+
+ public void GetNativeInfo()
+ {
+ if (getNativeInfoCalled)
+ {
+ return;
+ }
+
+ if (Header.ComDescriptorDirectory.VirtualAddress != 0 && sizeof(IMAGE_COR20_HEADER) <= Header.ComDescriptorDirectory.Size)
+ {
+ var buff = AllocBuff();
+ var managedHeader = (IMAGE_COR20_HEADER*)FetchRVA(Header.ComDescriptorDirectory.VirtualAddress, sizeof(IMAGE_COR20_HEADER), buff);
+ if (managedHeader->ManagedNativeHeader.VirtualAddress != 0)
+ {
+ hasPrecomiledManagedCode = true;
+ if (sizeof(READYTORUN_HEADER) <= managedHeader->ManagedNativeHeader.Size)
+ {
+ var r2rHeader = (READYTORUN_HEADER*)FetchRVA(managedHeader->ManagedNativeHeader.VirtualAddress, sizeof(READYTORUN_HEADER), buff);
+ if (r2rHeader->Signature == READYTORUN_SIGNATURE)
+ {
+ isManagedReadyToRun = true;
+ readyToRunMajor = r2rHeader->MajorVersion;
+ readyToRunMinor = r2rHeader->MinorVersion;
+ }
+ }
+ }
+ FreeBuff(buff);
+ }
+ }
+
+ private PEBuffer m_headerBuff;
+ private PEBuffer m_freeBuff;
+ private FileStream m_stream;
+
+ internal byte* FetchRVA(int rva, int size, PEBuffer buffer)
+ {
+ return buffer.Fetch(Header.RvaToFileOffset(rva), size);
+ }
+ internal PEBuffer AllocBuff()
+ {
+ var ret = m_freeBuff;
+ if (ret == null)
+ {
+ return new PEBuffer(m_stream);
+ }
+
+ m_freeBuff = null;
+ return ret;
+ }
+ internal void FreeBuff(PEBuffer buffer)
+ {
+ if (m_freeBuff != null)
+ {
+ buffer.Dispose(); // Get rid of it, since we already have cached one
+ }
+ else
+ {
+ m_freeBuff = buffer;
+ }
+ }
+ #endregion
+ };
+
+ ///
+ /// A PEHeader is a reader of the data at the beginning of a PEFile. If the header bytes of a
+ /// PEFile are read or mapped into memory, this class can parse it when given a poitner to it.
+ /// It can read both 32 and 64 bit PE files.
+ ///
+#if PEFILE_PUBLIC
+ public
+#endif
+ sealed unsafe class PEHeader
+ {
+ ///
+ /// Returns a PEHeader for void* pointer in memory. It does NO validity checking.
+ ///
+ public PEHeader(void* startOfPEFile)
+ {
+ dosHeader = (IMAGE_DOS_HEADER*)startOfPEFile;
+ if (dosHeader->e_magic != IMAGE_DOS_HEADER.IMAGE_DOS_SIGNATURE)
+ {
+ goto ThrowBadHeader;
+ }
+
+ var imageHeaderOffset = dosHeader->e_lfanew;
+ if (!(sizeof(IMAGE_DOS_HEADER) <= imageHeaderOffset && imageHeaderOffset <= 512))
+ {
+ goto ThrowBadHeader;
+ }
+
+ ntHeader = (IMAGE_NT_HEADERS*)((byte*)startOfPEFile + imageHeaderOffset);
+
+ var optionalHeaderSize = ntHeader->FileHeader.SizeOfOptionalHeader;
+ if (!(sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_OPTIONAL_HEADER32) <= optionalHeaderSize))
+ {
+ goto ThrowBadHeader;
+ }
+
+ sections = (IMAGE_SECTION_HEADER*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS) + ntHeader->FileHeader.SizeOfOptionalHeader);
+ if (!((byte*)sections - (byte*)startOfPEFile < 1024))
+ {
+ goto ThrowBadHeader;
+ }
+
+ return;
+ ThrowBadHeader:
+ throw new InvalidOperationException("Bad PE Header.");
+ }
+
+ ///
+ /// The total s,ize of the header, including section array of the the PE header.
+ ///
+ public int PEHeaderSize
+ {
+ get
+ {
+ return VirtualAddressToRva(sections) + sizeof(IMAGE_SECTION_HEADER) * ntHeader->FileHeader.NumberOfSections;
+ }
+ }
+
+ ///
+ /// Given a virtual address to data in a mapped PE file, return the relative virtual address (displacement from start of the image)
+ ///
+ public int VirtualAddressToRva(void* ptr)
+ {
+ return (int)((byte*)ptr - (byte*)dosHeader);
+ }
+ ///
+ /// Given a relative virtual address (displacement from start of the image) return the virtual address to data in a mapped PE file
+ ///
+ public void* RvaToVirtualAddress(int rva)
+ {
+ return ((byte*)dosHeader) + rva;
+ }
+ ///
+ /// Given a relative virtual address (displacement from start of the image) return a offset in the file data for that data.
+ ///
+ public int RvaToFileOffset(int rva)
+ {
+ for (int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++)
+ {
+
+ if (sections[i].VirtualAddress <= rva && rva < sections[i].VirtualAddress + sections[i].VirtualSize)
+ {
+ return (int)sections[i].PointerToRawData + (rva - (int)sections[i].VirtualAddress);
+ }
+ }
+ throw new InvalidOperationException("Illegal RVA 0x" + rva.ToString("x"));
+ }
+
+ ///
+ /// Returns true if this is PE file for a 64 bit architecture.
+ ///
+ public bool IsPE64 { get { return OptionalHeader32->Magic == 0x20b; } }
+ ///
+ /// Returns true if this file contains managed code (might also contain native code).
+ ///
+ public bool IsManaged { get { return ComDescriptorDirectory.VirtualAddress != 0; } }
+
+ // fields of code:IMAGE_NT_HEADERS
+ ///
+ /// Returns the 'Signature' of the PE HEader PE\0\0 = 0x4550, used for sanity checking.
+ ///
+ public uint Signature { get { return ntHeader->Signature; } }
+
+ // fields of code:IMAGE_FILE_HEADER
+ ///
+ /// The machine this PE file is intended to run on
+ ///
+ public MachineType Machine { get { return (MachineType)ntHeader->FileHeader.Machine; } }
+ ///
+ /// PE files have a number of sections that represent regions of memory with the access permisions. This is the nubmer of such sections.
+ ///
+ public ushort NumberOfSections { get { return ntHeader->FileHeader.NumberOfSections; } }
+ ///
+ /// The the PE file was created represented as the number of seconds since Jan 1 1970
+ ///
+ public int TimeDateStampSec { get { return (int)ntHeader->FileHeader.TimeDateStamp; } }
+ ///
+ /// The the PE file was created represented as a DateTime object
+ ///
+ public DateTime TimeDateStamp
+ {
+ get
+ {
+ return TimeDateStampToDate(TimeDateStampSec);
+ }
+ }
+
+ ///
+ /// PointerToSymbolTable (see IMAGE_FILE_HEADER in PE File spec)
+ ///
+ public ulong PointerToSymbolTable { get { return ntHeader->FileHeader.PointerToSymbolTable; } }
+ ///
+ /// NumberOfSymbols (see IMAGE_FILE_HEADER PE File spec)
+ ///
+ public ulong NumberOfSymbols { get { return ntHeader->FileHeader.NumberOfSymbols; } }
+ ///
+ /// SizeOfOptionalHeader (see IMAGE_FILE_HEADER PE File spec)
+ ///
+ public ushort SizeOfOptionalHeader { get { return ntHeader->FileHeader.SizeOfOptionalHeader; } }
+ ///
+ /// Characteristics (see IMAGE_FILE_HEADER PE File spec)
+ ///
+ public ushort Characteristics { get { return ntHeader->FileHeader.Characteristics; } }
+
+ // fields of code:IMAGE_OPTIONAL_HEADER32 (or code:IMAGE_OPTIONAL_HEADER64)
+ ///
+ /// Magic (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ushort Magic { get { return OptionalHeader32->Magic; } }
+ ///
+ /// MajorLinkerVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public byte MajorLinkerVersion { get { return OptionalHeader32->MajorLinkerVersion; } }
+ ///
+ /// MinorLinkerVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public byte MinorLinkerVersion { get { return OptionalHeader32->MinorLinkerVersion; } }
+ ///
+ /// SizeOfCode (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint SizeOfCode { get { return OptionalHeader32->SizeOfCode; } }
+ ///
+ /// SizeOfInitializedData (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint SizeOfInitializedData { get { return OptionalHeader32->SizeOfInitializedData; } }
+ ///
+ /// SizeOfUninitializedData (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint SizeOfUninitializedData { get { return OptionalHeader32->SizeOfUninitializedData; } }
+ ///
+ /// AddressOfEntryPoint (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint AddressOfEntryPoint { get { return OptionalHeader32->AddressOfEntryPoint; } }
+ ///
+ /// BaseOfCode (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint BaseOfCode { get { return OptionalHeader32->BaseOfCode; } }
+
+ // These depend on the whether you are PE32 or PE64
+ ///
+ /// ImageBase (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ulong ImageBase
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->ImageBase;
+ }
+ else
+ {
+ return OptionalHeader32->ImageBase;
+ }
+ }
+ }
+ ///
+ /// SectionAlignment (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint SectionAlignment
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->SectionAlignment;
+ }
+ else
+ {
+ return OptionalHeader32->SectionAlignment;
+ }
+ }
+ }
+ ///
+ /// FileAlignment (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint FileAlignment
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->FileAlignment;
+ }
+ else
+ {
+ return OptionalHeader32->FileAlignment;
+ }
+ }
+ }
+ ///
+ /// MajorOperatingSystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ushort MajorOperatingSystemVersion
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->MajorOperatingSystemVersion;
+ }
+ else
+ {
+ return OptionalHeader32->MajorOperatingSystemVersion;
+ }
+ }
+ }
+ ///
+ /// MinorOperatingSystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ushort MinorOperatingSystemVersion
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->MinorOperatingSystemVersion;
+ }
+ else
+ {
+ return OptionalHeader32->MinorOperatingSystemVersion;
+ }
+ }
+ }
+ ///
+ /// MajorImageVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ushort MajorImageVersion
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->MajorImageVersion;
+ }
+ else
+ {
+ return OptionalHeader32->MajorImageVersion;
+ }
+ }
+ }
+ ///
+ /// MinorImageVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ushort MinorImageVersion
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->MinorImageVersion;
+ }
+ else
+ {
+ return OptionalHeader32->MinorImageVersion;
+ }
+ }
+ }
+ ///
+ /// MajorSubsystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ushort MajorSubsystemVersion
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->MajorSubsystemVersion;
+ }
+ else
+ {
+ return OptionalHeader32->MajorSubsystemVersion;
+ }
+ }
+ }
+ ///
+ /// MinorSubsystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ushort MinorSubsystemVersion
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->MinorSubsystemVersion;
+ }
+ else
+ {
+ return OptionalHeader32->MinorSubsystemVersion;
+ }
+ }
+ }
+ ///
+ /// Win32VersionValue (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint Win32VersionValue
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->Win32VersionValue;
+ }
+ else
+ {
+ return OptionalHeader32->Win32VersionValue;
+ }
+ }
+ }
+ ///
+ /// SizeOfImage (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint SizeOfImage
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->SizeOfImage;
+ }
+ else
+ {
+ return OptionalHeader32->SizeOfImage;
+ }
+ }
+ }
+ ///
+ /// SizeOfHeaders (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint SizeOfHeaders
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->SizeOfHeaders;
+ }
+ else
+ {
+ return OptionalHeader32->SizeOfHeaders;
+ }
+ }
+ }
+ ///
+ /// CheckSum (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint CheckSum
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->CheckSum;
+ }
+ else
+ {
+ return OptionalHeader32->CheckSum;
+ }
+ }
+ }
+ ///
+ /// Subsystem (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ushort Subsystem
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->Subsystem;
+ }
+ else
+ {
+ return OptionalHeader32->Subsystem;
+ }
+ }
+ }
+ ///
+ /// DllCharacteristics (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ushort DllCharacteristics
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->DllCharacteristics;
+ }
+ else
+ {
+ return OptionalHeader32->DllCharacteristics;
+ }
+ }
+ }
+ ///
+ /// SizeOfStackReserve (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ulong SizeOfStackReserve
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->SizeOfStackReserve;
+ }
+ else
+ {
+ return OptionalHeader32->SizeOfStackReserve;
+ }
+ }
+ }
+ ///
+ /// SizeOfStackCommit (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ulong SizeOfStackCommit
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->SizeOfStackCommit;
+ }
+ else
+ {
+ return OptionalHeader32->SizeOfStackCommit;
+ }
+ }
+ }
+ ///
+ /// SizeOfHeapReserve (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ulong SizeOfHeapReserve
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->SizeOfHeapReserve;
+ }
+ else
+ {
+ return OptionalHeader32->SizeOfHeapReserve;
+ }
+ }
+ }
+ ///
+ /// SizeOfHeapCommit (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public ulong SizeOfHeapCommit
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->SizeOfHeapCommit;
+ }
+ else
+ {
+ return OptionalHeader32->SizeOfHeapCommit;
+ }
+ }
+ }
+ ///
+ /// LoaderFlags (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint LoaderFlags
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->LoaderFlags;
+ }
+ else
+ {
+ return OptionalHeader32->LoaderFlags;
+ }
+ }
+ }
+ ///
+ /// NumberOfRvaAndSizes (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
+ ///
+ public uint NumberOfRvaAndSizes
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return OptionalHeader64->NumberOfRvaAndSizes;
+ }
+ else
+ {
+ return OptionalHeader32->NumberOfRvaAndSizes;
+ }
+ }
+ }
+
+ // Well known data blobs (directories)
+ ///
+ /// Returns the data directory (virtual address an blob, of a data directory with index 'idx'. 14 are currently defined.
+ ///
+ public IMAGE_DATA_DIRECTORY Directory(int idx)
+ {
+ if (idx >= NumberOfRvaAndSizes)
+ {
+ return new IMAGE_DATA_DIRECTORY();
+ }
+
+ return ntDirectories[idx];
+ }
+ ///
+ /// Returns the data directory for DLL Exports see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY ExportDirectory { get { return Directory(0); } }
+ ///
+ /// Returns the data directory for DLL Imports see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY ImportDirectory { get { return Directory(1); } }
+ ///
+ /// Returns the data directory for DLL Resources see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY ResourceDirectory { get { return Directory(2); } }
+ ///
+ /// Returns the data directory for DLL Exceptions see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY ExceptionDirectory { get { return Directory(3); } }
+ ///
+ /// Returns the data directory for DLL securiy certificates (Authenticode) see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY CertificatesDirectory { get { return Directory(4); } }
+ ///
+ /// Returns the data directory Image Base Relocations (RELOCS) see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY BaseRelocationDirectory { get { return Directory(5); } }
+ ///
+ /// Returns the data directory for Debug information see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY DebugDirectory { get { return Directory(6); } }
+ ///
+ /// Returns the data directory for DLL Exports see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY ArchitectureDirectory { get { return Directory(7); } }
+ ///
+ /// Returns the data directory for GlobalPointer (IA64) see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY GlobalPointerDirectory { get { return Directory(8); } }
+ ///
+ /// Returns the data directory for THread local storage see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY ThreadStorageDirectory { get { return Directory(9); } }
+ ///
+ /// Returns the data directory for Load Configuration see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY LoadConfigurationDirectory { get { return Directory(10); } }
+ ///
+ /// Returns the data directory for Bound Imports see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY BoundImportDirectory { get { return Directory(11); } }
+ ///
+ /// Returns the data directory for the DLL Import Address Table (IAT) see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY ImportAddressTableDirectory { get { return Directory(12); } }
+ ///
+ /// Returns the data directory for Delayed Imports see PE file spec for more
+ ///
+ public IMAGE_DATA_DIRECTORY DelayImportDirectory { get { return Directory(13); } }
+ ///
+ /// see PE file spec for more .NET Runtime infomration.
+ ///
+ public IMAGE_DATA_DIRECTORY ComDescriptorDirectory { get { return Directory(14); } }
+
+ #region private
+ internal static DateTime TimeDateStampToDate(int timeDateStampSec)
+ {
+ // Convert seconds from Jan 1 1970 to DateTime ticks.
+ // The 621356004000000000L represents Jan 1 1970 as DateTime 100ns ticks.
+ DateTime ret = new DateTime(((long)(uint)timeDateStampSec) * 10000000 + 621356004000000000L, DateTimeKind.Utc).ToLocalTime();
+
+ // Calculation above seems to be off by an hour Don't know why
+ ret = ret.AddHours(-1.0);
+ return ret;
+ }
+
+ internal int FileOffsetOfResources
+ {
+ get
+ {
+ if (ResourceDirectory.VirtualAddress == 0)
+ {
+ return 0;
+ }
+
+ return RvaToFileOffset(ResourceDirectory.VirtualAddress);
+ }
+ }
+
+ private IMAGE_OPTIONAL_HEADER32* OptionalHeader32 { get { return (IMAGE_OPTIONAL_HEADER32*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS)); } }
+ private IMAGE_OPTIONAL_HEADER64* OptionalHeader64 { get { return (IMAGE_OPTIONAL_HEADER64*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS)); } }
+ private IMAGE_DATA_DIRECTORY* ntDirectories
+ {
+ get
+ {
+ if (IsPE64)
+ {
+ return (IMAGE_DATA_DIRECTORY*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_OPTIONAL_HEADER64));
+ }
+ else
+ {
+ return (IMAGE_DATA_DIRECTORY*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_OPTIONAL_HEADER32));
+ }
+ }
+ }
+
+ private IMAGE_DOS_HEADER* dosHeader;
+ private IMAGE_NT_HEADERS* ntHeader;
+ private IMAGE_SECTION_HEADER* sections;
+ #endregion
+ }
+
+ ///
+ /// The Machine types supported by the portable executable (PE) File format
+ ///
+#if PEFILE_PUBLIC
+ public
+#endif
+ enum MachineType : ushort
+ {
+ ///
+ /// Unknown machine type
+ ///
+ Native = 0,
+ ///
+ /// Intel X86 CPU
+ ///
+ X86 = 0x014c,
+ ///
+ /// Intel IA64
+ ///
+ ia64 = 0x0200,
+ ///
+ /// ARM 32 bit
+ ///
+ ARM = 0x01c0,
+ ///
+ /// Arm 64 bit
+ ///
+ Amd64 = 0x8664,
+ };
+
+ ///
+ /// Represents a Portable Executable (PE) Data directory. This is just a well known optional 'Blob' of memory (has a starting point and size)
+ ///
+#if PEFILE_PUBLIC
+ public
+#endif
+ struct IMAGE_DATA_DIRECTORY
+ {
+ ///
+ /// The start of the data blob when the file is mapped into memory
+ ///
+ public int VirtualAddress;
+ ///
+ /// The length of the data blob.
+ ///
+ public int Size;
+ }
+
+ ///
+ /// FileVersionInfo represents the extended version formation that is optionally placed in the PE file resource area.
+ ///
+#if PEFILE_PUBLIC
+ public
+#endif
+ sealed unsafe class FileVersionInfo
+ {
+ // TODO incomplete, but this is all I need.
+ ///
+ /// The version string
+ ///
+ public string FileVersion { get; private set; }
+ #region private
+ internal FileVersionInfo(byte* data, int dataLen)
+ {
+ FileVersion = "";
+ if (dataLen <= 0x5c)
+ {
+ return;
+ }
+
+ // See http://msdn.microsoft.com/en-us/library/ms647001(v=VS.85).aspx
+ byte* stringInfoPtr = data + 0x5c; // Gets to first StringInfo
+
+ // TODO hack, search for FileVersion string ...
+ string dataAsString = new string((char*)stringInfoPtr, 0, (dataLen - 0x5c) / 2);
+
+ string fileVersionKey = "FileVersion";
+ int fileVersionIdx = dataAsString.IndexOf(fileVersionKey);
+ if (fileVersionIdx >= 0)
+ {
+ int valIdx = fileVersionIdx + fileVersionKey.Length;
+ for (; ; )
+ {
+ valIdx++;
+ if (valIdx >= dataAsString.Length)
+ {
+ return;
+ }
+
+ if (dataAsString[valIdx] != (char)0)
+ {
+ break;
+ }
+ }
+ int varEndIdx = dataAsString.IndexOf((char)0, valIdx);
+ if (varEndIdx < 0)
+ {
+ return;
+ }
+
+ FileVersion = dataAsString.Substring(valIdx, varEndIdx - valIdx);
+ }
+ }
+
+ #endregion
+ }
+
+ #region private classes we may want to expose
+
+ ///
+ /// A PEBuffer represents a buffer (efficient) scanner of the
+ ///
+ internal sealed unsafe class PEBuffer : IDisposable
+ {
+ public PEBuffer(Stream stream, int buffSize = 512)
+ {
+ m_stream = stream;
+ GetBuffer(buffSize);
+ }
+ public byte* Fetch(int filePos, int size)
+ {
+ if (size > m_buff.Length)
+ {
+ GetBuffer(size);
+ }
+ if (!(m_buffPos <= filePos && filePos + size <= m_buffPos + m_buffLen))
+ {
+ // Read in the block of 'size' bytes at filePos
+ m_buffPos = filePos;
+ m_stream.Seek(m_buffPos, SeekOrigin.Begin);
+ m_buffLen = 0;
+ while (m_buffLen < m_buff.Length)
+ {
+ var count = m_stream.Read(m_buff, m_buffLen, size - m_buffLen);
+ if (count == 0)
+ {
+ break;
+ }
+
+ m_buffLen += count;
+ }
+ }
+ return &m_buffPtr[filePos - m_buffPos];
+ }
+ public int Length { get { return m_buffLen; } }
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ m_pinningHandle.Free();
+ }
+ #region private
+ ~PEBuffer()
+ {
+ FreeBuffer();
+ }
+
+ private void FreeBuffer()
+ {
+ try
+ {
+ if (m_pinningHandle.IsAllocated)
+ {
+ m_pinningHandle.Free();
+ }
+ }
+ catch (Exception) { }
+ }
+
+ private void GetBuffer(int buffSize)
+ {
+ FreeBuffer();
+
+ m_buff = new byte[buffSize];
+ fixed (byte* ptr = m_buff)
+ {
+ m_buffPtr = ptr;
+ }
+
+ m_buffLen = 0;
+ m_pinningHandle = GCHandle.Alloc(m_buff, GCHandleType.Pinned);
+ }
+
+ private int m_buffPos;
+ private int m_buffLen; // Number of valid bytes in m_buff
+ private byte[] m_buff;
+ private byte* m_buffPtr;
+ private GCHandle m_pinningHandle;
+ private Stream m_stream;
+ #endregion
+ }
+
+ internal sealed unsafe class ResourceNode
+ {
+ public string Name { get; private set; }
+ public bool IsLeaf { get; private set; }
+
+ // If IsLeaf is true
+ public int DataLength { get { return m_dataLen; } }
+ public byte* FetchData(int offsetInResourceData, int size, PEBuffer buff)
+ {
+ return buff.Fetch(m_dataFileOffset + offsetInResourceData, size);
+ }
+ public FileVersionInfo GetFileVersionInfo()
+ {
+ var buff = m_file.AllocBuff();
+ byte* bytes = FetchData(0, DataLength, buff);
+ var ret = new FileVersionInfo(bytes, DataLength);
+ m_file.FreeBuff(buff);
+ return ret;
+ }
+
+ public override string ToString()
+ {
+ StringWriter sw = new StringWriter();
+ ToString(sw, "");
+ return sw.ToString();
+ }
+
+ public static ResourceNode GetChild(ResourceNode node, string name)
+ {
+ if (node == null)
+ {
+ return null;
+ }
+
+ foreach (var child in node.Children)
+ {
+ if (child.Name == name)
+ {
+ return child;
+ }
+ }
+
+ return null;
+ }
+
+ // If IsLeaf is false
+ public List Children
+ {
+ get
+ {
+ if (m_Children == null && !IsLeaf)
+ {
+ var buff = m_file.AllocBuff();
+ var resourceStartFileOffset = m_file.Header.FileOffsetOfResources;
+
+ IMAGE_RESOURCE_DIRECTORY* resourceHeader = (IMAGE_RESOURCE_DIRECTORY*)buff.Fetch(
+ m_nodeFileOffset, sizeof(IMAGE_RESOURCE_DIRECTORY));
+
+ int totalCount = resourceHeader->NumberOfNamedEntries + resourceHeader->NumberOfIdEntries;
+ int totalSize = totalCount * sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY);
+
+ IMAGE_RESOURCE_DIRECTORY_ENTRY* entries = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)buff.Fetch(
+ m_nodeFileOffset + sizeof(IMAGE_RESOURCE_DIRECTORY), totalSize);
+
+ var nameBuff = m_file.AllocBuff();
+ m_Children = new List();
+ for (int i = 0; i < totalCount; i++)
+ {
+ var entry = &entries[i];
+ string entryName = null;
+ if (m_isTop)
+ {
+ entryName = IMAGE_RESOURCE_DIRECTORY_ENTRY.GetTypeNameForTypeId(entry->Id);
+ }
+ else
+ {
+ entryName = entry->GetName(nameBuff, resourceStartFileOffset);
+ }
+
+ Children.Add(new ResourceNode(entryName, resourceStartFileOffset + entry->DataOffset, m_file, entry->IsLeaf));
+ }
+ m_file.FreeBuff(nameBuff);
+ m_file.FreeBuff(buff);
+ }
+ return m_Children;
+ }
+ }
+
+ #region private
+ private void ToString(StringWriter sw, string indent)
+ {
+ sw.Write("{0}");
+ }
+ else
+ {
+ sw.Write("ChildCount=\"{0}\"", Children.Count);
+ sw.WriteLine(">");
+ foreach (var child in Children)
+ {
+ child.ToString(sw, indent + " ");
+ }
+
+ sw.WriteLine("{0}", indent);
+ }
+ }
+
+ internal ResourceNode(string name, int nodeFileOffset, PEFile file, bool isLeaf, bool isTop = false)
+ {
+ m_file = file;
+ m_nodeFileOffset = nodeFileOffset;
+ m_isTop = isTop;
+ IsLeaf = isLeaf;
+ Name = name;
+
+ if (isLeaf)
+ {
+ var buff = m_file.AllocBuff();
+ IMAGE_RESOURCE_DATA_ENTRY* dataDescr = (IMAGE_RESOURCE_DATA_ENTRY*)buff.Fetch(nodeFileOffset, sizeof(IMAGE_RESOURCE_DATA_ENTRY));
+
+ m_dataLen = dataDescr->Size;
+ m_dataFileOffset = file.Header.RvaToFileOffset(dataDescr->RvaToData);
+ var data = FetchData(0, m_dataLen, buff);
+ m_file.FreeBuff(buff);
+ }
+ }
+
+ private PEFile m_file;
+ private int m_nodeFileOffset;
+ private List m_Children;
+ private bool m_isTop;
+ private int m_dataLen;
+ private int m_dataFileOffset;
+ #endregion
+ }
+ #endregion
+
+ #region private classes
+ [StructLayout(LayoutKind.Explicit, Size = 64)]
+ internal struct IMAGE_DOS_HEADER
+ {
+ public const short IMAGE_DOS_SIGNATURE = 0x5A4D; // MZ.
+ [FieldOffset(0)]
+ public short e_magic;
+ [FieldOffset(60)]
+ public int e_lfanew; // Offset to the IMAGE_FILE_HEADER
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct IMAGE_NT_HEADERS
+ {
+ public uint Signature;
+ public IMAGE_FILE_HEADER FileHeader;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct IMAGE_FILE_HEADER
+ {
+ public ushort Machine;
+ public ushort NumberOfSections;
+ public uint TimeDateStamp;
+ public uint PointerToSymbolTable;
+ public uint NumberOfSymbols;
+ public ushort SizeOfOptionalHeader;
+ public ushort Characteristics;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct IMAGE_OPTIONAL_HEADER32
+ {
+ public ushort Magic;
+ public byte MajorLinkerVersion;
+ public byte MinorLinkerVersion;
+ public uint SizeOfCode;
+ public uint SizeOfInitializedData;
+ public uint SizeOfUninitializedData;
+ public uint AddressOfEntryPoint;
+ public uint BaseOfCode;
+ public uint BaseOfData;
+ public uint ImageBase;
+ public uint SectionAlignment;
+ public uint FileAlignment;
+ public ushort MajorOperatingSystemVersion;
+ public ushort MinorOperatingSystemVersion;
+ public ushort MajorImageVersion;
+ public ushort MinorImageVersion;
+ public ushort MajorSubsystemVersion;
+ public ushort MinorSubsystemVersion;
+ public uint Win32VersionValue;
+ public uint SizeOfImage;
+ public uint SizeOfHeaders;
+ public uint CheckSum;
+ public ushort Subsystem;
+ public ushort DllCharacteristics;
+ public uint SizeOfStackReserve;
+ public uint SizeOfStackCommit;
+ public uint SizeOfHeapReserve;
+ public uint SizeOfHeapCommit;
+ public uint LoaderFlags;
+ public uint NumberOfRvaAndSizes;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct IMAGE_OPTIONAL_HEADER64
+ {
+ public ushort Magic;
+ public byte MajorLinkerVersion;
+ public byte MinorLinkerVersion;
+ public uint SizeOfCode;
+ public uint SizeOfInitializedData;
+ public uint SizeOfUninitializedData;
+ public uint AddressOfEntryPoint;
+ public uint BaseOfCode;
+ public ulong ImageBase;
+ public uint SectionAlignment;
+ public uint FileAlignment;
+ public ushort MajorOperatingSystemVersion;
+ public ushort MinorOperatingSystemVersion;
+ public ushort MajorImageVersion;
+ public ushort MinorImageVersion;
+ public ushort MajorSubsystemVersion;
+ public ushort MinorSubsystemVersion;
+ public uint Win32VersionValue;
+ public uint SizeOfImage;
+ public uint SizeOfHeaders;
+ public uint CheckSum;
+ public ushort Subsystem;
+ public ushort DllCharacteristics;
+ public ulong SizeOfStackReserve;
+ public ulong SizeOfStackCommit;
+ public ulong SizeOfHeapReserve;
+ public ulong SizeOfHeapCommit;
+ public uint LoaderFlags;
+ public uint NumberOfRvaAndSizes;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct IMAGE_SECTION_HEADER
+ {
+ public string Name
+ {
+ get
+ {
+ fixed (byte* ptr = NameBytes)
+ {
+ if (ptr[7] == 0)
+ {
+ return System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)ptr);
+ }
+ else
+ {
+ return System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)ptr, 8);
+ }
+ }
+ }
+ }
+ public fixed byte NameBytes[8];
+ public uint VirtualSize;
+ public uint VirtualAddress;
+ public uint SizeOfRawData;
+ public uint PointerToRawData;
+ public uint PointerToRelocations;
+ public uint PointerToLinenumbers;
+ public ushort NumberOfRelocations;
+ public ushort NumberOfLinenumbers;
+ public uint Characteristics;
+ };
+
+ internal struct IMAGE_DEBUG_DIRECTORY
+ {
+ public int Characteristics;
+ public int TimeDateStamp;
+ public short MajorVersion;
+ public short MinorVersion;
+ public IMAGE_DEBUG_TYPE Type;
+ public int SizeOfData;
+ public int AddressOfRawData;
+ public int PointerToRawData;
+ };
+
+ internal enum IMAGE_DEBUG_TYPE
+ {
+ UNKNOWN = 0,
+ COFF = 1,
+ CODEVIEW = 2,
+ FPO = 3,
+ MISC = 4,
+ BBT = 10,
+ };
+
+ internal unsafe struct CV_INFO_PDB70
+ {
+ public const int PDB70CvSignature = 0x53445352; // RSDS in ascii
+
+ public int CvSignature;
+ public Guid Signature;
+ public int Age;
+ public fixed byte bytePdbFileName[1]; // Actually variable sized.
+ public string PdbFileName
+ {
+ get
+ {
+ fixed (byte* ptr = bytePdbFileName)
+ {
+ return System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)ptr);
+ }
+ }
+ }
+ };
+
+
+ /* Resource information */
+ // Resource directory consists of two counts, following by a variable length
+ // array of directory entries. The first count is the number of entries at
+ // beginning of the array that have actual names associated with each entry.
+ // The entries are in ascending order, case insensitive strings. The second
+ // count is the number of entries that immediately follow the named entries.
+ // This second count identifies the number of entries that have 16-bit integer
+ // Ids as their name. These entries are also sorted in ascending order.
+ //
+ // This structure allows fast lookup by either name or number, but for any
+ // given resource entry only one form of lookup is supported, not both.
+ internal unsafe struct IMAGE_RESOURCE_DIRECTORY
+ {
+ public int Characteristics;
+ public int TimeDateStamp;
+ public short MajorVersion;
+ public short MinorVersion;
+ public ushort NumberOfNamedEntries;
+ public ushort NumberOfIdEntries;
+ // IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
+ };
+
+ //
+ // Each directory contains the 32-bit Name of the entry and an offset,
+ // relative to the beginning of the resource directory of the data associated
+ // with this directory entry. If the name of the entry is an actual text
+ // string instead of an integer Id, then the high order bit of the name field
+ // is set to one and the low order 31-bits are an offset, relative to the
+ // beginning of the resource directory of the string, which is of type
+ // IMAGE_RESOURCE_DIRECTORY_STRING. Otherwise the high bit is clear and the
+ // low-order 16-bits are the integer Id that identify this resource directory
+ // entry. If the directory entry is yet another resource directory (i.e. a
+ // subdirectory), then the high order bit of the offset field will be
+ // set to indicate this. Otherwise the high bit is clear and the offset
+ // field points to a resource data entry.
+ internal unsafe struct IMAGE_RESOURCE_DIRECTORY_ENTRY
+ {
+ public bool IsStringName { get { return NameOffsetAndFlag < 0; } }
+ public int NameOffset { get { return NameOffsetAndFlag & 0x7FFFFFFF; } }
+
+ public bool IsLeaf { get { return (0x80000000 & DataOffsetAndFlag) == 0; } }
+ public int DataOffset { get { return DataOffsetAndFlag & 0x7FFFFFFF; } }
+ public int Id { get { return 0xFFFF & NameOffsetAndFlag; } }
+
+ private int NameOffsetAndFlag;
+ private int DataOffsetAndFlag;
+
+ internal unsafe string GetName(PEBuffer buff, int resourceStartFileOffset)
+ {
+ if (IsStringName)
+ {
+ int nameLen = *((ushort*)buff.Fetch(NameOffset + resourceStartFileOffset, 2));
+ char* namePtr = (char*)buff.Fetch(NameOffset + resourceStartFileOffset + 2, nameLen);
+ return new string(namePtr);
+ }
+ else
+ {
+ return Id.ToString();
+ }
+ }
+
+ internal static string GetTypeNameForTypeId(int typeId)
+ {
+ switch (typeId)
+ {
+ case 1:
+ return "Cursor";
+ case 2:
+ return "BitMap";
+ case 3:
+ return "Icon";
+ case 4:
+ return "Menu";
+ case 5:
+ return "Dialog";
+ case 6:
+ return "String";
+ case 7:
+ return "FontDir";
+ case 8:
+ return "Font";
+ case 9:
+ return "Accelerator";
+ case 10:
+ return "RCData";
+ case 11:
+ return "MessageTable";
+ case 12:
+ return "GroupCursor";
+ case 14:
+ return "GroupIcon";
+ case 16:
+ return "Version";
+ case 19:
+ return "PlugPlay";
+ case 20:
+ return "Vxd";
+ case 21:
+ return "Aniicursor";
+ case 22:
+ return "Aniicon";
+ case 23:
+ return "Html";
+ case 24:
+ return "RT_MANIFEST";
+ }
+ return typeId.ToString();
+ }
+ }
+
+ // Each resource data entry describes a leaf node in the resource directory
+ // tree. It contains an offset, relative to the beginning of the resource
+ // directory of the data for the resource, a size field that gives the number
+ // of bytes of data at that offset, a CodePage that should be used when
+ // decoding code point values within the resource data. Typically for new
+ // applications the code page would be the unicode code page.
+ internal unsafe struct IMAGE_RESOURCE_DATA_ENTRY
+ {
+ public int RvaToData;
+ public int Size;
+ public int CodePage;
+ public int Reserved;
+ };
+
+ #endregion
+}
diff --git a/src/TraceEvent/TraceUtilities/PEFile.cs b/src/TraceEvent/TraceUtilities/PEFile.cs
index d4ec3dd97..4123f8f90 100644
--- a/src/TraceEvent/TraceUtilities/PEFile.cs
+++ b/src/TraceEvent/TraceUtilities/PEFile.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace PEFile
@@ -17,23 +18,27 @@ namespace PEFile
#endif
sealed unsafe class PEFile : IDisposable
{
+ private const int InitialReadSize = 1024;
+ private const int MinimumHeaderSize = 512;
+ private const int MaxHeaderSize = 1024 * 1024;
+
///
/// Create a new PEFile header reader that inspects the
///
public PEFile(string filePath)
{
m_stream = File.OpenRead(filePath);
- m_headerBuff = new PEBuffer(m_stream);
+ m_headerBuff = new PEBufferedReader(m_stream);
- byte* ptr = m_headerBuff.Fetch(0, 1024);
- if (m_headerBuff.Length < 512)
+ PEBufferedSlice slice = m_headerBuff.EnsureRead(0, InitialReadSize);
+ if (m_headerBuff.Length < MinimumHeaderSize)
{
goto ThrowBadHeader;
}
- Header = new PEHeader(ptr);
+ Header = new PEHeader(slice);
- if (Header.PEHeaderSize > 1024 * 64) // prevent insane numbers;
+ if (Header.PEHeaderSize > MaxHeaderSize)
{
goto ThrowBadHeader;
}
@@ -41,13 +46,13 @@ public PEFile(string filePath)
// We did not read in the complete header, Try again using the right sized buffer.
if (Header.PEHeaderSize > m_headerBuff.Length)
{
- ptr = m_headerBuff.Fetch(0, Header.PEHeaderSize);
+ slice = m_headerBuff.EnsureRead(0, Header.PEHeaderSize);
if (m_headerBuff.Length < Header.PEHeaderSize)
{
goto ThrowBadHeader;
}
- Header = new PEHeader(ptr);
+ Header = new PEHeader(slice);
}
return;
ThrowBadHeader:
@@ -307,26 +312,26 @@ public void GetNativeInfo()
}
}
- private PEBuffer m_headerBuff;
- private PEBuffer m_freeBuff;
+ private PEBufferedReader m_headerBuff;
+ private PEBufferedReader m_freeBuff;
private FileStream m_stream;
- internal byte* FetchRVA(int rva, int size, PEBuffer buffer)
+ internal byte* FetchRVA(int rva, int size, PEBufferedReader buffer)
{
return buffer.Fetch(Header.RvaToFileOffset(rva), size);
}
- internal PEBuffer AllocBuff()
+ internal PEBufferedReader AllocBuff()
{
var ret = m_freeBuff;
if (ret == null)
{
- return new PEBuffer(m_stream);
+ return new PEBufferedReader(m_stream);
}
m_freeBuff = null;
return ret;
}
- internal void FreeBuff(PEBuffer buffer)
+ internal void FreeBuff(PEBufferedReader buffer)
{
if (m_freeBuff != null)
{
@@ -342,7 +347,7 @@ internal void FreeBuff(PEBuffer buffer)
///
/// A PEHeader is a reader of the data at the beginning of a PEFile. If the header bytes of a
- /// PEFile are read or mapped into memory, this class can parse it when given a poitner to it.
+ /// PEFile are read or mapped into memory, this class can parse it when given a buffer slice to it.
/// It can read both 32 and 64 bit PE files.
///
#if PEFILE_PUBLIC
@@ -351,36 +356,55 @@ internal void FreeBuff(PEBuffer buffer)
sealed unsafe class PEHeader
{
///
- /// Returns a PEHeader for void* pointer in memory. It does NO validity checking.
+ /// Returns a PEHeader that references an existing buffer without copying. Validates buffer bounds.
///
- public PEHeader(void* startOfPEFile)
+ internal PEHeader(PEBufferedSlice slice)
{
- dosHeader = (IMAGE_DOS_HEADER*)startOfPEFile;
- if (dosHeader->e_magic != IMAGE_DOS_HEADER.IMAGE_DOS_SIGNATURE)
+ m_slice = slice;
+
+ var span = slice.AsSpan();
+ if (span.Length < sizeof(IMAGE_DOS_HEADER))
{
goto ThrowBadHeader;
}
- var imageHeaderOffset = dosHeader->e_lfanew;
- if (!(sizeof(IMAGE_DOS_HEADER) <= imageHeaderOffset && imageHeaderOffset <= 512))
+ IMAGE_DOS_HEADER dosHdr = MemoryMarshal.Read(span);
+
+ if (dosHdr.e_magic != IMAGE_DOS_HEADER.IMAGE_DOS_SIGNATURE)
{
goto ThrowBadHeader;
}
- ntHeader = (IMAGE_NT_HEADERS*)((byte*)startOfPEFile + imageHeaderOffset);
+ var imageHeaderOffset = dosHdr.e_lfanew;
+ if (imageHeaderOffset < sizeof(IMAGE_DOS_HEADER))
+ {
+ goto ThrowBadHeader;
+ }
- var optionalHeaderSize = ntHeader->FileHeader.SizeOfOptionalHeader;
- if (!(sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_OPTIONAL_HEADER32) <= optionalHeaderSize))
+ if (span.Length < imageHeaderOffset + sizeof(IMAGE_NT_HEADERS))
{
goto ThrowBadHeader;
}
- sections = (IMAGE_SECTION_HEADER*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS) + ntHeader->FileHeader.SizeOfOptionalHeader);
- if (!((byte*)sections - (byte*)startOfPEFile < 1024))
+ m_ntHeaderOffset = imageHeaderOffset;
+ IMAGE_NT_HEADERS ntHdr = MemoryMarshal.Read(span.Slice(m_ntHeaderOffset));
+
+ var optionalHeaderSize = ntHdr.FileHeader.SizeOfOptionalHeader;
+ if (!(sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_OPTIONAL_HEADER32) <= optionalHeaderSize))
{
goto ThrowBadHeader;
}
+ m_sectionsOffset = m_ntHeaderOffset + sizeof(IMAGE_NT_HEADERS) + ntHdr.FileHeader.SizeOfOptionalHeader;
+
+ // Note: We don't validate that the span contains all section headers at this point.
+ // This is intentional to support the progressive read pattern in PEFile:
+ // 1. PEFile creates PEHeader with initial 1024-byte buffer
+ // 2. PEFile checks Header.PEHeaderSize (which needs m_sectionsOffset + section count)
+ // 3. If PEHeaderSize > buffer length, PEFile re-reads with correct size
+ // 4. ReadOnlySpan bounds checking catches any out-of-bounds access when sections are actually read
+ // This pattern allows PE files with large headers (>1024 bytes) to work correctly.
+
return;
ThrowBadHeader:
throw new InvalidOperationException("Bad PE Header.");
@@ -393,35 +417,22 @@ public int PEHeaderSize
{
get
{
- return VirtualAddressToRva(sections) + sizeof(IMAGE_SECTION_HEADER) * ntHeader->FileHeader.NumberOfSections;
+ return m_sectionsOffset + sizeof(IMAGE_SECTION_HEADER) * NtHeader.FileHeader.NumberOfSections;
}
}
- ///
- /// Given a virtual address to data in a mapped PE file, return the relative virtual address (displacement from start of the image)
- ///
- public int VirtualAddressToRva(void* ptr)
- {
- return (int)((byte*)ptr - (byte*)dosHeader);
- }
- ///
- /// Given a relative virtual address (displacement from start of the image) return the virtual address to data in a mapped PE file
- ///
- public void* RvaToVirtualAddress(int rva)
- {
- return ((byte*)dosHeader) + rva;
- }
///
/// Given a relative virtual address (displacement from start of the image) return a offset in the file data for that data.
///
public int RvaToFileOffset(int rva)
{
- for (int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++)
+ ushort numSections = NtHeader.FileHeader.NumberOfSections;
+ for (int i = 0; i < numSections; i++)
{
-
- if (sections[i].VirtualAddress <= rva && rva < sections[i].VirtualAddress + sections[i].VirtualSize)
+ ref readonly IMAGE_SECTION_HEADER section = ref GetSectionHeader(i);
+ if (section.VirtualAddress <= rva && rva < section.VirtualAddress + section.VirtualSize)
{
- return (int)sections[i].PointerToRawData + (rva - (int)sections[i].VirtualAddress);
+ return (int)section.PointerToRawData + (rva - (int)section.VirtualAddress);
}
}
throw new InvalidOperationException("Illegal RVA 0x" + rva.ToString("x"));
@@ -430,7 +441,7 @@ public int RvaToFileOffset(int rva)
///
/// Returns true if this is PE file for a 64 bit architecture.
///
- public bool IsPE64 { get { return OptionalHeader32->Magic == 0x20b; } }
+ public bool IsPE64 { get { return OptionalHeader32Span.Magic == 0x20b; } }
///
/// Returns true if this file contains managed code (might also contain native code).
///
@@ -440,21 +451,21 @@ public int RvaToFileOffset(int rva)
///
/// Returns the 'Signature' of the PE HEader PE\0\0 = 0x4550, used for sanity checking.
///
- public uint Signature { get { return ntHeader->Signature; } }
+ public uint Signature { get { return NtHeader.Signature; } }
// fields of code:IMAGE_FILE_HEADER
///
/// The machine this PE file is intended to run on
///
- public MachineType Machine { get { return (MachineType)ntHeader->FileHeader.Machine; } }
+ public MachineType Machine { get { return (MachineType)NtHeader.FileHeader.Machine; } }
///
/// PE files have a number of sections that represent regions of memory with the access permisions. This is the nubmer of such sections.
///
- public ushort NumberOfSections { get { return ntHeader->FileHeader.NumberOfSections; } }
+ public ushort NumberOfSections { get { return NtHeader.FileHeader.NumberOfSections; } }
///
/// The the PE file was created represented as the number of seconds since Jan 1 1970
///
- public int TimeDateStampSec { get { return (int)ntHeader->FileHeader.TimeDateStamp; } }
+ public int TimeDateStampSec { get { return (int)NtHeader.FileHeader.TimeDateStamp; } }
///
/// The the PE file was created represented as a DateTime object
///
@@ -469,53 +480,53 @@ public DateTime TimeDateStamp
///
/// PointerToSymbolTable (see IMAGE_FILE_HEADER in PE File spec)
///
- public ulong PointerToSymbolTable { get { return ntHeader->FileHeader.PointerToSymbolTable; } }
+ public ulong PointerToSymbolTable { get { return NtHeader.FileHeader.PointerToSymbolTable; } }
///
/// NumberOfSymbols (see IMAGE_FILE_HEADER PE File spec)
///
- public ulong NumberOfSymbols { get { return ntHeader->FileHeader.NumberOfSymbols; } }
+ public ulong NumberOfSymbols { get { return NtHeader.FileHeader.NumberOfSymbols; } }
///
/// SizeOfOptionalHeader (see IMAGE_FILE_HEADER PE File spec)
///
- public ushort SizeOfOptionalHeader { get { return ntHeader->FileHeader.SizeOfOptionalHeader; } }
+ public ushort SizeOfOptionalHeader { get { return NtHeader.FileHeader.SizeOfOptionalHeader; } }
///
/// Characteristics (see IMAGE_FILE_HEADER PE File spec)
///
- public ushort Characteristics { get { return ntHeader->FileHeader.Characteristics; } }
+ public ushort Characteristics { get { return NtHeader.FileHeader.Characteristics; } }
// fields of code:IMAGE_OPTIONAL_HEADER32 (or code:IMAGE_OPTIONAL_HEADER64)
///
/// Magic (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
///
- public ushort Magic { get { return OptionalHeader32->Magic; } }
+ public ushort Magic { get { return OptionalHeader32Span.Magic; } }
///
/// MajorLinkerVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
///
- public byte MajorLinkerVersion { get { return OptionalHeader32->MajorLinkerVersion; } }
+ public byte MajorLinkerVersion { get { return OptionalHeader32Span.MajorLinkerVersion; } }
///
/// MinorLinkerVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
///
- public byte MinorLinkerVersion { get { return OptionalHeader32->MinorLinkerVersion; } }
+ public byte MinorLinkerVersion { get { return OptionalHeader32Span.MinorLinkerVersion; } }
///
/// SizeOfCode (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
///
- public uint SizeOfCode { get { return OptionalHeader32->SizeOfCode; } }
+ public uint SizeOfCode { get { return OptionalHeader32Span.SizeOfCode; } }
///
/// SizeOfInitializedData (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
///
- public uint SizeOfInitializedData { get { return OptionalHeader32->SizeOfInitializedData; } }
+ public uint SizeOfInitializedData { get { return OptionalHeader32Span.SizeOfInitializedData; } }
///
/// SizeOfUninitializedData (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
///
- public uint SizeOfUninitializedData { get { return OptionalHeader32->SizeOfUninitializedData; } }
+ public uint SizeOfUninitializedData { get { return OptionalHeader32Span.SizeOfUninitializedData; } }
///
/// AddressOfEntryPoint (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
///
- public uint AddressOfEntryPoint { get { return OptionalHeader32->AddressOfEntryPoint; } }
+ public uint AddressOfEntryPoint { get { return OptionalHeader32Span.AddressOfEntryPoint; } }
///
/// BaseOfCode (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec)
///
- public uint BaseOfCode { get { return OptionalHeader32->BaseOfCode; } }
+ public uint BaseOfCode { get { return OptionalHeader32Span.BaseOfCode; } }
// These depend on the whether you are PE32 or PE64
///
@@ -527,11 +538,11 @@ public ulong ImageBase
{
if (IsPE64)
{
- return OptionalHeader64->ImageBase;
+ return OptionalHeader64Span.ImageBase;
}
else
{
- return OptionalHeader32->ImageBase;
+ return OptionalHeader32Span.ImageBase;
}
}
}
@@ -544,11 +555,11 @@ public uint SectionAlignment
{
if (IsPE64)
{
- return OptionalHeader64->SectionAlignment;
+ return OptionalHeader64Span.SectionAlignment;
}
else
{
- return OptionalHeader32->SectionAlignment;
+ return OptionalHeader32Span.SectionAlignment;
}
}
}
@@ -561,11 +572,11 @@ public uint FileAlignment
{
if (IsPE64)
{
- return OptionalHeader64->FileAlignment;
+ return OptionalHeader64Span.FileAlignment;
}
else
{
- return OptionalHeader32->FileAlignment;
+ return OptionalHeader32Span.FileAlignment;
}
}
}
@@ -578,11 +589,11 @@ public ushort MajorOperatingSystemVersion
{
if (IsPE64)
{
- return OptionalHeader64->MajorOperatingSystemVersion;
+ return OptionalHeader64Span.MajorOperatingSystemVersion;
}
else
{
- return OptionalHeader32->MajorOperatingSystemVersion;
+ return OptionalHeader32Span.MajorOperatingSystemVersion;
}
}
}
@@ -595,11 +606,11 @@ public ushort MinorOperatingSystemVersion
{
if (IsPE64)
{
- return OptionalHeader64->MinorOperatingSystemVersion;
+ return OptionalHeader64Span.MinorOperatingSystemVersion;
}
else
{
- return OptionalHeader32->MinorOperatingSystemVersion;
+ return OptionalHeader32Span.MinorOperatingSystemVersion;
}
}
}
@@ -612,11 +623,11 @@ public ushort MajorImageVersion
{
if (IsPE64)
{
- return OptionalHeader64->MajorImageVersion;
+ return OptionalHeader64Span.MajorImageVersion;
}
else
{
- return OptionalHeader32->MajorImageVersion;
+ return OptionalHeader32Span.MajorImageVersion;
}
}
}
@@ -629,11 +640,11 @@ public ushort MinorImageVersion
{
if (IsPE64)
{
- return OptionalHeader64->MinorImageVersion;
+ return OptionalHeader64Span.MinorImageVersion;
}
else
{
- return OptionalHeader32->MinorImageVersion;
+ return OptionalHeader32Span.MinorImageVersion;
}
}
}
@@ -646,11 +657,11 @@ public ushort MajorSubsystemVersion
{
if (IsPE64)
{
- return OptionalHeader64->MajorSubsystemVersion;
+ return OptionalHeader64Span.MajorSubsystemVersion;
}
else
{
- return OptionalHeader32->MajorSubsystemVersion;
+ return OptionalHeader32Span.MajorSubsystemVersion;
}
}
}
@@ -663,11 +674,11 @@ public ushort MinorSubsystemVersion
{
if (IsPE64)
{
- return OptionalHeader64->MinorSubsystemVersion;
+ return OptionalHeader64Span.MinorSubsystemVersion;
}
else
{
- return OptionalHeader32->MinorSubsystemVersion;
+ return OptionalHeader32Span.MinorSubsystemVersion;
}
}
}
@@ -680,11 +691,11 @@ public uint Win32VersionValue
{
if (IsPE64)
{
- return OptionalHeader64->Win32VersionValue;
+ return OptionalHeader64Span.Win32VersionValue;
}
else
{
- return OptionalHeader32->Win32VersionValue;
+ return OptionalHeader32Span.Win32VersionValue;
}
}
}
@@ -697,11 +708,11 @@ public uint SizeOfImage
{
if (IsPE64)
{
- return OptionalHeader64->SizeOfImage;
+ return OptionalHeader64Span.SizeOfImage;
}
else
{
- return OptionalHeader32->SizeOfImage;
+ return OptionalHeader32Span.SizeOfImage;
}
}
}
@@ -714,11 +725,11 @@ public uint SizeOfHeaders
{
if (IsPE64)
{
- return OptionalHeader64->SizeOfHeaders;
+ return OptionalHeader64Span.SizeOfHeaders;
}
else
{
- return OptionalHeader32->SizeOfHeaders;
+ return OptionalHeader32Span.SizeOfHeaders;
}
}
}
@@ -731,11 +742,11 @@ public uint CheckSum
{
if (IsPE64)
{
- return OptionalHeader64->CheckSum;
+ return OptionalHeader64Span.CheckSum;
}
else
{
- return OptionalHeader32->CheckSum;
+ return OptionalHeader32Span.CheckSum;
}
}
}
@@ -748,11 +759,11 @@ public ushort Subsystem
{
if (IsPE64)
{
- return OptionalHeader64->Subsystem;
+ return OptionalHeader64Span.Subsystem;
}
else
{
- return OptionalHeader32->Subsystem;
+ return OptionalHeader32Span.Subsystem;
}
}
}
@@ -765,11 +776,11 @@ public ushort DllCharacteristics
{
if (IsPE64)
{
- return OptionalHeader64->DllCharacteristics;
+ return OptionalHeader64Span.DllCharacteristics;
}
else
{
- return OptionalHeader32->DllCharacteristics;
+ return OptionalHeader32Span.DllCharacteristics;
}
}
}
@@ -782,11 +793,11 @@ public ulong SizeOfStackReserve
{
if (IsPE64)
{
- return OptionalHeader64->SizeOfStackReserve;
+ return OptionalHeader64Span.SizeOfStackReserve;
}
else
{
- return OptionalHeader32->SizeOfStackReserve;
+ return OptionalHeader32Span.SizeOfStackReserve;
}
}
}
@@ -799,11 +810,11 @@ public ulong SizeOfStackCommit
{
if (IsPE64)
{
- return OptionalHeader64->SizeOfStackCommit;
+ return OptionalHeader64Span.SizeOfStackCommit;
}
else
{
- return OptionalHeader32->SizeOfStackCommit;
+ return OptionalHeader32Span.SizeOfStackCommit;
}
}
}
@@ -816,11 +827,11 @@ public ulong SizeOfHeapReserve
{
if (IsPE64)
{
- return OptionalHeader64->SizeOfHeapReserve;
+ return OptionalHeader64Span.SizeOfHeapReserve;
}
else
{
- return OptionalHeader32->SizeOfHeapReserve;
+ return OptionalHeader32Span.SizeOfHeapReserve;
}
}
}
@@ -833,11 +844,11 @@ public ulong SizeOfHeapCommit
{
if (IsPE64)
{
- return OptionalHeader64->SizeOfHeapCommit;
+ return OptionalHeader64Span.SizeOfHeapCommit;
}
else
{
- return OptionalHeader32->SizeOfHeapCommit;
+ return OptionalHeader32Span.SizeOfHeapCommit;
}
}
}
@@ -850,11 +861,11 @@ public uint LoaderFlags
{
if (IsPE64)
{
- return OptionalHeader64->LoaderFlags;
+ return OptionalHeader64Span.LoaderFlags;
}
else
{
- return OptionalHeader32->LoaderFlags;
+ return OptionalHeader32Span.LoaderFlags;
}
}
}
@@ -867,11 +878,11 @@ public uint NumberOfRvaAndSizes
{
if (IsPE64)
{
- return OptionalHeader64->NumberOfRvaAndSizes;
+ return OptionalHeader64Span.NumberOfRvaAndSizes;
}
else
{
- return OptionalHeader32->NumberOfRvaAndSizes;
+ return OptionalHeader32Span.NumberOfRvaAndSizes;
}
}
}
@@ -887,7 +898,7 @@ public IMAGE_DATA_DIRECTORY Directory(int idx)
return new IMAGE_DATA_DIRECTORY();
}
- return ntDirectories[idx];
+ return GetDirectory(idx);
}
///
/// Returns the data directory for DLL Exports see PE file spec for more
@@ -975,26 +986,83 @@ internal int FileOffsetOfResources
}
}
- private IMAGE_OPTIONAL_HEADER32* OptionalHeader32 { get { return (IMAGE_OPTIONAL_HEADER32*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS)); } }
- private IMAGE_OPTIONAL_HEADER64* OptionalHeader64 { get { return (IMAGE_OPTIONAL_HEADER64*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS)); } }
- private IMAGE_DATA_DIRECTORY* ntDirectories
+ // Helper method to get a span from the buffer with bounds checking
+ private ReadOnlySpan GetBufferSpan(int offset, int length)
+ {
+ var span = m_slice.AsSpan();
+ if (offset < 0 || offset + length > span.Length)
+ {
+ throw new ArgumentOutOfRangeException($"Attempted to read {length} bytes at offset {offset}, but buffer is only {span.Length} bytes.");
+ }
+ return span.Slice(offset, length);
+ }
+
+ // Helper properties to access structures from span with bounds checking
+ private ref readonly IMAGE_DOS_HEADER DosHeader
{
get
{
- if (IsPE64)
- {
- return (IMAGE_DATA_DIRECTORY*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_OPTIONAL_HEADER64));
- }
- else
- {
- return (IMAGE_DATA_DIRECTORY*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_OPTIONAL_HEADER32));
- }
+ var span = GetBufferSpan(0, sizeof(IMAGE_DOS_HEADER));
+ return ref MemoryMarshal.Cast(span)[0];
+ }
+ }
+
+ private ref readonly IMAGE_NT_HEADERS NtHeader
+ {
+ get
+ {
+ var span = GetBufferSpan(m_ntHeaderOffset, sizeof(IMAGE_NT_HEADERS));
+ return ref MemoryMarshal.Cast(span)[0];
+ }
+ }
+
+ private ref readonly IMAGE_SECTION_HEADER GetSectionHeader(int index)
+ {
+ int offset = m_sectionsOffset + index * sizeof(IMAGE_SECTION_HEADER);
+ var span = GetBufferSpan(offset, sizeof(IMAGE_SECTION_HEADER));
+ return ref MemoryMarshal.Cast(span)[0];
+ }
+
+ private ref readonly IMAGE_OPTIONAL_HEADER32 OptionalHeader32Span
+ {
+ get
+ {
+ int offset = m_ntHeaderOffset + sizeof(IMAGE_NT_HEADERS);
+ var span = GetBufferSpan(offset, sizeof(IMAGE_OPTIONAL_HEADER32));
+ return ref MemoryMarshal.Cast(span)[0];
+ }
+ }
+
+ private ref readonly IMAGE_OPTIONAL_HEADER64 OptionalHeader64Span
+ {
+ get
+ {
+ int offset = m_ntHeaderOffset + sizeof(IMAGE_NT_HEADERS);
+ var span = GetBufferSpan(offset, sizeof(IMAGE_OPTIONAL_HEADER64));
+ return ref MemoryMarshal.Cast(span)[0];
+ }
+ }
+
+ private ref readonly IMAGE_DATA_DIRECTORY GetDirectory(int index)
+ {
+ int dirOffset;
+ if (IsPE64)
+ {
+ dirOffset = m_ntHeaderOffset + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_OPTIONAL_HEADER64);
+ }
+ else
+ {
+ dirOffset = m_ntHeaderOffset + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_OPTIONAL_HEADER32);
}
+ dirOffset += index * sizeof(IMAGE_DATA_DIRECTORY);
+ var span = GetBufferSpan(dirOffset, sizeof(IMAGE_DATA_DIRECTORY));
+ return ref MemoryMarshal.Cast(span)[0];
}
- private IMAGE_DOS_HEADER* dosHeader;
- private IMAGE_NT_HEADERS* ntHeader;
- private IMAGE_SECTION_HEADER* sections;
+ // Span-based fields
+ private PEBufferedSlice m_slice;
+ private int m_ntHeaderOffset;
+ private int m_sectionsOffset;
#endregion
}
@@ -1108,11 +1176,33 @@ internal FileVersionInfo(byte* data, int dataLen)
#region private classes we may want to expose
///
- /// A PEBuffer represents a buffer (efficient) scanner of the
+ /// Represents a slice of a buffered PE file with buffer, offset, and length information.
+ ///
+ internal struct PEBufferedSlice
+ {
+ public byte[] Buffer { get; }
+ public int Offset { get; }
+ public int Length { get; }
+
+ public PEBufferedSlice(byte[] buffer, int offset, int length)
+ {
+ Buffer = buffer;
+ Offset = offset;
+ Length = length;
+ }
+
+ public ReadOnlySpan AsSpan()
+ {
+ return new ReadOnlySpan(Buffer, Offset, Length);
+ }
+ }
+
+ ///
+ /// A PEBufferedReader represents a buffer (efficient) scanner of the
///
- internal sealed unsafe class PEBuffer : IDisposable
+ internal sealed unsafe class PEBufferedReader : IDisposable
{
- public PEBuffer(Stream stream, int buffSize = 512)
+ public PEBufferedReader(Stream stream, int buffSize = 512)
{
m_stream = stream;
GetBuffer(buffSize);
@@ -1142,14 +1232,53 @@ public PEBuffer(Stream stream, int buffSize = 512)
}
return &m_buffPtr[filePos - m_buffPos];
}
+ public ReadOnlySpan FetchSpan(int filePos, int size)
+ {
+ if (size > m_buff.Length)
+ {
+ GetBuffer(size);
+ }
+ if (!(m_buffPos <= filePos && filePos + size <= m_buffPos + m_buffLen))
+ {
+ // Read in the block of 'size' bytes at filePos
+ m_buffPos = filePos;
+ m_stream.Seek(m_buffPos, SeekOrigin.Begin);
+ m_buffLen = 0;
+ while (m_buffLen < m_buff.Length)
+ {
+ var count = m_stream.Read(m_buff, m_buffLen, size - m_buffLen);
+ if (count == 0)
+ {
+ break;
+ }
+
+ m_buffLen += count;
+ }
+ }
+ int offset = filePos - m_buffPos;
+ int actualSize = Math.Min(size, m_buffLen - offset);
+ return new ReadOnlySpan(m_buff, offset, actualSize);
+ }
public int Length { get { return m_buffLen; } }
+
+ // Internal method to ensure data is read and return buffer slice for zero-copy PEHeader construction
+ internal PEBufferedSlice EnsureRead(int filePos, int size)
+ {
+ // Ensure the data is fetched
+ FetchSpan(filePos, size);
+
+ int offset = filePos - m_buffPos;
+ int length = Math.Min(size, m_buffLen - offset);
+ return new PEBufferedSlice(m_buff, offset, length);
+ }
+
public void Dispose()
{
GC.SuppressFinalize(this);
m_pinningHandle.Free();
}
#region private
- ~PEBuffer()
+ ~PEBufferedReader()
{
FreeBuffer();
}
@@ -1196,7 +1325,7 @@ internal sealed unsafe class ResourceNode
// If IsLeaf is true
public int DataLength { get { return m_dataLen; } }
- public byte* FetchData(int offsetInResourceData, int size, PEBuffer buff)
+ public byte* FetchData(int offsetInResourceData, int size, PEBufferedReader buff)
{
return buff.Fetch(m_dataFileOffset + offsetInResourceData, size);
}
@@ -1553,7 +1682,7 @@ internal unsafe struct IMAGE_RESOURCE_DIRECTORY_ENTRY
private int NameOffsetAndFlag;
private int DataOffsetAndFlag;
- internal unsafe string GetName(PEBuffer buff, int resourceStartFileOffset)
+ internal unsafe string GetName(PEBufferedReader buff, int resourceStartFileOffset)
{
if (IsStringName)
{