-
Notifications
You must be signed in to change notification settings - Fork 744
Refactor PEFile and PEHeader to use ReadOnlySpan exclusively with zero-copy buffer sharing #2317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
78c2cb5
bd267a9
675ff78
6026d57
2f6476d
fd344b1
fe420c0
ff06902
e41e449
de5fab3
b00ebf4
1031ef0
e0b7361
d46532d
fb99913
c99640b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.IO; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Runtime.InteropServices; | ||
|
|
||
| namespace PEFile | ||
|
|
@@ -25,13 +26,15 @@ public PEFile(string filePath) | |
| m_stream = File.OpenRead(filePath); | ||
| m_headerBuff = new PEBuffer(m_stream); | ||
|
|
||
| byte* ptr = m_headerBuff.Fetch(0, 1024); | ||
| byte[] buffer; | ||
| int offset, length; | ||
| m_headerBuff.GetBufferInfo(0, 1024, out buffer, out offset, out length); | ||
| if (m_headerBuff.Length < 512) | ||
| { | ||
| goto ThrowBadHeader; | ||
| } | ||
|
|
||
| Header = new PEHeader(ptr); | ||
| Header = new PEHeader(buffer, offset, length); | ||
|
|
||
| if (Header.PEHeaderSize > 1024 * 64) // prevent insane numbers; | ||
| { | ||
|
|
@@ -41,13 +44,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); | ||
| m_headerBuff.GetBufferInfo(0, Header.PEHeaderSize, out buffer, out offset, out length); | ||
| if (m_headerBuff.Length < Header.PEHeaderSize) | ||
| { | ||
| goto ThrowBadHeader; | ||
| } | ||
|
|
||
| Header = new PEHeader(ptr); | ||
| Header = new PEHeader(buffer, offset, length); | ||
| } | ||
| return; | ||
| ThrowBadHeader: | ||
|
|
@@ -386,6 +389,79 @@ public PEHeader(void* startOfPEFile) | |
| throw new InvalidOperationException("Bad PE Header."); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns a PEHeader for ReadOnlySpan of bytes in memory. Validates buffer bounds. | ||
| /// </summary> | ||
| public PEHeader(ReadOnlySpan<byte> peFileData) | ||
| : this(peFileData.ToArray(), 0, peFileData.Length) | ||
| { | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns a PEHeader that references an existing buffer without copying. Validates buffer bounds. | ||
| /// </summary> | ||
| internal PEHeader(byte[] buffer, int offset, int length) | ||
|
||
| { | ||
| m_buffer = buffer; | ||
| m_bufferOffset = offset; | ||
| m_bufferLength = length; | ||
|
|
||
| if (m_bufferLength < sizeof(IMAGE_DOS_HEADER)) | ||
| { | ||
| goto ThrowBadHeader; | ||
| } | ||
|
|
||
| IMAGE_DOS_HEADER dosHdr; | ||
| fixed (byte* bufferPtr = m_buffer) | ||
|
||
| { | ||
| dosHdr = *(IMAGE_DOS_HEADER*)(bufferPtr + m_bufferOffset); | ||
| } | ||
|
|
||
| if (dosHdr.e_magic != IMAGE_DOS_HEADER.IMAGE_DOS_SIGNATURE) | ||
| { | ||
| goto ThrowBadHeader; | ||
| } | ||
|
|
||
| var imageHeaderOffset = dosHdr.e_lfanew; | ||
| if (!(sizeof(IMAGE_DOS_HEADER) <= imageHeaderOffset && imageHeaderOffset <= 512)) | ||
|
||
| { | ||
| goto ThrowBadHeader; | ||
| } | ||
|
|
||
| if (m_bufferLength < imageHeaderOffset + sizeof(IMAGE_NT_HEADERS)) | ||
| { | ||
| goto ThrowBadHeader; | ||
| } | ||
|
|
||
| m_ntHeaderOffset = imageHeaderOffset; | ||
| IMAGE_NT_HEADERS ntHdr; | ||
| fixed (byte* bufferPtr = m_buffer) | ||
| { | ||
| ntHdr = *(IMAGE_NT_HEADERS*)(bufferPtr + m_bufferOffset + 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; | ||
| if (m_sectionsOffset >= 1024) | ||
|
||
| { | ||
| goto ThrowBadHeader; | ||
| } | ||
|
|
||
| if (m_bufferLength < m_sectionsOffset + sizeof(IMAGE_SECTION_HEADER) * ntHdr.FileHeader.NumberOfSections) | ||
| { | ||
| goto ThrowBadHeader; | ||
| } | ||
|
|
||
| return; | ||
| ThrowBadHeader: | ||
| throw new InvalidOperationException("Bad PE Header."); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// The total s,ize of the header, including section array of the the PE header. | ||
| /// </summary> | ||
|
|
@@ -975,12 +1051,118 @@ 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)); } } | ||
| // Helper method to get a span from the buffer with bounds checking | ||
| private ReadOnlySpan<byte> GetBufferSpan(int offset, int length) | ||
| { | ||
| if (m_buffer == null) | ||
| { | ||
| throw new InvalidOperationException("Buffer not available in pointer-based PEHeader."); | ||
| } | ||
| if (offset < 0 || offset + length > m_bufferLength) | ||
| { | ||
| throw new ArgumentOutOfRangeException($"Attempted to read {length} bytes at offset {offset}, but buffer is only {m_bufferLength} bytes."); | ||
| } | ||
| return new ReadOnlySpan<byte>(m_buffer, m_bufferOffset + offset, length); | ||
| } | ||
|
|
||
| // Helper properties to access structures from span with bounds checking | ||
| private ref readonly IMAGE_DOS_HEADER DosHeader | ||
| { | ||
| get | ||
| { | ||
| if (m_buffer != null) | ||
| { | ||
| var span = GetBufferSpan(0, sizeof(IMAGE_DOS_HEADER)); | ||
| return ref MemoryMarshal.Cast<byte, IMAGE_DOS_HEADER>(span)[0]; | ||
| } | ||
| throw new InvalidOperationException("DosHeader property only available with span-based PEHeader."); | ||
| } | ||
| } | ||
|
|
||
| private ref readonly IMAGE_NT_HEADERS NtHeader | ||
| { | ||
| get | ||
| { | ||
| if (m_buffer != null) | ||
| { | ||
| var span = GetBufferSpan(m_ntHeaderOffset, sizeof(IMAGE_NT_HEADERS)); | ||
| return ref MemoryMarshal.Cast<byte, IMAGE_NT_HEADERS>(span)[0]; | ||
| } | ||
| throw new InvalidOperationException("NtHeader property only available with span-based PEHeader."); | ||
| } | ||
| } | ||
|
|
||
| private ref readonly IMAGE_SECTION_HEADER GetSectionHeader(int index) | ||
| { | ||
| if (m_buffer != null) | ||
| { | ||
| int offset = m_sectionsOffset + index * sizeof(IMAGE_SECTION_HEADER); | ||
| var span = GetBufferSpan(offset, sizeof(IMAGE_SECTION_HEADER)); | ||
| return ref MemoryMarshal.Cast<byte, IMAGE_SECTION_HEADER>(span)[0]; | ||
| } | ||
| throw new InvalidOperationException("GetSectionHeader only available with span-based PEHeader."); | ||
| } | ||
|
|
||
| private IMAGE_OPTIONAL_HEADER32* OptionalHeader32 | ||
| { | ||
| get | ||
| { | ||
| if (m_buffer != null) | ||
| { | ||
| throw new InvalidOperationException("Use OptionalHeader32Span with span-based PEHeader."); | ||
| } | ||
| return (IMAGE_OPTIONAL_HEADER32*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS)); | ||
| } | ||
| } | ||
|
|
||
| private ref readonly IMAGE_OPTIONAL_HEADER32 OptionalHeader32Span | ||
| { | ||
| get | ||
| { | ||
| if (m_buffer != null) | ||
| { | ||
| int offset = m_ntHeaderOffset + sizeof(IMAGE_NT_HEADERS); | ||
| var span = GetBufferSpan(offset, sizeof(IMAGE_OPTIONAL_HEADER32)); | ||
| return ref MemoryMarshal.Cast<byte, IMAGE_OPTIONAL_HEADER32>(span)[0]; | ||
| } | ||
| throw new InvalidOperationException("OptionalHeader32Span only available with span-based PEHeader."); | ||
| } | ||
| } | ||
|
|
||
| private IMAGE_OPTIONAL_HEADER64* OptionalHeader64 | ||
| { | ||
| get | ||
| { | ||
| if (m_buffer != null) | ||
| { | ||
| throw new InvalidOperationException("Use OptionalHeader64Span with span-based PEHeader."); | ||
| } | ||
| return (IMAGE_OPTIONAL_HEADER64*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS)); | ||
| } | ||
| } | ||
|
|
||
| private ref readonly IMAGE_OPTIONAL_HEADER64 OptionalHeader64Span | ||
| { | ||
| get | ||
| { | ||
| if (m_buffer != null) | ||
| { | ||
| int offset = m_ntHeaderOffset + sizeof(IMAGE_NT_HEADERS); | ||
| var span = GetBufferSpan(offset, sizeof(IMAGE_OPTIONAL_HEADER64)); | ||
| return ref MemoryMarshal.Cast<byte, IMAGE_OPTIONAL_HEADER64>(span)[0]; | ||
| } | ||
| throw new InvalidOperationException("OptionalHeader64Span only available with span-based PEHeader."); | ||
| } | ||
| } | ||
|
|
||
| private IMAGE_DATA_DIRECTORY* ntDirectories | ||
| { | ||
| get | ||
| { | ||
| if (m_buffer != null) | ||
| { | ||
| throw new InvalidOperationException("Use GetDirectory with span-based PEHeader."); | ||
| } | ||
| if (IsPE64) | ||
| { | ||
| return (IMAGE_DATA_DIRECTORY*)(((byte*)ntHeader) + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_OPTIONAL_HEADER64)); | ||
|
|
@@ -992,9 +1174,36 @@ private IMAGE_DATA_DIRECTORY* ntDirectories | |
| } | ||
| } | ||
|
|
||
| private ref readonly IMAGE_DATA_DIRECTORY GetDirectory(int index) | ||
| { | ||
| if (m_buffer != null) | ||
| { | ||
| 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<byte, IMAGE_DATA_DIRECTORY>(span)[0]; | ||
| } | ||
| throw new InvalidOperationException("GetDirectory only available with span-based PEHeader."); | ||
| } | ||
|
|
||
| private IMAGE_DOS_HEADER* dosHeader; | ||
|
||
| private IMAGE_NT_HEADERS* ntHeader; | ||
| private IMAGE_SECTION_HEADER* sections; | ||
|
|
||
| // Span-based fields (used when constructed with ReadOnlySpan<byte>) | ||
| private byte[] m_buffer; | ||
| private int m_bufferOffset; // Offset into m_buffer where our data starts | ||
| private int m_bufferLength; // Length of valid data in m_buffer | ||
| private int m_ntHeaderOffset; | ||
| private int m_sectionsOffset; | ||
| #endregion | ||
| } | ||
|
|
||
|
|
@@ -1142,7 +1351,46 @@ public PEBuffer(Stream stream, int buffSize = 512) | |
| } | ||
| return &m_buffPtr[filePos - m_buffPos]; | ||
| } | ||
| public ReadOnlySpan<byte> FetchSpan(int filePos, int size) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed to |
||
| { | ||
| 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<byte>(m_buff, offset, actualSize); | ||
| } | ||
| public int Length { get { return m_buffLen; } } | ||
|
|
||
| // Internal method to get buffer parameters for zero-copy PEHeader construction | ||
| internal void GetBufferInfo(int filePos, int size, out byte[] buffer, out int offset, out int length) | ||
| { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename this to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than returning 3 out parameters, please return a struct called
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Created |
||
| // Ensure the data is fetched | ||
| FetchSpan(filePos, size); | ||
|
|
||
| buffer = m_buff; | ||
| offset = filePos - m_buffPos; | ||
| length = Math.Min(size, m_buffLen - offset); | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| GC.SuppressFinalize(this); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constructor is unused. Please remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the unused constructor in commit ec49a3a.