Skip to content

Align manifest assembly MVID table to 4 bytes in composite R2R images - #129017

Merged
jtschuster merged 7 commits into
dotnet:mainfrom
jtschuster:jtschuster/ideal-funicular
Jun 11, 2026
Merged

Align manifest assembly MVID table to 4 bytes in composite R2R images#129017
jtschuster merged 7 commits into
dotnet:mainfrom
jtschuster:jtschuster/ideal-funicular

Conversation

@jtschuster

@jtschuster jtschuster commented Jun 4, 2026

Copy link
Copy Markdown
Member

Summary

Fixes a 32-bit ARM SIGBUS (alignment fault) when running composite Ready-to-Run images.

ManifestAssemblyMvidHeaderNode emits 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 (ReadyToRunInfo in readytoruninfo.cpp), and GUID has a natural alignment of 4. When the table landed on a non-4-aligned RVA, ARM32 faulted with BUS_ADRALN because it does not permit unaligned multi-word loads. x64/arm64 tolerate the unaligned access, so the crash only manifested on ARM32.

Why the table could land unaligned

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 (RSDS record) sorts between RuntimeFunctionsTableNode and the MVID table and can push it off a 4-byte boundary.

The fix

Set the node alignment to 4 so the table is padded to a valid boundary regardless of predecessor size.

Validation

Reproduced on current main with a controlled experiment (identical inputs and output name, so the predecessor node is byte-for-byte identical; the only variable is the alignment value):

NativeDebugDirectoryEntryNode @ 0x6D440, size 0x22 (34 ≡ 2 mod 4) -> ends 0x6D462
  alignment:1 (before):  MVID @ 0x6D462   &3 = 2  -> UNALIGNED -> SIGBUS
  alignment:4 (after):   MVID @ 0x6D464   &3 = 0  -> ALIGNED (2 bytes padding)

This matches the structure of a real failing ARM32 image, whose MVID table sat at RVA 0x28f49 (&3 = 1), consistent with the fault's si_addr in the kernel core.

Note

This pull request was authored with assistance from GitHub Copilot.

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>
Copilot AI review requested due to automatic review settings June 4, 2026 22:10
@github-actions github-actions Bot added the area-crossgen2-coreclr only use for closed issues label Jun 4, 2026
@jtschuster

Copy link
Copy Markdown
Member Author

/azp run runtime-coreclr crossgen2-composite

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the ReadyToRun (crossgen2) emitter for composite images so the manifest assembly MVID table is emitted with 4-byte alignment, preventing potential unaligned GUID loads by the CoreCLR runtime on alignment-sensitive architectures (notably 32-bit ARM).

Changes:

  • Change ManifestAssemblyMvidHeaderNode output alignment from 1 to 4 bytes.
  • Add an explanatory comment documenting the runtime read pattern and the alignment requirement.

…yAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs

Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Copilot AI review requested due to automatic review settings June 5, 2026 16:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

jtschuster and others added 5 commits June 5, 2026 09:48
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>
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>
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>
…re-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>
Copilot AI review requested due to automatic review settings June 5, 2026 20:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

@jtschuster
jtschuster requested a review from davidwrighton June 5, 2026 22:08
@jtschuster jtschuster added area-ReadyToRun and removed area-crossgen2-coreclr only use for closed issues labels Jun 5, 2026
@github-project-automation github-project-automation Bot moved this to Done in AppModel Jun 11, 2026
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview6 milestone Jun 17, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…#129017)

## Summary

Fixes an ARM32 alignment fault when running composite
Ready-to-Run images.

`ManifestAssemblyMvidHeaderNode` emits 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 (`ReadyToRunInfo` in
`readytoruninfo.cpp`), and `GUID` has a natural alignment of 4. When the
table landed on a non-4-byte-aligned RVA, ARM32 faulted with `BUS_ADRALN`
because it does not permit unaligned multi-word loads. x64/arm64
tolerate the unaligned access, so the crash only manifested on ARM32. In addition,
the alignment of preceding sections would typically end at a 4-byte-aligned offset,
so this only manifested when the --pdb option was used and the NativeDebugDirectoryEntryNode
section was an odd number.

The fix is simply to use `alignment: 4` for the section.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
simonrozsival added a commit that referenced this pull request Jul 16, 2026
…ns to 4 bytes (#130564)

Follow-up to #129017.

## Summary

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` (the metadata
root) and `AssemblyTableNode` (`ComponentAssemblies`) have the identical
latent bug: both are packed arrays of `DWORD`-typed fields that the
CoreCLR runtime reads in place, but both are emitted with `alignment:
1`, so they can land on a non-4-aligned RVA. On ARM32 that faults with
`BUS_ADRALN` (unaligned multi-word load); x64/arm64 tolerate it, so it
stayed latent until android-arm composite R2R was exercised.

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`
during `coreclr_initialize`. So #129017 was necessary but not sufficient
for this configuration; this change completes it by aligning the
remaining manifest-region sections.

## Root cause

Loading a composite image parses the metadata root via
`PEDecoder::CheckCorHeader`, which walks `STORAGESTREAM` headers (`ULONG
iOffset; ULONG iSize; char rcName[]`). The compiler coalesces the two
adjacent `DWORD` reads (`iOffset`/`iSize`) into a single `LDRD`, which
requires 4-byte alignment on ARM32:

```
Fatal signal 7 (SIGBUS), code 1 (BUS_ADRALN)
  #00 pc ... libcoreclr.so  PEDecoder::CheckCorHeader() const   (pedecoder.cpp)
  ...
  #23 pc ... libcoreclr.so  coreclr_initialize
```

`ComponentAssemblies` (`READYTORUN_COMPONENT_ASSEMBLIES_ENTRY`) is
likewise a packed `DWORD` array read in place, and the regression test
shows it landing unaligned without the fix.

## Provenance

`alignment: 1` on `ManifestMetadataTableNode` dates back to the initial
population of crossgen2 from the CoreRT sources (`ac857a2e`, 2019) and
was never a deliberate choice — no commit or discussion in the file's
history addressed alignment; the value was carried verbatim through
later refactors (e.g. #71271 only switched to named arguments). The
sibling `ManifestAssemblyMvidHeaderNode` shared the same default and was
corrected in #129017.

## Why 4, and why not configurable / a size concern

- The metadata root and component-assembly entries are all `DWORD`
fields whose natural alignment is 4; ECMA-335 already pads the metadata
version string so stream headers are 4-aligned within the blob. 4-byte
section-start alignment is the correct requirement (same rationale as
#129017's "GUID has a natural alignment of 4").
- No meaningful size impact: these are singleton sections (once per
composite image), so the change adds at most 3 padding bytes per
section.
- Not a user-facing knob: every other crossgen2 section uses a fixed
natural alignment (RuntimeFunctions=4, GCRefMap=4, …); a configurable
"may emit a misaligned image" switch would be a footgun, so this stays a
fixed value.

## Testing

Mirrors #129017's test approach: a
`CompositeManifestSectionsAreAligned(...)` checker in
`R2RResultChecker`, driven by a plain `[Fact]` and a Windows-only
`[ConditionalFact(IsWindows)]` `--pdb` variant that uses the same
composite inputs.

## Validation

Mirrors #129017: the `--pdb` variant emits an odd-sized
`NativeDebugDirectoryEntryNode` that, without the fix, shifts the
manifest sections off a 4-byte boundary; with the fix they are padded to
a 4-byte RVA. Verified end-to-end by building an android-arm CoreCLR
composite R2R APK with the fixed crossgen2 — R2RDump confirms
`ManifestMetadata`, `ComponentAssemblies`, and `ManifestAssemblyMvids`
all start on 4-byte-aligned RVAs — resolving the crash reported in
dotnet/android#12026.

cc @jtschuster (author of the sibling fix #129017)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants