Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,29 @@ protected override IEnumerable<Asm> Decode(byte[] code, ulong startAddress, Stat
var decoder = Decoder.Create(state.Runtime.DataTarget.DataReader.PointerSize * 8, reader);
decoder.IP = startAddress;

// Track values loaded by MOV reg, imm64 so we can resolve subsequent CALL/JMP through
// the register (e.g. `mov rax, imm64; call qword ptr [rax]`). The JIT emits this
// 2-instruction form instead of a RIP-relative `call [rip+disp32]` when the target
// slot is out of ±2 GB range from the call site — common under fragmented layouts.
var trackedRegs = new System.Collections.Generic.Dictionary<Register, ulong>();

while (reader.CanReadByte)
{
decoder.Decode(out var instruction);

UpdateRegisterTracking(instruction, trackedRegs);

bool isIndirect = instruction.IsCallFarIndirect || instruction.IsCallNearIndirect || instruction.IsJmpFarIndirect || instruction.IsJmpNearIndirect;
bool isPrestubMD = false;

ulong address = 0;
if (TryGetReferencedAddress(instruction, (uint)state.Runtime.DataTarget.DataReader.PointerSize, out address))
bool haveAddress = TryGetReferencedAddress(instruction, (uint)state.Runtime.DataTarget.DataReader.PointerSize, out address);
if (!haveAddress && isIndirect
&& TryResolveIndirectThroughTrackedReg(instruction, trackedRegs, out address))
{
haveAddress = true;
}
if (haveAddress)
{
if (isIndirect)
{
Expand Down Expand Up @@ -132,6 +146,52 @@ private static bool TryResolvePrecode(IDataReader reader, ref ulong address, out
return false;
}

// Update the register-value tracker as we walk the instruction stream. We only track
// constants loaded by `MOV reg, imm64` (and its 32-bit-immediate sign-extended forms).
// Any other write to a register invalidates that entry. This is intentionally simple:
// it handles the common JIT-emitted `MOV reg, imm64 ; CALL/JMP [reg]` pattern without
// trying to model general dataflow.
private static void UpdateRegisterTracking(Instruction instruction, System.Collections.Generic.Dictionary<Register, ulong> trackedRegs)
{
if (instruction.Mnemonic == Mnemonic.Mov
&& instruction.OpCount == 2
&& instruction.Op0Kind == OpKind.Register
&& (instruction.Op1Kind == OpKind.Immediate64
|| instruction.Op1Kind == OpKind.Immediate32to64
|| instruction.Op1Kind == OpKind.Immediate32))
{
trackedRegs[instruction.Op0Register] = instruction.GetImmediate(1);
return;
}

// Conservative invalidation: any instruction whose first operand is a register
// clobbers our tracked value for that register.
if (instruction.OpCount > 0 && instruction.Op0Kind == OpKind.Register)
{
trackedRegs.Remove(instruction.Op0Register);
}
}

// Handle `CALL/JMP qword ptr [reg]` (or `[reg+disp]`) where we've previously tracked
// `reg` via a `MOV reg, imm64`. In that case the referenced slot lives at `reg + disp`,
// which the base-line `TryGetReferencedAddress` can't compute since MemoryDisplacement64
// there is just `disp`, not `reg + disp`.
private static bool TryResolveIndirectThroughTrackedReg(Instruction instruction, System.Collections.Generic.Dictionary<Register, ulong> trackedRegs, out ulong slotAddress)
{
slotAddress = 0;
if (instruction.OpCount == 0 || instruction.Op0Kind != OpKind.Memory)
return false;
if (instruction.IsIPRelativeMemoryOperand)
return false;
Register memBase = instruction.MemoryBase;
if (memBase == Register.None || instruction.MemoryIndex != Register.None)
return false;
if (!trackedRegs.TryGetValue(memBase, out ulong baseValue))
return false;
slotAddress = baseValue + instruction.MemoryDisplacement64;
return slotAddress > ushort.MaxValue;
}

private static bool TryGetReferencedAddress(Instruction instruction, uint pointerSize, out ulong referencedAddress)
{
for (int i = 0; i < instruction.OpCount; i++)
Expand Down