From b24ed8903f95e317b3badde7fdd4e3f8c375b544 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Sat, 11 Jul 2026 23:54:22 +0200 Subject: [PATCH] [R2R] Align composite ManifestMetadata and ComponentAssemblies sections to 4 bytes Follow-up to #129017. That PR fixed a 32-bit ARM SIGBUS (BUS_ADRALN) in composite Ready-to-Run images caused by emitting the manifest MVID table with alignment: 1. The adjacent ManifestMetadataTableNode (metadata root) and AssemblyTableNode (ComponentAssemblies) have the identical latent bug: both are packed arrays of DWORD-typed fields the runtime reads in place, but are emitted with alignment: 1, so they can land on a non-4-aligned RVA and fault on ARM32 (unaligned multi-word load) during coreclr_initialize. x64/arm64 tolerate the unaligned access. In the failing android-arm MAUI R2R crash (dotnet/android#12026) the MVID table was already aligned (thanks to #129017), but the metadata root landed on an odd RVA and faulted in PEDecoder::CheckCorHeader, so #129017 alone did not cover this case. Align both sections to 4 bytes (their contents' natural alignment) and add a regression test mirroring the MVID-table tests (a plain composite case plus a Windows-only --pdb variant that deterministically exposes the misalignment). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 77c12619-9a25-4209-9469-b44bd280f2bc --- .../TestCases/R2RTestSuites.cs | 87 +++++++++++++++++++ .../TestCasesRunner/R2RResultChecker.cs | 44 ++++++++++ .../ReadyToRun/AssemblyTableNode.cs | 1 + .../ReadyToRun/ManifestMetadataTableNode.cs | 5 +- 4 files changed, 135 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index af2c1d90e7de49..d78aa99e1e8e7f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -401,6 +401,93 @@ static void Validate(ReadyToRunReader reader) } } + /// + /// Regression test for an ARM32 alignment fault (SIGBUS / BUS_ADRALN) when loading a composite + /// Ready-to-Run image. The manifest metadata root (STORAGESIGNATURE/STORAGEHEADER/STORAGESTREAM) + /// and the component assembly table (READYTORUN_COMPONENT_ASSEMBLIES_ENTRY) are packed arrays of + /// DWORD fields that the runtime reads in place, so their sections must start on a 4-byte + /// boundary. When they landed on an unaligned RVA the runtime faulted on ARM32 (which does not + /// permit unaligned multi-word loads) during coreclr_initialize; x64/arm64 tolerated it. + /// + [Fact] + public void CompositeManifestSectionsAreAligned() + { + var compositeLib = new CompiledAssembly + { + AssemblyName = "CompositeLib", + SourceResourceNames = ["CrossModuleInlining/Dependencies/CompositeLib.cs"], + }; + var compositeMain = new CompiledAssembly + { + AssemblyName = nameof(CompositeManifestSectionsAreAligned), + SourceResourceNames = ["CrossModuleInlining/CompositeBasic.cs"], + References = [compositeLib] + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(CompositeManifestSectionsAreAligned), + [ + new(nameof(CompositeManifestSectionsAreAligned), + [ + new CrossgenAssembly(compositeLib), + new CrossgenAssembly(compositeMain), + ]) + { + Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], + Validate = Validate, + }, + ])); + + static void Validate(ReadyToRunReader reader) + { + string diag; + Assert.True(R2RAssert.CompositeManifestSectionsAreAligned(reader, out diag), diag); + } + } + + /// + /// Complements using the same trigger as the + /// MVID-table test: --pdb emits an odd-sized debug directory section that shifts the manifest + /// sections off a 4-byte boundary without the fix. Windows-only because it relies on Windows PDB + /// generation. + /// + [ConditionalFact(nameof(IsWindows))] + public void CompositeManifestSectionsArePaddedWhenPdbPresent() + { + var compositeLib = new CompiledAssembly + { + AssemblyName = "CompositeLib", + SourceResourceNames = ["CrossModuleInlining/Dependencies/CompositeLib.cs"], + }; + var compositeMain = new CompiledAssembly + { + AssemblyName = nameof(CompositeManifestSectionsArePaddedWhenPdbPresent), + SourceResourceNames = ["CrossModuleInlining/CompositeBasic.cs"], + References = [compositeLib] + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(CompositeManifestSectionsArePaddedWhenPdbPresent), + [ + new(nameof(CompositeManifestSectionsArePaddedWhenPdbPresent), + [ + new CrossgenAssembly(compositeLib), + new CrossgenAssembly(compositeMain), + ]) + { + Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], + AdditionalArgs = ["--pdb"], + Validate = Validate, + }, + ])); + + static void Validate(ReadyToRunReader reader) + { + string diag; + Assert.True(R2RAssert.CompositeManifestSectionsAreAligned(reader, out diag), diag); + } + } + [Fact] public void RuntimeAsyncMethodEmission() { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs index 8a29c9de0bb128..9efea17f33f495 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs @@ -116,6 +116,26 @@ public static bool HasExpectedArmHotColdRuntimeFunctionTargets(ReadyToRunReader return result; } + /// + /// Returns true if the manifest metadata and component assembly tables in a composite image + /// start on 4-byte aligned RVAs. Both sections contain DWORD fields that the runtime reads + /// directly, so unaligned sections can fault on architectures such as 32-bit ARM. + /// + public static bool CompositeManifestSectionsAreAligned(ReadyToRunReader reader, out string diagnostic) + { + const int RequiredAlignment = 4; + var failures = new List(); + + bool result = true; + result &= SectionRVAIsAligned(reader, ReadyToRunSectionType.ManifestMetadata, RequiredAlignment, failures); + result &= SectionRVAIsAligned(reader, ReadyToRunSectionType.ComponentAssemblies, RequiredAlignment, failures); + + diagnostic = result + ? $"Composite manifest sections are {RequiredAlignment}-byte aligned." + : string.Join(Environment.NewLine, failures); + return result; + } + /// /// Returns true if the manifest assembly MVID table in a composite image is present, holds a /// whole number of 16-byte GUID entries, and starts on a 4-byte aligned RVA. The runtime reads @@ -150,6 +170,30 @@ public static bool ManifestAssemblyMvidsTableIsAligned(ReadyToRunReader reader, return failures.Count == 0; } + private static bool SectionRVAIsAligned(ReadyToRunReader reader, ReadyToRunSectionType sectionType, int requiredAlignment, List failures) + { + if (!reader.ReadyToRunHeader.Sections.TryGetValue(sectionType, out ReadyToRunSection section)) + { + failures.Add($"Expected {sectionType} section not found."); + return false; + } + + bool result = true; + if (section.Size <= 0) + { + failures.Add($"Expected {sectionType} section to be non-empty."); + result = false; + } + + if ((section.RelativeVirtualAddress % requiredAlignment) != 0) + { + failures.Add($"{sectionType} section RVA 0x{section.RelativeVirtualAddress:X8} should be aligned to {requiredAlignment} bytes."); + result = false; + } + + return result; + } + private static bool SectionRVAIsEven(ReadyToRunReader reader, ReadyToRunSectionType sectionType, List failures) { if (!reader.ReadyToRunHeader.Sections.TryGetValue(sectionType, out ReadyToRunSection section)) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AssemblyTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AssemblyTableNode.cs index e1e4ddfba80820..104c8b48a7c89d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AssemblyTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AssemblyTableNode.cs @@ -39,6 +39,7 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly); + builder.RequireInitialAlignment(sizeof(uint)); builder.AddSymbol(this); foreach (AssemblyHeaderNode assemblyHeader in _assemblyHeaders) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestMetadataTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestMetadataTableNode.cs index 162c54373ccaf7..ea617d1ca454c5 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestMetadataTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestMetadataTableNode.cs @@ -294,7 +294,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { if (relocsOnly) { - return new ObjectData(Array.Empty(), null, 1, null); + return new ObjectData(Array.Empty(), null, 4, null); } ComputeLastSetOfModuleIndices(); @@ -313,7 +313,8 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) return new ObjectData( data: _mutableModule.MetadataBlob, relocs: Array.Empty(), - alignment: 1, + // Metadata stream headers contain DWORD fields and require 4-byte alignment. + alignment: 4, definedSymbols: new ISymbolDefinitionNode[] { this }); }