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) {