fix(relinker): report RIP-relative operands of far indirect CALL and JMP

DecodeInstruction returned before reading the ModRM of FF /3 and FF /5,
so a far CALL or JMP through a RIP-relative pointer had no RIP-relative
displacement. A stub that moved such a JMP copied the displacement
without relocating it.

The decoder was compared with iced-x86 (AMD decoding) over 3.99 million
encodings: every opcode of the legacy, 0F, 0F38 and 0F3A maps under 34
prefix combinations and the VEX 0F, 0F38 and 0F3A maps, each with 48
ModRM forms. Apart from this, it agrees on length, RIP-relative offset,
control flow kind and branch target for every valid Zen 2 instruction
except near branches with a 66 prefix, recorded as technical debt, and
kernel-only returns, which it treats as sequential.

Add the comparison's metadata as cases: one RIP-relative encoding per
opcode, map and immediate size, every FF /r form, and the branch forms
with their flow kind and target. The existing cases check lengths only;
a loop or jrcxz decoded as an unconditional jump passed the whole suite.
This commit is contained in:
Alexej Szpevák committed 2026-10-14 11:33:59 +02:00
1 parent 43349d4337
commit 8e812f4b73
4 files changed
+821 -6

No files matched your search

@@ -1,4 +1,5 @@
#include "DecoderLengthCases.hpp"
#include "DecoderMetadataCases.hpp"
#include <codegen/CodegenException.hpp>
#include <codegen/x86/X64InstructionDecoder.hpp>
#include <cstdint>
@@ -77,6 +78,27 @@ int main() {
} catch (const Codegen::CodegenException&) {
}
}
for (const auto& testCase : kDecoderRipRelativeCases) {
const std::string name = testCase.Bytes;
auto bytes = parse(testCase.Bytes);
require(bytes.size() == testCase.Length, "RIP-relative case " + name + " does not hold its own length");
bytes.insert(bytes.end(), 15, 0x90);
require(decodedLength(decoder, bytes, name) == testCase.Length, "Wrong length for " + name);
const auto info = decoder.DecodeInstruction(bytes.data(), bytes.size());
require(info.HasRipRelativeDisp, "RIP-relative operand of " + name + " was not reported");
require(info.RipRelativeDispOffset == testCase.DisplacementOffset, "Wrong RIP-relative displacement offset for " + name);
}
for (const auto& testCase : kDecoderBranchCases) {
const std::string name = testCase.Bytes;
auto bytes = parse(testCase.Bytes);
require(bytes.size() == testCase.Length, "Branch case " + name + " does not hold its own length");
bytes.insert(bytes.end(), 15, 0x90);
const auto info = decoder.DecodeInstruction(bytes.data(), bytes.size());
require(info.Length == testCase.Length, "Wrong length for " + name);
require(info.FlowKind == testCase.FlowKind, "Wrong control flow kind for " + name);
require(info.HasBranchTarget == testCase.HasBranchTarget, "Wrong branch target presence for " + name);
require(!testCase.HasBranchTarget || info.BranchDisp == testCase.BranchDisp, "Wrong branch displacement for " + name);
}
const auto threeDNow = parse("0f0f0d11223344a6");
const auto threeDNowInfo = decoder.DecodeInstruction(threeDNow.data(), threeDNow.size());
require(threeDNowInfo.HasModRm && threeDNowInfo.ModRmByte == 0x0d, "3DNow! ModRM was not reported");