From 40c16ba51bb77bb9dcba35fe9fdd32228d5edc3e Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Sat, 25 Jul 2026 22:00:37 -0400 Subject: [PATCH] fix: teach Arm64 disassembler about .NET 10 DMB-prefixed stubs .NET 10 prefixes AArch64 precode/stub shapes (at least FixupPrecodeCode_Fixup) with a leading DMB ISH for concurrent stub-patching safety. The existing TryResolvePrecode / TryFollowJumpTrampoline read a fixed 12-byte window and match a 3-instruction pattern, so the barrier caused every prefixed stub to miss. Symptom: after landing on a benchmark-method call site under CsProjCoreToolchain.NetCoreApp10_0 on ARM64, the disassembler stopped instead of chasing into the target, so DisassemblyDiagnoser only recovered the wrapper method (surfacing as failures in tests like CanDisassembleInlinableBenchmarks once the toolchain is bumped to .NET 10). Add TryReadStubHead: read up to 16 bytes, detect DMB ISH (0xD50339BF) at instr0, skip 4 bytes and return an effective parseBase so LDR-literal offset math stays correct. Use it from both stub-parsing entry points; slot addresses now use parseBase instead of address. TryFollowJumpTrampoline's short 'B imm26' path is intentionally left as a 4-byte read since the barrier is only emitted for precode/stub shapes, not JIT-emitted jump trampolines. Verified on Ubuntu 24.04 ARM64 GitHub-hosted runner (.NET SDK 10.0.302 / runtime 10.0.10): the full DisassemblyDiagnoserTests suite passes end-to-end under both InProcessEmit and CsProjCore .NET 10 toolchains. --- .../Disassemblers/Arm64Disassembler.cs | 75 ++++++++++++------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs b/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs index 3899e91b19..d8c1486793 100644 --- a/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs +++ b/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs @@ -197,20 +197,15 @@ protected override IEnumerable Decode(byte[] code, ulong startAddress, Stat private static bool TryResolvePrecode(IDataReader reader, ref ulong address, out bool isPrestubMD) { isPrestubMD = false; - byte[] buffer = new byte[12]; - if (reader.Read(address, buffer) != 12) + if (!TryReadStubHead(reader, address, out ulong parseBase, out uint instr0, out uint instr1, out uint instr2)) return false; - uint instr0 = ReadInstr(buffer, 0); - uint instr1 = ReadInstr(buffer, 4); - uint instr2 = ReadInstr(buffer, 8); - // StubPrecode: LDR x10, Target ; LDR x12, MethodDesc ; BR x10 if (IsLdrLiteral64(instr0, out int rt0, out int _) && rt0 == 10 && IsLdrLiteral64(instr1, out int rt1, out int off1) && rt1 == 12 && instr2 == 0xD61F0140u) { - ulong mdSlot = unchecked(address + 4 + (ulong)(long)off1); + ulong mdSlot = unchecked(parseBase + 4 + (ulong)(long)off1); if (reader.ReadPointer(mdSlot, out ulong md) && IsValidAddress(md)) { address = md; @@ -225,7 +220,7 @@ private static bool TryResolvePrecode(IDataReader reader, ref ulong address, out && instr1 == 0xD61F0160u && IsLdrLiteral64(instr2, out int rtB, out int off2) && rtB == 12) { - ulong mdSlot = unchecked(address + 8 + (ulong)(long)off2); + ulong mdSlot = unchecked(parseBase + 8 + (ulong)(long)off2); if (reader.ReadPointer(mdSlot, out ulong md) && IsValidAddress(md)) { address = md; @@ -243,7 +238,7 @@ private static bool TryResolvePrecode(IDataReader reader, ref ulong address, out && IsLdrLiteral64(instr1, out int rtF1, out int _) && rtF1 == 11 && instr2 == 0xD61F0160u) { - ulong mdSlot = unchecked(address + (ulong)(long)offF0); + ulong mdSlot = unchecked(parseBase + (ulong)(long)offF0); if (reader.ReadPointer(mdSlot, out ulong md) && IsValidAddress(md)) { address = md; @@ -260,7 +255,7 @@ private static bool TryResolvePrecode(IDataReader reader, ref ulong address, out && instr1 == 0x7940012Au && instr2 == 0x7100054Au) { - ulong countSlot = unchecked(address + (ulong)(long)offCount); + ulong countSlot = unchecked(parseBase + (ulong)(long)offCount); if (reader.ReadPointer(countSlot + 8, out ulong target) && IsValidAddress(target)) { address = target; @@ -272,6 +267,39 @@ private static bool TryResolvePrecode(IDataReader reader, ref ulong address, out return false; } + // .NET 10 prefixes some AArch64 precode/stub shapes with `DMB ISH` for concurrent + // stub-patching safety. Detect and skip the barrier so the existing 3-instruction pattern + // match still works, and return the effective PC of the first real stub instruction so + // LDR-literal offsets are calculated relative to it. + // Encoding: DMB ISH = 0xD50339BF. + private const uint DmbIshInstr = 0xD50339BFu; + + private static bool TryReadStubHead(IDataReader reader, ulong address, out ulong parseBase, out uint instr0, out uint instr1, out uint instr2) + { + parseBase = address; + instr0 = instr1 = instr2 = 0; + + byte[] buffer = new byte[16]; + int read = reader.Read(address, buffer); + if (read < 12) + return false; + + int offset = 0; + uint first = ReadInstr(buffer, 0); + if (first == DmbIshInstr) + { + if (read < 16) + return false; + offset = 4; + parseBase = address + 4; + } + + instr0 = ReadInstr(buffer, offset + 0); + instr1 = ReadInstr(buffer, offset + 4); + instr2 = ReadInstr(buffer, offset + 8); + return true; + } + private static uint ReadInstr(byte[] buffer, int offset) => (uint)buffer[offset] | ((uint)buffer[offset + 1] << 8) @@ -346,17 +374,16 @@ protected override bool TryFollowJumpTrampoline(State state, ulong address, out { target = 0; IDataReader dataReader = state.Runtime.DataTarget.DataReader; - byte[] buffer = new byte[12]; - int read = dataReader.Read(address, buffer); - if (read < 4) - return false; - - uint instr0 = ReadInstr(buffer, 0); - // B imm26 — bits[31:26] == 0b000101 (0x5) - if ((instr0 >> 26) == 0x5) + // B imm26 shape is 4 bytes and cannot be prefixed with DMB (the barrier is only + // emitted for precode/stub shapes, not for JIT-emitted jump trampolines). + byte[] head = new byte[4]; + if (dataReader.Read(address, head) < 4) + return false; + uint firstInstr = ReadInstr(head, 0); + if ((firstInstr >> 26) == 0x5) { - uint imm26 = instr0 & 0x03FFFFFFu; + uint imm26 = firstInstr & 0x03FFFFFFu; // Sign-extend the 26-bit immediate to 32 bits, then multiply by 4 (instructions are 4-byte aligned). int offset = (int)(imm26 & 0x02000000u) != 0 ? unchecked((int)(imm26 | 0xFC000000u)) << 2 @@ -365,17 +392,15 @@ protected override bool TryFollowJumpTrampoline(State state, ulong address, out return IsValidAddress(target); } - if (read < 12) + if (!TryReadStubHead(dataReader, address, out ulong parseBase, out uint instr0, out uint instr1, out uint instr2)) return false; - uint instr1 = ReadInstr(buffer, 4); - uint instr2 = ReadInstr(buffer, 8); // StubPrecode: LDR x10, Target ; LDR x12, MethodDesc ; BR x10. Follow the first LDR. if (IsLdrLiteral64(instr0, out int rt0, out int off0) && rt0 == 10 && IsLdrLiteral64(instr1, out int rt1, out int _) && rt1 == 12 && instr2 == 0xD61F0140u) { - ulong targetSlot = unchecked(address + (ulong)(long)off0); + ulong targetSlot = unchecked(parseBase + (ulong)(long)off0); if (dataReader.ReadPointer(targetSlot, out target) && IsValidAddress(target)) return true; target = 0; @@ -387,7 +412,7 @@ protected override bool TryFollowJumpTrampoline(State state, ulong address, out && instr1 == 0xD61F0160u && IsLdrLiteral64(instr2, out int rtB, out int _) && rtB == 12) { - ulong targetSlot = unchecked(address + (ulong)(long)offA); + ulong targetSlot = unchecked(parseBase + (ulong)(long)offA); if (dataReader.ReadPointer(targetSlot, out target) && IsValidAddress(target)) return true; target = 0; @@ -400,7 +425,7 @@ protected override bool TryFollowJumpTrampoline(State state, ulong address, out && instr1 == 0x7940012Au && instr2 == 0x7100054Au) { - ulong countSlot = unchecked(address + (ulong)(long)offCount); + ulong countSlot = unchecked(parseBase + (ulong)(long)offCount); if (dataReader.ReadPointer(countSlot + 8, out target) && IsValidAddress(target)) return true; target = 0;