From 458d981f31c5a77e5b06e0a6b08e1e4b608bc522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Wed, 7 Oct 2026 02:25:36 +0200 Subject: [PATCH] feat(disasm): name the CLDEMOTE encoding the decoder refuses The hint NOP opcode 0F 1C /r with a memory operand is CLDEMOTE, a memory-only instruction the toolchain's own table carries; the decoder rejects the encoding instead of naming it. The rejected-encoding side of the supplementary table names it from the bytes, and the corpus row 0f1c03 pins the text in the unlisted fixture, round trip byte exact. Assisted-by: GLM 5.3 Flash --- disasm/disasm.go | 6 ++++++ disasm/naming_amd64.go | 21 +++++++++++++++++++++ disasm/naming_amd64_test.go | 11 ++++++++--- disasm/testdata/parity_amd64_unlisted.txt | 1 + 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/disasm/disasm.go b/disasm/disasm.go index 8a59308..aad4aac 100644 --- a/disasm/disasm.go +++ b/disasm/disasm.go @@ -68,6 +68,12 @@ func Decode(a arch.Arch, code []byte, addr uint64) (Instruction, error) { default: // amd64 inst, err := x86asm.Decode(code, 64) if err != nil { + // A few named families the decoder refuses outright live in + // the same supplementary table; anything else keeps the + // placeholder. + if text, n, ok := nameAMD64Rejected(code); ok { + return Instruction{Addr: addr, Text: text, Len: n}, nil + } return Instruction{Addr: addr, Text: "???", Len: 1}, nil } if inst.Op == 0 { diff --git a/disasm/naming_amd64.go b/disasm/naming_amd64.go index db67981..57b3ee2 100644 --- a/disasm/naming_amd64.go +++ b/disasm/naming_amd64.go @@ -318,3 +318,24 @@ func nameAMD64Endbr(p amd64Prefixes, tail []byte) (string, int, bool) { } return "", 0, false } + +// nameAMD64Rejected names an amd64 encoding the decoder refuses outright, +// one family at a time as the corpus rows land. CLDEMOTE, NP 0F 1C /r +// with a memory operand, is the first: the toolchain's own table carries +// it as a memory-only instruction, and the decoder rejects the encoding +// instead of naming it. The register forms of the same opcode are the +// hint NOPs the corpus does not spell, and they stay rejected. +func nameAMD64Rejected(code []byte) (string, int, bool) { + p, ok := scanAMD64Prefixes(code) + if !ok || p.osz || p.rep || p.repne { + return "", 0, false + } + if len(code) < p.n+3 || code[p.n] != 0x0f || code[p.n+1] != 0x1c { + return "", 0, false + } + rm, ok := decodeAMD64RM(code[p.n+2:], p.rexR, p.rexX, p.rexB) + if !ok || rm.regForm { + return "", 0, false + } + return "CLDEMOTE " + rm.text(), p.n + 2 + rm.n, true +} diff --git a/disasm/naming_amd64_test.go b/disasm/naming_amd64_test.go index f8f923c..e994b46 100644 --- a/disasm/naming_amd64_test.go +++ b/disasm/naming_amd64_test.go @@ -76,6 +76,10 @@ func TestDegenerateNaming(t *testing.T) { // toolchain spelling. {[]byte{0xf3, 0x0f, 0x1e, 0xfa}, "ENDBR64"}, {[]byte{0xf3, 0x0f, 0x1e, 0xfb}, "ENDBR32"}, + // amd64enc_extra.s: CLDEMOTE (BX) // 0f1c03. The decoder + // rejects the encoding outright; the table names it from the + // bytes. + {[]byte{0x0f, 0x1c, 0x03}, "CLDEMOTE 0(BX)"}, } { ins, err := Decode(arch.AMD64, tt.code, 0) if err != nil { @@ -102,12 +106,13 @@ func TestDegenerateNamingBoundaries(t *testing.T) { text string }{ // Rejected outright: RDSEED without the operand-size override - // (the bare 0F C7 /7 register form), MONITORX and MWAITX, the - // hint NOP CLDEMOTE. + // (the bare 0F C7 /7 register form), MONITORX and MWAITX, and + // the register form of the hint NOP opcode, which the corpus + // does not spell. {[]byte{0x0f, 0xc7, 0xfa}, "???"}, {[]byte{0x0f, 0x01, 0xfa}, "???"}, {[]byte{0x0f, 0x01, 0xfb}, "???"}, - {[]byte{0x0f, 0x1c, 0x03}, "???"}, + {[]byte{0x0f, 0x1c, 0xc3}, "???"}, // Degenerate but outside the table's prefix gates: repne ADCX is // no instruction the corpus names. {[]byte{0xf2, 0x0f, 0x38, 0xf6, 0xd2}, "REPNE; Op(0)"}, diff --git a/disasm/testdata/parity_amd64_unlisted.txt b/disasm/testdata/parity_amd64_unlisted.txt index adf59c6..10b4c46 100644 --- a/disasm/testdata/parity_amd64_unlisted.txt +++ b/disasm/testdata/parity_amd64_unlisted.txt @@ -1228,3 +1228,4 @@ f3410fc7fb RDPID R11 f30faef3 UMONITOR BX f20faef3 UMWAIT BX f30f1efa ENDBR64 +0f1c03 CLDEMOTE 0(BX)