From 42c5d7b23a4e4664e9278e27f341c0928353a074 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 4 Jun 2026 15:09:02 -0700 Subject: [PATCH 1/6] Align manifest assembly MVID table to 4 bytes in composite R2R images ManifestAssemblyMvidHeaderNode emitted the assembly MVID table (a packed array of 16-byte GUIDs) with alignment 1. The CoreCLR runtime reads each entry as a GUID by value, and GUID has a natural alignment of 4. When the table landed on a non-4-aligned RVA, 32-bit ARM faulted with BUS_ADRALN (SIGBUS) because it does not permit unaligned multi-word loads. Other architectures tolerated the unaligned access, so the crash only manifested on ARM32. The table's RVA depends on the size of the preceding Ordered ReadOnlyData node. In composites built with debug directory entries (e.g. --pdb), an odd-sized NativeDebugDirectoryEntryNode can precede the MVID table and push it off a 4-byte boundary. Setting the alignment to 4 pads the table to a valid boundary regardless of predecessor size. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ReadyToRun/ManifestAssemblyMvidHeaderNode.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs index 96ce435250cdcb..9d2e2f0f3b2d54 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs @@ -56,7 +56,13 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) } byte[] manifestAssemblyMvidTable = _manifestNode.GetManifestAssemblyMvidTableData(); - return new ObjectData(manifestAssemblyMvidTable, Array.Empty(), alignment: 1, new ISymbolDefinitionNode[] { this }); + + // The runtime reads each entry of this table as a GUID by value (see + // ReadyToRunInfo::ReadyToRunInfo in readytoruninfo.cpp). GUID has a natural + // alignment of 4, so the table must be at least 4-byte aligned. Without this, + // a misaligned base causes an alignment fault (SIGBUS) on architectures that + // do not permit unaligned multi-word loads, such as 32-bit ARM. + return new ObjectData(manifestAssemblyMvidTable, Array.Empty(), alignment: 4, new ISymbolDefinitionNode[] { this }); } } } From f57ef581cb855c5b6e472b0691529111ef3ad8e3 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 5 Jun 2026 09:28:20 -0700 Subject: [PATCH 2/6] Update src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs Co-authored-by: Jan Kotas --- .../ReadyToRun/ManifestAssemblyMvidHeaderNode.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs index 9d2e2f0f3b2d54..5d59c55f4ce2cd 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs @@ -57,11 +57,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) byte[] manifestAssemblyMvidTable = _manifestNode.GetManifestAssemblyMvidTableData(); - // The runtime reads each entry of this table as a GUID by value (see - // ReadyToRunInfo::ReadyToRunInfo in readytoruninfo.cpp). GUID has a natural - // alignment of 4, so the table must be at least 4-byte aligned. Without this, - // a misaligned base causes an alignment fault (SIGBUS) on architectures that - // do not permit unaligned multi-word loads, such as 32-bit ARM. + // GUID has a natural alignment of 4, so the table must be at least 4-byte aligned. return new ObjectData(manifestAssemblyMvidTable, Array.Empty(), alignment: 4, new ISymbolDefinitionNode[] { this }); } } From 70b813e1a77f8232538967c53037c00649f770c1 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 5 Jun 2026 09:48:08 -0700 Subject: [PATCH 3/6] Add regression test for ManifestAssemblyMvids table alignment Locks the 4-byte alignment contract of the manifest assembly MVID table in composite R2R images. The runtime reads each entry as a GUID by value, so a misaligned table causes alignment faults (SIGBUS) on 32-bit ARM. Adds the R2RAssert.ManifestAssemblyMvidsTableIsAligned helper and a composite test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TestCases/R2RTestSuites.cs | 39 +++++++++++++++++++ .../TestCasesRunner/R2RResultChecker.cs | 34 ++++++++++++++++ 2 files changed, 73 insertions(+) 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 2dd6625bdd286f..a1face46447293 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -320,6 +320,45 @@ static void Validate(ReadyToRunReader reader) } } + [Fact] + public void CompositeManifestAssemblyMvidsAreAligned() + { + var compositeLib = new CompiledAssembly + { + AssemblyName = "MvidCompositeLib", + SourceResourceNames = ["CrossModuleInlining/Dependencies/CompositeLib.cs"], + }; + var compositeMain = new CompiledAssembly + { + AssemblyName = nameof(CompositeManifestAssemblyMvidsAreAligned), + SourceResourceNames = ["CrossModuleInlining/CompositeBasic.cs"], + References = [compositeLib] + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(CompositeManifestAssemblyMvidsAreAligned), + [ + new(nameof(CompositeManifestAssemblyMvidsAreAligned), + [ + new CrossgenAssembly(compositeLib), + new CrossgenAssembly(compositeMain), + ]) + { + Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], + Validate = Validate, + }, + ])); + + // The manifest assembly MVID table is read by the runtime as an array of GUIDs by value, + // which requires 4-byte alignment. Lock that contract so future node ordering/size changes + // cannot reintroduce a misaligned table (an ARM32 alignment fault / SIGBUS). + static void Validate(ReadyToRunReader reader) + { + string diag; + Assert.True(R2RAssert.ManifestAssemblyMvidsTableIsAligned(reader, alignment: 4, 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 05db82b928afad..0a38f4ad8c775d 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,40 @@ public static bool HasExpectedArmHotColdRuntimeFunctionTargets(ReadyToRunReader 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 an RVA aligned to . + /// The runtime reads each entry as a GUID by value, so the table must be at least 4-byte aligned + /// to avoid alignment faults (SIGBUS) on architectures such as 32-bit ARM that do not permit + /// unaligned multi-word loads. + /// + public static bool ManifestAssemblyMvidsTableIsAligned(ReadyToRunReader reader, int alignment, out string diagnostic) + { + const int GuidByteSize = 16; + + if (!reader.ReadyToRunHeader.Sections.TryGetValue(ReadyToRunSectionType.ManifestAssemblyMvids, out ReadyToRunSection section)) + { + diagnostic = "Expected ManifestAssemblyMvids section not found."; + return false; + } + + var failures = new List(); + + if (section.Size <= 0) + failures.Add("Expected ManifestAssemblyMvids section to be non-empty."); + + if (section.Size % GuidByteSize != 0) + failures.Add($"ManifestAssemblyMvids section size {section.Size} should be a multiple of {GuidByteSize} (a table of GUIDs)."); + + if ((section.RelativeVirtualAddress % alignment) != 0) + failures.Add($"ManifestAssemblyMvids section RVA 0x{section.RelativeVirtualAddress:X8} should be aligned to {alignment} bytes."); + + diagnostic = failures.Count == 0 + ? $"ManifestAssemblyMvids table is {alignment}-byte aligned ({section.Size / GuidByteSize} entries)." + : string.Join(Environment.NewLine, failures); + return failures.Count == 0; + } + private static bool SectionRVAIsEven(ReadyToRunReader reader, ReadyToRunSectionType sectionType, List failures) { if (!reader.ReadyToRunHeader.Sections.TryGetValue(sectionType, out ReadyToRunSection section)) From f3ed7173b8a114d84f03702ce3dbf2bae5937959 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:05:31 -0700 Subject: [PATCH 4/6] Add Windows-only --pdb red-before-green test for MVID alignment The cross-platform invariant test cannot reproduce the original misalignment because a plain composite always lays the MVID table 4-byte aligned. Passing --pdb injects an odd-sized NativeDebugDirectoryEntryNode immediately before the table, which is the exact layout that exposed the bug: without the fix the MVID table lands on a non-4-aligned RVA (verified: RVA 0x107AF unfixed vs 0x107B0 fixed for these inputs). --pdb writes the NI PDB through Microsoft.DiaSymReader.Native, which only exists on Windows, so the test is gated with [ConditionalFact(nameof(IsWindows))] and skips elsewhere. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TestCases/R2RTestSuites.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) 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 a1face46447293..5c6ecb9f16f9b1 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -359,6 +359,52 @@ static void Validate(ReadyToRunReader reader) } } + public static bool IsWindows => System.OperatingSystem.IsWindows(); + + [ConditionalFact(nameof(IsWindows))] + public void CompositeManifestAssemblyMvidsArePaddedWhenPdbPresent() + { + var compositeLib = new CompiledAssembly + { + AssemblyName = "MvidCompositeLib", + SourceResourceNames = ["CrossModuleInlining/Dependencies/CompositeLib.cs"], + }; + var compositeMain = new CompiledAssembly + { + AssemblyName = nameof(CompositeManifestAssemblyMvidsArePaddedWhenPdbPresent), + SourceResourceNames = ["CrossModuleInlining/CompositeBasic.cs"], + References = [compositeLib] + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(CompositeManifestAssemblyMvidsArePaddedWhenPdbPresent), + [ + new(nameof(CompositeManifestAssemblyMvidsArePaddedWhenPdbPresent), + [ + new CrossgenAssembly(compositeLib), + new CrossgenAssembly(compositeMain), + ]) + { + // --pdb injects an odd-sized NativeDebugDirectoryEntryNode immediately before the + // MVID table, which is the layout that originally exposed the misalignment bug: + // without the alignment fix the table starts on a non-4-aligned RVA. (--pdb writes + // the NI PDB via Microsoft.DiaSymReader.Native, which only exists on Windows, hence + // the Windows gate above.) + Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], + AdditionalArgs = ["--pdb"], + Validate = Validate, + }, + ])); + + // Even with the odd-sized debug directory entry pushing the table, the MVID GUIDs must stay + // 4-byte aligned so the runtime can read them by value without faulting on 32-bit ARM. + static void Validate(ReadyToRunReader reader) + { + string diag; + Assert.True(R2RAssert.ManifestAssemblyMvidsTableIsAligned(reader, alignment: 4, out diag), diag); + } + } + [Fact] public void RuntimeAsyncMethodEmission() { From 183e2c3063f3898f3b5205618ae249afebb0b918 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 5 Jun 2026 11:08:52 -0700 Subject: [PATCH 5/6] Address review feedback: trim comments, drop alignment parameter Remove redundant comments where the assertion is self-describing, condense the --pdb rationale, and replace the always-4 alignment parameter with a RequiredAlignment constant so the helper's contract matches its documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TestCases/R2RTestSuites.cs | 16 ++++------------ .../TestCasesRunner/R2RResultChecker.cs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 20 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 5c6ecb9f16f9b1..c93a764f15230e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -349,13 +349,10 @@ public void CompositeManifestAssemblyMvidsAreAligned() }, ])); - // The manifest assembly MVID table is read by the runtime as an array of GUIDs by value, - // which requires 4-byte alignment. Lock that contract so future node ordering/size changes - // cannot reintroduce a misaligned table (an ARM32 alignment fault / SIGBUS). static void Validate(ReadyToRunReader reader) { string diag; - Assert.True(R2RAssert.ManifestAssemblyMvidsTableIsAligned(reader, alignment: 4, out diag), diag); + Assert.True(R2RAssert.ManifestAssemblyMvidsTableIsAligned(reader, out diag), diag); } } @@ -385,23 +382,18 @@ public void CompositeManifestAssemblyMvidsArePaddedWhenPdbPresent() new CrossgenAssembly(compositeMain), ]) { - // --pdb injects an odd-sized NativeDebugDirectoryEntryNode immediately before the - // MVID table, which is the layout that originally exposed the misalignment bug: - // without the alignment fix the table starts on a non-4-aligned RVA. (--pdb writes - // the NI PDB via Microsoft.DiaSymReader.Native, which only exists on Windows, hence - // the Windows gate above.) + // --pdb creates an odd-sized debug directory section that exposes the MVID table + // misalignment bug. Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], AdditionalArgs = ["--pdb"], Validate = Validate, }, ])); - // Even with the odd-sized debug directory entry pushing the table, the MVID GUIDs must stay - // 4-byte aligned so the runtime can read them by value without faulting on 32-bit ARM. static void Validate(ReadyToRunReader reader) { string diag; - Assert.True(R2RAssert.ManifestAssemblyMvidsTableIsAligned(reader, alignment: 4, out diag), diag); + Assert.True(R2RAssert.ManifestAssemblyMvidsTableIsAligned(reader, out diag), diag); } } 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 0a38f4ad8c775d..2b543617fe14b4 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs @@ -118,14 +118,14 @@ public static bool HasExpectedArmHotColdRuntimeFunctionTargets(ReadyToRunReader /// /// 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 an RVA aligned to . - /// The runtime reads each entry as a GUID by value, so the table must be at least 4-byte aligned - /// to avoid alignment faults (SIGBUS) on architectures such as 32-bit ARM that do not permit - /// unaligned multi-word loads. + /// whole number of 16-byte GUID entries, and starts on a 4-byte aligned RVA. The runtime reads + /// each entry as a GUID by value, so the table must be 4-byte aligned to avoid alignment faults + /// (SIGBUS) on architectures such as 32-bit ARM that do not permit unaligned multi-word loads. /// - public static bool ManifestAssemblyMvidsTableIsAligned(ReadyToRunReader reader, int alignment, out string diagnostic) + public static bool ManifestAssemblyMvidsTableIsAligned(ReadyToRunReader reader, out string diagnostic) { const int GuidByteSize = 16; + const int RequiredAlignment = 4; if (!reader.ReadyToRunHeader.Sections.TryGetValue(ReadyToRunSectionType.ManifestAssemblyMvids, out ReadyToRunSection section)) { @@ -141,11 +141,11 @@ public static bool ManifestAssemblyMvidsTableIsAligned(ReadyToRunReader reader, if (section.Size % GuidByteSize != 0) failures.Add($"ManifestAssemblyMvids section size {section.Size} should be a multiple of {GuidByteSize} (a table of GUIDs)."); - if ((section.RelativeVirtualAddress % alignment) != 0) - failures.Add($"ManifestAssemblyMvids section RVA 0x{section.RelativeVirtualAddress:X8} should be aligned to {alignment} bytes."); + if ((section.RelativeVirtualAddress % RequiredAlignment) != 0) + failures.Add($"ManifestAssemblyMvids section RVA 0x{section.RelativeVirtualAddress:X8} should be aligned to {RequiredAlignment} bytes."); diagnostic = failures.Count == 0 - ? $"ManifestAssemblyMvids table is {alignment}-byte aligned ({section.Size / GuidByteSize} entries)." + ? $"ManifestAssemblyMvids table is {RequiredAlignment}-byte aligned ({section.Size / GuidByteSize} entries)." : string.Join(Environment.NewLine, failures); return failures.Count == 0; } From caf0b1aaeb184e4009b527189bd3655d5126133b Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 5 Jun 2026 11:12:47 -0700 Subject: [PATCH 6/6] Document that the test name length is load-bearing for --pdb red-before-green The odd-sized native debug directory entry that exposes the misalignment derives from the composite output name length, so a rename can silently move the MVID table back onto a 4-byte boundary. Warn future maintainers to re-verify. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 c93a764f15230e..061abd0b02f11b 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -383,7 +383,10 @@ public void CompositeManifestAssemblyMvidsArePaddedWhenPdbPresent() ]) { // --pdb creates an odd-sized debug directory section that exposes the MVID table - // misalignment bug. + // misalignment bug. The odd size derives from the composite output name length, so + // renaming this test can shift the table back onto a 4-byte boundary and silently + // neutralize the regression coverage; verify it still misaligns without the fix if + // the name changes. Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], AdditionalArgs = ["--pdb"], Validate = Validate,