From 72a1484a714846a9cc8a03825c8b522951c82146 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 2 Sep 2025 23:51:02 -0400 Subject: [PATCH] More granular locks --- .../Wrappers/NewExecutable.cs | 213 ++++++-------- .../Wrappers/PortableExecutable.cs | 270 +++++++++--------- 2 files changed, 236 insertions(+), 247 deletions(-) diff --git a/SabreTools.Serialization/Wrappers/NewExecutable.cs b/SabreTools.Serialization/Wrappers/NewExecutable.cs index 34ac1b1b..e4e05f50 100644 --- a/SabreTools.Serialization/Wrappers/NewExecutable.cs +++ b/SabreTools.Serialization/Wrappers/NewExecutable.cs @@ -36,7 +36,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_overlayAddressLock) { // Use the cached data if possible if (_overlayAddress != null) @@ -51,52 +51,59 @@ namespace SabreTools.Serialization.Wrappers if (Header == null || SegmentTable == null || ResourceTable?.ResourceTypes == null) return -1; - // Search through the segments table to find the furthest - long endOfSectionData = -1; - foreach (var entry in SegmentTable) + lock (_sourceDataLock) { - // Get end of segment data - long offset = (entry.Offset * (1 << Header.SegmentAlignmentShiftCount)) + entry.Length; + // Search through the segments table to find the furthest + long endOfSectionData = -1; + foreach (var entry in SegmentTable) + { + // Get end of segment data + long offset = (entry.Offset * (1 << Header.SegmentAlignmentShiftCount)) + entry.Length; - // Read and find the end of the relocation data + // Read and find the end of the relocation data #if NET20 || NET35 if ((entry.FlagWord & SegmentTableEntryFlag.RELOCINFO) != 0) #else - if (entry.FlagWord.HasFlag(SegmentTableEntryFlag.RELOCINFO)) + if (entry.FlagWord.HasFlag(SegmentTableEntryFlag.RELOCINFO)) #endif - { - _dataSource.Seek(offset, SeekOrigin.Begin); - var relocationData = Deserializers.NewExecutable.ParsePerSegmentData(_dataSource); + { + _dataSource.Seek(offset, SeekOrigin.Begin); + var relocationData = Deserializers.NewExecutable.ParsePerSegmentData(_dataSource); - offset = _dataSource.Position; - } + offset = _dataSource.Position; + } - if (offset > endOfSectionData) - endOfSectionData = offset; - } - - // Search through the resources table to find the furthest - foreach (var entry in ResourceTable.ResourceTypes) - { - // Skip invalid entries - if (entry.ResourceCount == 0 || entry.Resources == null || entry.Resources.Length == 0) - continue; - - foreach (var resource in entry.Resources) - { - int offset = (resource.Offset << ResourceTable.AlignmentShiftCount) + resource.Length; if (offset > endOfSectionData) endOfSectionData = offset; } + + // Search through the resources table to find the furthest + foreach (var entry in ResourceTable.ResourceTypes) + { + // Skip invalid entries + if (entry.ResourceCount == 0 || entry.Resources == null || entry.Resources.Length == 0) + continue; + + foreach (var resource in entry.Resources) + { + int offset = (resource.Offset << ResourceTable.AlignmentShiftCount) + resource.Length; + if (offset > endOfSectionData) + endOfSectionData = offset; + } + } + + // If we didn't find the end of section data + if (endOfSectionData <= 0) + endOfSectionData = -1; + + // Adjust the position of the data by 705 bytes + // TODO: Investigate what the byte data is + endOfSectionData += 705; + + // Cache and return the position + _overlayAddress = endOfSectionData; + return _overlayAddress.Value; } - - // If we didn't find the end of section data - if (endOfSectionData <= 0) - endOfSectionData = -1; - - // Cache and return the position - _overlayAddress = endOfSectionData; - return _overlayAddress.Value; } } } @@ -109,7 +116,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_overlayDataLock) { // Use the cached data if possible if (_overlayData != null) @@ -124,44 +131,8 @@ namespace SabreTools.Serialization.Wrappers if (Header == null || SegmentTable == null || ResourceTable?.ResourceTypes == null) return null; - // Search through the segments table to find the furthest - long endOfSectionData = -1; - foreach (var entry in SegmentTable) - { - // Get end of segment data - long offset = (entry.Offset * (1 << Header.SegmentAlignmentShiftCount)) + entry.Length; - - // Read and find the end of the relocation data -#if NET20 || NET35 - if ((entry.FlagWord & SegmentTableEntryFlag.RELOCINFO) != 0) -#else - if (entry.FlagWord.HasFlag(SegmentTableEntryFlag.RELOCINFO)) -#endif - { - _dataSource.Seek(offset, SeekOrigin.Begin); - var relocationData = Deserializers.NewExecutable.ParsePerSegmentData(_dataSource); - - offset = _dataSource.Position; - } - - if (offset > endOfSectionData) - endOfSectionData = offset; - } - - // Search through the resources table to find the furthest - foreach (var entry in ResourceTable.ResourceTypes) - { - // Skip invalid entries - if (entry.ResourceCount == 0 || entry.Resources == null || entry.Resources.Length == 0) - continue; - - foreach (var resource in entry.Resources) - { - int offset = (resource.Offset << ResourceTable.AlignmentShiftCount) + resource.Length; - if (offset > endOfSectionData) - endOfSectionData = offset; - } - } + // Get the overlay address if possible + long endOfSectionData = OverlayAddress; // If we didn't find the end of section data if (endOfSectionData <= 0) @@ -175,9 +146,12 @@ namespace SabreTools.Serialization.Wrappers } // Otherwise, cache and return the data - long overlayLength = dataLength - endOfSectionData; - _overlayData = _dataSource.ReadFrom((int)endOfSectionData, (int)overlayLength, retainPosition: true); - return _overlayData; + lock (_sourceDataLock) + { + long overlayLength = dataLength - endOfSectionData; + _overlayData = _dataSource.ReadFrom((int)endOfSectionData, (int)overlayLength, retainPosition: true); + return _overlayData; + } } } } @@ -189,7 +163,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_overlayStringsLock) { // Use the cached data if possible if (_overlayStrings != null) @@ -204,52 +178,30 @@ namespace SabreTools.Serialization.Wrappers if (Header == null || SegmentTable == null || ResourceTable?.ResourceTypes == null) return null; - // Search through the segments table to find the furthest - int endOfSectionData = -1; - foreach (var entry in SegmentTable) - { - int offset = (entry.Offset << Header.SegmentAlignmentShiftCount) + entry.Length; - if (offset > endOfSectionData) - endOfSectionData = offset; - } - - // Search through the resources table to find the furthest - foreach (var entry in ResourceTable.ResourceTypes) - { - // Skip invalid entries - if (entry.ResourceCount == 0 || entry.Resources == null || entry.Resources.Length == 0) - continue; - - foreach (var resource in entry.Resources) - { - int offset = (resource.Offset << ResourceTable.AlignmentShiftCount) + resource.Length; - if (offset > endOfSectionData) - endOfSectionData = offset; - } - } + // Get the overlay address if possible + long endOfSectionData = OverlayAddress; // If we didn't find the end of section data if (endOfSectionData <= 0) return null; - // Adjust the position of the data by 705 bytes - // TODO: Investigate what the byte data is - endOfSectionData += 705; - - // If we're at the end of the file, cache an empty list + // If we're at the end of the file, cache an empty byte array if (endOfSectionData >= dataLength) { _overlayStrings = []; return _overlayStrings; } - // TODO: Revisit the 16 MiB limit - // Cap the check for overlay strings to 16 MiB (arbitrary) - long overlayLength = Math.Min(dataLength - endOfSectionData, 16 * 1024 * 1024); + lock (_sourceDataLock) + { + // TODO: Revisit the 16 MiB limit + // Cap the check for overlay strings to 16 MiB (arbitrary) + long overlayLength = Math.Min(dataLength - endOfSectionData, 16 * 1024 * 1024); - // Otherwise, cache and return the strings - _overlayStrings = _dataSource.ReadStringsFrom(endOfSectionData, (int)overlayLength, charLimit: 3); - return _overlayStrings; + // Otherwise, cache and return the strings + _overlayStrings = _dataSource.ReadStringsFrom((int)endOfSectionData, (int)overlayLength, charLimit: 3); + return _overlayStrings; + } } } } @@ -273,7 +225,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_stubExecutableDataLock) { // If we already have cached data, just use that immediately if (_stubExecutableData != null) @@ -282,13 +234,16 @@ namespace SabreTools.Serialization.Wrappers if (Stub?.Header?.NewExeHeaderAddr == null) return null; - // Populate the raw stub executable data based on the source - int endOfStubHeader = 0x40; - int lengthOfStubExecutableData = (int)Stub.Header.NewExeHeaderAddr - endOfStubHeader; - _stubExecutableData = _dataSource.ReadFrom(endOfStubHeader, lengthOfStubExecutableData, retainPosition: true); + lock (_sourceDataLock) + { + // Populate the raw stub executable data based on the source + int endOfStubHeader = 0x40; + int lengthOfStubExecutableData = (int)Stub.Header.NewExeHeaderAddr - endOfStubHeader; + _stubExecutableData = _dataSource.ReadFrom(endOfStubHeader, lengthOfStubExecutableData, retainPosition: true); - // Cache and return the stub executable data, even if null - return _stubExecutableData; + // Cache and return the stub executable data, even if null + return _stubExecutableData; + } } } } @@ -302,21 +257,41 @@ namespace SabreTools.Serialization.Wrappers /// private long? _overlayAddress = null; + /// + /// Lock object for + /// + private readonly object _overlayAddressLock = new(); + /// /// Overlay data, if it exists /// private byte[]? _overlayData = null; + /// + /// Lock object for + /// + private readonly object _overlayDataLock = new(); + /// /// Overlay strings, if they exist /// private List? _overlayStrings = null; + /// + /// Lock object for + /// + private readonly object _overlayStringsLock = new(); + /// /// Stub executable data, if it exists /// private byte[]? _stubExecutableData = null; + /// + /// Lock object for + /// + private readonly object _stubExecutableDataLock = new(); + /// /// Lock object for reading from the source /// diff --git a/SabreTools.Serialization/Wrappers/PortableExecutable.cs b/SabreTools.Serialization/Wrappers/PortableExecutable.cs index cbded142..6ce94e9c 100644 --- a/SabreTools.Serialization/Wrappers/PortableExecutable.cs +++ b/SabreTools.Serialization/Wrappers/PortableExecutable.cs @@ -31,7 +31,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_debugDataLock) { // Use the cached data if possible if (_debugData != null && _debugData.Count != 0) @@ -42,8 +42,11 @@ namespace SabreTools.Serialization.Wrappers return null; // Otherwise, build and return the cached dictionary - ParseDebugTable(); - return _debugData; + lock (_sourceDataLock) + { + ParseDebugTable(); + return _debugData; + } } } } @@ -59,8 +62,12 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_entryPointDataLock) { + // If we already have cached data, just use that immediately + if (_entryPointData != null) + return _entryPointData; + // If the section table is missing if (SectionTable == null) return null; @@ -77,17 +84,17 @@ namespace SabreTools.Serialization.Wrappers // If the entry point matches with the start of a section, use that int entryPointSection = FindEntryPointSectionIndex(); if (entryPointSection >= 0 && OptionalHeader.AddressOfEntryPoint == SectionTable[entryPointSection]?.VirtualAddress) - return GetSectionData(entryPointSection); - - // If we already have cached data, just use that immediately - if (_entryPointData != null) + { + _entryPointData = GetSectionData(entryPointSection); return _entryPointData; + } // Read the first 128 bytes of the entry point - _entryPointData = _dataSource.ReadFrom(entryPointAddress, length: 128, retainPosition: true); - - // Cache and return the entry point padding data, even if null - return _entryPointData; + lock (_sourceDataLock) + { + _entryPointData = _dataSource.ReadFrom(entryPointAddress, length: 128, retainPosition: true); + return _entryPointData; + } } } } @@ -102,7 +109,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_headerPaddingDataLock) { // If we already have cached data, just use that immediately if (_headerPaddingData != null) @@ -133,12 +140,16 @@ namespace SabreTools.Serialization.Wrappers // Check if the header length is more than 0 before reading data int headerLength = (int)(firstSectionAddress - headerStartAddress); if (headerLength <= 0) + { _headerPaddingData = []; - else - _headerPaddingData = _dataSource.ReadFrom((int)headerStartAddress, headerLength, retainPosition: true); + return _headerPaddingData; + } - // Cache and return the header padding data, even if null - return _headerPaddingData; + lock (_sourceDataLock) + { + _headerPaddingData = _dataSource.ReadFrom((int)headerStartAddress, headerLength, retainPosition: true); + return _headerPaddingData; + } } } } @@ -150,7 +161,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_headerPaddingStringsLock) { // If we already have cached data, just use that immediately if (_headerPaddingStrings != null) @@ -181,12 +192,16 @@ namespace SabreTools.Serialization.Wrappers // Check if the header length is more than 0 before reading strings int headerLength = (int)(firstSectionAddress - headerStartAddress); if (headerLength <= 0) + { _headerPaddingStrings = []; - else - _headerPaddingStrings = _dataSource.ReadStringsFrom((int)headerStartAddress, headerLength, charLimit: 3); + return _headerPaddingStrings; + } - // Cache and return the header padding data, even if null - return _headerPaddingStrings; + lock (_sourceDataLock) + { + _headerPaddingStrings = _dataSource.ReadStringsFrom((int)headerStartAddress, headerLength, charLimit: 3); + return _headerPaddingStrings; + } } } } @@ -205,7 +220,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_overlayAddressLock) { // Use the cached data if possible if (_overlayAddress != null) @@ -257,6 +272,10 @@ namespace SabreTools.Serialization.Wrappers if (endOfSectionData <= 0) endOfSectionData = -1; + // If the section data is followed by the end of the data + if (endOfSectionData >= dataLength) + endOfSectionData = -1; + // Cache and return the position _overlayAddress = endOfSectionData; return _overlayAddress.Value; @@ -272,7 +291,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_overlayDataLock) { // Use the cached data if possible if (_overlayData != null) @@ -287,38 +306,8 @@ namespace SabreTools.Serialization.Wrappers if (SectionTable == null) return null; - // If we have certificate data, use that as the end - if (OptionalHeader?.CertificateTable != null) - { - int certificateTableAddress = (int)OptionalHeader.CertificateTable.VirtualAddress.ConvertVirtualAddress(SectionTable); - if (certificateTableAddress != 0 && certificateTableAddress < dataLength) - dataLength = certificateTableAddress; - } - - // Search through all sections and find the furthest a section goes - int endOfSectionData = -1; - foreach (var section in SectionTable) - { - // If we have an invalid section - if (section == null) - continue; - - // If we have an invalid section address - int sectionAddress = (int)section.VirtualAddress.ConvertVirtualAddress(SectionTable); - if (sectionAddress == 0) - continue; - - // If we have an invalid section size - if (section.SizeOfRawData == 0 && section.VirtualSize == 0) - continue; - - // Get the real section size - int sectionSize = (int)section.SizeOfRawData; - - // Compare and set the end of section data - if (sectionAddress + sectionSize > endOfSectionData) - endOfSectionData = sectionAddress + sectionSize; - } + // Get the overlay address if possible + long endOfSectionData = OverlayAddress; // If we didn't find the end of section data if (endOfSectionData <= 0) @@ -332,9 +321,12 @@ namespace SabreTools.Serialization.Wrappers } // Otherwise, cache and return the data - long overlayLength = dataLength - endOfSectionData; - _overlayData = _dataSource.ReadFrom(endOfSectionData, (int)overlayLength, retainPosition: true); - return _overlayData; + lock (_sourceDataLock) + { + long overlayLength = dataLength - endOfSectionData; + _overlayData = _dataSource.ReadFrom(endOfSectionData, (int)overlayLength, retainPosition: true); + return _overlayData; + } } } } @@ -346,7 +338,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_overlayStringsLock) { // Use the cached data if possible if (_overlayStrings != null) @@ -361,61 +353,30 @@ namespace SabreTools.Serialization.Wrappers if (SectionTable == null) return null; - // If we have certificate data, use that as the end - if (OptionalHeader?.CertificateTable != null) - { - int certificateTableAddress = (int)OptionalHeader.CertificateTable.VirtualAddress.ConvertVirtualAddress(SectionTable); - if (certificateTableAddress != 0 && certificateTableAddress < dataLength) - dataLength = certificateTableAddress; - } - - // Search through all sections and find the furthest a section goes - int endOfSectionData = -1; - foreach (var section in SectionTable) - { - // If we have an invalid section - if (section == null) - continue; - - // If we have an invalid section address - int sectionAddress = (int)section.VirtualAddress.ConvertVirtualAddress(SectionTable); - if (sectionAddress == 0) - continue; - - // If we have an invalid section size - if (section.SizeOfRawData == 0 && section.VirtualSize == 0) - continue; - - // Get the real section size - int sectionSize; - if (section.SizeOfRawData < section.VirtualSize) - sectionSize = (int)section.VirtualSize; - else - sectionSize = (int)section.SizeOfRawData; - - // Compare and set the end of section data - if (sectionAddress + sectionSize > endOfSectionData) - endOfSectionData = sectionAddress + sectionSize; - } + // Get the overlay address if possible + long endOfSectionData = OverlayAddress; // If we didn't find the end of section data if (endOfSectionData <= 0) return null; - // If we're at the end of the file, cache an empty list + // If we're at the end of the file, cache an empty byte array if (endOfSectionData >= dataLength) { _overlayStrings = []; return _overlayStrings; } - // TODO: Revisit the 16 MiB limit - // Cap the check for overlay strings to 16 MiB (arbitrary) - long overlayLength = Math.Min(dataLength - endOfSectionData, 16 * 1024 * 1024); + lock (_sourceDataLock) + { + // TODO: Revisit the 16 MiB limit + // Cap the check for overlay strings to 16 MiB (arbitrary) + long overlayLength = Math.Min(dataLength - endOfSectionData, 16 * 1024 * 1024); - // Otherwise, cache and return the strings - _overlayStrings = _dataSource.ReadStringsFrom(endOfSectionData, (int)overlayLength, charLimit: 3); - return _overlayStrings; + // Otherwise, cache and return the strings + _overlayStrings = _dataSource.ReadStringsFrom((int)endOfSectionData, (int)overlayLength, charLimit: 3); + return _overlayStrings; + } } } } @@ -430,7 +391,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_sectionNamesLock) { // Use the cached data if possible if (_sectionNames != null) @@ -475,7 +436,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_stubExecutableDataLock) { // If we already have cached data, just use that immediately if (_stubExecutableData != null) @@ -484,13 +445,16 @@ namespace SabreTools.Serialization.Wrappers if (Stub?.Header?.NewExeHeaderAddr == null) return null; - // Populate the raw stub executable data based on the source - int endOfStubHeader = 0x40; - int lengthOfStubExecutableData = (int)Stub.Header.NewExeHeaderAddr - endOfStubHeader; - _stubExecutableData = _dataSource.ReadFrom(endOfStubHeader, lengthOfStubExecutableData, retainPosition: true); + lock (_sourceDataLock) + { + // Populate the raw stub executable data based on the source + int endOfStubHeader = 0x40; + int lengthOfStubExecutableData = (int)Stub.Header.NewExeHeaderAddr - endOfStubHeader; + _stubExecutableData = _dataSource.ReadFrom(endOfStubHeader, lengthOfStubExecutableData, retainPosition: true); - // Cache and return the stub executable data, even if null - return _stubExecutableData; + // Cache and return the stub executable data, even if null + return _stubExecutableData; + } } } } @@ -502,7 +466,7 @@ namespace SabreTools.Serialization.Wrappers { get { - lock (_sourceDataLock) + lock (_resourceDataLock) { // Use the cached data if possible if (_resourceData != null && _resourceData.Count != 0) @@ -692,45 +656,95 @@ namespace SabreTools.Serialization.Wrappers #region Instance Variables /// - /// Header padding data, if it exists + /// Cached debug data /// - private byte[]? _headerPaddingData = null; + private readonly Dictionary _debugData = []; /// - /// Header padding strings, if they exist + /// Lock object for /// - private List? _headerPaddingStrings = null; + private readonly object _debugDataLock = new(); /// /// Entry point data, if it exists and isn't aligned to a section /// private byte[]? _entryPointData = null; + /// + /// Lock object for + /// + private readonly object _entryPointDataLock = new(); + + /// + /// Header padding data, if it exists + /// + private byte[]? _headerPaddingData = null; + + /// + /// Lock object for + /// + private readonly object _headerPaddingDataLock = new(); + + /// + /// Header padding strings, if they exist + /// + private List? _headerPaddingStrings = null; + + /// + /// Lock object for + /// + private readonly object _headerPaddingStringsLock = new(); + /// /// Address of the overlay, if it exists /// private int? _overlayAddress = null; + /// + /// Lock object for + /// + private readonly object _overlayAddressLock = new(); + /// /// Overlay data, if it exists /// private byte[]? _overlayData = null; + /// + /// Lock object for + /// + private readonly object _overlayDataLock = new(); + /// /// Overlay strings, if they exist /// private List? _overlayStrings = null; /// - /// Stub executable data, if it exists + /// Lock object for /// - private byte[]? _stubExecutableData = null; + private readonly object _overlayStringsLock = new(); + + /// + /// Cached resource data + /// + private readonly Dictionary _resourceData = []; + + /// + /// Lock object for + /// + private readonly object _resourceDataLock = new(); /// /// Sanitized section names /// private string[]? _sectionNames = null; + /// + /// Lock object for + /// + private readonly object _sectionNamesLock = new(); + /// /// Cached raw section data /// @@ -741,6 +755,16 @@ namespace SabreTools.Serialization.Wrappers /// private List[]? _sectionStringData = null; + /// + /// Stub executable data, if it exists + /// + private byte[]? _stubExecutableData = null; + + /// + /// Lock object for + /// + private readonly object _stubExecutableDataLock = new(); + /// /// Cached raw table data /// @@ -751,16 +775,6 @@ namespace SabreTools.Serialization.Wrappers /// private List[]? _tableStringData = null; - /// - /// Cached debug data - /// - private readonly Dictionary _debugData = []; - - /// - /// Cached resource data - /// - private readonly Dictionary _resourceData = []; - /// /// Cached version info data ///