diff --git a/src/LibObjectFile/Ar/ArArchiveFile.cs b/src/LibObjectFile/Ar/ArArchiveFile.cs index 7c9e763..9947e01 100644 --- a/src/LibObjectFile/Ar/ArArchiveFile.cs +++ b/src/LibObjectFile/Ar/ArArchiveFile.cs @@ -81,7 +81,7 @@ public void AddFile(ArFile file) } file.Parent = this; - file.Index = (uint)_files.Count; + file.Index = _files.Count; _files.Add(file); } @@ -123,7 +123,7 @@ public void InsertFileAt(int index, ArFile file) } } - file.Index = (uint)index; + file.Index = index; _files.Insert(index, file); file.Parent = this; diff --git a/src/LibObjectFile/DiagnosticBag.cs b/src/LibObjectFile/DiagnosticBag.cs index 370dcdf..34d1aa1 100644 --- a/src/LibObjectFile/DiagnosticBag.cs +++ b/src/LibObjectFile/DiagnosticBag.cs @@ -8,100 +8,99 @@ using System.Text; using LibObjectFile.Utils; -namespace LibObjectFile +namespace LibObjectFile; + +/// +/// A container for used for error reporting while reading/writing object files. +/// +[DebuggerDisplay("Count = {Messages.Count}, HasErrors = {" + nameof(HasErrors) + "}")] +public class DiagnosticBag { - /// - /// A container for used for error reporting while reading/writing object files. - /// - [DebuggerDisplay("Count = {Messages.Count}, HasErrors = {" + nameof(HasErrors) + "}")] - public class DiagnosticBag - { - private readonly List _messages; + private readonly List _messages; - public DiagnosticBag() - { - _messages = new List(); - } + public DiagnosticBag() + { + _messages = new List(); + } - /// - /// List of messages. - /// - public ReadOnlyList Messages => _messages; + /// + /// List of messages. + /// + public ReadOnlyList Messages => _messages; - /// - /// If this instance contains error messages. - /// - public bool HasErrors { get; private set; } + /// + /// If this instance contains error messages. + /// + public bool HasErrors { get; private set; } - /// - /// Clear all messages. - /// - public void Clear() - { - _messages.Clear(); - HasErrors = false; - } + /// + /// Clear all messages. + /// + public void Clear() + { + _messages.Clear(); + HasErrors = false; + } - /// - /// Copy all the in this bag to another bag. - /// - /// The diagnostics receiving the copy of the - public void CopyTo(DiagnosticBag diagnostics) + /// + /// Copy all the in this bag to another bag. + /// + /// The diagnostics receiving the copy of the + public void CopyTo(DiagnosticBag diagnostics) + { + if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); + foreach (var diagnosticMessage in Messages) { - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); - foreach (var diagnosticMessage in Messages) - { - diagnostics.Log(diagnosticMessage); - } + diagnostics.Log(diagnosticMessage); } + } - /// - /// Logs the specified . - /// - /// The diagnostic message - public void Log(DiagnosticMessage message) + /// + /// Logs the specified . + /// + /// The diagnostic message + public void Log(DiagnosticMessage message) + { + if (message.Message == null) throw new InvalidOperationException($"{nameof(DiagnosticMessage)}.{nameof(DiagnosticMessage.Message)} cannot be null"); + _messages.Add(message); + if (message.Kind == DiagnosticKind.Error) { - if (message.Message == null) throw new InvalidOperationException($"{nameof(DiagnosticMessage)}.{nameof(DiagnosticMessage.Message)} cannot be null"); - _messages.Add(message); - if (message.Kind == DiagnosticKind.Error) - { - HasErrors = true; - } + HasErrors = true; } + } - /// - /// Log an error . - /// - /// The identifier of the diagnostic. - /// The text of the message - /// An optional context - public void Error(DiagnosticId id, string message, object? context = null) - { - if (message == null) throw new ArgumentNullException(nameof(message)); - Log(new DiagnosticMessage(DiagnosticKind.Error, id, message, context)); - } + /// + /// Log an error . + /// + /// The identifier of the diagnostic. + /// The text of the message + /// An optional context + public void Error(DiagnosticId id, string message, object? context = null) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + Log(new DiagnosticMessage(DiagnosticKind.Error, id, message, context)); + } - /// - /// Log an error . - /// - /// The identifier of the diagnostic. - /// The text of the message - /// An optional context - public void Warning(DiagnosticId id, string message, object? context = null) - { - if (message == null) throw new ArgumentNullException(nameof(message)); - Log(new DiagnosticMessage(DiagnosticKind.Warning, id, message, context)); - } + /// + /// Log an error . + /// + /// The identifier of the diagnostic. + /// The text of the message + /// An optional context + public void Warning(DiagnosticId id, string message, object? context = null) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + Log(new DiagnosticMessage(DiagnosticKind.Warning, id, message, context)); + } - public override string ToString() + public override string ToString() + { + var builder = new StringBuilder(); + foreach (var diagnosticMessage in Messages) { - var builder = new StringBuilder(); - foreach (var diagnosticMessage in Messages) - { - builder.AppendLine(diagnosticMessage.ToString()); - } - - return builder.ToString(); + builder.AppendLine(diagnosticMessage.ToString()); } + + return builder.ToString(); } } \ No newline at end of file diff --git a/src/LibObjectFile/DiagnosticId.cs b/src/LibObjectFile/DiagnosticId.cs index d6280b1..35a53ff 100644 --- a/src/LibObjectFile/DiagnosticId.cs +++ b/src/LibObjectFile/DiagnosticId.cs @@ -135,5 +135,9 @@ public enum DiagnosticId // PE Import PE_ERR_ImportDirectoryInvalidEndOfStream = 3040, PE_ERR_ImportLookupTableInvalidEndOfStream = 3041, + PE_ERR_ImportLookupTableInvalidHintNameTableRVA = 3042, + PE_ERR_ImportLookupTableInvalidParent = 3043, + PE_ERR_ImportDirectoryInvalidImportAddressTableRVA = 3044, + PE_ERR_ImportDirectoryInvalidImportLookupTableRVA = 3045, } -} \ No newline at end of file +} diff --git a/src/LibObjectFile/Elf/ElfObjectFile.cs b/src/LibObjectFile/Elf/ElfObjectFile.cs index 91ed97d..98c5dfc 100644 --- a/src/LibObjectFile/Elf/ElfObjectFile.cs +++ b/src/LibObjectFile/Elf/ElfObjectFile.cs @@ -332,7 +332,7 @@ public void AddSegment(ElfSegment segment) } segment.Parent = this; - segment.Index = (uint)_segments.Count; + segment.Index = _segments.Count; _segments.Add(segment); } @@ -351,7 +351,7 @@ public void InsertSegmentAt(int index, ElfSegment segment) if (segment.Parent != this) throw new InvalidOperationException($"Cannot add the segment as it is already added to another {nameof(ElfObjectFile)} instance"); } - segment.Index = (uint)index; + segment.Index = index; _segments.Insert(index, segment); segment.Parent = this; @@ -415,7 +415,7 @@ public TSection AddSection(TSection section) where TSection : ElfSecti } section.Parent = this; - section.Index = (uint)_sections.Count; + section.Index = _sections.Count; _sections.Add(section); if (section.IsShadow) @@ -454,7 +454,7 @@ public void InsertSectionAt(int index, ElfSection section) } section.Parent = this; - section.Index = (uint)index; + section.Index = index; _sections.Insert(index, section); if (section.IsShadow) diff --git a/src/LibObjectFile/ObjectFileExtensions.cs b/src/LibObjectFile/ObjectFileExtensions.cs index 2338d31..2378333 100644 --- a/src/LibObjectFile/ObjectFileExtensions.cs +++ b/src/LibObjectFile/ObjectFileExtensions.cs @@ -23,7 +23,7 @@ public static void Add(this List list, TParent parent, } element.Parent = parent; - element.Index = (uint)list.Count; + element.Index = list.Count; list.Add(element); } @@ -63,7 +63,7 @@ public static void AddSorted(this List list, TParent pa list.Insert(index, element); } - element.Index = (uint)index; + element.Index = index; // Update the index of following attributes for (int i = index + 1; i < list.Count; i++) @@ -88,7 +88,7 @@ public static void InsertAt(this List list, TParent par if (element.Parent != parent) throw new InvalidOperationException($"Cannot add the {element.GetType()} as it is already added to another {parent.GetType()} instance"); } - element.Index = (uint)index; + element.Index = index; list.Insert(index, element); element.Parent = parent; diff --git a/src/LibObjectFile/ObjectFileNodeBase.cs b/src/LibObjectFile/ObjectFileNodeBase.cs index f3b3a81..c2a5eb1 100644 --- a/src/LibObjectFile/ObjectFileNodeBase.cs +++ b/src/LibObjectFile/ObjectFileNodeBase.cs @@ -13,6 +13,11 @@ public abstract class ObjectFileNodeBase [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ObjectFileNodeBase? _parent; + protected ObjectFileNodeBase() + { + Index = -1; + } + /// /// Gets or sets the position of this element relative to the top level parent. /// @@ -45,9 +50,14 @@ protected virtual void ValidateParent(ObjectFileNodeBase parent) } /// - /// Index within the containing list in a parent. + /// Index within the containing list in a parent. If this object is not part of a list, this value is -1. /// - public uint Index { get; internal set; } + public int Index { get; internal set; } + + internal void ResetIndex() + { + Index = -1; + } /// /// Gets or sets the size of this section or segment in the parent . diff --git a/src/LibObjectFile/PE/DataDirectory/PEDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEDirectory.cs index 693af6b..7ab7bcd 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEDirectory.cs @@ -39,11 +39,19 @@ internal static PEDirectory Create(ImageDataDirectoryKind kind, RVALink new PELoadConfigDirectory(), ImageDataDirectoryKind.BoundImport => new PEBoundImportDirectory(), ImageDataDirectoryKind.DelayImport => new PEDelayImportDirectory(), - ImageDataDirectoryKind.ImportAddressTable => new PEImportAddressTable(), + ImageDataDirectoryKind.ImportAddressTable => new PEImportAddressTableDirectory(), ImageDataDirectoryKind.ClrMetadata => new PEClrMetadata(), _ => throw new ArgumentOutOfRangeException(nameof(kind)) }; } + + protected override void ValidateParent(ObjectFileNodeBase parent) + { + if (parent is not PESection) + { + throw new ArgumentException($"Invalid parent type [{parent?.GetType()}] for [{GetType()}]"); + } + } } public sealed class PEExportDirectory : PEDirectory @@ -178,28 +186,6 @@ protected override void Write(PEImageWriter writer) } } -public sealed class PEImportAddressTable : PEDirectory -{ - public PEImportAddressTable() : base(ImageDataDirectoryKind.ImportAddressTable) - { - } - - public override void UpdateLayout(DiagnosticBag diagnostics) - { - throw new NotImplementedException(); - } - - protected override void Read(PEImageReader reader) - { - throw new NotImplementedException(); - } - - protected override void Write(PEImageWriter writer) - { - throw new NotImplementedException(); - } -} - public sealed class PETlsDirectory : PEDirectory { public PETlsDirectory() : base(ImageDataDirectoryKind.Tls) diff --git a/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs b/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs index ba09832..e7edd4a 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs @@ -99,7 +99,7 @@ internal PEDirectoryTable() /// /// Gets the import address table directory information from the PE file. /// - public PEImportAddressTable? ImportAddressTable => (PEImportAddressTable?)this[ImageDataDirectoryKind.ImportAddressTable]; + public PEImportAddressTableDirectory? ImportAddressTable => (PEImportAddressTableDirectory?)this[ImageDataDirectoryKind.ImportAddressTable]; /// /// Gets the CLR metadata directory information from the PE file, if present. diff --git a/src/LibObjectFile/PE/DataDirectory/PEImportAddressTable.cs b/src/LibObjectFile/PE/DataDirectory/PEImportAddressTable.cs new file mode 100644 index 0000000..7b5eb1d --- /dev/null +++ b/src/LibObjectFile/PE/DataDirectory/PEImportAddressTable.cs @@ -0,0 +1,50 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace LibObjectFile.PE; + +public class PEImportAddressTable : PEObject +{ + internal readonly PEImportFunctionTable FunctionTable; + + public PEImportAddressTable() + { + FunctionTable = new PEImportFunctionTable(); + } + + public new PEImportAddressTableDirectory? Parent => (PEImportAddressTableDirectory?)base.Parent; + + public List Entries => FunctionTable.Entries; + + public override void UpdateLayout(DiagnosticBag diagnostics) + { + var peFile = Parent?.Parent?.Parent; + if (peFile is null) + { + diagnostics.Error(DiagnosticId.PE_ERR_ImportLookupTableInvalidParent, "The parent of the Import Address Table is null."); + return; + } + + Size = FunctionTable.CalculateSize(peFile, diagnostics); + } + + protected override void Read(PEImageReader reader) + { + FunctionTable.Read(reader, Position); + UpdateLayout(reader.Diagnostics); + } + + protected override void Write(PEImageWriter writer) => FunctionTable.Write(writer); + + protected override void ValidateParent(ObjectFileNodeBase parent) + { + if (parent is not PEImportAddressTableDirectory) + { + throw new ArgumentException($"Invalid parent type [{parent?.GetType()}] for [{GetType()}]"); + } + } +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEImportAddressTableDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEImportAddressTableDirectory.cs new file mode 100644 index 0000000..fa25382 --- /dev/null +++ b/src/LibObjectFile/PE/DataDirectory/PEImportAddressTableDirectory.cs @@ -0,0 +1,35 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +using System; +using LibObjectFile.Utils; + +namespace LibObjectFile.PE; + +public sealed class PEImportAddressTableDirectory : PEDirectory +{ + private readonly ObjectList _tables; + + public PEImportAddressTableDirectory() : base(ImageDataDirectoryKind.ImportAddressTable) + { + _tables = new ObjectList(this); + } + + public ObjectList Tables => _tables; + + public override void UpdateLayout(DiagnosticBag diagnostics) + { + ulong size = 0; + foreach (var table in _tables) + { + table.UpdateLayout(diagnostics); + size += table.Size; + } + Size = size; + } + + protected override void Read(PEImageReader reader) => throw new NotSupportedException(); // Not called directly for this object, we are calling on tables directly + + protected override void Write(PEImageWriter writer) => throw new NotSupportedException(); // Not called directly for this object, we are calling on tables directly +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEImportDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEImportDirectory.cs index c9537b4..a8f1a58 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEImportDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEImportDirectory.cs @@ -3,29 +3,37 @@ // See the license.txt file in the project root for more information. using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using LibObjectFile.PE.Internal; +using LibObjectFile.Utils; namespace LibObjectFile.PE; public sealed class PEImportDirectory : PEDirectory { + private readonly ObjectList _entries; + public PEImportDirectory() : base(ImageDataDirectoryKind.Import) { + _entries = new(this); } - - public override void UpdateLayout(DiagnosticBag diagnostics) + public ObjectList Entries => _entries; + + public override unsafe void UpdateLayout(DiagnosticBag diagnostics) { - throw new NotImplementedException(); + Size = (ulong)((_entries.Count + 1) * sizeof(RawImportDirectoryEntry)); } protected override void Read(PEImageReader reader) { var diagnostics = reader.Diagnostics; + reader.Position = Position; + // Read Import Directory Entries - RawImportDirectoryEntry entry = default; - var entrySpan = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref entry, 1)); + RawImportDirectoryEntry rawEntry = default; + var entrySpan = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref rawEntry, 1)); while (true) { @@ -36,12 +44,60 @@ protected override void Read(PEImageReader reader) return; } + // TODO: handle bound imports through entry.TimeDateStamp + // Check for null entry (last entry in the import directory) - if (entry.ImportLookupTableRVA == 0 && entry.TimeDateStamp == 0 && entry.ForwarderChain == 0 && entry.NameRVA == 0 && entry.ImportAddressTableRVA == 0) + if (rawEntry.ImportLookupTableRVA == 0 && rawEntry.TimeDateStamp == 0 && rawEntry.ForwarderChain == 0 && rawEntry.NameRVA == 0 && rawEntry.ImportAddressTableRVA == 0) { // Last entry break; } + + // Find the section data for the ImportLookupTableRVA + if (!reader.PEFile.TryFindSectionData(rawEntry.ImportAddressTableRVA, out var sectionData)) + { + diagnostics.Error(DiagnosticId.PE_ERR_ImportDirectoryInvalidImportAddressTableRVA, $"Unable to find the section data for ImportAddressTableRVA {rawEntry.ImportAddressTableRVA}"); + return; + } + + // Calculate its position within the original stream + var importLookupAddressTablePositionInFile = sectionData.Position + rawEntry.ImportLookupTableRVA - sectionData.VirtualAddress; + + // Find the section data for the ImportLookupTableRVA + if (!reader.PEFile.TryFindSectionData(rawEntry.ImportLookupTableRVA, out sectionData)) + { + diagnostics.Error(DiagnosticId.PE_ERR_ImportDirectoryInvalidImportLookupTableRVA, $"Unable to find the section data for ImportLookupTableRVA {rawEntry.ImportLookupTableRVA}"); + return; + } + + // Calculate its position within the original stream + var importLookupTablePositionInFile = sectionData.Position + rawEntry.ImportLookupTableRVA - sectionData.VirtualAddress; + + // Store a fake entry for post-processing section data to allow to recreate PEImportLookupTable from existing PESectionStreamData + _entries.Add( + new PEImportDirectoryEntry( + // Name + new(new(PESectionDataTemp.Instance, rawEntry.NameRVA)), + // ImportAddressTable + new PEImportAddressTable() + { + Position = importLookupAddressTablePositionInFile + }, + // ImportLookupTable + new PEImportLookupTable() + { + Position = importLookupTablePositionInFile + } + ) + ); + } + + // Resolve ImportLookupTable and ImportAddressTable section data links + var entries = CollectionsMarshal.AsSpan(_entries.UnsafeList); + foreach (ref var entry in entries) + { + entry.ImportAddressTable.ReadInternal(reader); + entry.ImportLookupTable.ReadInternal(reader); } } diff --git a/src/LibObjectFile/PE/DataDirectory/PEImportDirectoryEntry.cs b/src/LibObjectFile/PE/DataDirectory/PEImportDirectoryEntry.cs index 537b99a..ba1a215 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEImportDirectoryEntry.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEImportDirectoryEntry.cs @@ -2,13 +2,67 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using LibObjectFile.PE.Internal; +using System; + namespace LibObjectFile.PE; -#pragma warning disable CS0649 -public class PEImportDirectoryEntry +public sealed class PEImportDirectoryEntry : PEObject { - public ZeroTerminatedAsciiStringLink ImportDllNameLink; + private PEImportLookupTable? _importLookupTable; + + public PEImportDirectoryEntry(ZeroTerminatedAsciiStringLink importDllNameLink, PEImportAddressTable importAddressTable, PEImportLookupTable importLookupTable) + { + ImportDllNameLink = importDllNameLink; + ImportAddressTable = importAddressTable; + ImportLookupTable = importLookupTable; + } + + public new PEImportDirectory? Parent + { + get => (PEImportDirectory?)base.Parent; + set => base.Parent = value; + } + + public ZeroTerminatedAsciiStringLink ImportDllNameLink { get; set; } + + public PEImportAddressTable ImportAddressTable { get; set; } + + public PEImportLookupTable ImportLookupTable + { + get => _importLookupTable!; + set + { + ArgumentNullException.ThrowIfNull(value); + if (value == _importLookupTable) + { + return; + } + + if (value.Parent is not null) + { + throw new InvalidOperationException("The import lookup table is already attached to another parent"); + } + + if (_importLookupTable is not null) + { + _importLookupTable.Parent = null; + } + + value.Parent = this; + _importLookupTable = value; + } + } + + public override unsafe void UpdateLayout(DiagnosticBag diagnostics) + { + Size = (ulong)sizeof(RawImportDirectoryEntry); + + // Update the layout of the import lookup table + ImportLookupTable.UpdateLayout(diagnostics); + } - public uint ImportAddressTableEntryIndex; + protected override void Read(PEImageReader reader) => throw new System.NotSupportedException(); + protected override void Write(PEImageWriter writer) => throw new System.NotSupportedException(); } \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEImportFunctionEntry.cs b/src/LibObjectFile/PE/DataDirectory/PEImportFunctionEntry.cs new file mode 100644 index 0000000..6909088 --- /dev/null +++ b/src/LibObjectFile/PE/DataDirectory/PEImportFunctionEntry.cs @@ -0,0 +1,52 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +namespace LibObjectFile.PE; + +/// +/// A PE Import Function Entry used in and . +/// +public readonly struct PEImportFunctionEntry +{ + // Encodes the RVA through a link to the PE section data and the offset in the section data + // If the PE section data is null, the offset is the ordinal + private readonly PESectionData? _peSectionData; + private readonly uint _offset; + + /// + /// Initializes a new instance of the class by name. + /// + /// The name of the import. + public PEImportFunctionEntry(ZeroTerminatedAsciiStringLink name) + { + _peSectionData = name.Link.Element; + _offset = name.Link.OffsetInElement; + } + + /// + /// Initializes a new instance of the class by ordinal. + /// + /// The ordinal of the import. + public PEImportFunctionEntry(ushort ordinal) + { + _peSectionData = null; + _offset = ordinal; + } + + /// + /// Gets a value indicating whether this import is by ordinal. + /// + public bool IsImportByOrdinal => _peSectionData is null; + + /// + /// Gets the name of the import if not by ordinal. + /// + public ZeroTerminatedAsciiStringLink Name => _peSectionData is null ? default : new ZeroTerminatedAsciiStringLink(new(_peSectionData, _offset)); + + /// + /// Gets the ordinal of the import if by ordinal. + /// + public ushort Ordinal => _peSectionData is null ? (ushort)(_offset) : (ushort)0; + +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEImportFunctionTable.cs b/src/LibObjectFile/PE/DataDirectory/PEImportFunctionTable.cs new file mode 100644 index 0000000..fb37aa2 --- /dev/null +++ b/src/LibObjectFile/PE/DataDirectory/PEImportFunctionTable.cs @@ -0,0 +1,169 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using LibObjectFile.PE.Internal; + +namespace LibObjectFile.PE; + +internal readonly struct PEImportFunctionTable() +{ + public List Entries { get; } = new(); + + public unsafe ulong CalculateSize(PEFile peFile, DiagnosticBag diagnostics) + { + // +1 for the null terminator + return (ulong)((Entries.Count + 1) * (peFile.IsPE32 ? sizeof(RawImportFunctionEntry32) : sizeof(RawImportFunctionEntry64))); + } + + public void Read(PEImageReader reader, ulong position) + { + var peFile = reader.PEFile; + reader.Position = position; + + if (peFile.IsPE32) + { + Read32(reader); + } + else + { + Read64(reader); + } + + CalculateSize(peFile, reader.Diagnostics); + } + + public void ResolveSectionDataLinks(PEFile peFile, DiagnosticBag diagnostics) + { + var entries = CollectionsMarshal.AsSpan(Entries); + foreach (ref var entry in entries) + { + if (!entry.IsImportByOrdinal) + { + var va = entry.Name.Link.OffsetInElement; + if (!peFile.TryFindSectionData(va, out var sectionData)) + { + diagnostics.Error(DiagnosticId.PE_ERR_ImportLookupTableInvalidHintNameTableRVA, $"Unable to find the section data for HintNameTableRVA {va}"); + return; + } + + entry = new PEImportFunctionEntry(new ZeroTerminatedAsciiStringLink(new(sectionData, va - sectionData.VirtualAddress))); + } + } + } + + private unsafe void Read32(PEImageReader reader) + { + while (true) + { + RawImportFunctionEntry32 entry = default; + var span = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref entry, 1)); + int read = reader.Read(span); + if (read != sizeof(RawImportFunctionEntry32)) + { + reader.Diagnostics.Error(DiagnosticId.PE_ERR_ImportLookupTableInvalidEndOfStream, $"Unable to read the full content of the Import Lookup Table. Expected {sizeof(RawImportFunctionEntry32)} bytes, but read {read} bytes"); + return; + } + + if (entry.IsNull) + { + break; + } + + Entries.Add( + entry.IsImportByOrdinal + ? new PEImportFunctionEntry(entry.Ordinal) + : new PEImportFunctionEntry(new ZeroTerminatedAsciiStringLink(new(PESectionDataTemp.Instance, entry.HintNameTableRVA))) + ); + } + } + + private unsafe void Read64(PEImageReader reader) + { + while (true) + { + RawImportFunctionEntry64 entry = default; + var span = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref entry, 1)); + int read = reader.Read(span); + if (read != sizeof(RawImportFunctionEntry64)) + { + reader.Diagnostics.Error(DiagnosticId.PE_ERR_ImportLookupTableInvalidEndOfStream, $"Unable to read the full content of the Import Lookup Table. Expected {sizeof(RawImportFunctionEntry64)} bytes, but read {read} bytes"); + return; + } + + if (entry.IsNull) + { + break; + } + + Entries.Add( + entry.IsImportByOrdinal + ? new PEImportFunctionEntry(entry.Ordinal) + : new PEImportFunctionEntry(new ZeroTerminatedAsciiStringLink(new(PESectionDataTemp.Instance, entry.HintNameTableRVA))) + ); + } + } + + public void Write(PEImageWriter writer) + { + if (writer.PEFile.IsPE32) + { + Write32(writer); + } + else + { + Write64(writer); + } + } + + private unsafe void Write32(PEImageWriter writer) + { + var buffer = ArrayPool.Shared.Rent(Entries.Count * sizeof(RawImportFunctionEntry32)); + try + { + var span = MemoryMarshal.Cast(buffer.AsSpan(0, (Entries.Count + 1) * sizeof(RawImportFunctionEntry32))); + for (var i = 0; i < Entries.Count; i++) + { + var entry = Entries[i]; + var va = entry.Name.Link.VirtualAddress; + span[i] = new RawImportFunctionEntry32(entry.IsImportByOrdinal ? 0x8000_0000U | entry.Ordinal : va); + } + + // Last entry is null terminator + span[^1] = default; + + writer.Write(MemoryMarshal.AsBytes(span)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + private unsafe void Write64(PEImageWriter writer) + { + var buffer = ArrayPool.Shared.Rent(Entries.Count * sizeof(RawImportFunctionEntry64)); + try + { + var span = MemoryMarshal.Cast(buffer.AsSpan(0, (Entries.Count + 1) * sizeof(RawImportFunctionEntry64))); + for (var i = 0; i < Entries.Count; i++) + { + var entry = Entries[i]; + var va = entry.Name.Link.VirtualAddress; + span[i] = new RawImportFunctionEntry64(entry.IsImportByOrdinal ? 0x8000_0000_0000_0000UL | entry.Ordinal : va); + } + // Last entry is null terminator + span[^1] = default; + + writer.Write(MemoryMarshal.AsBytes(span)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEImportLookupEntry.cs b/src/LibObjectFile/PE/DataDirectory/PEImportLookupEntry.cs deleted file mode 100644 index 134d6d6..0000000 --- a/src/LibObjectFile/PE/DataDirectory/PEImportLookupEntry.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. -// This file is licensed under the BSD-Clause 2 license. -// See the license.txt file in the project root for more information. - -namespace LibObjectFile.PE; - -#pragma warning disable CS0649 -public struct PEImportLookupEntry -{ - public PEImportLookupEntry(ZeroTerminatedAsciiStringLink functionNameLink) - { - FunctionNameLink = functionNameLink; - } - - public PEImportLookupEntry(ushort ordinal) - { - Ordinal = ordinal; - } - - public ZeroTerminatedAsciiStringLink FunctionNameLink; - - public ushort Ordinal; - public bool IsImportByOrdinal => FunctionNameLink.Link.IsNull; -} \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEImportLookupTable.cs b/src/LibObjectFile/PE/DataDirectory/PEImportLookupTable.cs index 6bb6c9b..c497843 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEImportLookupTable.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEImportLookupTable.cs @@ -3,116 +3,52 @@ // See the license.txt file in the project root for more information. using System; -using System.Buffers; using System.Collections.Generic; -using System.Runtime.InteropServices; -using LibObjectFile.PE.Internal; namespace LibObjectFile.PE; -#pragma warning disable CS0649 -public class PEImportLookupTable : PESectionData +public sealed class PEImportLookupTable : PEObject { - public List Entries { get; } = new(); + internal readonly PEImportFunctionTable FunctionTable; - public override unsafe void UpdateLayout(DiagnosticBag diagnostics) + public PEImportLookupTable() { - var parent = Parent?.Parent; - if (parent is null) - { - diagnostics.Error(DiagnosticId.PE_ERR_InvalidParent, $"Parent is null for {nameof(PEImportLookupTable)} section data"); - return; - } - - // +1 for the null terminator - Size = (ulong)((Entries.Count + 1) * (parent.OptionalHeader.Magic == ImageOptionalHeaderMagic.PE32 ? sizeof(RawImportFunctionEntry32) : sizeof(RawImportFunctionEntry64))); + FunctionTable = new PEImportFunctionTable(); } - protected override unsafe void Read(PEImageReader reader) + public new PEImportDirectoryEntry? Parent { - var peFile = reader.PEFile; - var diagnostics = reader.Diagnostics; - - if (peFile.OptionalHeader.Magic == ImageOptionalHeaderMagic.PE32) - { - while (true) - { - var entry = new RawImportFunctionEntry32(reader.ReadU32()); - - if (entry.HintNameTableRVA == 0) - { - break; - } - - - if (peFile.TryFindSection(entry.HintNameTableRVA, out var section)) - { - - } - - - - //Entries.Add(new PEImportLookupEntry(new ZeroTerminatedAsciiStringLink(new RVALink(entry.HintNameTableRVA, this)))); - - - - } - - } - + get => (PEImportDirectoryEntry?)base.Parent; + set => base.Parent = value; } - protected override void Write(PEImageWriter writer) + public List Entries => FunctionTable.Entries; + + public override void UpdateLayout(DiagnosticBag diagnostics) { - if (writer.PEFile.OptionalHeader.Magic == ImageOptionalHeaderMagic.PE32) - { - Write32(writer); - } - else + var peFile = Parent?.Parent?.Parent?.Parent; + if (peFile is null) { - Write64(writer); + diagnostics.Error(DiagnosticId.PE_ERR_ImportLookupTableInvalidParent, "The parent of the Import Lookup Table is null."); + return; } + + Size = FunctionTable.CalculateSize(peFile, diagnostics); } - private unsafe void Write32(PEImageWriter writer) + protected override void Read(PEImageReader reader) { - var buffer = ArrayPool.Shared.Rent(Entries.Count * sizeof(RawImportFunctionEntry32)); - try - { - var span = MemoryMarshal.Cast(buffer.AsSpan(0, Entries.Count * sizeof(RawImportFunctionEntry32))); - for (var i = 0; i < Entries.Count; i++) - { - var entry = Entries[i]; - var va = entry.FunctionNameLink.Link.VirtualAddress; - span[i] = new RawImportFunctionEntry32(entry.IsImportByOrdinal ? 0x8000_0000U | entry.Ordinal : va); - } - - writer.Write(MemoryMarshal.AsBytes(span)); - } - finally - { - ArrayPool.Shared.Return(buffer); - } + FunctionTable.Read(reader, Position); + UpdateLayout(reader.Diagnostics); } - private unsafe void Write64(PEImageWriter writer) + protected override void Write(PEImageWriter writer) => FunctionTable.Write(writer); + + protected override void ValidateParent(ObjectFileNodeBase parent) { - var buffer = ArrayPool.Shared.Rent(Entries.Count * sizeof(RawImportFunctionEntry64)); - try - { - var span = MemoryMarshal.Cast(buffer.AsSpan(0, Entries.Count * sizeof(RawImportFunctionEntry64))); - for (var i = 0; i < Entries.Count; i++) - { - var entry = Entries[i]; - var va = entry.FunctionNameLink.Link.VirtualAddress; - span[i] = new RawImportFunctionEntry64(entry.IsImportByOrdinal ? 0x8000_0000_0000_0000UL | entry.Ordinal : va); - } - - writer.Write(MemoryMarshal.AsBytes(span)); - } - finally + if (parent is not PEImportDirectoryEntry) { - ArrayPool.Shared.Return(buffer); + throw new ArgumentException($"Invalid parent type [{parent?.GetType()}] for [{GetType()}]"); } } } \ No newline at end of file diff --git a/src/LibObjectFile/PE/Internal/RawImportFunctionEntry32.cs b/src/LibObjectFile/PE/Internal/RawImportFunctionEntry32.cs index 933da76..a53ce58 100644 --- a/src/LibObjectFile/PE/Internal/RawImportFunctionEntry32.cs +++ b/src/LibObjectFile/PE/Internal/RawImportFunctionEntry32.cs @@ -5,14 +5,18 @@ namespace LibObjectFile.PE.Internal; #pragma warning disable CS0649 -internal struct RawImportFunctionEntry32 +internal readonly struct RawImportFunctionEntry32 { + private readonly uint _hintNameTableRVA; + public RawImportFunctionEntry32(uint hintNameTableRVA) { - HintNameTableRVA = hintNameTableRVA; + _hintNameTableRVA = hintNameTableRVA; } - public uint HintNameTableRVA; + public uint HintNameTableRVA => IsImportByOrdinal ? 0U : _hintNameTableRVA; + + public bool IsNull => HintNameTableRVA == 0; public bool IsImportByOrdinal => (HintNameTableRVA & 0x8000_0000U) != 0; diff --git a/src/LibObjectFile/PE/Internal/RawImportFunctionEntry64.cs b/src/LibObjectFile/PE/Internal/RawImportFunctionEntry64.cs index c38a9b9..38161de 100644 --- a/src/LibObjectFile/PE/Internal/RawImportFunctionEntry64.cs +++ b/src/LibObjectFile/PE/Internal/RawImportFunctionEntry64.cs @@ -5,14 +5,18 @@ namespace LibObjectFile.PE.Internal; #pragma warning disable CS0649 -internal struct RawImportFunctionEntry64 +internal readonly struct RawImportFunctionEntry64 { + private readonly ulong _hintNameTableRVA; + public RawImportFunctionEntry64(ulong hintNameTableRVA) { - HintNameTableRVA = hintNameTableRVA; + _hintNameTableRVA = hintNameTableRVA; } - public ulong HintNameTableRVA; + public uint HintNameTableRVA => IsImportByOrdinal ? 0U : (uint)_hintNameTableRVA; + + public bool IsNull => HintNameTableRVA == 0; public ushort Ordinal => IsImportByOrdinal ? (ushort)HintNameTableRVA : (ushort)0; diff --git a/src/LibObjectFile/PE/PEFile.cs b/src/LibObjectFile/PE/PEFile.cs index b890e36..c5744bc 100644 --- a/src/LibObjectFile/PE/PEFile.cs +++ b/src/LibObjectFile/PE/PEFile.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection.PortableExecutable; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using LibObjectFile.Utils; @@ -72,6 +73,16 @@ public Stream? DosStubExtra /// public ImageOptionalHeader OptionalHeader; + /// + /// Gets a boolean indicating whether this instance is a PE32 image. + /// + public bool IsPE32 => OptionalHeader.Magic == ImageOptionalHeaderMagic.PE32; + + /// + /// Gets a boolean indicating whether this instance is a PE32+ image. + /// + public bool IsPE32Plus => OptionalHeader.Magic == ImageOptionalHeaderMagic.PE32Plus; + /// /// Gets the directories. /// @@ -106,20 +117,42 @@ public bool TryFindSection(RVA virtualAddress, [NotNullWhen(true)] out PESection public bool TryFindSection(RVA virtualAddress, uint virtualSize, [NotNullWhen(true)] out PESection? section) { + nint low = 0; var sections = CollectionsMarshal.AsSpan(_sections); - foreach (var trySection in sections) + nint high = sections.Length - 1; + ref var firstSection = ref MemoryMarshal.GetReference(sections); + + while (low <= high) { - if (trySection.ContainsVirtual(virtualAddress, virtualSize)) + nint mid = low + ((high - low) >>> 1); + var midSection = Unsafe.Add(ref firstSection, mid); + + if (midSection.ContainsVirtual(virtualAddress, virtualSize)) { - section = trySection; + section = midSection; return true; } + + if (midSection.VirtualAddress < virtualAddress) + { + low = mid + 1; + } + else + { + high = mid - 1; + } } section = null; return false; } + public bool TryFindSectionData(RVA virtualAddress, [NotNullWhen(true)] out PESectionData? sectionData) + { + sectionData = null; + return TryFindSection(virtualAddress, out var section) && section.TryFindSectionData(virtualAddress, out sectionData); + } + public void RemoveSection(PESectionName name) { ArgumentNullException.ThrowIfNull(name); @@ -191,7 +224,8 @@ public List GetAllSectionData() public override void UpdateLayout(DiagnosticBag diagnostics) { - // TODO + + } protected override bool PrintMembers(StringBuilder builder) diff --git a/src/LibObjectFile/PE/PEObject.cs b/src/LibObjectFile/PE/PEObject.cs index 576f2eb..38002ce 100644 --- a/src/LibObjectFile/PE/PEObject.cs +++ b/src/LibObjectFile/PE/PEObject.cs @@ -10,6 +10,11 @@ namespace LibObjectFile.PE; [DebuggerDisplay("{ToString(),nq}")] public abstract class PEObject : ObjectFileNode { + internal void ReadInternal(PEImageReader reader) => Read(reader); + + internal void WriteInternal(PEImageWriter writer) => Write(writer); + + protected abstract void Read(PEImageReader reader); protected abstract void Write(PEImageWriter writer); diff --git a/src/LibObjectFile/PE/PESection.cs b/src/LibObjectFile/PE/PESection.cs index a8bf6fd..3053c4d 100644 --- a/src/LibObjectFile/PE/PESection.cs +++ b/src/LibObjectFile/PE/PESection.cs @@ -102,7 +102,7 @@ public void AddData(PESectionData data) ArgumentNullException.ThrowIfNull(data); if (data.Parent != null) throw new ArgumentException("Data is already associated with a section", nameof(data)); data.Parent = this; - data.Index = (uint)_dataParts.Count; + data.Index = _dataParts.Count; _dataParts.Add(data); UpdateSectionDataIndicesAndVirtualAddress((int)data.Index); } @@ -114,7 +114,7 @@ public void InsertData(int index, PESectionData data) if (data.Parent != null) throw new ArgumentException("Data is already associated with a section", nameof(data)); data.Parent = this; - data.Index = (uint)index; + data.Index = index; _dataParts.Insert(index, data); UpdateSectionDataIndicesAndVirtualAddress(index); } @@ -157,15 +157,15 @@ public void RemoveDataAt(int index) public bool TryFindSectionData(RVA virtualAddress, [NotNullWhen(true)] out PESectionData? sectionData) { // Binary search - nint left = 0; + nint low = 0; var dataParts = CollectionsMarshal.AsSpan(_dataParts); - nint right = dataParts.Length - 1; + nint high = dataParts.Length - 1; ref var firstData = ref MemoryMarshal.GetReference(dataParts); - while (left <= right) + while (low <= high) { - nint mid = left + (right - left) >>> 1; + nint mid = low + (high - low) >>> 1; var trySectionData = Unsafe.Add(ref firstData, mid); if (trySectionData.ContainsVirtual(virtualAddress)) @@ -176,11 +176,11 @@ public bool TryFindSectionData(RVA virtualAddress, [NotNullWhen(true)] out PESec if (trySectionData.VirtualAddress < virtualAddress) { - left = mid + 1; + low = mid + 1; } else { - right = mid - 1; + high = mid - 1; } } @@ -193,10 +193,9 @@ public bool TryFindSectionData(RVA virtualAddress, [NotNullWhen(true)] out PESec /// /// The virtual address to check if it belongs to this section. /// true if the virtual address is within the section range. - public bool ContainsVirtual(RVA virtualAddress) - { - return virtualAddress >= VirtualAddress && virtualAddress < VirtualAddress + VirtualSize; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ContainsVirtual(RVA virtualAddress) + => virtualAddress >= VirtualAddress && virtualAddress < VirtualAddress + VirtualSize; /// /// Checks if the specified virtual address and size is contained by this section. @@ -204,11 +203,10 @@ public bool ContainsVirtual(RVA virtualAddress) /// The virtual address to check if it belongs to this section. /// The size to check if it belongs to this section. /// true if the virtual address and size is within the section range. - public bool ContainsVirtual(RVA virtualAddress, uint size) - { - return virtualAddress >= VirtualAddress && virtualAddress + size <= VirtualAddress + VirtualSize; - } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ContainsVirtual(RVA virtualAddress, uint size) + => virtualAddress >= VirtualAddress && virtualAddress + size <= VirtualAddress + VirtualSize; + /// public override void UpdateLayout(DiagnosticBag diagnostics) { @@ -287,7 +285,7 @@ private void UpdateSectionDataIndicesAndVirtualAddress(int startIndex) for (int i = startIndex; i < list.Count; i++) { var data = list[i]; - data.Index = (uint)i; + data.Index = i; data.VirtualAddress = va; va += (uint)data.Size; } diff --git a/src/LibObjectFile/PE/PESectionData.cs b/src/LibObjectFile/PE/PESectionData.cs index e61c40a..c9b4c86 100644 --- a/src/LibObjectFile/PE/PESectionData.cs +++ b/src/LibObjectFile/PE/PESectionData.cs @@ -3,7 +3,6 @@ // See the license.txt file in the project root for more information. using System; -using System.IO; using System.Runtime.CompilerServices; using System.Text; @@ -54,10 +53,6 @@ public virtual void WriteAt(uint offset, ReadOnlySpan source) throw new NotSupportedException($"The write operation is not supported for {this.GetType().FullName}"); } - internal void ReadInternal(PEImageReader reader) => Read(reader); - - internal void WriteInternal(PEImageWriter writer) => Write(writer); - protected override bool PrintMembers(StringBuilder builder) { builder.Append($"VirtualAddress: {VirtualAddress}, Size = 0x{Size:X4}"); @@ -65,104 +60,15 @@ protected override bool PrintMembers(StringBuilder builder) } } -/// -/// Defines a raw section data in a Portable Executable (PE) image. -/// -public sealed class PESectionMemoryData : PESectionData -{ - /// - /// Initializes a new instance of the class. - /// - public PESectionMemoryData() : this(Array.Empty()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The raw data. - public PESectionMemoryData(Memory data) - { - Data = data; - } - /// - /// Gets the raw data. - /// - public Memory Data { get; set; } - - /// - public override ulong Size - { - get => (ulong)Data.Length; - set => throw new InvalidOperationException(); - } - - /// - public override void UpdateLayout(DiagnosticBag diagnostics) - { - } - - protected override void Read(PEImageReader reader) - { - // No need to read, as the data is already provided via a stream - } - - protected override void Write(PEImageWriter writer) => writer.Write(Data.Span); -} -/// -/// Gets a stream section data in a Portable Executable (PE) image. -/// -public sealed class PESectionStreamData : PESectionData +internal sealed class PESectionDataTemp : PESectionData { - private Stream _stream; - - /// - /// Initializes a new instance of the class. - /// - public PESectionStreamData() - { - _stream = Stream.Null; - } + public static readonly PESectionDataTemp Instance = new (); + + protected override void Read(PEImageReader reader) => throw new NotSupportedException(); - /// - /// Initializes a new instance of the class. - /// - /// The stream containing the data of this section data. - public PESectionStreamData(Stream stream) - { - ArgumentNullException.ThrowIfNull(stream); - _stream = stream; - } + protected override void Write(PEImageWriter writer) => throw new NotSupportedException(); - /// - /// Gets the stream containing the data of this section data. - /// - public Stream Stream - { - get => _stream; - set => _stream = value ?? throw new ArgumentNullException(nameof(value)); - } - - public override ulong Size - { - get => (ulong)Stream.Length; - set => throw new InvalidOperationException(); - } - - public override void UpdateLayout(DiagnosticBag diagnostics) - { - } - - protected override void Read(PEImageReader reader) - { - // No need to read, as the data is already provided via a stream - } - - protected override void Write(PEImageWriter writer) - { - Stream.Position = 0; - Stream.CopyTo(writer.Stream); - } + public override void UpdateLayout(DiagnosticBag diagnostics) => throw new NotSupportedException(); } \ No newline at end of file diff --git a/src/LibObjectFile/PE/PESectionStreamData.cs b/src/LibObjectFile/PE/PESectionStreamData.cs new file mode 100644 index 0000000..58f3ce4 --- /dev/null +++ b/src/LibObjectFile/PE/PESectionStreamData.cs @@ -0,0 +1,64 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +using System; +using System.IO; + +namespace LibObjectFile.PE; + +/// +/// Gets a stream section data in a Portable Executable (PE) image. +/// +public class PESectionStreamData : PESectionData +{ + private Stream _stream; + + /// + /// Initializes a new instance of the class. + /// + public PESectionStreamData() + { + _stream = Stream.Null; + } + + /// + /// Initializes a new instance of the class. + /// + /// The stream containing the data of this section data. + public PESectionStreamData(Stream stream) + { + ArgumentNullException.ThrowIfNull(stream); + _stream = stream; + } + + /// + /// Gets the stream containing the data of this section data. + /// + public Stream Stream + { + get => _stream; + set => _stream = value ?? throw new ArgumentNullException(nameof(value)); + } + + public override ulong Size + { + get => (ulong)Stream.Length; + set => throw new InvalidOperationException(); + } + + public override void UpdateLayout(DiagnosticBag diagnostics) + { + } + + protected override void Read(PEImageReader reader) + { + // No need to read, as the data is already provided via a stream + } + + protected override void Write(PEImageWriter writer) + { + Stream.Position = 0; + Stream.CopyTo(writer.Stream); + } +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/ZeroTerminatedAsciiStringLink.cs b/src/LibObjectFile/PE/ZeroTerminatedAsciiStringLink.cs index 3ea9613..d7eb8a6 100644 --- a/src/LibObjectFile/PE/ZeroTerminatedAsciiStringLink.cs +++ b/src/LibObjectFile/PE/ZeroTerminatedAsciiStringLink.cs @@ -6,5 +6,7 @@ namespace LibObjectFile.PE; public record struct ZeroTerminatedAsciiStringLink(RVALink Link) { - public string? GetName() => Link.Element?.ReadZeroTerminatedAsciiString(Link.OffsetInElement); + public bool IsNull => Link.IsNull; + + public string? ToText() => Link.Element?.ReadZeroTerminatedAsciiString(Link.OffsetInElement); } \ No newline at end of file diff --git a/src/LibObjectFile/Utils/ObjectList.cs b/src/LibObjectFile/Utils/ObjectList.cs new file mode 100644 index 0000000..62c19d6 --- /dev/null +++ b/src/LibObjectFile/Utils/ObjectList.cs @@ -0,0 +1,196 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace LibObjectFile.Utils; + +/// +/// A list of objects that are attached to a parent object. +/// +/// The type of the object file. +[DebuggerDisplay("Count = {Count}")] +[DebuggerTypeProxy(typeof(ObjectList<>.ObjectListDebuggerView))] +public readonly struct ObjectList : IList + where TObject : ObjectFileNode +{ + // We are using an internal list to keep track of the parent object + private readonly InternalList _items; + + [Obsolete("This constructor is not supported", true)] + public ObjectList() => throw new NotSupportedException("This constructor is not supported"); + + /// + /// Initializes a new instance of the class. + /// + /// The parent object file node. + public ObjectList(ObjectFileNode parent, Action? added = null, Action? removing = null) + { + ArgumentNullException.ThrowIfNull(parent); + _items = new InternalList(parent, added, removing); + } + + public int Count => _items.Count; + + public bool IsReadOnly => false; + + public List UnsafeList => _items; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(TObject item) + { + var items = _items; + int index = items.Count; + items.Add(CheckAdd(item)); + item.Index = index; + items.Added(item); + } + + public void Clear() + { + var items = _items; + for (var i = items.Count - 1; i >= 0; i++) + { + var item = items[i]; + items.Removing(item); + items.RemoveAt(i); + + item.Parent = null; + item.ResetIndex(); + } + + items.Clear(); + } + + public bool Contains(TObject item) => _items.Contains(item); + + public void CopyTo(TObject[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); + + public bool Remove(TObject item) + { + var items = _items; + if (item.Parent != items.Parent) + { + return false; + } + + item.Parent = null; + item.ResetIndex(); + + return items.Remove(item); + } + + public int IndexOf(TObject item) => _items.IndexOf(item); + + public void Insert(int index, TObject item) + { + var items = _items; + items.Insert(index, CheckAdd(item)); + + for (int i = index; i < items.Count; i++) + { + items[i].Index = i; + } + + items.Added(item); + } + + public void RemoveAt(int index) + { + var items = _items; + var item = items[index]; + item.Parent = null; + item.Index = 0; + + items.RemoveAt(index); + + for (int i = index; i < items.Count; i++) + { + items[i].Index = i; + } + } + + public TObject this[int index] + { + get => _items[index]; + set + { + value = CheckAdd(value); + + // Unbind previous entry + var items = _items; + var previousItem = items[index]; + items.Removing(previousItem); + previousItem.Parent = null; + previousItem.ResetIndex(); + + // Bind new entry + items[index] = value; + value.Index = index; + items.Added(value); + } + } + + public List.Enumerator GetEnumerator() => _items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + { + return _items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_items).GetEnumerator(); + } + + public TObject CheckAdd(TObject item) + { + ArgumentNullException.ThrowIfNull(item); + if (item.Parent != null) + { + throw new ArgumentException($"The object is already attached to another parent", nameof(item)); + } + item.Parent = _items.Parent; + return item; + } + + private sealed class InternalList(ObjectFileNode parent, Action? added, Action? removing) : List + { + private readonly Action? _added = added; + private readonly Action? _removing = removing; + public readonly ObjectFileNode Parent = parent; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Added(TObject item) => _added?.Invoke(Parent, item); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Removing(TObject item) => _removing?.Invoke(Parent, item); + } + + internal sealed class ObjectListDebuggerView + { + private readonly List _collection; + + public ObjectListDebuggerView(ObjectList collection) + { + ArgumentNullException.ThrowIfNull((object)collection, nameof(collection)); + this._collection = collection.UnsafeList; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public TObject[] Items + { + get + { + var array = new TObject[this._collection.Count]; + _collection.CopyTo(array, 0); + return array; + } + } + } +}