diff --git a/Examples/Example6.cs b/Examples/Example6.cs index 83a3ce48e..1a823fc18 100644 --- a/Examples/Example6.cs +++ b/Examples/Example6.cs @@ -79,6 +79,7 @@ public void SetOffset(FileOffset offset, RVA rva) { public uint GetFileLength() => (uint)heapData.Length; public uint GetVirtualSize() => GetFileLength(); + public uint CalculateAlignment() => 0; public void WriteTo(DataWriter writer) => writer.WriteBytes(heapData); } diff --git a/src/DotNet/Utils.cs b/src/DotNet/Utils.cs index f9eeb8227..90d3797ce 100644 --- a/src/DotNet/Utils.cs +++ b/src/DotNet/Utils.cs @@ -275,5 +275,19 @@ static string GetCanonicalLocale(string locale) { /// Value /// Alignment public static int AlignUp(int v, uint alignment) => (int)AlignUp((uint)v, alignment); + + /// + /// Rounds up the provided number to the next power of 2 + /// + /// The number to round + public static uint RoundToNextPowerOfTwo(uint num) { + num--; + num |= num >> 1; + num |= num >> 2; + num |= num >> 4; + num |= num >> 8; + num |= num >> 16; + return num + 1; + } } } diff --git a/src/DotNet/Writer/ArrayWriter.cs b/src/DotNet/Writer/ArrayWriter.cs index 721c0b46f..d997ee5d8 100644 --- a/src/DotNet/Writer/ArrayWriter.cs +++ b/src/DotNet/Writer/ArrayWriter.cs @@ -11,7 +11,10 @@ public unsafe struct ArrayWriter { /// /// Gets the current position /// - public int Position => position; + public int Position { + get => position; + set => position = value; + } readonly byte[] data; int position; diff --git a/src/DotNet/Writer/ByteArrayChunk.cs b/src/DotNet/Writer/ByteArrayChunk.cs index 0bd6c5f37..4a6b4c3a4 100644 --- a/src/DotNet/Writer/ByteArrayChunk.cs +++ b/src/DotNet/Writer/ByteArrayChunk.cs @@ -10,6 +10,7 @@ namespace dnlib.DotNet.Writer { /// public sealed class ByteArrayChunk : IReuseChunk { readonly byte[] array; + readonly uint alignment; FileOffset offset; RVA rva; @@ -32,7 +33,11 @@ public sealed class ByteArrayChunk : IReuseChunk { /// return value will be different if you modify the array). If /// it's never inserted as a key in a dictionary, then the contents can be modified, /// but shouldn't be resized after has been called. - public ByteArrayChunk(byte[] array) => this.array = array ?? Array2.Empty(); + /// The alignment of the data + public ByteArrayChunk(byte[] array, uint alignment = 0) { + this.array = array ?? Array2.Empty(); + this.alignment = alignment; + } bool IReuseChunk.CanReuse(RVA origRva, uint origSize) => (uint)array.Length <= origSize; @@ -48,6 +53,9 @@ public void SetOffset(FileOffset offset, RVA rva) { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => alignment; + /// public void WriteTo(DataWriter writer) => writer.WriteBytes(array); diff --git a/src/DotNet/Writer/ChunkListBase.cs b/src/DotNet/Writer/ChunkListBase.cs index 321809ac2..b7bd2b40d 100644 --- a/src/DotNet/Writer/ChunkListBase.cs +++ b/src/DotNet/Writer/ChunkListBase.cs @@ -1,5 +1,6 @@ // dnlib: See LICENSE.txt for more info +using System; using System.Collections.Generic; using dnlib.IO; using dnlib.PE; @@ -114,5 +115,19 @@ public void WriteTo(DataWriter writer) { offset2 += (uint)paddingF + elem.chunk.GetFileLength(); } } + + /// + public virtual uint CalculateAlignment() { + uint alignment = 0; + for (int i = 0; i < chunks.Count; i++) { + var elem = chunks[i]; + uint newAlignment = Math.Max(elem.alignment, elem.chunk.CalculateAlignment()); + chunks[i] = new Elem(elem.chunk, newAlignment); + + alignment = Math.Max(alignment, newAlignment); + } + + return alignment; + } } } diff --git a/src/DotNet/Writer/DataReaderChunk.cs b/src/DotNet/Writer/DataReaderChunk.cs index defaa43ec..cf9a20c0d 100644 --- a/src/DotNet/Writer/DataReaderChunk.cs +++ b/src/DotNet/Writer/DataReaderChunk.cs @@ -86,6 +86,9 @@ public void SetOffset(FileOffset offset, RVA rva) { /// public uint GetVirtualSize() => virtualSize; + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { data.Position = 0; diff --git a/src/DotNet/Writer/DebugDirectory.cs b/src/DotNet/Writer/DebugDirectory.cs index 3d62d6582..f8d09faaa 100644 --- a/src/DotNet/Writer/DebugDirectory.cs +++ b/src/DotNet/Writer/DebugDirectory.cs @@ -139,6 +139,9 @@ static uint GetLength(List entries, FileOffset offset, RVA /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { uint offset = 0; diff --git a/src/DotNet/Writer/HeapBase.cs b/src/DotNet/Writer/HeapBase.cs index 99831e387..567850e4c 100644 --- a/src/DotNet/Writer/HeapBase.cs +++ b/src/DotNet/Writer/HeapBase.cs @@ -51,6 +51,9 @@ public virtual void SetOffset(FileOffset offset, RVA rva) { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// /// Gets the raw length of the heap /// diff --git a/src/DotNet/Writer/IChunk.cs b/src/DotNet/Writer/IChunk.cs index d358d3020..061321a48 100644 --- a/src/DotNet/Writer/IChunk.cs +++ b/src/DotNet/Writer/IChunk.cs @@ -40,6 +40,13 @@ public interface IChunk { /// Virtual size of this chunk uint GetVirtualSize(); + /// + /// Calculates the requires alignment of this chunk. + /// Returns 0 for default/no alignment. + /// + /// Required alignment + uint CalculateAlignment(); + /// /// Writes all data to at its current location. It's only /// called after and have been called. diff --git a/src/DotNet/Writer/ImageCor20Header.cs b/src/DotNet/Writer/ImageCor20Header.cs index 4385fd9c5..154c3fa6d 100644 --- a/src/DotNet/Writer/ImageCor20Header.cs +++ b/src/DotNet/Writer/ImageCor20Header.cs @@ -113,6 +113,9 @@ public void SetOffset(FileOffset offset, RVA rva) { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { writer.WriteInt32(0x48); // cb diff --git a/src/DotNet/Writer/ImportAddressTable.cs b/src/DotNet/Writer/ImportAddressTable.cs index 05ce2e9ee..d789e7890 100644 --- a/src/DotNet/Writer/ImportAddressTable.cs +++ b/src/DotNet/Writer/ImportAddressTable.cs @@ -47,6 +47,9 @@ public uint GetFileLength() { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { if (!Enable) diff --git a/src/DotNet/Writer/ImportDirectory.cs b/src/DotNet/Writer/ImportDirectory.cs index c871aed8f..23fe2b931 100644 --- a/src/DotNet/Writer/ImportDirectory.cs +++ b/src/DotNet/Writer/ImportDirectory.cs @@ -104,6 +104,9 @@ public uint GetFileLength() { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { if (!Enable) diff --git a/src/DotNet/Writer/ManagedExportsWriter.cs b/src/DotNet/Writer/ManagedExportsWriter.cs index 8f9bdf87a..6455b2ccb 100644 --- a/src/DotNet/Writer/ManagedExportsWriter.cs +++ b/src/DotNet/Writer/ManagedExportsWriter.cs @@ -44,6 +44,7 @@ sealed class ExportDir : IChunk { void IChunk.SetOffset(FileOffset offset, RVA rva) => throw new NotSupportedException(); public uint GetFileLength() => owner.ExportDirSize; public uint GetVirtualSize() => GetFileLength(); + public uint CalculateAlignment() => 0; void IChunk.WriteTo(DataWriter writer) => throw new NotSupportedException(); } @@ -61,6 +62,7 @@ public void SetOffset(FileOffset offset, RVA rva) { } public uint GetFileLength() => length; public uint GetVirtualSize() => GetFileLength(); + public uint CalculateAlignment() => 0; public void WriteTo(DataWriter writer) => owner.WriteVtableFixups(writer); } @@ -78,6 +80,7 @@ public void SetOffset(FileOffset offset, RVA rva) { } public uint GetFileLength() => length; public uint GetVirtualSize() => GetFileLength(); + public uint CalculateAlignment() => 0; public void WriteTo(DataWriter writer) => owner.WriteStubs(writer); } @@ -95,6 +98,7 @@ public void SetOffset(FileOffset offset, RVA rva) { } public uint GetFileLength() => length; public uint GetVirtualSize() => GetFileLength(); + public uint CalculateAlignment() => 0; public void WriteTo(DataWriter writer) => owner.WriteSdata(writer); } diff --git a/src/DotNet/Writer/Metadata.cs b/src/DotNet/Writer/Metadata.cs index d683548e9..28e874ae3 100644 --- a/src/DotNet/Writer/Metadata.cs +++ b/src/DotNet/Writer/Metadata.cs @@ -2704,7 +2704,12 @@ protected void AddFieldRVA(FieldDef field) { if (!VerifyFieldSize(field, ivBytes.Length)) Error("Field '{0}' (0x{1:X8}) initial value size != size of field type.", field, field.MDToken.Raw); uint rid = GetRid(field); - var iv = constants.Add(new ByteArrayChunk(ivBytes), ModuleWriterBase.DEFAULT_CONSTANTS_ALIGNMENT); + + uint alignment = ModuleWriterBase.DEFAULT_CONSTANTS_ALIGNMENT; + if (field.FieldType is TypeDefOrRefSig tdrSig && tdrSig.TypeDef?.ClassLayout is {} classLayout) + alignment = Math.Max(alignment, Utils.RoundToNextPowerOfTwo(classLayout.PackingSize)); + + var iv = constants.Add(new ByteArrayChunk(ivBytes, alignment), alignment); fieldToInitialValue[field] = iv; var row = new RawFieldRVARow(0, rid); fieldRVAInfos.Add(field, row); @@ -3774,6 +3779,9 @@ IList GetHeaps() { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { var rva2 = rva; diff --git a/src/DotNet/Writer/MetadataHeader.cs b/src/DotNet/Writer/MetadataHeader.cs index aabcd7372..4fe2841fe 100644 --- a/src/DotNet/Writer/MetadataHeader.cs +++ b/src/DotNet/Writer/MetadataHeader.cs @@ -135,6 +135,9 @@ public void SetOffset(FileOffset offset, RVA rva) { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { writer.WriteUInt32(options.Signature ?? MetadataHeaderOptions.DEFAULT_SIGNATURE); diff --git a/src/DotNet/Writer/MethodBody.cs b/src/DotNet/Writer/MethodBody.cs index b6c4cfef0..3c6464dd7 100644 --- a/src/DotNet/Writer/MethodBody.cs +++ b/src/DotNet/Writer/MethodBody.cs @@ -132,6 +132,9 @@ public void SetOffset(FileOffset offset, RVA rva) { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { writer.WriteBytes(code); diff --git a/src/DotNet/Writer/MethodBodyChunks.cs b/src/DotNet/Writer/MethodBodyChunks.cs index ef08416c3..63ab29ae2 100644 --- a/src/DotNet/Writer/MethodBodyChunks.cs +++ b/src/DotNet/Writer/MethodBodyChunks.cs @@ -171,6 +171,9 @@ public void SetOffset(FileOffset offset, RVA rva) { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { var rva2 = rva; diff --git a/src/DotNet/Writer/ModuleWriterBase.cs b/src/DotNet/Writer/ModuleWriterBase.cs index 5c77af110..e1b56d6fb 100644 --- a/src/DotNet/Writer/ModuleWriterBase.cs +++ b/src/DotNet/Writer/ModuleWriterBase.cs @@ -783,8 +783,15 @@ protected void CreateMetadataChunks(ModuleDef module) { /// Section alignment protected void CalculateRvasAndFileOffsets(List chunks, FileOffset offset, RVA rva, uint fileAlignment, uint sectionAlignment) { int count = chunks.Count; + var maxAlignment = Math.Min(fileAlignment, sectionAlignment); for (int i = 0; i < count; i++) { var chunk = chunks[i]; + // We don't need to align to result of CalculateAlignment as all the chunks in `chunks` either + // don't need a specific alignment or align themselves. + var alignment = chunk.CalculateAlignment(); + if (alignment > maxAlignment) + Error("Chunk alignment is too big. Chunk: {0}, alignment: {1:X4}", chunk, alignment); + chunk.SetOffset(offset, rva); // If it has zero size, it's not present in the file (eg. a section that wasn't needed) if (chunk.GetVirtualSize() != 0) { diff --git a/src/DotNet/Writer/NetResources.cs b/src/DotNet/Writer/NetResources.cs index e04a9cf6b..f2986f2ea 100644 --- a/src/DotNet/Writer/NetResources.cs +++ b/src/DotNet/Writer/NetResources.cs @@ -74,6 +74,9 @@ public void SetOffset(FileOffset offset, RVA rva) { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { var rva2 = rva; diff --git a/src/DotNet/Writer/PEHeaders.cs b/src/DotNet/Writer/PEHeaders.cs index 899f7ec9d..34502b779 100644 --- a/src/DotNet/Writer/PEHeaders.cs +++ b/src/DotNet/Writer/PEHeaders.cs @@ -325,6 +325,9 @@ int SectionsCount { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + IEnumerable GetSectionSizeInfos() { foreach (var section in sections) { uint virtSize = section.GetVirtualSize(); diff --git a/src/DotNet/Writer/RelocDirectory.cs b/src/DotNet/Writer/RelocDirectory.cs index ab3c332b1..6b7d4c03e 100644 --- a/src/DotNet/Writer/RelocDirectory.cs +++ b/src/DotNet/Writer/RelocDirectory.cs @@ -81,6 +81,9 @@ public void SetOffset(FileOffset offset, RVA rva) { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { bool is64bit = machine.Is64Bit(); diff --git a/src/DotNet/Writer/StartupStub.cs b/src/DotNet/Writer/StartupStub.cs index e5c36f64d..fc212cead 100644 --- a/src/DotNet/Writer/StartupStub.cs +++ b/src/DotNet/Writer/StartupStub.cs @@ -82,6 +82,9 @@ public uint GetFileLength() { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { if (!Enable) diff --git a/src/DotNet/Writer/StrongNameSignature.cs b/src/DotNet/Writer/StrongNameSignature.cs index 088f8e3ff..c485cba6b 100644 --- a/src/DotNet/Writer/StrongNameSignature.cs +++ b/src/DotNet/Writer/StrongNameSignature.cs @@ -38,6 +38,9 @@ public void SetOffset(FileOffset offset, RVA rva) { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) => writer.WriteZeroes(size); } diff --git a/src/DotNet/Writer/TablesHeap.cs b/src/DotNet/Writer/TablesHeap.cs index c79ac83a8..a578a7938 100644 --- a/src/DotNet/Writer/TablesHeap.cs +++ b/src/DotNet/Writer/TablesHeap.cs @@ -331,6 +331,9 @@ public uint GetFileLength() { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// /// Calculates the length. This will set all MD tables to read-only. /// diff --git a/src/DotNet/Writer/UniqueChunkList.cs b/src/DotNet/Writer/UniqueChunkList.cs index c8cf0f15b..225bf801c 100644 --- a/src/DotNet/Writer/UniqueChunkList.cs +++ b/src/DotNet/Writer/UniqueChunkList.cs @@ -53,5 +53,30 @@ public T Add(T chunk, uint alignment) { chunks.Add(elem); return elem.chunk; } + + /// + public override uint CalculateAlignment() { + uint alignment = base.CalculateAlignment(); + + var keys = new KeyValuePair[chunks.Count]; + for (var i = 0; i < chunks.Count; i++) + keys[i] = new KeyValuePair(i, chunks[i]); + Array.Sort(keys, DescendingStableComparer.Instance); + for (var i = 0; i < keys.Length; i++) + chunks[i] = keys[i].Value; + + return alignment; + } + + sealed class DescendingStableComparer : IComparer> { + internal static readonly DescendingStableComparer Instance = new DescendingStableComparer(); + + public int Compare(KeyValuePair x, KeyValuePair y) { + var result = -x.Value.alignment.CompareTo(y.Value.alignment); + if (result != 0) + return result; + return x.Key.CompareTo(y.Key); + } + } } } diff --git a/src/DotNet/Writer/Win32ResourcesChunk.cs b/src/DotNet/Writer/Win32ResourcesChunk.cs index 3898d9cd3..cf75e819b 100644 --- a/src/DotNet/Writer/Win32ResourcesChunk.cs +++ b/src/DotNet/Writer/Win32ResourcesChunk.cs @@ -353,6 +353,9 @@ void FindDirectoryEntries(ResourceDirectory dir) { /// public uint GetVirtualSize() => GetFileLength(); + /// + public uint CalculateAlignment() => 0; + /// public void WriteTo(DataWriter writer) { uint offset = 0;