All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/5] x86/alternatives: Adjust all insn-relative fields
@ 2026-08-03  7:20 Andrew Cooper
  2026-08-03  7:20 ` [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite() Andrew Cooper
                   ` (4 more replies)
  0 siblings, 5 replies; 14+ messages in thread
From: Andrew Cooper @ 2026-08-03  7:20 UTC (permalink / raw)
  To: Xen-devel; +Cc: Andrew Cooper, Jan Beulich, Roger Pau Monné, Teddy Astie

Alternatives have had a reasonably severe restriction since their
introduction.  This has been the source of several bugs, and several
inefficiencies particularly in the speculative safety paths.

https://gitlab.com/xen-project/hardware/xen-staging/-/pipelines/2724019865

v3:
 * Run through Gitlab CI.
 * Fix up Eclair issues, Clang issues, and older-binutils issues.

Andrew Cooper (5):
  x86/emul: Introduce x86_decode_lite()
  tests/x86: Introduce a userspace test harness for x86_decode_lite()
  x86/alternative: Walk all replacements during self tests
  x86/alternative: Relocate all insn-relative fields
  x86/spec-ctrl: Introduce and use DO_COND_BHB_SEQ

 tools/tests/Makefile                      |   1 +
 tools/tests/x86-decode-lite/.gitignore    |   1 +
 tools/tests/x86-decode-lite/Makefile      |  56 ++
 tools/tests/x86-decode-lite/insns.S       | 703 ++++++++++++++++++++++
 tools/tests/x86-decode-lite/macro-magic.h |  62 ++
 tools/tests/x86-decode-lite/main.c        | 111 ++++
 tools/tests/x86-decode-lite/x86-emulate.h |  27 +
 xen/arch/x86/alternative.c                | 102 +++-
 xen/arch/x86/hvm/vmx/entry.S              |  12 +-
 xen/arch/x86/include/asm/spec_ctrl_asm.h  |  43 +-
 xen/arch/x86/x86_emulate/Makefile         |   6 +
 xen/arch/x86/x86_emulate/decode-lite.c    | 330 ++++++++++
 xen/arch/x86/x86_emulate/x86_emulate.h    |  14 +
 13 files changed, 1434 insertions(+), 34 deletions(-)
 create mode 100644 tools/tests/x86-decode-lite/.gitignore
 create mode 100644 tools/tests/x86-decode-lite/Makefile
 create mode 100644 tools/tests/x86-decode-lite/insns.S
 create mode 100644 tools/tests/x86-decode-lite/macro-magic.h
 create mode 100644 tools/tests/x86-decode-lite/main.c
 create mode 100644 tools/tests/x86-decode-lite/x86-emulate.h
 create mode 100644 xen/arch/x86/x86_emulate/decode-lite.c

-- 
2.39.5



^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite()
  2026-08-03  7:20 [PATCH v3 0/5] x86/alternatives: Adjust all insn-relative fields Andrew Cooper
@ 2026-08-03  7:20 ` Andrew Cooper
  2026-08-03  9:14   ` Andrew Cooper
  2026-08-03 15:26   ` Jan Beulich
  2026-08-03  7:20 ` [PATCH v3 2/5] tests/x86: Introduce a userspace test harness for x86_decode_lite() Andrew Cooper
                   ` (3 subsequent siblings)
  4 siblings, 2 replies; 14+ messages in thread
From: Andrew Cooper @ 2026-08-03  7:20 UTC (permalink / raw)
  To: Xen-devel; +Cc: Andrew Cooper, Jan Beulich, Roger Pau Monné, Teddy Astie

In order to relocate all IP-relative fields in an alternative replacement
block, we need to decode the instructions enough to obtain their length and
any relative fields.

Full x86_decode() is far too heavyweight, so introduce a minimal form which
can make several simplifying assumptions.

This a mostly-complete decoder for integer instruction in the onebyte and
twobyte maps.  Some instructions are intentionally unrecognised, as finding
them in an alternative is more likely to be a bug than intentional.  Some
instruction groups and prefixes are unimplemented to reduce decode complexity.

This logic can decode all alternative blocks that exist in Xen right now.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
---
CC: Jan Beulich <jbeulich@suse.com>
CC: Roger Pau Monné <roger@xenproject.org>
CC: Teddy Astie <teddy.astie@vates.tech>

v3:
 * Rearrange decode tables to satisfy comment requests without splitting
 * Recognise UDB now it's used by Xen
 * Fix MISRA violations
 * Misc other changes

v2:
 * Switch to 0 on failure, rel_sz in bytes
 * Mostly complete the integer instructions; paird with userspace harness
 * Put in .init when !CONFIG_LIVEPATCH
---
 xen/arch/x86/x86_emulate/Makefile      |   6 +
 xen/arch/x86/x86_emulate/decode-lite.c | 330 +++++++++++++++++++++++++
 xen/arch/x86/x86_emulate/x86_emulate.h |  14 ++
 3 files changed, 350 insertions(+)
 create mode 100644 xen/arch/x86/x86_emulate/decode-lite.c

diff --git a/xen/arch/x86/x86_emulate/Makefile b/xen/arch/x86/x86_emulate/Makefile
index 295e602f6b86..679bddbb1584 100644
--- a/xen/arch/x86/x86_emulate/Makefile
+++ b/xen/arch/x86/x86_emulate/Makefile
@@ -17,3 +17,9 @@ obj-y += decode.o
 obj-$(CONFIG_HVM) += fpu.o
 obj-y += util.o
 obj-y += util-xen.o
+
+ifeq ($(CONFIG_LIVEPATCH),y)
+obj-y += decode-lite.o
+else
+obj-bin-y += decode-lite.init.o
+endif
diff --git a/xen/arch/x86/x86_emulate/decode-lite.c b/xen/arch/x86/x86_emulate/decode-lite.c
new file mode 100644
index 000000000000..131cc07d5516
--- /dev/null
+++ b/xen/arch/x86/x86_emulate/decode-lite.c
@@ -0,0 +1,330 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifdef __XEN__
+# include <xen/init.h>
+# include <xen/livepatch.h>
+#endif
+
+#include "private.h"
+
+#undef ModRM
+
+/*
+ * Bare minimum x86 instruction decoder to parse the alternative replacement
+ * instructions and locate the IP-relative references that may need updating.
+ *
+ * These are:
+ *  - disp8/32 from near direct branches
+ *  - RIP-relative memory references
+ *
+ * The following simplifications are used:
+ *  - All code is 64bit, the instruction stream is well formed and safe to
+ *    read.
+ *  - Instruction groups and prefixes not used by Xen's current alternatives
+ *    are not implemented in order to reduce the decode complexity.
+ *  - Certain instructions are intentionally not recognised, when it is more
+ *    likely for their presence to be an error than intentional.
+ *
+ * Inputs:
+ *  @ip  The position to start decoding from.
+ *  @end End of the replacement block.  Exceeding this is considered an error.
+ *
+ * Returns: x86_decode_lite_t
+ *  - On failure, length of 0.
+ *  - On success, length > 0.  For rel_sz > 0, rel points at the relative
+ *    field in the instruction stream.
+ */
+x86_decode_lite_t init_or_livepatch x86_decode_lite(void *ip, void *end)
+{
+#define Imm8   (1 << 0)
+#define Imm    (1 << 1)
+#define Moffs  (1 << 2)
+#define Branch (1 << 5) /* Near direct branches, which have a displacement */
+#define ModRM  (1 << 6)
+#define Known  (1 << 7)
+
+    static const uint8_t init_or_livepatch_const onebyte[256] = {
+
+#define ALU_OPS(x)                              \
+        [(x) + 0] = (Known|ModRM),              \
+        [(x) + 1] = (Known|ModRM),              \
+        [(x) + 2] = (Known|ModRM),              \
+        [(x) + 3] = (Known|ModRM),              \
+        [(x) + 4] = (Known|Imm8),               \
+        [(x) + 5] = (Known|Imm)
+
+        ALU_OPS(0x00) /* ADD */, ALU_OPS(0x08) /* OR  */,
+        ALU_OPS(0x10) /* ADC */, ALU_OPS(0x18) /* SBB */,
+        ALU_OPS(0x20) /* AND */, ALU_OPS(0x28) /* SUB */,
+        ALU_OPS(0x30) /* XOR */, ALU_OPS(0x38) /* CMP */,
+
+#undef ALU_OPS
+
+        [0x50 ... 0x5f] = (Known),             /* PUSH/POP %reg */
+
+        [0x62]          = 0,                   /* BOUND, but also EVEX prefix, not implemented. */
+        [0x63]          = (Known|ModRM),       /* MOVSxd */
+
+        [0x68]          = (Known|Imm),         /* PUSH $imm */
+        [0x69]          = (Known|ModRM|Imm),   /* IMUL $imm */
+        [0x6a]          = (Known|Imm8),        /* PUSH $imm8 */
+        [0x6b]          = (Known|ModRM|Imm8),  /* PUSH $imm8 */
+        [0x6c ... 0x6f] = (Known),             /* INS/OUTS */
+        [0x70 ... 0x7f] = (Known|Branch|Imm8), /* Jcc disp8 */
+        [0x80]          = (Known|ModRM|Imm8),  /* Grp1 */
+        [0x81]          = (Known|ModRM|Imm),   /* Grp1 */
+
+        [0x83]          = (Known|ModRM|Imm8),  /* Grp1 */
+        [0x84 ... 0x8e] = (Known|ModRM),       /* TEST/XCHG/MOV/MOV-SREG/LEA */
+        [0x8f]          = 0,                   /* Grp1A - POP but also XOP prefix, not implemented. */
+        [0x90 ... 0x99] = (Known),             /* NOP/XCHG %rAX/CLTQ/CQTO */
+
+        [0x9b ... 0x9f] = (Known),             /* FWAIT/PUSHF/POPF/SAHF/LAHF */
+        [0xa0 ... 0xa3] = (Known|Moffs),       /* MOVABS */
+        [0xa4 ... 0xa7] = (Known),             /* MOVS/CMPS */
+        [0xa8]          = (Known|Imm8),        /* TEST %al */
+        [0xa9]          = (Known|Imm),         /* TEST %rAX */
+        [0xaa ... 0xaf] = (Known),             /* STOS/LODS/SCAS */
+        [0xb0 ... 0xb7] = (Known|Imm8),        /* MOV $imm8, %reg */
+        [0xb8 ... 0xbf] = (Known|Imm),         /* MOV $imm{16,32,64}, %reg */
+        [0xc0 ... 0xc1] = (Known|ModRM|Imm8),  /* Grp2 (ROL..SAR $imm8, %reg) */
+
+        [0xc3]          = (Known),             /* RET */
+        [0xc4 ... 0xc5] = 0,                   /* LES/LDS but also VEX prefixes, not implemented. */
+        [0xc6]          = (Known|ModRM|Imm8),  /* Grp11, Further ModRM decode */
+        [0xc7]          = (Known|ModRM|Imm),   /* Grp11, Further ModRM decode */
+
+        [0xcb ... 0xcc] = (Known),             /* LRET/INT3 */
+        [0xcd]          = (Known|Imm8),        /* INT $imm8 */
+
+        [0xd0 ... 0xd3] = (Known|ModRM),       /* Grp2 (ROL..SAR {$1,%cl}, %reg) */
+
+        [0xd6]          = (Known),             /* UDB */
+
+        [0xe4 ... 0xe7] = (Known|Imm8),        /* IN/OUT $imm8 */
+        [0xe8 ... 0xe9] = (Known|Branch|Imm),  /* CALL/JMP disp32 */
+
+        [0xeb]          = (Known|Branch|Imm8), /* JMP disp8 */
+        [0xec ... 0xef] = (Known),             /* IN/OUT %dx */
+
+        [0xf1]          = (Known),             /* ICEBP */
+
+        [0xf4]          = (Known),             /* HLT */
+        [0xf5]          = (Known),             /* CMC */
+        [0xf6 ... 0xf7] = (Known|ModRM),       /* Grp3, Further ModRM decode */
+        [0xf8 ... 0xfd] = (Known),             /* CLC ... STD */
+        [0xfe ... 0xff] = (Known|ModRM),       /* Grp4 */
+    };
+    static const uint8_t init_or_livepatch_const twobyte[256] = {
+        [0x00 ... 0x03] = (Known|ModRM),       /* Grp6/Grp7/LAR/LSL */
+
+        [0x0b]          = (Known),             /* UD2 */
+
+        [0x18 ... 0x1f] = (Known|ModRM),       /* Grp16 (Hint Nop) */
+        [0x20 ... 0x23] = (Known|ModRM),       /* MOV %cr/%dr */
+
+        [0x30 ... 0x33] = (Known),             /* WRMSR/RDTSC/RDMSR/RDPMC */
+
+        [0x40 ... 0x4f] = (Known|ModRM),       /* CMOVcc */
+
+        [0x80 ... 0x8f] = (Known|Branch|Imm),  /* Jcc disp32 */
+        [0x90 ... 0x9f] = (Known|ModRM),       /* SETcc */
+
+        [0xa0 ... 0xa2] = (Known),             /* PUSH/POP %fs/CPUID */
+        [0xa3]          = (Known|ModRM),       /* BT */
+        [0xa4]          = (Known|ModRM|Imm8),  /* SHLD $imm8 */
+        [0xa5]          = (Known|ModRM),       /* SHLD %cl */
+
+        [0xa8 ... 0xa9] = (Known),             /* PUSH/POP %gs */
+
+        [0xab]          = (Known|ModRM),       /* BTS */
+        [0xac]          = (Known|ModRM|Imm8),  /* SHRD $imm8 */
+        [0xad ... 0xaf] = (Known|ModRM),       /* SHRD %cl/Grp15/IMUL */
+
+        [0xb0 ... 0xb9] = (Known|ModRM),       /* CMPXCHG/LSS/BTR/LFS/LGS/MOVZxx/POPCNT/UD1 */
+        [0xba]          = (Known|ModRM|Imm8),  /* Grp8 */
+        [0xbb ... 0xbf] = (Known|ModRM),       /* BTC/BSF/BSR/MOVSX */
+        [0xc0 ... 0xc1] = (Known|ModRM),       /* XADD */
+        [0xc7]          = (Known|ModRM),       /* Grp9 */
+        [0xc8 ... 0xcf] = (Known),             /* BSWAP */
+    };
+
+    void *start = ip, *rel = NULL;
+    unsigned int opc, rel_sz = 0;
+    uint8_t b, d, rex = 0, osize = 4;
+
+#define OPC_TWOBYTE (1 << 8)
+
+    /* Mutates IP, uses END. */
+#define FETCH(ty)                                       \
+    ({                                                  \
+        ty _val;                                        \
+                                                        \
+        if ( (ip + sizeof(ty)) > end )                  \
+            goto overrun;                               \
+        _val = *(ty *)ip;                               \
+        ip += sizeof(ty);                               \
+        _val;                                           \
+    })
+
+    for ( ;; ) /* Prefixes */
+    {
+        switch ( b = FETCH(uint8_t) )
+        {
+        case 0x26: /* ES override */
+        case 0x2e: /* CS override */
+        case 0x36: /* DS override */
+        case 0x3e: /* SS override */
+        case 0x64: /* FS override */
+        case 0x65: /* GS override */
+        case 0xf0: /* LOCK */
+        case 0xf2: /* REPNE */
+        case 0xf3: /* REP */
+            break;
+
+        case 0x66: /* Operand size override */
+            osize = 2;
+            break;
+
+        /* case 0x67: Address size override, not implemented */
+
+        case 0x40 ... 0x4f: /* REX */
+            rex = b;
+            continue;
+
+        default:
+            goto prefixes_done;
+        }
+        rex = 0; /* REX cancelled by subsequent legacy prefix. */
+    }
+ prefixes_done:
+
+    if ( rex & REX_W )
+        osize = 8;
+
+    /* Fetch the main opcode byte(s) */
+    if ( b == 0x0f )
+    {
+        b = FETCH(uint8_t);
+        opc = OPC_TWOBYTE | b;
+
+        d = twobyte[b];
+    }
+    else
+    {
+        opc = b;
+        d = onebyte[b];
+    }
+
+    if ( unlikely(!(d & Known)) )
+        goto unknown;
+
+    if ( d & ModRM )
+    {
+        uint8_t modrm = FETCH(uint8_t);
+        uint8_t mod = modrm >> 6;
+        uint8_t reg = (modrm >> 3) & 7;
+        uint8_t rm = modrm & 7;
+
+        /* ModRM/SIB decode */
+        if ( mod == 0 && rm == 5 ) /* RIP relative */
+        {
+            rel = ip;
+            rel_sz = 4;
+            FETCH(int32_t);
+        }
+        else if ( mod != 3 && rm == 4 ) /* SIB */
+        {
+            uint8_t sib = FETCH(uint8_t);
+            uint8_t base = sib & 7;
+
+            if ( mod == 0 && base == 5 )
+                goto disp32;
+        }
+
+        if ( mod == 1 ) /* disp8 */
+            FETCH(int8_t);
+        else if ( mod == 2 ) /* disp32 */
+        {
+        disp32:
+            FETCH(int32_t);
+        }
+
+        /* ModRM based decode adjustements */
+        switch ( opc )
+        {
+        case 0xc7: /* Grp11 XBEGIN is a near direct branch. */
+            if ( modrm == 0xf8 )
+                d |= Branch;
+            break;
+
+        case 0xf6: /* Grp3 TEST(s) have extra Imm8 */
+            if ( reg == 0 || reg == 1 )
+                d |= Imm8;
+            break;
+
+        case 0xf7: /* Grp3 TEST(s) have extra Imm */
+            if ( reg == 0 || reg == 1 )
+                d |= Imm;
+            break;
+        }
+    }
+
+    if ( d & Branch )
+    {
+        /*
+         * We don't tolerate 66-prefixed call/jmp in alternatives.  Some are
+         * genuinely decoded differently between Intel and AMD CPUs.
+         *
+         * We also don't implement APX instructions, so don't have to cope
+         * with JMPABS which is the first branch to have an 8-byte immediate.
+         */
+        if ( osize < 4 )
+            goto bad_osize;
+
+        rel = ip;
+        rel_sz = (d & Imm8) ? 1 : 4;
+    }
+
+    if ( d & (Imm | Imm8 | Moffs) )
+    {
+        if ( d & Imm8 )
+            osize = 1;
+        else if ( d & Moffs )
+            osize = 8;
+        else if ( osize == 8 && !(opc >= 0xb8 && opc <= 0xbf) )
+            osize = 4;
+
+        switch ( osize )
+        {
+        case 1: FETCH(uint8_t);  break;
+        case 2: FETCH(uint16_t); break;
+        case 4: FETCH(uint32_t); break;
+        case 8: FETCH(uint64_t); break;
+        default: goto bad_osize;
+        }
+    }
+
+    return (x86_decode_lite_t){ ip - start, rel_sz, rel };
+
+ bad_osize:
+    printk(XENLOG_ERR "%s() Bad osize %u in %*ph\n",
+           __func__, osize,
+           (int)(unsigned long)(end - start), start);
+    return (x86_decode_lite_t){ 0, 0, NULL };
+
+ unknown:
+    printk(XENLOG_ERR "%s() Unknown opcode in %*ph <%02x> %*ph\n",
+           __func__,
+           (int)(unsigned long)(ip - 1 - start), start, b,
+           (int)(unsigned long)(end - ip), ip);
+    return (x86_decode_lite_t){ 0, 0, NULL };
+
+ overrun:
+    printk(XENLOG_ERR "%s() Decode overrun, got %*ph\n",
+           __func__,
+           (int)(unsigned long)(end - start), start);
+    return (x86_decode_lite_t){ 0, 0, NULL };
+
+#undef FETCH
+}
diff --git a/xen/arch/x86/x86_emulate/x86_emulate.h b/xen/arch/x86/x86_emulate/x86_emulate.h
index 0fd20747dc43..566a8297d8a5 100644
--- a/xen/arch/x86/x86_emulate/x86_emulate.h
+++ b/xen/arch/x86/x86_emulate/x86_emulate.h
@@ -835,4 +835,18 @@ static inline void x86_emul_reset_event(struct x86_emulate_ctxt *ctxt)
     ctxt->event = (struct x86_event){};
 }
 
+/*
+ * x86_decode_lite().  Very minimal decoder for managing alternatives.
+ *
+ * @len is 0 on error, or nonzero on success.  If the instruction has a
+ * relative field, @rel_sz is nonzero, and @rel points at the field.
+ */
+typedef struct {
+    uint8_t len;
+    uint8_t rel_sz; /* bytes: 0, 1 or 4 */
+    void *rel;
+} x86_decode_lite_t;
+
+x86_decode_lite_t x86_decode_lite(void *ip, void *end);
+
 #endif /* __X86_EMULATE_H__ */
-- 
2.39.5



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 2/5] tests/x86: Introduce a userspace test harness for x86_decode_lite()
  2026-08-03  7:20 [PATCH v3 0/5] x86/alternatives: Adjust all insn-relative fields Andrew Cooper
  2026-08-03  7:20 ` [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite() Andrew Cooper
@ 2026-08-03  7:20 ` Andrew Cooper
  2026-08-03 16:03   ` Jan Beulich
  2026-08-03  7:20 ` [PATCH v3 3/5] x86/alternative: Walk all replacements during self tests Andrew Cooper
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 14+ messages in thread
From: Andrew Cooper @ 2026-08-03  7:20 UTC (permalink / raw)
  To: Xen-devel; +Cc: Andrew Cooper, Jan Beulich, Roger Pau Monné, Teddy Astie

All the interesting behaviour is in insns.S.

There are 4 interesting cases; "not an instruction we tolerate", and one we do
tolerate, split by no relation, disp8 or disp32.  The DECL()/END() macros
start and terminate the tests_*[] arrays used by C.

Between DECL()/END(), a macro named _ adds an entry into the array, including
a name and the length of the instruction according to the assembler, while
being as visually unintrusive as possible.

Plain labels are ad-hoc and there to aid legibility during disassembly.  In a
couple of cases, the macro named n (for name) allows for choosing a name
manually, and is used for cases where the assembler doesn't like the mnemonic.

Clang IAS doesn't like the convience macro, and Binutils of around 2.30 don't
like sysexitl or movsxd with a 32bit operand.  As it's only Ubuntu 18.04
affected by this, skip building the harness in old environments.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
---
CC: Jan Beulich <jbeulich@suse.com>
CC: Roger Pau Monné <roger@xenproject.org>
CC: Teddy Astie <teddy.astie@vates.tech>

v3:
 * Force disable Clang IAS.  It doesn't like the _ macro.
 * Support 32bit builds

v2:
 * New
---
 tools/tests/Makefile                      |   1 +
 tools/tests/x86-decode-lite/.gitignore    |   1 +
 tools/tests/x86-decode-lite/Makefile      |  56 ++
 tools/tests/x86-decode-lite/insns.S       | 703 ++++++++++++++++++++++
 tools/tests/x86-decode-lite/macro-magic.h |  62 ++
 tools/tests/x86-decode-lite/main.c        | 111 ++++
 tools/tests/x86-decode-lite/x86-emulate.h |  27 +
 7 files changed, 961 insertions(+)
 create mode 100644 tools/tests/x86-decode-lite/.gitignore
 create mode 100644 tools/tests/x86-decode-lite/Makefile
 create mode 100644 tools/tests/x86-decode-lite/insns.S
 create mode 100644 tools/tests/x86-decode-lite/macro-magic.h
 create mode 100644 tools/tests/x86-decode-lite/main.c
 create mode 100644 tools/tests/x86-decode-lite/x86-emulate.h

diff --git a/tools/tests/Makefile b/tools/tests/Makefile
index fc0ed8091510..ca4f0c707638 100644
--- a/tools/tests/Makefile
+++ b/tools/tests/Makefile
@@ -14,6 +14,7 @@ SUBDIRS-y += xenstore
 
 SUBDIRS-$(CONFIG_X86) += cpu-policy
 SUBDIRS-$(CONFIG_X86) += tsx
+SUBDIRS-$(CONFIG_X86) += x86-decode-lite
 ifneq ($(clang),y)
 SUBDIRS-$(CONFIG_X86) += x86_emulator
 endif
diff --git a/tools/tests/x86-decode-lite/.gitignore b/tools/tests/x86-decode-lite/.gitignore
new file mode 100644
index 000000000000..e726b493c993
--- /dev/null
+++ b/tools/tests/x86-decode-lite/.gitignore
@@ -0,0 +1 @@
+test-x86-decode-lite
diff --git a/tools/tests/x86-decode-lite/Makefile b/tools/tests/x86-decode-lite/Makefile
new file mode 100644
index 000000000000..dc33d5fd173a
--- /dev/null
+++ b/tools/tests/x86-decode-lite/Makefile
@@ -0,0 +1,56 @@
+XEN_ROOT = $(CURDIR)/../../..
+include $(XEN_ROOT)/tools/Rules.mk
+
+TARGET :=
+
+# Clang IAS doesn't like the convenience macros we use
+$(call cc-option-add,CFLAGS,CC,-no-integrated-as)
+
+# Binutils around 2.30 have mutually exclusive expectations of instruction
+# suffix validities compared to later versions.  Among the distro we test,
+# this only excludes Ubuntu 18.04.
+ifeq ($(shell echo 'asm(".code64;sysexitl");' | $(CC) -x c -c -o /dev/null 2>/dev/null - && echo y),y)
+TARGET += test-x86-decode-lite
+endif
+
+.PHONY: all
+all: $(TARGET)
+
+.PHONY: run
+run: $(TARGET)
+	./$<
+
+.PHONY: clean
+clean:
+	$(RM) -- *.o $(TARGET) $(DEPS_RM)
+
+.PHONY: distclean
+distclean: clean
+	$(RM) -- *~
+
+.PHONY: install
+install: all
+	$(INSTALL_DIR) $(DESTDIR)$(LIBEXEC_BIN)/tests
+	$(if $(TARGET),$(INSTALL_PROG) $(TARGET) $(DESTDIR)$(LIBEXEC_BIN)/tests)
+
+.PHONY: uninstall
+uninstall:
+	$(RM) -- $(DESTDIR)$(LIBEXEC_BIN)/$(TARGET)
+
+.PHONY: uninstall
+uninstall:
+
+vpath decode-lite.c $(XEN_ROOT)/xen/arch/x86/x86_emulate
+
+CFLAGS += $(CFLAGS_xeninclude) -I. -I$(XEN_ROOT)/xen/arch/x86
+CFLAGS += $(APPEND_CFLAGS)
+
+
+LDFLAGS += $(APPEND_LDFLAGS)
+
+%.o: Makefile
+
+$(TARGET): main.o insns.o decode-lite.o
+	$(CC) -o $@ $^ $(LDFLAGS)
+
+-include $(DEPS_INCLUDE)
diff --git a/tools/tests/x86-decode-lite/insns.S b/tools/tests/x86-decode-lite/insns.S
new file mode 100644
index 000000000000..8b299cfb594e
--- /dev/null
+++ b/tools/tests/x86-decode-lite/insns.S
@@ -0,0 +1,703 @@
+#include "macro-magic.h"
+
+        .code64
+
+        .allow_index_reg
+
+        .text
+
+DECL(tests_rel0)
+modrm:
+        /* Mod=0, Reg=0, RM {0..f} */
+        _ add %al, (%rax)
+        _ add %al, (%rcx)
+        _ add %al, (%rdx)
+        _ add %al, (%rbx)
+        _ add %al, (%rsp) /* SIB */
+        /*add %al, (%rbp)    RIP --> tests_rel4 */
+        _ add %al, (%rsi)
+        _ add %al, (%rdi)
+        _ add %al, (%r8)
+        _ add %al, (%r9)
+        _ add %al, (%r10)
+        _ add %al, (%r11)
+        _ add %al, (%r12) /* SIB */
+        /*add %al, (%r13)    RIP --> tests_rel4 */
+        _ add %al, (%r14)
+        _ add %al, (%r15)
+
+        /* Mod=1, Reg=0, RM {0..f} */
+        _ add %al, 0x01(%rax)
+        _ add %al, 0x01(%rcx)
+        _ add %al, 0x01(%rdx)
+        _ add %al, 0x01(%rbx)
+        _ add %al, 0x01(%rsp) /* SIB */
+        _ add %al, 0x01(%rbp)
+        _ add %al, 0x01(%rsi)
+        _ add %al, 0x01(%rdi)
+        _ add %al, 0x01(%r8)
+        _ add %al, 0x01(%r9)
+        _ add %al, 0x01(%r10)
+        _ add %al, 0x01(%r11)
+        _ add %al, 0x01(%r12) /* SIB */
+        _ add %al, 0x01(%r13)
+        _ add %al, 0x01(%r14)
+        _ add %al, 0x01(%r15)
+
+        /* Mod=2, Reg=0, RM {0..f} */
+        _ add %al, 0x7f000001(%rax)
+        _ add %al, 0x7f000001(%rcx)
+        _ add %al, 0x7f000001(%rdx)
+        _ add %al, 0x7f000001(%rbx)
+        _ add %al, 0x7f000001(%rsp) /* SIB */
+        _ add %al, 0x7f000001(%rbp)
+        _ add %al, 0x7f000001(%rsi)
+        _ add %al, 0x7f000001(%rdi)
+        _ add %al, 0x7f000001(%r8)
+        _ add %al, 0x7f000001(%r9)
+        _ add %al, 0x7f000001(%r10)
+        _ add %al, 0x7f000001(%r11)
+        _ add %al, 0x7f000001(%r12) /* SIB */
+        _ add %al, 0x7f000001(%r13)
+        _ add %al, 0x7f000001(%r14)
+        _ add %al, 0x7f000001(%r15)
+
+        /* Mod=3, Reg=0, RM {0..f} */
+        _ add %al, %al
+        _ add %al, %cl
+        _ add %al, %dl
+        _ add %al, %bl
+        _ add %al, %ah
+        _ add %al, %ch
+        _ add %al, %dh
+        _ add %al, %dl
+        _ add %al, %r8b
+        _ add %al, %r9b
+        _ add %al, %r10b
+        _ add %al, %r11b
+        _ add %al, %r12b
+        _ add %al, %r13b
+        _ add %al, %r14b
+        _ add %al, %r15b
+
+sib:
+        /* Mod=0, Reg=0, RM=4, SIB S=3, I=0, B {0..f} */
+        _ add %al, (%rax, %rax, 8)
+        _ add %al, (%rcx, %rax, 8)
+        _ add %al, (%rdx, %rax, 8)
+        _ add %al, (%rbx, %rax, 8)
+        _ add %al, (%rsp, %rax, 8)
+        _ add %al, (    , %rax, 8) /* "none", %rbp encoded with mod=1/2 */
+        _ add %al, (%rsi, %rax, 8)
+        _ add %al, (%rdi, %rax, 8)
+        _ add %al, (%r8,  %rax, 8)
+        _ add %al, (%r9,  %rax, 8)
+        _ add %al, (%r10, %rax, 8)
+        _ add %al, (%r11, %rax, 8)
+        _ add %al, (%r12, %rax, 8)
+        _ rex.b add %al,(,%rax, 8) /* "none", %r13 encoded with mod=1/2 */
+        _ add %al, (%r14, %rax, 8)
+        _ add %al, (%r15, %rax, 8)
+
+        /* Mod=1, Reg=0, RM=4, SIB S=3, I=0, B {0..f} */
+        _ add %al, 0x01(%rax, %rax, 8)
+        _ add %al, 0x01(%rcx, %rax, 8)
+        _ add %al, 0x01(%rdx, %rax, 8)
+        _ add %al, 0x01(%rbx, %rax, 8)
+        _ add %al, 0x01(%rsp, %rax, 8)
+        _ add %al, 0x01(%rbp, %rax, 8)
+        _ add %al, 0x01(%rsi, %rax, 8)
+        _ add %al, 0x01(%rdi, %rax, 8)
+        _ add %al, 0x01(%r8,  %rax, 8)
+        _ add %al, 0x01(%r9,  %rax, 8)
+        _ add %al, 0x01(%r10, %rax, 8)
+        _ add %al, 0x01(%r11, %rax, 8)
+        _ add %al, 0x01(%r12, %rax, 8)
+        _ add %al, 0x01(%r13, %rax, 8)
+        _ add %al, 0x01(%r14, %rax, 8)
+        _ add %al, 0x01(%r15, %rax, 8)
+
+        /* Mod=2, Reg=0, RM=4, SIB S=3, I=0, B {0..f} */
+        _ add %al, 0x7f000001(%rax, %rax, 8)
+        _ add %al, 0x7f000001(%rcx, %rax, 8)
+        _ add %al, 0x7f000001(%rdx, %rax, 8)
+        _ add %al, 0x7f000001(%rbx, %rax, 8)
+        _ add %al, 0x7f000001(%rsp, %rax, 8)
+        _ add %al, 0x7f000001(%rbp, %rax, 8)
+        _ add %al, 0x7f000001(%rsi, %rax, 8)
+        _ add %al, 0x7f000001(%rdi, %rax, 8)
+        _ add %al, 0x7f000001(%r8,  %rax, 8)
+        _ add %al, 0x7f000001(%r9,  %rax, 8)
+        _ add %al, 0x7f000001(%r10, %rax, 8)
+        _ add %al, 0x7f000001(%r11, %rax, 8)
+        _ add %al, 0x7f000001(%r12, %rax, 8)
+        _ add %al, 0x7f000001(%r13, %rax, 8)
+        _ add %al, 0x7f000001(%r14, %rax, 8)
+        _ add %al, 0x7f000001(%r15, %rax, 8)
+
+        /* Mod=0, Reg=0, RM=4, SIB S=3, I=4, B {0..f} */
+        _ add %al, (%rax, %riz, 8)
+        _ add %al, (%rcx, %riz, 8)
+        _ add %al, (%rdx, %riz, 8)
+        _ add %al, (%rbx, %riz, 8)
+        _ add %al, (%rsp, %riz, 8)
+        _ add %al, (    , %riz, 8) /* %rbp encoded with mod=1/2 */
+        _ add %al, (%rsi, %riz, 8)
+        _ add %al, (%rdi, %riz, 8)
+        _ add %al, (%r8,  %riz, 8)
+        _ add %al, (%r9,  %riz, 8)
+        _ add %al, (%r10, %riz, 8)
+        _ add %al, (%r11, %riz, 8)
+        _ add %al, (%r12, %riz, 8)
+        _ rex.b add %al,(,%riz, 8) /* %r13 encoded with mod=1/2 */
+        _ add %al, (%r14, %riz, 8)
+        _ add %al, (%r15, %riz, 8)
+
+        /* Mod=1, Reg=0, RM=4, SIB S=3, I=4, B {0..f} */
+        _ add %al, 0x01(%rax, %riz, 8)
+        _ add %al, 0x01(%rcx, %riz, 8)
+        _ add %al, 0x01(%rdx, %riz, 8)
+        _ add %al, 0x01(%rbx, %riz, 8)
+        _ add %al, 0x01(%rsp, %riz, 8)
+        _ add %al, 0x01(%rbp, %riz, 8)
+        _ add %al, 0x01(%rsi, %riz, 8)
+        _ add %al, 0x01(%rdi, %riz, 8)
+        _ add %al, 0x01(%r8,  %riz, 8)
+        _ add %al, 0x01(%r9,  %riz, 8)
+        _ add %al, 0x01(%r10, %riz, 8)
+        _ add %al, 0x01(%r11, %riz, 8)
+        _ add %al, 0x01(%r12, %riz, 8)
+        _ add %al, 0x01(%r13, %riz, 8)
+        _ add %al, 0x01(%r14, %riz, 8)
+        _ add %al, 0x01(%r15, %riz, 8)
+
+        /* Mod=2, Reg=0, RM=4, SIB S=3, I=4, B {0..f} */
+        _ add %al, 0x7f000001(%rax, %riz, 8)
+        _ add %al, 0x7f000001(%rcx, %riz, 8)
+        _ add %al, 0x7f000001(%rdx, %riz, 8)
+        _ add %al, 0x7f000001(%rbx, %riz, 8)
+        _ add %al, 0x7f000001(%rsp, %riz, 8)
+        _ add %al, 0x7f000001(%rbp, %riz, 8)
+        _ add %al, 0x7f000001(%rsi, %riz, 8)
+        _ add %al, 0x7f000001(%rdi, %riz, 8)
+        _ add %al, 0x7f000001(%r8,  %riz, 8)
+        _ add %al, 0x7f000001(%r9,  %riz, 8)
+        _ add %al, 0x7f000001(%r10, %riz, 8)
+        _ add %al, 0x7f000001(%r11, %riz, 8)
+        _ add %al, 0x7f000001(%r12, %riz, 8)
+        _ add %al, 0x7f000001(%r13, %riz, 8)
+        _ add %al, 0x7f000001(%r14, %riz, 8)
+        _ add %al, 0x7f000001(%r15, %riz, 8)
+
+        .macro alu_ops op
+        _ \op %al, (%rax)
+        _ \op %eax, (%rax)
+        _ \op (%rax), %al
+        _ \op (%rax), %eax
+        _ \op $1, %al
+        _ \op $0x7f000001, %eax
+
+        /* Vary osize on imm fields */
+        _ data16 \op $1, %al
+        _ rex.w \op $1, %al
+        _ data16 rex.w \op $1, %al
+
+        _ \op $0x7f01, %ax
+        _ \op $0x7f000001, %rax
+        _ data16 \op $0x7f000001, %rax
+        .endm
+
+onebyte_row_0x:
+        alu_ops add
+        alu_ops or
+
+onebyte_row_1x:
+        alu_ops adc
+        alu_ops sbb
+
+onebyte_row_2x:
+        alu_ops and
+        .code32
+        _ es nop
+        .code64
+        alu_ops sub
+        _ cs nop
+
+onebyte_row_3x:
+        alu_ops xor
+        .code32
+        _ ss nop
+        .code64
+        alu_ops cmp
+        _ ds nop
+
+/* onebyte_row_4x --> rex prefixes */
+
+onebyte_row_5x:
+        _ push %rax
+        _ push %rcx
+        _ push %rdx
+        _ push %rbx
+        _ push %rsp
+        _ push %rbp
+        _ push %rsi
+        _ push %rdi
+        _ pop %rax
+        _ pop %rcx
+        _ pop %rdx
+        _ pop %rbx
+        _ pop %rsp
+        _ pop %rbp
+        _ pop %rsi
+        _ pop %rdi
+
+onebyte_row_6x:
+        /*pusha,popa,bound --> not supported */
+        _ movsxd (%rax), %eax
+        _ movslq (%rax), %rax
+        _ fs nop
+        _ gs nop
+        _ data16 nop
+        /* addr32 --> not supported */
+        _ pushq $0x7f000001
+        _ pushw $0x7f01
+        _ rex.w pushq $0x7f000001
+        _ imul $0x7f01, %ax, %ax
+        _ imul $0x7f000001, %eax, %eax
+        _ imul $0x7f000001, %rax, %rax
+        _ pushq $0
+        _ pushw $0
+        _ rex.w pushq $0
+        _ imul $0, %ax, %ax
+        _ imul $0, %eax, %eax
+        _ imul $0, %rax, %rax
+        _ insb
+        _ insw
+        _ insl
+        _ outsb
+        _ outsw
+        _ outsl
+
+/* onebyte_row_7x: --> Jcc disp8 */
+
+onebyte_row_8x:
+        _ add $0, %cl /* Grp1 */
+        _ data16 add $0, %cl
+        _ rex.w add $0, %cl
+        _ add $0x7f01, %cx
+        _ add $0x7f000001, %ecx
+        _ add $0x7f000001, %rcx
+        _ add $0, %cx
+        _ add $0, %ecx
+        _ add $0, %rcx
+        _ test %cl, %cl
+        _ test %ecx, %ecx
+        _ xchg %cl, %cl
+        _ xchg %ecx, %ecx
+        _ mov %cl, (%rax)
+        _ mov %ecx, (%rax)
+        _ mov (%rax), %cl
+        _ mov (%rax), %ecx
+        _ mov %cs, (%rax)
+        _ lea (%rax), %eax
+        _ mov (%rax), %cs
+        /*pop mem --> Grp1a, Not supported (XOP prefix adjacent) */
+
+onebyte_row_9x:
+        _ nop
+        _ pause
+        _ xchg %ax, %ax
+        _ xchg %eax, %eax
+        _ xchg %rax, %rax
+        _ rex.w xchg %rax, %rax
+        _ cltq
+        _ cqto
+        _ wait
+        _ pushf
+        _ popf
+        _ sahf
+        _ lahf
+
+onebyte_row_ax:
+        _ mov 0x8000000000000001, %al
+        _ mov 0x8000000000000001, %ax
+        _ mov 0x8000000000000001, %eax
+        _ mov 0x8000000000000001, %rax
+        _ mov %al,  0x8000000000000001
+        _ mov %ax,  0x8000000000000001
+        _ mov %eax, 0x8000000000000001
+        _ mov %rax, 0x8000000000000001
+        _ movsb
+        _ movsl
+        _ cmpsb
+        _ cmpsl
+        _ test $0, %al
+        _ test $0x80000001, %eax
+        _ test $0x7f000001, %rax
+        _ stosb
+        _ stosl
+        _ lodsb
+        _ lodsl
+        _ scasb
+        _ scasl
+
+onebyte_row_bx:
+        _ mov $0, %al
+        _ mov $0, %cl
+        _ mov $0x7f01, %ax
+        _ mov $0x7f01, %cx
+        _ mov $0x7f000001, %eax
+        _ mov $0x7f000001, %ecx
+        _ mov $0x7f00000000000001, %rax
+        _ mov $0x7f00000000000001, %rcx
+
+onebyte_row_cx:
+        _ rol $0, %al /* Grp2 */
+        _ rol $0, %ax
+        _ rol $0, %eax
+        _ rol $0, %rax
+        /*ret $0 --> not supported */
+        _ ret
+        /*les,lds --> not supported */
+        _ movb $0, (%rax) /* Grp11 */
+        _ movw $0, (%rax)
+        _ movl $0, (%rax)
+        _ movq $0, (%rax)
+        /*xbegin (Grp11) --> disp32 */
+        /*enter,leave,lretq $0 --> not supported */
+        _ lretq
+        _ int3
+        _ int $0
+        /*into,iret --> not supported */
+
+onebyte_row_dx:
+        _ rol $1, %al /* Grp2 */
+        _ rol $1, %ax
+        _ rol $1, %eax
+        _ rol $1, %rax
+        _ rol %cl, %al
+        _ rol %cl, %ax
+        _ rol %cl, %eax
+        _ rol %cl, %rax
+        /*aam,aad --> not supported */
+        n "udb" .byte 0xd6
+        /*xlat,d8...df --> not supported */
+
+onebyte_row_ex:
+        /*loop{ne,e,},jrcxz --> not supported */
+        _ in $0, %al
+        _ in $0, %eax
+        _ out %al,  $0
+        _ out %eax, $0
+        /*call,jmp --> disp32 */
+        /*ljmp --> not supported */
+        /*jmp --> disp8 */
+        _ in %dx, %al
+        _ in %dx, %eax
+        _ out %al,  %dx
+        _ out %eax, %dx
+
+onebyte_row_fx:
+        _ lock addb $0, (%rax)
+        n "icebp" .byte 0xf1 /* icebp */
+        _ repne nop
+        _ repe nop
+        _ hlt
+        _ cmc
+        _ test $0, %cl /* Grp3, /0 has extra Imm{8,} */
+        _ not %cl
+        _ test $0x7f01, %cx
+        _ not %cx
+        _ test $0x7f000001, %ecx
+        _ not %ecx
+        _ test $0x7f000001, %rcx
+        _ not %rcx
+        _ clc
+        _ stc
+        _ cli
+        _ sti
+        _ cld
+        _ std
+        _ inc %cl /* Grp4 */
+        _ dec %cl
+        _ inc %ecx /* Grp5 */
+        _ dec %ecx
+        _ call *(%rax)
+        _ lcall *(%rax)
+        _ jmp *(%rax)
+        _ ljmp *(%rax)
+        _ push (%rax)
+
+twobyte_row_0x:
+        _ sldt (%rax) /* Grp6 */
+        _ sgdt (%rax) /* Grp7 */
+        _ lar (%rax), %eax
+        _ lsl (%rax), %eax
+        _ ud2a
+
+twobyte_row_1x:
+        _ prefetchnta (%rax) /* Grp16 (Hint Nop) */
+        _ nopl (%rax)
+
+twobyte_row_2x:
+        _ mov %cr0, %rax
+        _ mov %dr0, %rax
+        _ mov %rax, %cr0
+        _ mov %rax, %dr0
+
+twobyte_row_3x:
+        _ wrmsr
+        _ rdtsc
+        _ rdmsr
+        _ rdpmc
+
+twobyte_row_4x:
+        _ cmovo (%rax), %eax
+        _ cmovg (%rax), %eax
+
+/* twobyte_row_8x: --> Jcc disp32 */
+
+twobyte_row_9x:
+        _ seto (%rax)
+        _ setg (%rax)
+
+twobyte_row_ax:
+        _ push %fs
+        _ pop %fs
+        _ cpuid
+        _ bt %eax, (%rax)
+        _ shld $0, %ax, (%rax)
+        _ shld $0, %eax, (%rax)
+        _ shld $0, %rax, (%rax)
+        _ shld %cl, %ax, (%rax)
+        _ shld %cl, %eax, (%rax)
+        _ shld %cl, %rax, (%rax)
+        _ push %gs
+        _ pop %gs
+        /*rsm --> not supported */
+        _ bts %eax, (%rax)
+        _ shrd $0, %ax, (%rax)
+        _ shrd $0, %eax, (%rax)
+        _ shrd $0, %rax, (%rax)
+        _ shrd %cl, %ax, (%rax)
+        _ shrd %cl, %eax, (%rax)
+        _ shrd %cl, %rax, (%rax)
+        _ fxsave (%rax) /* Grp15 */
+        _ imul (%rax), %eax
+
+twobyte_row_bx:
+        _ cmpxchg %al, (%rax)
+        _ cmpxchg %eax, (%rax)
+        _ lss (%rax), %eax
+        _ btr %eax, (%rax)
+        _ lfs (%rax), %eax
+        _ lgs (%rax), %eax
+        _ movzbl (%rax), %eax
+        _ movzwl (%rax), %eax
+        _ popcnt (%rax), %eax
+        _ ud1 (%rax), %eax /* Grp10 */
+        _ bt $0, %ax /* Grp8 */
+        _ bt $0, %eax
+        _ bt $0, %rax
+        _ btc %eax, (%rax)
+        _ bsf (%rax), %eax
+        _ bsr (%rax), %eax
+        _ movsbl (%rax), %eax
+        _ movswl (%rax), %eax
+
+twobyte_row_cx:
+        _ xadd %al, (%rax)
+        _ xadd %eax, (%rax)
+        _ cmpxchg8b (%rax) /* Grp9 */
+        _ bswap %eax
+        _ bswap %edi
+
+END(tests_rel0)
+
+DECL(tests_rel1)
+disp8:
+1:
+        _ jo   1b
+        _ jno  1b
+        _ jb   1b
+        _ jae  1b
+        _ je   1b
+        _ jne  1b
+        _ jbe  1b
+        _ ja   1b
+        _ js   1b
+        _ jns  1b
+        _ jp   1b
+        _ jnp  1b
+        _ jl   1b
+        _ jge  1b
+        _ jle  1b
+        _ jg   1b
+        _ jmp  1b
+
+disp8_rex:
+        _ rex.w jo   1b
+        _ rex.w jno  1b
+        _ rex.w jb   1b
+        _ rex.w jae  1b
+        _ rex.w je   1b
+        _ rex.w jne  1b
+        _ rex.w jbe  1b
+        _ rex.w ja   1b
+        _ rex.w js   1b
+        _ rex.w jns  1b
+        _ rex.w jp   1b
+        _ rex.w jnp  1b
+        _ rex.w jl   1b
+        _ rex.w jge  1b
+        _ rex.w jle  1b
+        _ rex.w jg   1b
+        _ rex.w jmp  1b
+END(tests_rel1)
+
+DECL(tests_rel4)
+disp32:
+        _ call   other_section
+        _ jmp    other_section
+        _ jo     other_section
+        _ jno    other_section
+        _ jb     other_section
+        _ jae    other_section
+        _ je     other_section
+        _ jne    other_section
+        _ jbe    other_section
+        _ ja     other_section
+        _ js     other_section
+        _ jns    other_section
+        _ jp     other_section
+        _ jnp    other_section
+        _ jl     other_section
+        _ jge    other_section
+        _ jle    other_section
+        _ jg     other_section
+        _ xbegin other_section
+
+disp32_rex:
+        _ rex.w call   other_section
+        _ rex.w jmp    other_section
+        _ rex.w jo     other_section
+        _ rex.w jno    other_section
+        _ rex.w jb     other_section
+        _ rex.w jae    other_section
+        _ rex.w je     other_section
+        _ rex.w jne    other_section
+        _ rex.w jbe    other_section
+        _ rex.w ja     other_section
+        _ rex.w js     other_section
+        _ rex.w jns    other_section
+        _ rex.w jp     other_section
+        _ rex.w jnp    other_section
+        _ rex.w jl     other_section
+        _ rex.w jge    other_section
+        _ rex.w jle    other_section
+        _ rex.w jg     other_section
+        _ rex.w xbegin other_section
+
+riprel:
+        _ add %al, 0(%rip)
+        _ rex.b add %al, 0(%rip)
+
+        _ addb $1, 0(%rip)
+        _ rex.b addb $1, 0(%rip)
+
+        _ addl $0x7f000001, 0(%rip)
+        _ rex.b addl $0x7f000001, 0(%rip)
+END(tests_rel4)
+
+DECL(tests_unsup)
+
+unsup_prefix: /* Prefixes unimplemented for simplicity. */
+        _ vaddpd %zmm0, %zmm0, %zmm0 /* 0x62 EVEX */
+        _ addr32 nop                 /* 0x67 Address size override */
+        _ bextr $0, %eax, %eax       /* 0x8f XOP */
+        _ bextr %eax, %eax, %eax     /* 0xc4 VEX3 */
+        _ vaddpd %ymm0, %ymm0, %ymm0 /* 0xc5 VEX2 */
+        n "jmpabs 0" .byte 0xd5, 0x00, 0xa1, 0x01, 0, 0, 0, 0, 0, 0, 0x80 /* 0xd5 REX2 */
+        _ fadds (%rax)               /* 0xd8 ... 0xdf ESCAPE (x87) */
+        _ femms                      /* 0x0f,0x0e ... 0x0f 3DNOW */
+
+unsup_branch:
+1:
+        _ loopne 1b
+        _ loope  1b
+        _ loop   1b
+        _ jrcxz  1b
+
+opsize_branch: /* 66-prefixed branches are decoded differently by vendors */
+        _ data16 call   other_section
+        _ data16 jmp    other_section
+        _ data16 jo     other_section
+        _ data16 jno    other_section
+        _ data16 jb     other_section
+        _ data16 jae    other_section
+        _ data16 je     other_section
+        _ data16 jne    other_section
+        _ data16 jbe    other_section
+        _ data16 ja     other_section
+        _ data16 js     other_section
+        _ data16 jns    other_section
+        _ data16 jp     other_section
+        _ data16 jnp    other_section
+        _ data16 jl     other_section
+        _ data16 jge    other_section
+        _ data16 jle    other_section
+        _ data16 jg     other_section
+        _ data16 xbegin other_section
+
+not_64bit: /* Not valid/encodable in 64bit mode */
+        .code32
+        _ push %es
+        _ pop %es
+        _ push %cs
+        _ push %ss
+        _ pop %ss
+        _ push %ds
+        _ pop %ds
+        _ daa
+        _ das
+        _ aaa
+        _ aas
+        _ pusha
+        _ popa
+        _ bound %eax, (%eax)
+        /*arpl %ax, %ax --> movsxd in 64bit mode */
+        /* Grp1 */
+        _ lcall $-1, $-1
+        _ les (%eax), %eax
+        _ lds (%eax), %eax
+        _ into
+        _ aam $0
+        _ aad $0 /* Also REX2, also not supported */
+        _ ljmp $-1, $-1
+        .code64
+
+unsup_insn: /* Instructions that would complicated decode, or shouldn't be used */
+        _ ret $0
+        _ enter $0, $0
+        _ leave
+        _ lretq $0
+        _ iretq
+        _ xlat
+        _ clts
+        _ wbinvd
+        _ syscall
+        _ sysretl
+        _ invd
+        _ sysenter
+        _ sysexitl
+        _ rsm
+
+END(tests_unsup)
+
+        /* This is here to cause jmps to use their disp32 form. */
+        .section .text.other_section, "ax", @progbits
+other_section:
+        int3
+
+        /* Mark this file as not needing executable stacks. */
+        .section  .note.GNU-stack, "", @progbits
diff --git a/tools/tests/x86-decode-lite/macro-magic.h b/tools/tests/x86-decode-lite/macro-magic.h
new file mode 100644
index 000000000000..b3c8aae39acd
--- /dev/null
+++ b/tools/tests/x86-decode-lite/macro-magic.h
@@ -0,0 +1,62 @@
+#ifndef X86_DECODE_LITE_LINKAGE_H
+#define X86_DECODE_LITE_LINKAGE_H
+
+#ifdef __i386__
+# define PTR_ALIGN 4
+# define PTR .long
+#else
+# define PTR_ALIGN 8
+# define PTR .quad
+#endif
+
+
+/* Start a 'struct test' array */
+.macro start_arr aname
+    .pushsection .data.rel.ro.\aname, "aw", @progbits
+    .globl \aname
+    .align PTR_ALIGN
+    .type \aname, STT_OBJECT
+\aname:
+    .popsection
+
+    /* Declare a macro wrapping \aname */
+    .macro pushsection_arr
+    .pushsection .data.rel.ro.\aname, "aw", @progbits
+    .endm
+.endm
+
+/* Macro 'n' to wrap the metadata of an instruction.  Name can be different. */
+.macro n name:req insn:vararg
+    /* Emit the instruction, with start & end markers. */
+.Ls\@: \insn
+.Le\@:
+
+    /* Emit \name as a string. */
+    .pushsection .rodata.str1, "aMS", @progbits, 1
+.Ln\@: .asciz "\name"
+    .popsection
+
+    /* Emit an entry into the array. */
+    pushsection_arr
+    PTR .Ln\@, .Ls\@, .Le\@ - .Ls\@
+    .popsection
+.endm
+
+/* Macro '_' where the name is the instruction itself. */
+.macro _ insn:vararg
+    n "\insn" \insn
+.endm
+
+/* Finish a 'struct test' array */
+.macro finish_arr aname
+    pushsection_arr
+    PTR 0, 0, 0
+    .size \aname, . - \aname
+    .popsection
+    .purgem pushsection_arr
+.endm
+
+#define DECL(aname) start_arr aname
+#define END(aname) finish_arr aname
+
+#endif /* X86_DECODE_LITE_LINKAGE_H */
diff --git a/tools/tests/x86-decode-lite/main.c b/tools/tests/x86-decode-lite/main.c
new file mode 100644
index 000000000000..cdae7de8e90e
--- /dev/null
+++ b/tools/tests/x86-decode-lite/main.c
@@ -0,0 +1,111 @@
+/*
+ * Userspace test harness for x86_decode_lite().
+ */
+#include <stdio.h>
+
+#include "x86-emulate.h"
+
+static unsigned int nr_failures;
+#define fail(t, fmt, ...)                                       \
+({                                                              \
+    const unsigned char *insn = (t)->ip;                        \
+                                                                \
+    nr_failures++;                                              \
+                                                                \
+    (void)printf("  Fail '%s' [%02x", (t)->name, *insn);        \
+    for ( unsigned int i = 1; i < (t)->len; i++ )               \
+        printf(" %02x", insn[i]);                               \
+    printf("]\n");                                              \
+                                                                \
+    (void)printf(fmt, ##__VA_ARGS__);                           \
+})
+
+struct test {
+    const char *name;
+    void *ip;
+    unsigned long len;
+};
+
+extern const struct test
+/* Defined in insns.S, ends with sentinel */
+    tests_rel0[], /* No relocatable entry */
+    tests_rel1[], /* disp8 */
+    tests_rel4[], /* disp32 or RIP-relative */
+    tests_unsup[]; /* Unsupported instructions */
+
+static inline void run_tests(const struct test *tests, unsigned int rel_sz)
+{
+    printf("Test rel%u\n", rel_sz);
+
+    for ( unsigned int i = 0; tests[i].name; ++i )
+    {
+        const struct test *t = &tests[i];
+        x86_decode_lite_t r;
+
+        /*
+         * Don't end strictly at t->len.  This provides better diagnostics if
+         * too many bytes end up getting consumed.
+         */
+        r = x86_decode_lite(t->ip, t->ip + /* t->len */ 20);
+
+        if ( r.len == 0 )
+        {
+            fail(t, "    Failed to decode instruction\n");
+
+            if ( r.rel_sz != 0 || r.rel )
+                fail(t, "    Rel/sz despite no decode\n");
+
+            continue;
+        }
+
+        if ( r.len != t->len )
+        {
+            fail(t, "    Expected length %lu, got %u\n",
+                 t->len, r.len);
+            continue;
+        }
+
+        if ( r.rel_sz != rel_sz )
+        {
+            fail(t, "    Expected relocation size %u, got %u\n",
+                 rel_sz, r.rel_sz);
+            continue;
+        }
+
+        if ( r.rel_sz &&
+             (r.rel < t->ip ||
+              r.rel > t->ip + t->len ||
+              r.rel + r.rel_sz > t->ip + t->len) )
+        {
+            fail(t, "    Rel [%p,+%u) outside insn [%p,+%lu)\n",
+                 r.rel, r.rel_sz, t->ip, t->len);
+            continue;
+        }
+    }
+}
+
+static void run_tests_unsup(const struct test *tests)
+{
+    printf("Test unsup\n");
+
+    for ( unsigned int i = 0; tests[i].name; ++i )
+    {
+        const struct test *t = &tests[i];
+        x86_decode_lite_t r = x86_decode_lite(t->ip, t->ip + t->len);
+
+        if ( r.len )
+            fail(t, "    Got len %u\n", r.len);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    printf("Tests for x86_decode_lite()\n");
+
+    run_tests(tests_rel0, 0);
+    run_tests(tests_rel1, 1);
+    run_tests(tests_rel4, 4);
+    run_tests_unsup(tests_unsup);
+
+    return !!nr_failures;
+}
diff --git a/tools/tests/x86-decode-lite/x86-emulate.h b/tools/tests/x86-decode-lite/x86-emulate.h
new file mode 100644
index 000000000000..558dab1b768e
--- /dev/null
+++ b/tools/tests/x86-decode-lite/x86-emulate.h
@@ -0,0 +1,27 @@
+#ifndef X86_EMULATE_H
+#define X86_EMULATE_H
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xen/asm/x86-defns.h>
+#include <xen/asm/x86-vendors.h>
+
+#include <xen-tools/common-macros.h>
+
+#define ASSERT assert
+
+#define printk(...)
+
+#define likely
+#define unlikely
+#define cf_check
+#define init_or_livepatch
+#define init_or_livepatch_const
+
+#include "x86_emulate/x86_emulate.h"
+
+#endif /* X86_EMULATE_H */
-- 
2.39.5



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 3/5] x86/alternative: Walk all replacements during self tests
  2026-08-03  7:20 [PATCH v3 0/5] x86/alternatives: Adjust all insn-relative fields Andrew Cooper
  2026-08-03  7:20 ` [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite() Andrew Cooper
  2026-08-03  7:20 ` [PATCH v3 2/5] tests/x86: Introduce a userspace test harness for x86_decode_lite() Andrew Cooper
@ 2026-08-03  7:20 ` Andrew Cooper
  2026-08-03  7:20 ` [PATCH v3 4/5] x86/alternative: Relocate all insn-relative fields Andrew Cooper
  2026-08-03  7:20 ` [PATCH v3 5/5] x86/spec-ctrl: Introduce and use DO_COND_BHB_SEQ Andrew Cooper
  4 siblings, 0 replies; 14+ messages in thread
From: Andrew Cooper @ 2026-08-03  7:20 UTC (permalink / raw)
  To: Xen-devel
  Cc: Andrew Cooper, Jan Beulich, Jan Beulich, Roger Pau Monné,
	Teddy Astie

When self tests are active, walk all alternative replacements with
x86_decode_lite().

This checks that we can decode all instructions, and also lets us check that
disp8's don't leave the replacement block as such a case will definitely
malfunction.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Jan Beulich <JBeulich@suse.com>
---
CC: Jan Beulich <jbeulich@suse.com>
CC: Roger Pau Monné <roger@xenproject.org>
CC: Teddy Astie <teddy.astie@vates.tech>

v2:
 * Rebase over API changes in patch 1
 * Use +%lu and drop casts
 * Swap to CONFIG_SELF_TESTS
---
 xen/arch/x86/alternative.c | 52 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/xen/arch/x86/alternative.c b/xen/arch/x86/alternative.c
index 5ed0c2672589..fd03147bdd12 100644
--- a/xen/arch/x86/alternative.c
+++ b/xen/arch/x86/alternative.c
@@ -16,6 +16,7 @@
 #include <asm/traps.h>
 #include <asm/nmi.h>
 #include <asm/nops.h>
+#include <asm/x86_emulate.h>
 #include <xen/livepatch.h>
 
 #define MAX_PATCH_LEN (255-1)
@@ -586,6 +587,57 @@ static void __init _alternative_instructions(unsigned int what)
 void __init alternative_instructions(void)
 {
     arch_init_ideal_nops();
+
+    /*
+     * Walk all replacement instructions with x86_decode_lite().  This checks
+     * both that we can decode all instructions within the replacement, and
+     * that any near branch with a disp8 stays within the alternative itself.
+     */
+    if ( IS_ENABLED(CONFIG_SELF_TESTS) )
+    {
+        struct alt_instr *a;
+
+        for ( a = __alt_instructions;
+              a < __alt_instructions_end; ++a )
+        {
+            void *repl = ALT_REPL_PTR(a);
+            void *ip = repl, *end = ip + a->repl_len;
+
+            if ( !a->repl_len )
+                continue;
+
+            for ( x86_decode_lite_t res; ip < end; ip += res.len )
+            {
+                const int8_t *d8;
+                const void *target;
+
+                res = x86_decode_lite(ip, end);
+
+                if ( res.len == 0 )
+                {
+                    printk("Alt for %ps [%*ph]\n",
+                           ALT_ORIG_PTR(a), a->repl_len, repl);
+                    panic("  Unable to decode instruction at +%lu in alternative\n",
+                          ip - repl);
+                }
+
+                if ( res.rel_sz != 1 )
+                    continue;
+
+                d8 = res.rel;
+                target = ip + res.len + *d8;
+
+                if ( target < repl || target > end )
+                {
+                    printk("Alt for %ps [%*ph]\n",
+                           ALT_ORIG_PTR(a), a->repl_len, repl);
+                    panic("  'JMP/Jcc disp8' at +%lu leaves alternative block\n",
+                          ip - repl);
+                }
+            }
+        }
+    }
+
     _alternative_instructions(ALT_INSNS);
 }
 
-- 
2.39.5



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 4/5] x86/alternative: Relocate all insn-relative fields
  2026-08-03  7:20 [PATCH v3 0/5] x86/alternatives: Adjust all insn-relative fields Andrew Cooper
                   ` (2 preceding siblings ...)
  2026-08-03  7:20 ` [PATCH v3 3/5] x86/alternative: Walk all replacements during self tests Andrew Cooper
@ 2026-08-03  7:20 ` Andrew Cooper
  2026-08-03  7:20 ` [PATCH v3 5/5] x86/spec-ctrl: Introduce and use DO_COND_BHB_SEQ Andrew Cooper
  4 siblings, 0 replies; 14+ messages in thread
From: Andrew Cooper @ 2026-08-03  7:20 UTC (permalink / raw)
  To: Xen-devel; +Cc: Andrew Cooper, Jan Beulich, Roger Pau Monné, Teddy Astie

Right now, relocation of displacements is restricted to finding 0xe8/e9 as the
first byte of the replacement, but this is overly restrictive.

Use x86_decode_lite() to find and adjust all insn-relative fields.

As with disp8's not leaving the replacemnet block, some disp32's don't either.
e.g. the RSB stuffing loop.  These stay unmodified.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
---
CC: Jan Beulich <jbeulich@suse.com>
CC: Roger Pau Monné <roger@xenproject.org>
CC: Teddy Astie <teddy.astie@vates.tech>

v3:
 * Rebase over the split-out of altcall.  Substantially simpler.
---
 xen/arch/x86/alternative.c | 50 +++++++++++++++++++++++++++++++-------
 1 file changed, 41 insertions(+), 9 deletions(-)

diff --git a/xen/arch/x86/alternative.c b/xen/arch/x86/alternative.c
index fd03147bdd12..a4a65597b2fc 100644
--- a/xen/arch/x86/alternative.c
+++ b/xen/arch/x86/alternative.c
@@ -349,15 +349,47 @@ static int init_or_livepatch _apply_alternatives(struct alt_instr *start,
 
         memcpy(buf, repl, a->repl_len);
 
-        /* 0xe8/0xe9 are relative branches; fix the offset. */
-        if ( a->repl_len >= 5 && (*buf & 0xfe) == 0xe8 )
-            *(int32_t *)(buf + 1) += repl - orig;
-        else if ( IS_ENABLED(CONFIG_RETURN_THUNK) &&
-                  a->repl_len > 5 && buf[a->repl_len - 5] == 0xe9 &&
-                  ((long)repl + a->repl_len +
-                   *(int32_t *)(buf + a->repl_len - 4) ==
-                   (long)__x86_return_thunk) )
-            *(int32_t *)(buf + a->repl_len - 4) += repl - orig;
+        /*
+         * Walk buf[] and adjust any insn-relative operands which leave the
+         * replacement block.
+         */
+        if ( a->repl_len )
+        {
+            uint8_t *ip = buf, *repl_end = ip + a->repl_len;
+
+            for ( x86_decode_lite_t res; ip < repl_end; ip += res.len )
+            {
+                int32_t *d32;
+                const uint8_t *target;
+
+                res = x86_decode_lite(ip, repl_end);
+
+                if ( res.len == 0 )
+                {
+                    printk("Alt for %ps [%*ph]\n"
+                           "  Unable to decode instruction at +%lu in alternative\n",
+                           ALT_ORIG_PTR(a), a->repl_len, repl, ip - repl);
+                    return -EINVAL;
+                }
+
+                if ( res.rel_sz != 4 )
+                    continue;
+
+                d32 = res.rel;
+                target = ip + res.len + *d32;
+
+                if ( target >= buf && target <= repl_end )
+                {
+                    /*
+                     * Target doesn't leave the replacement block.  e.g. RSB
+                     * stuffing.  Leave it unmodified.
+                     */
+                    continue;
+                }
+
+                *d32 += repl - orig;
+            }
+        }
 
         a->priv = 1;
 
-- 
2.39.5



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 5/5] x86/spec-ctrl: Introduce and use DO_COND_BHB_SEQ
  2026-08-03  7:20 [PATCH v3 0/5] x86/alternatives: Adjust all insn-relative fields Andrew Cooper
                   ` (3 preceding siblings ...)
  2026-08-03  7:20 ` [PATCH v3 4/5] x86/alternative: Relocate all insn-relative fields Andrew Cooper
@ 2026-08-03  7:20 ` Andrew Cooper
  4 siblings, 0 replies; 14+ messages in thread
From: Andrew Cooper @ 2026-08-03  7:20 UTC (permalink / raw)
  To: Xen-devel; +Cc: Andrew Cooper, Jan Beulich, Roger Pau Monné, Teddy Astie

Now that alternatives can fix up call displacements even when they're not the
first instruction of the replacement, move the SCF_entry_bhb conditional
inside the replacement block.

This removes a conditional branch from the fastpaths of BHI-unaffected
hardware.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
---
CC: Jan Beulich <jbeulich@suse.com>
CC: Roger Pau Monné <roger@xenproject.org>
CC: Teddy Astie <teddy.astie@vates.tech>
---
 xen/arch/x86/hvm/vmx/entry.S             | 12 +++----
 xen/arch/x86/include/asm/spec_ctrl_asm.h | 43 +++++++++++++-----------
 2 files changed, 30 insertions(+), 25 deletions(-)

diff --git a/xen/arch/x86/hvm/vmx/entry.S b/xen/arch/x86/hvm/vmx/entry.S
index cebc70064048..76508c0de2f3 100644
--- a/xen/arch/x86/hvm/vmx/entry.S
+++ b/xen/arch/x86/hvm/vmx/entry.S
@@ -59,12 +59,12 @@ FUNC(vmx_asm_vmexit_handler)
          * Clear the BHB to mitigate BHI.  Used on eIBRS parts, and uses RETs
          * itself so must be after we've perfomed all the RET-safety we can.
          */
-        testb $SCF_entry_bhb, CPUINFO_scf(%rsp)
-        jz .L_skip_bhb
-        ALTERNATIVE_2 "",                                    \
-            "call clear_bhb_loops", X86_SPEC_BHB_LOOPS,      \
-            "call clear_bhb_tsx", X86_SPEC_BHB_TSX
-.L_skip_bhb:
+        .macro VMX_BHB_SEQ fn:req
+            DO_COND_BHB_SEQ \fn scf=CPUINFO_scf(%rsp)
+        .endm
+        ALTERNATIVE_2 "",                                         \
+            "VMX_BHB_SEQ fn=clear_bhb_loops", X86_SPEC_BHB_LOOPS, \
+            "VMX_BHB_SEQ fn=clear_bhb_tsx",   X86_SPEC_BHB_TSX
 
         ALTERNATIVE "lfence", "", X86_SPEC_NO_LFENCE_ENTRY_VMX
         /* WARNING! `ret`, `call *`, `jmp *` not safe before this point. */
diff --git a/xen/arch/x86/include/asm/spec_ctrl_asm.h b/xen/arch/x86/include/asm/spec_ctrl_asm.h
index abb64ad2b7f9..780ec57f4553 100644
--- a/xen/arch/x86/include/asm/spec_ctrl_asm.h
+++ b/xen/arch/x86/include/asm/spec_ctrl_asm.h
@@ -92,6 +92,21 @@
 .L\@_skip:
 .endm
 
+.macro DO_COND_BHB_SEQ fn:req, scf=%bl
+/*
+ * Requires SCF (defaults to %rbx), fn=clear_bhb_{loops,tsx}
+ * Clobbers %rax, %rcx
+ *
+ * Conditionally use a BHB clearing software sequence.
+ */
+    testb  $SCF_entry_bhb, \scf
+    jz     .L\@_skip_bhb
+
+    call   \fn
+
+.L\@_skip_bhb:
+.endm
+
 .macro DO_OVERWRITE_RSB tmp=rax, xu
 /*
  * Requires nothing
@@ -277,12 +292,9 @@
      * Clear the BHB to mitigate BHI.  Used on eIBRS parts, and uses RETs
      * itself so must be after we've perfomed all the RET-safety we can.
      */
-    testb $SCF_entry_bhb, %bl
-    jz .L\@_skip_bhb
-    ALTERNATIVE_2 "",                                    \
-        "call clear_bhb_loops", X86_SPEC_BHB_LOOPS,      \
-        "call clear_bhb_tsx", X86_SPEC_BHB_TSX
-.L\@_skip_bhb:
+    ALTERNATIVE_2 "",                                          \
+        "DO_COND_BHB_SEQ clear_bhb_loops", X86_SPEC_BHB_LOOPS, \
+        "DO_COND_BHB_SEQ clear_bhb_tsx",   X86_SPEC_BHB_TSX
 
     ALTERNATIVE "lfence", "", X86_SPEC_NO_LFENCE_ENTRY_PV
 .endm
@@ -322,12 +334,9 @@
     ALTERNATIVE "", __stringify(DO_SPEC_CTRL_ENTRY maybexen=1),         \
         X86_FEATURE_SC_MSR_PV
 
-    testb $SCF_entry_bhb, %bl
-    jz .L\@_skip_bhb
-    ALTERNATIVE_2 "",                                    \
-        "call clear_bhb_loops", X86_SPEC_BHB_LOOPS,      \
-        "call clear_bhb_tsx", X86_SPEC_BHB_TSX
-.L\@_skip_bhb:
+    ALTERNATIVE_2 "",                                          \
+        "DO_COND_BHB_SEQ clear_bhb_loops", X86_SPEC_BHB_LOOPS, \
+        "DO_COND_BHB_SEQ clear_bhb_tsx",   X86_SPEC_BHB_TSX
 
     ALTERNATIVE "lfence", "", X86_SPEC_NO_LFENCE_ENTRY_INTR
 .endm
@@ -433,13 +442,9 @@
      * Clear the BHB to mitigate BHI.  Used on eIBRS parts, and uses RETs
      * itself so must be after we've perfomed all the RET-safety we can.
      */
-    testb $SCF_entry_bhb, %bl
-    jz .L\@_skip_bhb
-
-    ALTERNATIVE_2 "",                                    \
-        "call clear_bhb_loops", X86_SPEC_BHB_LOOPS,      \
-        "call clear_bhb_tsx", X86_SPEC_BHB_TSX
-.L\@_skip_bhb:
+    ALTERNATIVE_2 "",                                          \
+        "DO_COND_BHB_SEQ clear_bhb_loops", X86_SPEC_BHB_LOOPS, \
+        "DO_COND_BHB_SEQ clear_bhb_tsx",   X86_SPEC_BHB_TSX
 
     lfence
 .endm
-- 
2.39.5



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite()
  2026-08-03  7:20 ` [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite() Andrew Cooper
@ 2026-08-03  9:14   ` Andrew Cooper
  2026-08-03 15:26   ` Jan Beulich
  1 sibling, 0 replies; 14+ messages in thread
From: Andrew Cooper @ 2026-08-03  9:14 UTC (permalink / raw)
  To: Xen-devel; +Cc: Andrew Cooper, Jan Beulich, Roger Pau Monné, Teddy Astie

On 03/08/2026 8:20 am, Andrew Cooper wrote:
> diff --git a/xen/arch/x86/x86_emulate/decode-lite.c b/xen/arch/x86/x86_emulate/decode-lite.c
> new file mode 100644
> index 000000000000..131cc07d5516
> --- /dev/null
> +++ b/xen/arch/x86/x86_emulate/decode-lite.c
> @@ -0,0 +1,330 @@
> +
> +    if ( d & (Imm | Imm8 | Moffs) )
> +    {
> +        if ( d & Imm8 )
> +            osize = 1;
> +        else if ( d & Moffs )
> +            osize = 8;
> +        else if ( osize == 8 && !(opc >= 0xb8 && opc <= 0xbf) )
> +            osize = 4;

GCC 12 does transform this into sub $0xb8; cmp $7.

> +
> +        switch ( osize )
> +        {
> +        case 1: FETCH(uint8_t);  break;
> +        case 2: FETCH(uint16_t); break;
> +        case 4: FETCH(uint32_t); break;
> +        case 8: FETCH(uint64_t); break;
> +        default: goto bad_osize;
> +        }

On further consideration:

        switch ( osize )
        {
        case 1:
        case 2:
        case 4:
        case 8:
            if ( ip + osize > end )
                goto overrun;
            ip += osize;
            break;

        default: goto bad_osize;
        }

drops nearly 10% of the function:

    add/remove: 0/0 grow/shrink: 0/1 up/down: 0/-91 (-91)
    Function                                     old     new   delta
    x86_decode_lite                              972     881     -91

GCC clearly can't reason about the relationship between osize and
sizeof(type), and needs the help.


I also tried the further simplification:

        if ( osize > 8 || (osize & (osize - 1)) != 0 )
            goto bad_osize;
        if ( ip + osize > end )
            goto overrun;

        ip += osize;

but interestingly this delta grows the function by 30 bytes.  It only
seems to add the block checking osize, meaning that GCC managed to
optimise away all of the switch dispatch previously.  In hindsight this
is probably quite easy; because we're 64bit only, osize only ever has
constant values that GCC can see.

Anyway, I've folded in the first optimisation.

~Andrew


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite()
  2026-08-03  7:20 ` [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite() Andrew Cooper
  2026-08-03  9:14   ` Andrew Cooper
@ 2026-08-03 15:26   ` Jan Beulich
  2026-08-04 15:39     ` Jan Beulich
  2026-08-04 18:56     ` Andrew Cooper
  1 sibling, 2 replies; 14+ messages in thread
From: Jan Beulich @ 2026-08-03 15:26 UTC (permalink / raw)
  To: Andrew Cooper; +Cc: Roger Pau Monné, Teddy Astie, Xen-devel

> --- /dev/null
> +++ b/xen/arch/x86/x86_emulate/decode-lite.c
> @@ -0,0 +1,330 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +#ifdef __XEN__
> +# include <xen/init.h>
> +# include <xen/livepatch.h>
> +#endif
> +
> +#include "private.h"
> +
> +#undef ModRM
> +
> +/*
> + * Bare minimum x86 instruction decoder to parse the alternative replacement
> + * instructions and locate the IP-relative references that may need updating.
> + *
> + * These are:
> + *  - disp8/32 from near direct branches
> + *  - RIP-relative memory references
> + *
> + * The following simplifications are used:
> + *  - All code is 64bit, the instruction stream is well formed and safe to
> + *    read.
> + *  - Instruction groups and prefixes not used by Xen's current alternatives
> + *    are not implemented in order to reduce the decode complexity.
> + *  - Certain instructions are intentionally not recognised, when it is more
> + *    likely for their presence to be an error than intentional.
> + *
> + * Inputs:
> + *  @ip  The position to start decoding from.
> + *  @end End of the replacement block.  Exceeding this is considered an error.

Why do you mention replacement blocks here? Are we entirely set on this
code not possibly gaining any purpose beyond the scanning of those?

> + * Returns: x86_decode_lite_t
> + *  - On failure, length of 0.
> + *  - On success, length > 0.  For rel_sz > 0, rel points at the relative
> + *    field in the instruction stream.
> + */
> +x86_decode_lite_t init_or_livepatch x86_decode_lite(void *ip, void *end)

Is there a reason the parameters can't be pointer-to-const? Hmm,
apparently for x86_decode_lite_t's "rel" field not be plaing void *, "ip"
needs to be this way as well. But not "end", I don't think.

> +{
> +#define Imm8   (1 << 0)
> +#define Imm    (1 << 1)
> +#define Moffs  (1 << 2)
> +#define Branch (1 << 5) /* Near direct branches, which have a displacement */
> +#define ModRM  (1 << 6)
> +#define Known  (1 << 7)
> +
> +    static const uint8_t init_or_livepatch_const onebyte[256] = {
> +
> +#define ALU_OPS(x)                              \
> +        [(x) + 0] = (Known|ModRM),              \
> +        [(x) + 1] = (Known|ModRM),              \
> +        [(x) + 2] = (Known|ModRM),              \
> +        [(x) + 3] = (Known|ModRM),              \
> +        [(x) + 4] = (Known|Imm8),               \
> +        [(x) + 5] = (Known|Imm)
> +
> +        ALU_OPS(0x00) /* ADD */, ALU_OPS(0x08) /* OR  */,
> +        ALU_OPS(0x10) /* ADC */, ALU_OPS(0x18) /* SBB */,
> +        ALU_OPS(0x20) /* AND */, ALU_OPS(0x28) /* SUB */,
> +        ALU_OPS(0x30) /* XOR */, ALU_OPS(0x38) /* CMP */,
> +
> +#undef ALU_OPS
> +
> +        [0x50 ... 0x5f] = (Known),             /* PUSH/POP %reg */
> +
> +        [0x62]          = 0,                   /* BOUND, but also EVEX prefix, not implemented. */
> +        [0x63]          = (Known|ModRM),       /* MOVSxd */
> +
> +        [0x68]          = (Known|Imm),         /* PUSH $imm */
> +        [0x69]          = (Known|ModRM|Imm),   /* IMUL $imm */
> +        [0x6a]          = (Known|Imm8),        /* PUSH $imm8 */
> +        [0x6b]          = (Known|ModRM|Imm8),  /* PUSH $imm8 */
> +        [0x6c ... 0x6f] = (Known),             /* INS/OUTS */
> +        [0x70 ... 0x7f] = (Known|Branch|Imm8), /* Jcc disp8 */
> +        [0x80]          = (Known|ModRM|Imm8),  /* Grp1 */
> +        [0x81]          = (Known|ModRM|Imm),   /* Grp1 */
> +
> +        [0x83]          = (Known|ModRM|Imm8),  /* Grp1 */
> +        [0x84 ... 0x8e] = (Known|ModRM),       /* TEST/XCHG/MOV/MOV-SREG/LEA */
> +        [0x8f]          = 0,                   /* Grp1A - POP but also XOP prefix, not implemented. */

POP doesn't look all that unlikely to be used in inline assembly, and
hence in alternatives. That said, of course using it with a memory
operand requires quite a bit of care. I don't see you excluding the
PUSH counterpart, though - being consistent for any such pairs would
seem somewhat desirable.

> +        [0x90 ... 0x99] = (Known),             /* NOP/XCHG %rAX/CLTQ/CQTO */
> +
> +        [0x9b ... 0x9f] = (Known),             /* FWAIT/PUSHF/POPF/SAHF/LAHF */
> +        [0xa0 ... 0xa3] = (Known|Moffs),       /* MOVABS */
> +        [0xa4 ... 0xa7] = (Known),             /* MOVS/CMPS */
> +        [0xa8]          = (Known|Imm8),        /* TEST %al */
> +        [0xa9]          = (Known|Imm),         /* TEST %rAX */
> +        [0xaa ... 0xaf] = (Known),             /* STOS/LODS/SCAS */
> +        [0xb0 ... 0xb7] = (Known|Imm8),        /* MOV $imm8, %reg */
> +        [0xb8 ... 0xbf] = (Known|Imm),         /* MOV $imm{16,32,64}, %reg */
> +        [0xc0 ... 0xc1] = (Known|ModRM|Imm8),  /* Grp2 (ROL..SAR $imm8, %reg) */
> +
> +        [0xc3]          = (Known),             /* RET */
> +        [0xc4 ... 0xc5] = 0,                   /* LES/LDS but also VEX prefixes, not implemented. */

This may bite us sooner or later, due to the VEX-encoded integer insns
that there are. Of course as long as we don't use this function on
compiled code, and as long as my "x86: allow Kconfig control over psABI
level" doesn't come close to going in, that's merely a theoretical
concern.

Same goes for not supporting the 3-byte opcodes, which also encode
certain integer insns.

> +        [0xc6]          = (Known|ModRM|Imm8),  /* Grp11, Further ModRM decode */
> +        [0xc7]          = (Known|ModRM|Imm),   /* Grp11, Further ModRM decode */
> +
> +        [0xcb ... 0xcc] = (Known),             /* LRET/INT3 */
> +        [0xcd]          = (Known|Imm8),        /* INT $imm8 */
> +
> +        [0xd0 ... 0xd3] = (Known|ModRM),       /* Grp2 (ROL..SAR {$1,%cl}, %reg) */
> +
> +        [0xd6]          = (Known),             /* UDB */

I guess you consider XLAT, LOOP*, and J*CXZ as too odd to use in alternatives?
Decoding-wise they're rather easy to implement.

> +        [0xe4 ... 0xe7] = (Known|Imm8),        /* IN/OUT $imm8 */
> +        [0xe8 ... 0xe9] = (Known|Branch|Imm),  /* CALL/JMP disp32 */
> +
> +        [0xeb]          = (Known|Branch|Imm8), /* JMP disp8 */
> +        [0xec ... 0xef] = (Known),             /* IN/OUT %dx */
> +
> +        [0xf1]          = (Known),             /* ICEBP */
> +
> +        [0xf4]          = (Known),             /* HLT */
> +        [0xf5]          = (Known),             /* CMC */
> +        [0xf6 ... 0xf7] = (Known|ModRM),       /* Grp3, Further ModRM decode */
> +        [0xf8 ... 0xfd] = (Known),             /* CLC ... STD */
> +        [0xfe ... 0xff] = (Known|ModRM),       /* Grp4 */
> +    };
> +    static const uint8_t init_or_livepatch_const twobyte[256] = {
> +        [0x00 ... 0x03] = (Known|ModRM),       /* Grp6/Grp7/LAR/LSL */

Leaving out INVD is surely find, but WBINVD?

> +        [0x0b]          = (Known),             /* UD2 */
> +
> +        [0x18 ... 0x1f] = (Known|ModRM),       /* Grp16 (Hint Nop) */
> +        [0x20 ... 0x23] = (Known|ModRM),       /* MOV %cr/%dr */
> +
> +        [0x30 ... 0x33] = (Known),             /* WRMSR/RDTSC/RDMSR/RDPMC */
> +
> +        [0x40 ... 0x4f] = (Known|ModRM),       /* CMOVcc */
> +
> +        [0x80 ... 0x8f] = (Known|Branch|Imm),  /* Jcc disp32 */
> +        [0x90 ... 0x9f] = (Known|ModRM),       /* SETcc */
> +
> +        [0xa0 ... 0xa2] = (Known),             /* PUSH/POP %fs/CPUID */
> +        [0xa3]          = (Known|ModRM),       /* BT */
> +        [0xa4]          = (Known|ModRM|Imm8),  /* SHLD $imm8 */
> +        [0xa5]          = (Known|ModRM),       /* SHLD %cl */
> +
> +        [0xa8 ... 0xa9] = (Known),             /* PUSH/POP %gs */
> +
> +        [0xab]          = (Known|ModRM),       /* BTS */
> +        [0xac]          = (Known|ModRM|Imm8),  /* SHRD $imm8 */
> +        [0xad ... 0xaf] = (Known|ModRM),       /* SHRD %cl/Grp15/IMUL */
> +
> +        [0xb0 ... 0xb9] = (Known|ModRM),       /* CMPXCHG/LSS/BTR/LFS/LGS/MOVZxx/POPCNT/UD1 */
> +        [0xba]          = (Known|ModRM|Imm8),  /* Grp8 */
> +        [0xbb ... 0xbf] = (Known|ModRM),       /* BTC/BSF/BSR/MOVSX */
> +        [0xc0 ... 0xc1] = (Known|ModRM),       /* XADD */

What about MOVNTI?

> +        [0xc7]          = (Known|ModRM),       /* Grp9 */
> +        [0xc8 ... 0xcf] = (Known),             /* BSWAP */
> +    };

What about UD0?

> +    void *start = ip, *rel = NULL;
> +    unsigned int opc, rel_sz = 0;
> +    uint8_t b, d, rex = 0, osize = 4;
> +
> +#define OPC_TWOBYTE (1 << 8)
> +
> +    /* Mutates IP, uses END. */
> +#define FETCH(ty)                                       \
> +    ({                                                  \
> +        ty _val;                                        \
> +                                                        \
> +        if ( (ip + sizeof(ty)) > end )                  \
> +            goto overrun;                               \
> +        _val = *(ty *)ip;                               \
> +        ip += sizeof(ty);                               \
> +        _val;                                           \
> +    })
> +
> +    for ( ;; ) /* Prefixes */
> +    {
> +        switch ( b = FETCH(uint8_t) )
> +        {
> +        case 0x26: /* ES override */
> +        case 0x2e: /* CS override */
> +        case 0x36: /* DS override */
> +        case 0x3e: /* SS override */
> +        case 0x64: /* FS override */
> +        case 0x65: /* GS override */
> +        case 0xf0: /* LOCK */
> +        case 0xf2: /* REPNE */
> +        case 0xf3: /* REP */
> +            break;
> +
> +        case 0x66: /* Operand size override */
> +            osize = 2;
> +            break;
> +
> +        /* case 0x67: Address size override, not implemented */
> +
> +        case 0x40 ... 0x4f: /* REX */
> +            rex = b;
> +            continue;
> +
> +        default:
> +            goto prefixes_done;
> +        }
> +        rex = 0; /* REX cancelled by subsequent legacy prefix. */
> +    }
> + prefixes_done:
> +
> +    if ( rex & REX_W )
> +        osize = 8;
> +
> +    /* Fetch the main opcode byte(s) */
> +    if ( b == 0x0f )
> +    {
> +        b = FETCH(uint8_t);
> +        opc = OPC_TWOBYTE | b;
> +
> +        d = twobyte[b];
> +    }
> +    else
> +    {
> +        opc = b;
> +        d = onebyte[b];
> +    }
> +
> +    if ( unlikely(!(d & Known)) )
> +        goto unknown;
> +
> +    if ( d & ModRM )
> +    {
> +        uint8_t modrm = FETCH(uint8_t);
> +        uint8_t mod = modrm >> 6;
> +        uint8_t reg = (modrm >> 3) & 7;
> +        uint8_t rm = modrm & 7;
> +
> +        /* ModRM/SIB decode */
> +        if ( mod == 0 && rm == 5 ) /* RIP relative */
> +        {
> +            rel = ip;
> +            rel_sz = 4;
> +            FETCH(int32_t);

FETCH() here but ...

> +        }
> +        else if ( mod != 3 && rm == 4 ) /* SIB */
> +        {
> +            uint8_t sib = FETCH(uint8_t);
> +            uint8_t base = sib & 7;
> +
> +            if ( mod == 0 && base == 5 )
> +                goto disp32;

... goto here?

> +        }
> +
> +        if ( mod == 1 ) /* disp8 */
> +            FETCH(int8_t);
> +        else if ( mod == 2 ) /* disp32 */
> +        {
> +        disp32:
> +            FETCH(int32_t);
> +        }

In several cases the FETCH()ed value isn't used. Compilers as well as Eclair
(and alike) are happy with that? And compilers also manage to eliminate the
memory accesses then?

> --- a/xen/arch/x86/x86_emulate/x86_emulate.h
> +++ b/xen/arch/x86/x86_emulate/x86_emulate.h
> @@ -835,4 +835,18 @@ static inline void x86_emul_reset_event(struct x86_emulate_ctxt *ctxt)
>      ctxt->event = (struct x86_event){};
>  }
>  
> +/*
> + * x86_decode_lite().  Very minimal decoder for managing alternatives.
> + *
> + * @len is 0 on error, or nonzero on success.  If the instruction has a
> + * relative field, @rel_sz is nonzero, and @rel points at the field.
> + */
> +typedef struct {
> +    uint8_t len;
> +    uint8_t rel_sz; /* bytes: 0, 1 or 4 */

Perhaps use bitfields in favor of fixed-width integers, seeing what
./CODING_STYLE says?

Jan


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 2/5] tests/x86: Introduce a userspace test harness for x86_decode_lite()
  2026-08-03  7:20 ` [PATCH v3 2/5] tests/x86: Introduce a userspace test harness for x86_decode_lite() Andrew Cooper
@ 2026-08-03 16:03   ` Jan Beulich
  2026-08-04 19:37     ` Andrew Cooper
  0 siblings, 1 reply; 14+ messages in thread
From: Jan Beulich @ 2026-08-03 16:03 UTC (permalink / raw)
  To: Andrew Cooper; +Cc: Roger Pau Monné, Teddy Astie, Xen-devel

On 03.08.2026 09:20, Andrew Cooper wrote:
> --- /dev/null
> +++ b/tools/tests/x86-decode-lite/insns.S
> @@ -0,0 +1,703 @@
> +#include "macro-magic.h"
> +
> +        .code64
> +
> +        .allow_index_reg
> +
> +        .text
> +
> +DECL(tests_rel0)
> +modrm:
> +        /* Mod=0, Reg=0, RM {0..f} */
> +        _ add %al, (%rax)
> +        _ add %al, (%rcx)
> +        _ add %al, (%rdx)
> +        _ add %al, (%rbx)
> +        _ add %al, (%rsp) /* SIB */
> +        /*add %al, (%rbp)    RIP --> tests_rel4 */
> +        _ add %al, (%rsi)
> +        _ add %al, (%rdi)
> +        _ add %al, (%r8)
> +        _ add %al, (%r9)
> +        _ add %al, (%r10)
> +        _ add %al, (%r11)
> +        _ add %al, (%r12) /* SIB */
> +        /*add %al, (%r13)    RIP --> tests_rel4 */
> +        _ add %al, (%r14)
> +        _ add %al, (%r15)
> +
> +        /* Mod=1, Reg=0, RM {0..f} */
> +        _ add %al, 0x01(%rax)
> +        _ add %al, 0x01(%rcx)
> +        _ add %al, 0x01(%rdx)
> +        _ add %al, 0x01(%rbx)
> +        _ add %al, 0x01(%rsp) /* SIB */
> +        _ add %al, 0x01(%rbp)
> +        _ add %al, 0x01(%rsi)
> +        _ add %al, 0x01(%rdi)
> +        _ add %al, 0x01(%r8)
> +        _ add %al, 0x01(%r9)
> +        _ add %al, 0x01(%r10)
> +        _ add %al, 0x01(%r11)
> +        _ add %al, 0x01(%r12) /* SIB */
> +        _ add %al, 0x01(%r13)
> +        _ add %al, 0x01(%r14)
> +        _ add %al, 0x01(%r15)
> +
> +        /* Mod=2, Reg=0, RM {0..f} */
> +        _ add %al, 0x7f000001(%rax)
> +        _ add %al, 0x7f000001(%rcx)
> +        _ add %al, 0x7f000001(%rdx)
> +        _ add %al, 0x7f000001(%rbx)
> +        _ add %al, 0x7f000001(%rsp) /* SIB */
> +        _ add %al, 0x7f000001(%rbp)
> +        _ add %al, 0x7f000001(%rsi)
> +        _ add %al, 0x7f000001(%rdi)
> +        _ add %al, 0x7f000001(%r8)
> +        _ add %al, 0x7f000001(%r9)
> +        _ add %al, 0x7f000001(%r10)
> +        _ add %al, 0x7f000001(%r11)
> +        _ add %al, 0x7f000001(%r12) /* SIB */
> +        _ add %al, 0x7f000001(%r13)
> +        _ add %al, 0x7f000001(%r14)
> +        _ add %al, 0x7f000001(%r15)
> +
> +        /* Mod=3, Reg=0, RM {0..f} */
> +        _ add %al, %al
> +        _ add %al, %cl
> +        _ add %al, %dl
> +        _ add %al, %bl
> +        _ add %al, %ah
> +        _ add %al, %ch
> +        _ add %al, %dh
> +        _ add %al, %dl

Perhaps also include %bpl, %sil, and %dil?

> +onebyte_row_9x:
> +        _ nop
> +        _ pause
> +        _ xchg %ax, %ax
> +        _ xchg %eax, %eax
> +        _ xchg %rax, %rax
> +        _ rex.w xchg %rax, %rax
> +        _ cltq
> +        _ cqto
> +        _ wait
> +        _ pushf
> +        _ popf
> +        _ sahf
> +        _ lahf
> +
> +onebyte_row_ax:

May I suggest onebyte_row_Ax?

> +DECL(tests_rel1)
> +disp8:
> +1:
> +        _ jo   1b
> +        _ jno  1b
> +        _ jb   1b
> +        _ jae  1b
> +        _ je   1b
> +        _ jne  1b
> +        _ jbe  1b
> +        _ ja   1b
> +        _ js   1b
> +        _ jns  1b
> +        _ jp   1b
> +        _ jnp  1b
> +        _ jl   1b
> +        _ jge  1b
> +        _ jle  1b
> +        _ jg   1b
> +        _ jmp  1b
> +
> +disp8_rex:
> +        _ rex.w jo   1b
> +        _ rex.w jno  1b
> +        _ rex.w jb   1b
> +        _ rex.w jae  1b
> +        _ rex.w je   1b
> +        _ rex.w jne  1b
> +        _ rex.w jbe  1b
> +        _ rex.w ja   1b
> +        _ rex.w js   1b
> +        _ rex.w jns  1b
> +        _ rex.w jp   1b
> +        _ rex.w jnp  1b
> +        _ rex.w jl   1b
> +        _ rex.w jge  1b
> +        _ rex.w jle  1b
> +        _ rex.w jg   1b
> +        _ rex.w jmp  1b
> +END(tests_rel1)

What's the idea behind the separate REX.W testing? It almost suggests that
tests with an operand size prefix also may want adding. Except that's
difficult, because of ...

> +DECL(tests_rel4)
> +disp32:
> +        _ call   other_section
> +        _ jmp    other_section
> +        _ jo     other_section
> +        _ jno    other_section
> +        _ jb     other_section
> +        _ jae    other_section
> +        _ je     other_section
> +        _ jne    other_section
> +        _ jbe    other_section
> +        _ ja     other_section
> +        _ js     other_section
> +        _ jns    other_section
> +        _ jp     other_section
> +        _ jnp    other_section
> +        _ jl     other_section
> +        _ jge    other_section
> +        _ jle    other_section
> +        _ jg     other_section
> +        _ xbegin other_section
> +
> +disp32_rex:
> +        _ rex.w call   other_section
> +        _ rex.w jmp    other_section
> +        _ rex.w jo     other_section
> +        _ rex.w jno    other_section
> +        _ rex.w jb     other_section
> +        _ rex.w jae    other_section
> +        _ rex.w je     other_section
> +        _ rex.w jne    other_section
> +        _ rex.w jbe    other_section
> +        _ rex.w ja     other_section
> +        _ rex.w js     other_section
> +        _ rex.w jns    other_section
> +        _ rex.w jp     other_section
> +        _ rex.w jnp    other_section
> +        _ rex.w jl     other_section
> +        _ rex.w jge    other_section
> +        _ rex.w jle    other_section
> +        _ rex.w jg     other_section
> +        _ rex.w xbegin other_section

... vendor differences here. Perhaps the decoder itself would better
reject handling of operand-size-prefixed branches.

> +opsize_branch: /* 66-prefixed branches are decoded differently by vendors */
> +        _ data16 call   other_section
> +        _ data16 jmp    other_section
> +        _ data16 jo     other_section
> +        _ data16 jno    other_section
> +        _ data16 jb     other_section
> +        _ data16 jae    other_section
> +        _ data16 je     other_section
> +        _ data16 jne    other_section
> +        _ data16 jbe    other_section
> +        _ data16 ja     other_section
> +        _ data16 js     other_section
> +        _ data16 jns    other_section
> +        _ data16 jp     other_section
> +        _ data16 jnp    other_section
> +        _ data16 jl     other_section
> +        _ data16 jge    other_section
> +        _ data16 jle    other_section
> +        _ data16 jg     other_section
> +        _ data16 xbegin other_section

Oh, you even cover the case here. For XBEGIN, however, this can only be pure
guesswork as to AMD behavior, I suppose.

I also don't see how you force which form you want.

> --- /dev/null
> +++ b/tools/tests/x86-decode-lite/main.c
> @@ -0,0 +1,111 @@
> +/*
> + * Userspace test harness for x86_decode_lite().
> + */
> +#include <stdio.h>
> +
> +#include "x86-emulate.h"
> +
> +static unsigned int nr_failures;
> +#define fail(t, fmt, ...)                                       \
> +({                                                              \
> +    const unsigned char *insn = (t)->ip;                        \
> +                                                                \
> +    nr_failures++;                                              \
> +                                                                \
> +    (void)printf("  Fail '%s' [%02x", (t)->name, *insn);        \
> +    for ( unsigned int i = 1; i < (t)->len; i++ )               \
> +        printf(" %02x", insn[i]);                               \
> +    printf("]\n");                                              \
> +                                                                \
> +    (void)printf(fmt, ##__VA_ARGS__);                           \
> +})
> +
> +struct test {
> +    const char *name;
> +    void *ip;
> +    unsigned long len;
> +};
> +
> +extern const struct test
> +/* Defined in insns.S, ends with sentinel */
> +    tests_rel0[], /* No relocatable entry */
> +    tests_rel1[], /* disp8 */
> +    tests_rel4[], /* disp32 or RIP-relative */
> +    tests_unsup[]; /* Unsupported instructions */
> +
> +static inline void run_tests(const struct test *tests, unsigned int rel_sz)
> +{
> +    printf("Test rel%u\n", rel_sz);
> +
> +    for ( unsigned int i = 0; tests[i].name; ++i )
> +    {
> +        const struct test *t = &tests[i];
> +        x86_decode_lite_t r;
> +
> +        /*
> +         * Don't end strictly at t->len.  This provides better diagnostics if
> +         * too many bytes end up getting consumed.
> +         */
> +        r = x86_decode_lite(t->ip, t->ip + /* t->len */ 20);

For the excess bytes to at least be legitimate to access (not causing UB),
shouldn't finish_arr emit enough filler bytes?

> --- /dev/null
> +++ b/tools/tests/x86-decode-lite/x86-emulate.h
> @@ -0,0 +1,27 @@
> +#ifndef X86_EMULATE_H
> +#define X86_EMULATE_H
> +
> +#include <assert.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <xen/asm/x86-defns.h>
> +#include <xen/asm/x86-vendors.h>
> +
> +#include <xen-tools/common-macros.h>
> +
> +#define ASSERT assert
> +
> +#define printk(...)
> +
> +#define likely
> +#define unlikely
> +#define cf_check
> +#define init_or_livepatch
> +#define init_or_livepatch_const
> +
> +#include "x86_emulate/x86_emulate.h"

Why does this end up being needed?

Jan


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite()
  2026-08-03 15:26   ` Jan Beulich
@ 2026-08-04 15:39     ` Jan Beulich
  2026-08-04 18:56     ` Andrew Cooper
  1 sibling, 0 replies; 14+ messages in thread
From: Jan Beulich @ 2026-08-04 15:39 UTC (permalink / raw)
  To: Andrew Cooper; +Cc: Roger Pau Monné, Teddy Astie, Xen-devel

On 03.08.2026 17:26, Jan Beulich wrote:
>> --- /dev/null
>> +++ b/xen/arch/x86/x86_emulate/decode-lite.c
>> @@ -0,0 +1,330 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +
>> +#ifdef __XEN__
>> +# include <xen/init.h>
>> +# include <xen/livepatch.h>
>> +#endif
>> +
>> +#include "private.h"
>> +
>> +#undef ModRM
>> +
>> +/*
>> + * Bare minimum x86 instruction decoder to parse the alternative replacement
>> + * instructions and locate the IP-relative references that may need updating.
>> + *
>> + * These are:
>> + *  - disp8/32 from near direct branches
>> + *  - RIP-relative memory references
>> + *
>> + * The following simplifications are used:
>> + *  - All code is 64bit, the instruction stream is well formed and safe to
>> + *    read.
>> + *  - Instruction groups and prefixes not used by Xen's current alternatives
>> + *    are not implemented in order to reduce the decode complexity.
>> + *  - Certain instructions are intentionally not recognised, when it is more
>> + *    likely for their presence to be an error than intentional.
>> + *
>> + * Inputs:
>> + *  @ip  The position to start decoding from.
>> + *  @end End of the replacement block.  Exceeding this is considered an error.
> 
> Why do you mention replacement blocks here? Are we entirely set on this
> code not possibly gaining any purpose beyond the scanning of those?
> 
>> + * Returns: x86_decode_lite_t
>> + *  - On failure, length of 0.
>> + *  - On success, length > 0.  For rel_sz > 0, rel points at the relative
>> + *    field in the instruction stream.
>> + */
>> +x86_decode_lite_t init_or_livepatch x86_decode_lite(void *ip, void *end)
> 
> Is there a reason the parameters can't be pointer-to-const? Hmm,
> apparently for x86_decode_lite_t's "rel" field not be plaing void *, "ip"
> needs to be this way as well. But not "end", I don't think.
> 
>> +{
>> +#define Imm8   (1 << 0)
>> +#define Imm    (1 << 1)
>> +#define Moffs  (1 << 2)
>> +#define Branch (1 << 5) /* Near direct branches, which have a displacement */
>> +#define ModRM  (1 << 6)
>> +#define Known  (1 << 7)
>> +
>> +    static const uint8_t init_or_livepatch_const onebyte[256] = {
>> +
>> +#define ALU_OPS(x)                              \
>> +        [(x) + 0] = (Known|ModRM),              \
>> +        [(x) + 1] = (Known|ModRM),              \
>> +        [(x) + 2] = (Known|ModRM),              \
>> +        [(x) + 3] = (Known|ModRM),              \
>> +        [(x) + 4] = (Known|Imm8),               \
>> +        [(x) + 5] = (Known|Imm)
>> +
>> +        ALU_OPS(0x00) /* ADD */, ALU_OPS(0x08) /* OR  */,
>> +        ALU_OPS(0x10) /* ADC */, ALU_OPS(0x18) /* SBB */,
>> +        ALU_OPS(0x20) /* AND */, ALU_OPS(0x28) /* SUB */,
>> +        ALU_OPS(0x30) /* XOR */, ALU_OPS(0x38) /* CMP */,
>> +
>> +#undef ALU_OPS
>> +
>> +        [0x50 ... 0x5f] = (Known),             /* PUSH/POP %reg */
>> +
>> +        [0x62]          = 0,                   /* BOUND, but also EVEX prefix, not implemented. */
>> +        [0x63]          = (Known|ModRM),       /* MOVSxd */
>> +
>> +        [0x68]          = (Known|Imm),         /* PUSH $imm */
>> +        [0x69]          = (Known|ModRM|Imm),   /* IMUL $imm */
>> +        [0x6a]          = (Known|Imm8),        /* PUSH $imm8 */
>> +        [0x6b]          = (Known|ModRM|Imm8),  /* PUSH $imm8 */
>> +        [0x6c ... 0x6f] = (Known),             /* INS/OUTS */
>> +        [0x70 ... 0x7f] = (Known|Branch|Imm8), /* Jcc disp8 */
>> +        [0x80]          = (Known|ModRM|Imm8),  /* Grp1 */
>> +        [0x81]          = (Known|ModRM|Imm),   /* Grp1 */
>> +
>> +        [0x83]          = (Known|ModRM|Imm8),  /* Grp1 */
>> +        [0x84 ... 0x8e] = (Known|ModRM),       /* TEST/XCHG/MOV/MOV-SREG/LEA */
>> +        [0x8f]          = 0,                   /* Grp1A - POP but also XOP prefix, not implemented. */
> 
> POP doesn't look all that unlikely to be used in inline assembly, and
> hence in alternatives. That said, of course using it with a memory
> operand requires quite a bit of care. I don't see you excluding the
> PUSH counterpart, though - being consistent for any such pairs would
> seem somewhat desirable.
> 
>> +        [0x90 ... 0x99] = (Known),             /* NOP/XCHG %rAX/CLTQ/CQTO */
>> +
>> +        [0x9b ... 0x9f] = (Known),             /* FWAIT/PUSHF/POPF/SAHF/LAHF */
>> +        [0xa0 ... 0xa3] = (Known|Moffs),       /* MOVABS */
>> +        [0xa4 ... 0xa7] = (Known),             /* MOVS/CMPS */
>> +        [0xa8]          = (Known|Imm8),        /* TEST %al */
>> +        [0xa9]          = (Known|Imm),         /* TEST %rAX */
>> +        [0xaa ... 0xaf] = (Known),             /* STOS/LODS/SCAS */
>> +        [0xb0 ... 0xb7] = (Known|Imm8),        /* MOV $imm8, %reg */
>> +        [0xb8 ... 0xbf] = (Known|Imm),         /* MOV $imm{16,32,64}, %reg */
>> +        [0xc0 ... 0xc1] = (Known|ModRM|Imm8),  /* Grp2 (ROL..SAR $imm8, %reg) */
>> +
>> +        [0xc3]          = (Known),             /* RET */
>> +        [0xc4 ... 0xc5] = 0,                   /* LES/LDS but also VEX prefixes, not implemented. */
> 
> This may bite us sooner or later, due to the VEX-encoded integer insns
> that there are. Of course as long as we don't use this function on
> compiled code, and as long as my "x86: allow Kconfig control over psABI
> level" doesn't come close to going in, that's merely a theoretical
> concern.

Actually perhaps sooner - we're meaning to use MSR-IMM insns after all, if
I'm not mistaken.

Jan


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite()
  2026-08-03 15:26   ` Jan Beulich
  2026-08-04 15:39     ` Jan Beulich
@ 2026-08-04 18:56     ` Andrew Cooper
  2026-08-05  6:24       ` Jan Beulich
  1 sibling, 1 reply; 14+ messages in thread
From: Andrew Cooper @ 2026-08-04 18:56 UTC (permalink / raw)
  To: Jan Beulich; +Cc: Andrew Cooper, Roger Pau Monné, Teddy Astie, Xen-devel

On 03/08/2026 4:26 pm, Jan Beulich wrote:
>> --- /dev/null
>> +++ b/xen/arch/x86/x86_emulate/decode-lite.c
>> @@ -0,0 +1,330 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +
>> +#ifdef __XEN__
>> +# include <xen/init.h>
>> +# include <xen/livepatch.h>
>> +#endif
>> +
>> +#include "private.h"
>> +
>> +#undef ModRM
>> +
>> +/*
>> + * Bare minimum x86 instruction decoder to parse the alternative replacement
>> + * instructions and locate the IP-relative references that may need updating.
>> + *
>> + * These are:
>> + *  - disp8/32 from near direct branches
>> + *  - RIP-relative memory references
>> + *
>> + * The following simplifications are used:
>> + *  - All code is 64bit, the instruction stream is well formed and safe to
>> + *    read.
>> + *  - Instruction groups and prefixes not used by Xen's current alternatives
>> + *    are not implemented in order to reduce the decode complexity.
>> + *  - Certain instructions are intentionally not recognised, when it is more
>> + *    likely for their presence to be an error than intentional.
>> + *
>> + * Inputs:
>> + *  @ip  The position to start decoding from.
>> + *  @end End of the replacement block.  Exceeding this is considered an error.
> Why do you mention replacement blocks here? Are we entirely set on this
> code not possibly gaining any purpose beyond the scanning of those?

It's just the end of the instruction stream wanting decoding.  I'll
adjust the comment.

>> +{
>> +#define Imm8   (1 << 0)
>> +#define Imm    (1 << 1)
>> +#define Moffs  (1 << 2)
>> +#define Branch (1 << 5) /* Near direct branches, which have a displacement */
>> +#define ModRM  (1 << 6)
>> +#define Known  (1 << 7)
>> +
>> +    static const uint8_t init_or_livepatch_const onebyte[256] = {
>> +
>> +#define ALU_OPS(x)                              \
>> +        [(x) + 0] = (Known|ModRM),              \
>> +        [(x) + 1] = (Known|ModRM),              \
>> +        [(x) + 2] = (Known|ModRM),              \
>> +        [(x) + 3] = (Known|ModRM),              \
>> +        [(x) + 4] = (Known|Imm8),               \
>> +        [(x) + 5] = (Known|Imm)
>> +
>> +        ALU_OPS(0x00) /* ADD */, ALU_OPS(0x08) /* OR  */,
>> +        ALU_OPS(0x10) /* ADC */, ALU_OPS(0x18) /* SBB */,
>> +        ALU_OPS(0x20) /* AND */, ALU_OPS(0x28) /* SUB */,
>> +        ALU_OPS(0x30) /* XOR */, ALU_OPS(0x38) /* CMP */,
>> +
>> +#undef ALU_OPS
>> +
>> +        [0x50 ... 0x5f] = (Known),             /* PUSH/POP %reg */
>> +
>> +        [0x62]          = 0,                   /* BOUND, but also EVEX prefix, not implemented. */
>> +        [0x63]          = (Known|ModRM),       /* MOVSxd */
>> +
>> +        [0x68]          = (Known|Imm),         /* PUSH $imm */
>> +        [0x69]          = (Known|ModRM|Imm),   /* IMUL $imm */
>> +        [0x6a]          = (Known|Imm8),        /* PUSH $imm8 */
>> +        [0x6b]          = (Known|ModRM|Imm8),  /* PUSH $imm8 */
>> +        [0x6c ... 0x6f] = (Known),             /* INS/OUTS */
>> +        [0x70 ... 0x7f] = (Known|Branch|Imm8), /* Jcc disp8 */
>> +        [0x80]          = (Known|ModRM|Imm8),  /* Grp1 */
>> +        [0x81]          = (Known|ModRM|Imm),   /* Grp1 */
>> +
>> +        [0x83]          = (Known|ModRM|Imm8),  /* Grp1 */
>> +        [0x84 ... 0x8e] = (Known|ModRM),       /* TEST/XCHG/MOV/MOV-SREG/LEA */
>> +        [0x8f]          = 0,                   /* Grp1A - POP but also XOP prefix, not implemented. */
> POP doesn't look all that unlikely to be used in inline assembly, and
> hence in alternatives. That said, of course using it with a memory
> operand requires quite a bit of care.

We have no alternatives playing with the stack (beyond CALL
instructions), and no alternatives which have any net %rsp delta.

PUSH/POP MEM are rare in general and Xen doesn't have any at all.

> I don't see you excluding the
> PUSH counterpart, though - being consistent for any such pairs would
> seem somewhat desirable.

It would be nice to be handled symmetrically, but this *is* an
odd-instruction-out in the x86 encoding space.

It ought to live in Grp5 where the encoding would be 0xff /7 (and beside
it's matching PUSH), except that's that's a rather important binary
pattern and wants to not be considered a valid instruction.

The fact that the group is split like this shows that the mistake was a
late discovery in the development of the 8086, where it was easier to
move the one opcode than the whole group.  (It's likely to have been a
metal-layer fix for the decode PAL, rather than adjusting the
transistors, which is typically an order of magnitude cheaper fix.)


>
>> +        [0x90 ... 0x99] = (Known),             /* NOP/XCHG %rAX/CLTQ/CQTO */
>> +
>> +        [0x9b ... 0x9f] = (Known),             /* FWAIT/PUSHF/POPF/SAHF/LAHF */
>> +        [0xa0 ... 0xa3] = (Known|Moffs),       /* MOVABS */
>> +        [0xa4 ... 0xa7] = (Known),             /* MOVS/CMPS */
>> +        [0xa8]          = (Known|Imm8),        /* TEST %al */
>> +        [0xa9]          = (Known|Imm),         /* TEST %rAX */
>> +        [0xaa ... 0xaf] = (Known),             /* STOS/LODS/SCAS */
>> +        [0xb0 ... 0xb7] = (Known|Imm8),        /* MOV $imm8, %reg */
>> +        [0xb8 ... 0xbf] = (Known|Imm),         /* MOV $imm{16,32,64}, %reg */
>> +        [0xc0 ... 0xc1] = (Known|ModRM|Imm8),  /* Grp2 (ROL..SAR $imm8, %reg) */
>> +
>> +        [0xc3]          = (Known),             /* RET */
>> +        [0xc4 ... 0xc5] = 0,                   /* LES/LDS but also VEX prefixes, not implemented. */
> This may bite us sooner or later, due to the VEX-encoded integer insns
> that there are. Of course as long as we don't use this function on
> compiled code, and as long as my "x86: allow Kconfig control over psABI
> level" doesn't come close to going in, that's merely a theoretical
> concern.
>
> Same goes for not supporting the 3-byte opcodes, which also encode
> certain integer insns.

I have no doubt that we're going to need to add support eventually.

But,
a) I don't have time right now
b) We have real bugs/limitations right now needing this functionality to
address (patch 5, and the xsave fixes, and bus lock trap enablement)
c) GitlabCI will reliably notice any new alternative instructions that
this can't decode (patch 3)
d) This function is a fastpath during the alternatives patching critical
region (patch 4)

Option d alone is a good reason not to decode VEX prefixes yet.


>
>> +        [0xc6]          = (Known|ModRM|Imm8),  /* Grp11, Further ModRM decode */
>> +        [0xc7]          = (Known|ModRM|Imm),   /* Grp11, Further ModRM decode */
>> +
>> +        [0xcb ... 0xcc] = (Known),             /* LRET/INT3 */
>> +        [0xcd]          = (Known|Imm8),        /* INT $imm8 */
>> +
>> +        [0xd0 ... 0xd3] = (Known|ModRM),       /* Grp2 (ROL..SAR {$1,%cl}, %reg) */
>> +
>> +        [0xd6]          = (Known),             /* UDB */
> I guess you consider XLAT, LOOP*, and J*CXZ as too odd to use in alternatives?
> Decoding-wise they're rather easy to implement.

They are easy, but they also shouldn't appear anywhere in Xen.

I know we've got one J*CXZ in the emulator.  I tried quite hard to find
an alternative before deciding it was an acceptable solution given the
constraints, but it's in plain code.

>
>> +        [0xe4 ... 0xe7] = (Known|Imm8),        /* IN/OUT $imm8 */
>> +        [0xe8 ... 0xe9] = (Known|Branch|Imm),  /* CALL/JMP disp32 */
>> +
>> +        [0xeb]          = (Known|Branch|Imm8), /* JMP disp8 */
>> +        [0xec ... 0xef] = (Known),             /* IN/OUT %dx */
>> +
>> +        [0xf1]          = (Known),             /* ICEBP */
>> +
>> +        [0xf4]          = (Known),             /* HLT */
>> +        [0xf5]          = (Known),             /* CMC */
>> +        [0xf6 ... 0xf7] = (Known|ModRM),       /* Grp3, Further ModRM decode */
>> +        [0xf8 ... 0xfd] = (Known),             /* CLC ... STD */
>> +        [0xfe ... 0xff] = (Known|ModRM),       /* Grp4 */
>> +    };
>> +    static const uint8_t init_or_livepatch_const twobyte[256] = {
>> +        [0x00 ... 0x03] = (Known|ModRM),       /* Grp6/Grp7/LAR/LSL */
> Leaving out INVD is surely find, but WBINVD?

Given now expensive WBINVD is, what possible reason can you think for
having it in an alternative ?

>
>> +        [0x0b]          = (Known),             /* UD2 */
>> +
>> +        [0x18 ... 0x1f] = (Known|ModRM),       /* Grp16 (Hint Nop) */
>> +        [0x20 ... 0x23] = (Known|ModRM),       /* MOV %cr/%dr */
>> +
>> +        [0x30 ... 0x33] = (Known),             /* WRMSR/RDTSC/RDMSR/RDPMC */
>> +
>> +        [0x40 ... 0x4f] = (Known|ModRM),       /* CMOVcc */
>> +
>> +        [0x80 ... 0x8f] = (Known|Branch|Imm),  /* Jcc disp32 */
>> +        [0x90 ... 0x9f] = (Known|ModRM),       /* SETcc */
>> +
>> +        [0xa0 ... 0xa2] = (Known),             /* PUSH/POP %fs/CPUID */
>> +        [0xa3]          = (Known|ModRM),       /* BT */
>> +        [0xa4]          = (Known|ModRM|Imm8),  /* SHLD $imm8 */
>> +        [0xa5]          = (Known|ModRM),       /* SHLD %cl */
>> +
>> +        [0xa8 ... 0xa9] = (Known),             /* PUSH/POP %gs */
>> +
>> +        [0xab]          = (Known|ModRM),       /* BTS */
>> +        [0xac]          = (Known|ModRM|Imm8),  /* SHRD $imm8 */
>> +        [0xad ... 0xaf] = (Known|ModRM),       /* SHRD %cl/Grp15/IMUL */
>> +
>> +        [0xb0 ... 0xb9] = (Known|ModRM),       /* CMPXCHG/LSS/BTR/LFS/LGS/MOVZxx/POPCNT/UD1 */
>> +        [0xba]          = (Known|ModRM|Imm8),  /* Grp8 */
>> +        [0xbb ... 0xbf] = (Known|ModRM),       /* BTC/BSF/BSR/MOVSX */
>> +        [0xc0 ... 0xc1] = (Known|ModRM),       /* XADD */
> What about MOVNTI?

I judged that to be on the unlikely side to be needed.

>
>> +        [0xc7]          = (Known|ModRM),       /* Grp9 */
>> +        [0xc8 ... 0xcf] = (Known),             /* BSWAP */
>> +    };
> What about UD0?

UD0 differs between vendors and product lines from Intel.

>
>> +    void *start = ip, *rel = NULL;
>> +    unsigned int opc, rel_sz = 0;
>> +    uint8_t b, d, rex = 0, osize = 4;
>> +
>> +#define OPC_TWOBYTE (1 << 8)
>> +
>> +    /* Mutates IP, uses END. */
>> +#define FETCH(ty)                                       \
>> +    ({                                                  \
>> +        ty _val;                                        \
>> +                                                        \
>> +        if ( (ip + sizeof(ty)) > end )                  \
>> +            goto overrun;                               \
>> +        _val = *(ty *)ip;                               \
>> +        ip += sizeof(ty);                               \
>> +        _val;                                           \
>> +    })
>> +
>> +    for ( ;; ) /* Prefixes */
>> +    {
>> +        switch ( b = FETCH(uint8_t) )
>> +        {
>> +        case 0x26: /* ES override */
>> +        case 0x2e: /* CS override */
>> +        case 0x36: /* DS override */
>> +        case 0x3e: /* SS override */
>> +        case 0x64: /* FS override */
>> +        case 0x65: /* GS override */
>> +        case 0xf0: /* LOCK */
>> +        case 0xf2: /* REPNE */
>> +        case 0xf3: /* REP */
>> +            break;
>> +
>> +        case 0x66: /* Operand size override */
>> +            osize = 2;
>> +            break;
>> +
>> +        /* case 0x67: Address size override, not implemented */
>> +
>> +        case 0x40 ... 0x4f: /* REX */
>> +            rex = b;
>> +            continue;
>> +
>> +        default:
>> +            goto prefixes_done;
>> +        }
>> +        rex = 0; /* REX cancelled by subsequent legacy prefix. */
>> +    }
>> + prefixes_done:
>> +
>> +    if ( rex & REX_W )
>> +        osize = 8;
>> +
>> +    /* Fetch the main opcode byte(s) */
>> +    if ( b == 0x0f )
>> +    {
>> +        b = FETCH(uint8_t);
>> +        opc = OPC_TWOBYTE | b;
>> +
>> +        d = twobyte[b];
>> +    }
>> +    else
>> +    {
>> +        opc = b;
>> +        d = onebyte[b];
>> +    }
>> +
>> +    if ( unlikely(!(d & Known)) )
>> +        goto unknown;
>> +
>> +    if ( d & ModRM )
>> +    {
>> +        uint8_t modrm = FETCH(uint8_t);
>> +        uint8_t mod = modrm >> 6;
>> +        uint8_t reg = (modrm >> 3) & 7;
>> +        uint8_t rm = modrm & 7;
>> +
>> +        /* ModRM/SIB decode */
>> +        if ( mod == 0 && rm == 5 ) /* RIP relative */
>> +        {
>> +            rel = ip;
>> +            rel_sz = 4;
>> +            FETCH(int32_t);
> FETCH() here but ...
>
>> +        }
>> +        else if ( mod != 3 && rm == 4 ) /* SIB */
>> +        {
>> +            uint8_t sib = FETCH(uint8_t);
>> +            uint8_t base = sib & 7;
>> +
>> +            if ( mod == 0 && base == 5 )
>> +                goto disp32;
> ... goto here?

Hmm.  That's an artefact of how it developed.  Swapping this goto for
FETCH() does drop 20 bytes, but the function is rearranged so much that
it's hard to tell if this is because real logic is getting dropped.


>
>> +        }
>> +
>> +        if ( mod == 1 ) /* disp8 */
>> +            FETCH(int8_t);
>> +        else if ( mod == 2 ) /* disp32 */
>> +        {
>> +        disp32:
>> +            FETCH(int32_t);
>> +        }
> In several cases the FETCH()ed value isn't used. Compilers as well as Eclair
> (and alike) are happy with that?

Yes.  The cover letter has a fully passing pipeline.

> And compilers also manage to eliminate the memory accesses then?

Yes.

>
>> --- a/xen/arch/x86/x86_emulate/x86_emulate.h
>> +++ b/xen/arch/x86/x86_emulate/x86_emulate.h
>> @@ -835,4 +835,18 @@ static inline void x86_emul_reset_event(struct x86_emulate_ctxt *ctxt)
>>      ctxt->event = (struct x86_event){};
>>  }
>>  
>> +/*
>> + * x86_decode_lite().  Very minimal decoder for managing alternatives.
>> + *
>> + * @len is 0 on error, or nonzero on success.  If the instruction has a
>> + * relative field, @rel_sz is nonzero, and @rel points at the field.
>> + */
>> +typedef struct {
>> +    uint8_t len;
>> +    uint8_t rel_sz; /* bytes: 0, 1 or 4 */
> Perhaps use bitfields in favor of fixed-width integers, seeing what
> ./CODING_STYLE says?

No.  That destroys the code generation improvements gained by returning
a pair like this in the first place.

~Andrew


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 2/5] tests/x86: Introduce a userspace test harness for x86_decode_lite()
  2026-08-03 16:03   ` Jan Beulich
@ 2026-08-04 19:37     ` Andrew Cooper
  2026-08-05  6:45       ` Jan Beulich
  0 siblings, 1 reply; 14+ messages in thread
From: Andrew Cooper @ 2026-08-04 19:37 UTC (permalink / raw)
  To: Jan Beulich; +Cc: Andrew Cooper, Roger Pau Monné, Teddy Astie, Xen-devel

On 03/08/2026 5:03 pm, Jan Beulich wrote:
> On 03.08.2026 09:20, Andrew Cooper wrote:
>> --- /dev/null
>> +++ b/tools/tests/x86-decode-lite/insns.S
>> @@ -0,0 +1,703 @@
>> +#include "macro-magic.h"
>> +
>> +        .code64
>> +
>> +        .allow_index_reg
>> +
>> +        .text
>> +
>> +DECL(tests_rel0)
>> +modrm:
>> +        /* Mod=0, Reg=0, RM {0..f} */
>> +        _ add %al, (%rax)
>> +        _ add %al, (%rcx)
>> +        _ add %al, (%rdx)
>> +        _ add %al, (%rbx)
>> +        _ add %al, (%rsp) /* SIB */
>> +        /*add %al, (%rbp)    RIP --> tests_rel4 */
>> +        _ add %al, (%rsi)
>> +        _ add %al, (%rdi)
>> +        _ add %al, (%r8)
>> +        _ add %al, (%r9)
>> +        _ add %al, (%r10)
>> +        _ add %al, (%r11)
>> +        _ add %al, (%r12) /* SIB */
>> +        /*add %al, (%r13)    RIP --> tests_rel4 */
>> +        _ add %al, (%r14)
>> +        _ add %al, (%r15)
>> +
>> +        /* Mod=1, Reg=0, RM {0..f} */
>> +        _ add %al, 0x01(%rax)
>> +        _ add %al, 0x01(%rcx)
>> +        _ add %al, 0x01(%rdx)
>> +        _ add %al, 0x01(%rbx)
>> +        _ add %al, 0x01(%rsp) /* SIB */
>> +        _ add %al, 0x01(%rbp)
>> +        _ add %al, 0x01(%rsi)
>> +        _ add %al, 0x01(%rdi)
>> +        _ add %al, 0x01(%r8)
>> +        _ add %al, 0x01(%r9)
>> +        _ add %al, 0x01(%r10)
>> +        _ add %al, 0x01(%r11)
>> +        _ add %al, 0x01(%r12) /* SIB */
>> +        _ add %al, 0x01(%r13)
>> +        _ add %al, 0x01(%r14)
>> +        _ add %al, 0x01(%r15)
>> +
>> +        /* Mod=2, Reg=0, RM {0..f} */
>> +        _ add %al, 0x7f000001(%rax)
>> +        _ add %al, 0x7f000001(%rcx)
>> +        _ add %al, 0x7f000001(%rdx)
>> +        _ add %al, 0x7f000001(%rbx)
>> +        _ add %al, 0x7f000001(%rsp) /* SIB */
>> +        _ add %al, 0x7f000001(%rbp)
>> +        _ add %al, 0x7f000001(%rsi)
>> +        _ add %al, 0x7f000001(%rdi)
>> +        _ add %al, 0x7f000001(%r8)
>> +        _ add %al, 0x7f000001(%r9)
>> +        _ add %al, 0x7f000001(%r10)
>> +        _ add %al, 0x7f000001(%r11)
>> +        _ add %al, 0x7f000001(%r12) /* SIB */
>> +        _ add %al, 0x7f000001(%r13)
>> +        _ add %al, 0x7f000001(%r14)
>> +        _ add %al, 0x7f000001(%r15)
>> +
>> +        /* Mod=3, Reg=0, RM {0..f} */
>> +        _ add %al, %al
>> +        _ add %al, %cl
>> +        _ add %al, %dl
>> +        _ add %al, %bl
>> +        _ add %al, %ah
>> +        _ add %al, %ch
>> +        _ add %al, %dh
>> +        _ add %al, %dl
> Perhaps also include %bpl, %sil, and %dil?

They're not relevant to this test, and interfere with the intentional
pattern set up.

>
>> +onebyte_row_9x:
>> +        _ nop
>> +        _ pause
>> +        _ xchg %ax, %ax
>> +        _ xchg %eax, %eax
>> +        _ xchg %rax, %rax
>> +        _ rex.w xchg %rax, %rax
>> +        _ cltq
>> +        _ cqto
>> +        _ wait
>> +        _ pushf
>> +        _ popf
>> +        _ sahf
>> +        _ lahf
>> +
>> +onebyte_row_ax:
> May I suggest onebyte_row_Ax?

Ok.

>
>> +DECL(tests_rel1)
>> +disp8:
>> +1:
>> +        _ jo   1b
>> +        _ jno  1b
>> +        _ jb   1b
>> +        _ jae  1b
>> +        _ je   1b
>> +        _ jne  1b
>> +        _ jbe  1b
>> +        _ ja   1b
>> +        _ js   1b
>> +        _ jns  1b
>> +        _ jp   1b
>> +        _ jnp  1b
>> +        _ jl   1b
>> +        _ jge  1b
>> +        _ jle  1b
>> +        _ jg   1b
>> +        _ jmp  1b
>> +
>> +disp8_rex:
>> +        _ rex.w jo   1b
>> +        _ rex.w jno  1b
>> +        _ rex.w jb   1b
>> +        _ rex.w jae  1b
>> +        _ rex.w je   1b
>> +        _ rex.w jne  1b
>> +        _ rex.w jbe  1b
>> +        _ rex.w ja   1b
>> +        _ rex.w js   1b
>> +        _ rex.w jns  1b
>> +        _ rex.w jp   1b
>> +        _ rex.w jnp  1b
>> +        _ rex.w jl   1b
>> +        _ rex.w jge  1b
>> +        _ rex.w jle  1b
>> +        _ rex.w jg   1b
>> +        _ rex.w jmp  1b
>> +END(tests_rel1)
> What's the idea behind the separate REX.W testing?

Testing osize handling vs Imm8/Imm.

>  It almost suggests that
> tests with an operand size prefix also may want adding. Except that's
> difficult, because of ...
>
>> +DECL(tests_rel4)
>> +disp32:
>> +        _ call   other_section
>> +        _ jmp    other_section
>> +        _ jo     other_section
>> +        _ jno    other_section
>> +        _ jb     other_section
>> +        _ jae    other_section
>> +        _ je     other_section
>> +        _ jne    other_section
>> +        _ jbe    other_section
>> +        _ ja     other_section
>> +        _ js     other_section
>> +        _ jns    other_section
>> +        _ jp     other_section
>> +        _ jnp    other_section
>> +        _ jl     other_section
>> +        _ jge    other_section
>> +        _ jle    other_section
>> +        _ jg     other_section
>> +        _ xbegin other_section
>> +
>> +disp32_rex:
>> +        _ rex.w call   other_section
>> +        _ rex.w jmp    other_section
>> +        _ rex.w jo     other_section
>> +        _ rex.w jno    other_section
>> +        _ rex.w jb     other_section
>> +        _ rex.w jae    other_section
>> +        _ rex.w je     other_section
>> +        _ rex.w jne    other_section
>> +        _ rex.w jbe    other_section
>> +        _ rex.w ja     other_section
>> +        _ rex.w js     other_section
>> +        _ rex.w jns    other_section
>> +        _ rex.w jp     other_section
>> +        _ rex.w jnp    other_section
>> +        _ rex.w jl     other_section
>> +        _ rex.w jge    other_section
>> +        _ rex.w jle    other_section
>> +        _ rex.w jg     other_section
>> +        _ rex.w xbegin other_section
> ... vendor differences here. Perhaps the decoder itself would better
> reject handling of operand-size-prefixed branches.

Excluding 66-prefix is easy, but excluding rex.w on jumps is hard and
would require extra logic.

>> +opsize_branch: /* 66-prefixed branches are decoded differently by vendors */
>> +        _ data16 call   other_section
>> +        _ data16 jmp    other_section
>> +        _ data16 jo     other_section
>> +        _ data16 jno    other_section
>> +        _ data16 jb     other_section
>> +        _ data16 jae    other_section
>> +        _ data16 je     other_section
>> +        _ data16 jne    other_section
>> +        _ data16 jbe    other_section
>> +        _ data16 ja     other_section
>> +        _ data16 js     other_section
>> +        _ data16 jns    other_section
>> +        _ data16 jp     other_section
>> +        _ data16 jnp    other_section
>> +        _ data16 jl     other_section
>> +        _ data16 jge    other_section
>> +        _ data16 jle    other_section
>> +        _ data16 jg     other_section
>> +        _ data16 xbegin other_section
> Oh, you even cover the case here. For XBEGIN, however, this can only be pure
> guesswork as to AMD behavior, I suppose.

Remember that RTM is available on Zen2 if you know which chickenbits to
clobber.

I've not tried.  I expect it's more likely that they behave consistently
than differently.

> I also don't see how you force which form you want.

Binutils always produces AMD behaviour.  (As far as I can see.)

This is in the negative-tests section, which confirms that
x86_decode_lite() rejects the byte pattern.

If Binutils changes behaviour, the test will start failing.

>
>> --- /dev/null
>> +++ b/tools/tests/x86-decode-lite/main.c
>> @@ -0,0 +1,111 @@
>> +/*
>> + * Userspace test harness for x86_decode_lite().
>> + */
>> +#include <stdio.h>
>> +
>> +#include "x86-emulate.h"
>> +
>> +static unsigned int nr_failures;
>> +#define fail(t, fmt, ...)                                       \
>> +({                                                              \
>> +    const unsigned char *insn = (t)->ip;                        \
>> +                                                                \
>> +    nr_failures++;                                              \
>> +                                                                \
>> +    (void)printf("  Fail '%s' [%02x", (t)->name, *insn);        \
>> +    for ( unsigned int i = 1; i < (t)->len; i++ )               \
>> +        printf(" %02x", insn[i]);                               \
>> +    printf("]\n");                                              \
>> +                                                                \
>> +    (void)printf(fmt, ##__VA_ARGS__);                           \
>> +})
>> +
>> +struct test {
>> +    const char *name;
>> +    void *ip;
>> +    unsigned long len;
>> +};
>> +
>> +extern const struct test
>> +/* Defined in insns.S, ends with sentinel */
>> +    tests_rel0[], /* No relocatable entry */
>> +    tests_rel1[], /* disp8 */
>> +    tests_rel4[], /* disp32 or RIP-relative */
>> +    tests_unsup[]; /* Unsupported instructions */
>> +
>> +static inline void run_tests(const struct test *tests, unsigned int rel_sz)
>> +{
>> +    printf("Test rel%u\n", rel_sz);
>> +
>> +    for ( unsigned int i = 0; tests[i].name; ++i )
>> +    {
>> +        const struct test *t = &tests[i];
>> +        x86_decode_lite_t r;
>> +
>> +        /*
>> +         * Don't end strictly at t->len.  This provides better diagnostics if
>> +         * too many bytes end up getting consumed.
>> +         */
>> +        r = x86_decode_lite(t->ip, t->ip + /* t->len */ 20);
> For the excess bytes to at least be legitimate to access (not causing UB),
> shouldn't finish_arr emit enough filler bytes?

finish_arr is the wrong place, but I've folded in:

diff --git a/tools/tests/x86-decode-lite/insns.S b/tools/tests/x86-decode-lite/insns.S
index e52c2934c8d8..dc017016b2d2 100644
--- a/tools/tests/x86-decode-lite/insns.S
+++ b/tools/tests/x86-decode-lite/insns.S
@@ -695,6 +695,13 @@ unsup_insn: /* Instructions that would complicated decode, or shouldn't be used
 
 END(tests_unsup)
 
+        /*
+         * For improved diagnostics, we allow some overreading of the
+         * instruction under test.  Ensure there are good bytes to read.
+         */
+overread_padding:
+        .skip 20
+
         /* This is here to cause jmps to use their disp32 form. */
         .section .text.other_section, "ax", @progbits
 other_section:



>
>> --- /dev/null
>> +++ b/tools/tests/x86-decode-lite/x86-emulate.h
>> @@ -0,0 +1,27 @@
>> +#ifndef X86_EMULATE_H
>> +#define X86_EMULATE_H
>> +
>> +#include <assert.h>
>> +#include <stdbool.h>
>> +#include <stdint.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +
>> +#include <xen/asm/x86-defns.h>
>> +#include <xen/asm/x86-vendors.h>
>> +
>> +#include <xen-tools/common-macros.h>
>> +
>> +#define ASSERT assert
>> +
>> +#define printk(...)
>> +
>> +#define likely
>> +#define unlikely
>> +#define cf_check
>> +#define init_or_livepatch
>> +#define init_or_livepatch_const
>> +
>> +#include "x86_emulate/x86_emulate.h"
> Why does this end up being needed?

Well, this for starters:

main.c: In function ‘run_tests’:
main.c:43:9: error: unknown type name ‘x86_decode_lite_t’
   43 |         x86_decode_lite_t r;
      |         ^~~~~~~~~~~~~~~~~
main.c:49:13: error: implicit declaration of function ‘x86_decode_lite’ [-Werror=implicit-function-declaration]
   49 |         r = x86_decode_lite(t->ip, t->ip + /* t->len */ 20);
      |             ^~~~~~~~~~~~~~~



~Andrew


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite()
  2026-08-04 18:56     ` Andrew Cooper
@ 2026-08-05  6:24       ` Jan Beulich
  0 siblings, 0 replies; 14+ messages in thread
From: Jan Beulich @ 2026-08-05  6:24 UTC (permalink / raw)
  To: Andrew Cooper; +Cc: Roger Pau Monné, Teddy Astie, Xen-devel

On 04.08.2026 20:56, Andrew Cooper wrote:
> On 03/08/2026 4:26 pm, Jan Beulich wrote:
>>> +        [0x50 ... 0x5f] = (Known),             /* PUSH/POP %reg */
>>> +
>>> +        [0x62]          = 0,                   /* BOUND, but also EVEX prefix, not implemented. */
>>> +        [0x63]          = (Known|ModRM),       /* MOVSxd */
>>> +
>>> +        [0x68]          = (Known|Imm),         /* PUSH $imm */
>>> +        [0x69]          = (Known|ModRM|Imm),   /* IMUL $imm */
>>> +        [0x6a]          = (Known|Imm8),        /* PUSH $imm8 */
>>> +        [0x6b]          = (Known|ModRM|Imm8),  /* PUSH $imm8 */
>>> +        [0x6c ... 0x6f] = (Known),             /* INS/OUTS */
>>> +        [0x70 ... 0x7f] = (Known|Branch|Imm8), /* Jcc disp8 */
>>> +        [0x80]          = (Known|ModRM|Imm8),  /* Grp1 */
>>> +        [0x81]          = (Known|ModRM|Imm),   /* Grp1 */
>>> +
>>> +        [0x83]          = (Known|ModRM|Imm8),  /* Grp1 */
>>> +        [0x84 ... 0x8e] = (Known|ModRM),       /* TEST/XCHG/MOV/MOV-SREG/LEA */
>>> +        [0x8f]          = 0,                   /* Grp1A - POP but also XOP prefix, not implemented. */
>> POP doesn't look all that unlikely to be used in inline assembly, and
>> hence in alternatives. That said, of course using it with a memory
>> operand requires quite a bit of care.
> 
> We have no alternatives playing with the stack (beyond CALL
> instructions), and no alternatives which have any net %rsp delta.
> 
> PUSH/POP MEM are rare in general and Xen doesn't have any at all.

Well, okay then. Nevertheless I'd like to mention that the encoding can
also be used for REG forms. If needed for size reasons, that may or may
not be more efficient than adding a NOP or no-op prefix.

>>> +        [0x90 ... 0x99] = (Known),             /* NOP/XCHG %rAX/CLTQ/CQTO */
>>> +
>>> +        [0x9b ... 0x9f] = (Known),             /* FWAIT/PUSHF/POPF/SAHF/LAHF */
>>> +        [0xa0 ... 0xa3] = (Known|Moffs),       /* MOVABS */
>>> +        [0xa4 ... 0xa7] = (Known),             /* MOVS/CMPS */
>>> +        [0xa8]          = (Known|Imm8),        /* TEST %al */
>>> +        [0xa9]          = (Known|Imm),         /* TEST %rAX */
>>> +        [0xaa ... 0xaf] = (Known),             /* STOS/LODS/SCAS */
>>> +        [0xb0 ... 0xb7] = (Known|Imm8),        /* MOV $imm8, %reg */
>>> +        [0xb8 ... 0xbf] = (Known|Imm),         /* MOV $imm{16,32,64}, %reg */
>>> +        [0xc0 ... 0xc1] = (Known|ModRM|Imm8),  /* Grp2 (ROL..SAR $imm8, %reg) */
>>> +
>>> +        [0xc3]          = (Known),             /* RET */
>>> +        [0xc4 ... 0xc5] = 0,                   /* LES/LDS but also VEX prefixes, not implemented. */
>> This may bite us sooner or later, due to the VEX-encoded integer insns
>> that there are. Of course as long as we don't use this function on
>> compiled code, and as long as my "x86: allow Kconfig control over psABI
>> level" doesn't come close to going in, that's merely a theoretical
>> concern.
>>
>> Same goes for not supporting the 3-byte opcodes, which also encode
>> certain integer insns.
> 
> I have no doubt that we're going to need to add support eventually.
> 
> But,
> a) I don't have time right now
> b) We have real bugs/limitations right now needing this functionality to
> address (patch 5, and the xsave fixes, and bus lock trap enablement)
> c) GitlabCI will reliably notice any new alternative instructions that
> this can't decode (patch 3)
> d) This function is a fastpath during the alternatives patching critical
> region (patch 4)
> 
> Option d alone is a good reason not to decode VEX prefixes yet.

Personally I think a is most relevant. As said in the later reply, once
we start using MSR-IMM insns, at least VEX3 map 7 will need decoding
anyway.

>>> +        [0xe4 ... 0xe7] = (Known|Imm8),        /* IN/OUT $imm8 */
>>> +        [0xe8 ... 0xe9] = (Known|Branch|Imm),  /* CALL/JMP disp32 */
>>> +
>>> +        [0xeb]          = (Known|Branch|Imm8), /* JMP disp8 */
>>> +        [0xec ... 0xef] = (Known),             /* IN/OUT %dx */
>>> +
>>> +        [0xf1]          = (Known),             /* ICEBP */
>>> +
>>> +        [0xf4]          = (Known),             /* HLT */
>>> +        [0xf5]          = (Known),             /* CMC */
>>> +        [0xf6 ... 0xf7] = (Known|ModRM),       /* Grp3, Further ModRM decode */
>>> +        [0xf8 ... 0xfd] = (Known),             /* CLC ... STD */
>>> +        [0xfe ... 0xff] = (Known|ModRM),       /* Grp4 */
>>> +    };
>>> +    static const uint8_t init_or_livepatch_const twobyte[256] = {
>>> +        [0x00 ... 0x03] = (Known|ModRM),       /* Grp6/Grp7/LAR/LSL */
>> Leaving out INVD is surely find, but WBINVD?
> 
> Given now expensive WBINVD is, what possible reason can you think for
> having it in an alternative ?

It's more like e.g. WBNOINVD, which could appear in an alternative in
principle, if its encoding didn't mean WBINVD anyway on older hardware.

>>> +        [0x0b]          = (Known),             /* UD2 */
>>> +
>>> +        [0x18 ... 0x1f] = (Known|ModRM),       /* Grp16 (Hint Nop) */
>>> +        [0x20 ... 0x23] = (Known|ModRM),       /* MOV %cr/%dr */
>>> +
>>> +        [0x30 ... 0x33] = (Known),             /* WRMSR/RDTSC/RDMSR/RDPMC */
>>> +
>>> +        [0x40 ... 0x4f] = (Known|ModRM),       /* CMOVcc */
>>> +
>>> +        [0x80 ... 0x8f] = (Known|Branch|Imm),  /* Jcc disp32 */
>>> +        [0x90 ... 0x9f] = (Known|ModRM),       /* SETcc */
>>> +
>>> +        [0xa0 ... 0xa2] = (Known),             /* PUSH/POP %fs/CPUID */
>>> +        [0xa3]          = (Known|ModRM),       /* BT */
>>> +        [0xa4]          = (Known|ModRM|Imm8),  /* SHLD $imm8 */
>>> +        [0xa5]          = (Known|ModRM),       /* SHLD %cl */
>>> +
>>> +        [0xa8 ... 0xa9] = (Known),             /* PUSH/POP %gs */
>>> +
>>> +        [0xab]          = (Known|ModRM),       /* BTS */
>>> +        [0xac]          = (Known|ModRM|Imm8),  /* SHRD $imm8 */
>>> +        [0xad ... 0xaf] = (Known|ModRM),       /* SHRD %cl/Grp15/IMUL */
>>> +
>>> +        [0xb0 ... 0xb9] = (Known|ModRM),       /* CMPXCHG/LSS/BTR/LFS/LGS/MOVZxx/POPCNT/UD1 */
>>> +        [0xba]          = (Known|ModRM|Imm8),  /* Grp8 */
>>> +        [0xbb ... 0xbf] = (Known|ModRM),       /* BTC/BSF/BSR/MOVSX */
>>> +        [0xc0 ... 0xc1] = (Known|ModRM),       /* XADD */
>> What about MOVNTI?
> 
> I judged that to be on the unlikely side to be needed.

Hmm, I'm not going to insist, but I think we'd better have it right away.

>>> +        [0xc7]          = (Known|ModRM),       /* Grp9 */
>>> +        [0xc8 ... 0xcf] = (Known),             /* BSWAP */
>>> +    };
>> What about UD0?
> 
> UD0 differs between vendors and product lines from Intel.

Would you mind leaving a commented (to this effect) 0 entry?

>>> +        if ( mod == 1 ) /* disp8 */
>>> +            FETCH(int8_t);
>>> +        else if ( mod == 2 ) /* disp32 */
>>> +        {
>>> +        disp32:
>>> +            FETCH(int32_t);
>>> +        }
>> In several cases the FETCH()ed value isn't used. Compilers as well as Eclair
>> (and alike) are happy with that?
> 
> Yes.  The cover letter has a fully passing pipeline.

Which you know as well as I do, says next to nothing, as we don't even come
close to covering the complete version range. That said, it's likely good
enough. I'm merely surprised no tool has to say anything about these unused
"return" values of the macro.

>>> --- a/xen/arch/x86/x86_emulate/x86_emulate.h
>>> +++ b/xen/arch/x86/x86_emulate/x86_emulate.h
>>> @@ -835,4 +835,18 @@ static inline void x86_emul_reset_event(struct x86_emulate_ctxt *ctxt)
>>>      ctxt->event = (struct x86_event){};
>>>  }
>>>  
>>> +/*
>>> + * x86_decode_lite().  Very minimal decoder for managing alternatives.
>>> + *
>>> + * @len is 0 on error, or nonzero on success.  If the instruction has a
>>> + * relative field, @rel_sz is nonzero, and @rel points at the field.
>>> + */
>>> +typedef struct {
>>> +    uint8_t len;
>>> +    uint8_t rel_sz; /* bytes: 0, 1 or 4 */
>> Perhaps use bitfields in favor of fixed-width integers, seeing what
>> ./CODING_STYLE says?
> 
> No.  That destroys the code generation improvements gained by returning
> a pair like this in the first place.

I was under the pretty clear impression that halfway recent compilers
treat 8-bit bitfields the same as uint8_t ordinary fields. (And no, I
did by no means suggest to shrink the width of the fields.)

Jan


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 2/5] tests/x86: Introduce a userspace test harness for x86_decode_lite()
  2026-08-04 19:37     ` Andrew Cooper
@ 2026-08-05  6:45       ` Jan Beulich
  0 siblings, 0 replies; 14+ messages in thread
From: Jan Beulich @ 2026-08-05  6:45 UTC (permalink / raw)
  To: Andrew Cooper; +Cc: Roger Pau Monné, Teddy Astie, Xen-devel

On 04.08.2026 21:37, Andrew Cooper wrote:
> On 03/08/2026 5:03 pm, Jan Beulich wrote:
>> On 03.08.2026 09:20, Andrew Cooper wrote:
>>> --- /dev/null
>>> +++ b/tools/tests/x86-decode-lite/insns.S
>>> @@ -0,0 +1,703 @@
>>> +#include "macro-magic.h"
>>> +
>>> +        .code64
>>> +
>>> +        .allow_index_reg
>>> +
>>> +        .text
>>> +
>>> +DECL(tests_rel0)
>>> +modrm:
>>> +        /* Mod=0, Reg=0, RM {0..f} */
>>> +        _ add %al, (%rax)
>>> +        _ add %al, (%rcx)
>>> +        _ add %al, (%rdx)
>>> +        _ add %al, (%rbx)
>>> +        _ add %al, (%rsp) /* SIB */
>>> +        /*add %al, (%rbp)    RIP --> tests_rel4 */
>>> +        _ add %al, (%rsi)
>>> +        _ add %al, (%rdi)
>>> +        _ add %al, (%r8)
>>> +        _ add %al, (%r9)
>>> +        _ add %al, (%r10)
>>> +        _ add %al, (%r11)
>>> +        _ add %al, (%r12) /* SIB */
>>> +        /*add %al, (%r13)    RIP --> tests_rel4 */
>>> +        _ add %al, (%r14)
>>> +        _ add %al, (%r15)
>>> +
>>> +        /* Mod=1, Reg=0, RM {0..f} */
>>> +        _ add %al, 0x01(%rax)
>>> +        _ add %al, 0x01(%rcx)
>>> +        _ add %al, 0x01(%rdx)
>>> +        _ add %al, 0x01(%rbx)
>>> +        _ add %al, 0x01(%rsp) /* SIB */
>>> +        _ add %al, 0x01(%rbp)
>>> +        _ add %al, 0x01(%rsi)
>>> +        _ add %al, 0x01(%rdi)
>>> +        _ add %al, 0x01(%r8)
>>> +        _ add %al, 0x01(%r9)
>>> +        _ add %al, 0x01(%r10)
>>> +        _ add %al, 0x01(%r11)
>>> +        _ add %al, 0x01(%r12) /* SIB */
>>> +        _ add %al, 0x01(%r13)
>>> +        _ add %al, 0x01(%r14)
>>> +        _ add %al, 0x01(%r15)
>>> +
>>> +        /* Mod=2, Reg=0, RM {0..f} */
>>> +        _ add %al, 0x7f000001(%rax)
>>> +        _ add %al, 0x7f000001(%rcx)
>>> +        _ add %al, 0x7f000001(%rdx)
>>> +        _ add %al, 0x7f000001(%rbx)
>>> +        _ add %al, 0x7f000001(%rsp) /* SIB */
>>> +        _ add %al, 0x7f000001(%rbp)
>>> +        _ add %al, 0x7f000001(%rsi)
>>> +        _ add %al, 0x7f000001(%rdi)
>>> +        _ add %al, 0x7f000001(%r8)
>>> +        _ add %al, 0x7f000001(%r9)
>>> +        _ add %al, 0x7f000001(%r10)
>>> +        _ add %al, 0x7f000001(%r11)
>>> +        _ add %al, 0x7f000001(%r12) /* SIB */
>>> +        _ add %al, 0x7f000001(%r13)
>>> +        _ add %al, 0x7f000001(%r14)
>>> +        _ add %al, 0x7f000001(%r15)
>>> +
>>> +        /* Mod=3, Reg=0, RM {0..f} */
>>> +        _ add %al, %al
>>> +        _ add %al, %cl
>>> +        _ add %al, %dl
>>> +        _ add %al, %bl
>>> +        _ add %al, %ah
>>> +        _ add %al, %ch
>>> +        _ add %al, %dh
>>> +        _ add %al, %dl
>> Perhaps also include %bpl, %sil, and %dil?
> 
> They're not relevant to this test, and interfere with the intentional
> pattern set up.

Hmm, how does a particular pattern matter here? I don't think you test those
cases (or more generally an empty REX prefix) anywhere else.

>>> +DECL(tests_rel1)
>>> +disp8:
>>> +1:
>>> +        _ jo   1b
>>> +        _ jno  1b
>>> +        _ jb   1b
>>> +        _ jae  1b
>>> +        _ je   1b
>>> +        _ jne  1b
>>> +        _ jbe  1b
>>> +        _ ja   1b
>>> +        _ js   1b
>>> +        _ jns  1b
>>> +        _ jp   1b
>>> +        _ jnp  1b
>>> +        _ jl   1b
>>> +        _ jge  1b
>>> +        _ jle  1b
>>> +        _ jg   1b
>>> +        _ jmp  1b
>>> +
>>> +disp8_rex:
>>> +        _ rex.w jo   1b
>>> +        _ rex.w jno  1b
>>> +        _ rex.w jb   1b
>>> +        _ rex.w jae  1b
>>> +        _ rex.w je   1b
>>> +        _ rex.w jne  1b
>>> +        _ rex.w jbe  1b
>>> +        _ rex.w ja   1b
>>> +        _ rex.w js   1b
>>> +        _ rex.w jns  1b
>>> +        _ rex.w jp   1b
>>> +        _ rex.w jnp  1b
>>> +        _ rex.w jl   1b
>>> +        _ rex.w jge  1b
>>> +        _ rex.w jle  1b
>>> +        _ rex.w jg   1b
>>> +        _ rex.w jmp  1b
>>> +END(tests_rel1)
>> What's the idea behind the separate REX.W testing?
> 
> Testing osize handling vs Imm8/Imm.

I see, albeit I very much hope osize would never, ever have an effect on Imm8
encodings, as far as the size of the immediate goes.

>>  It almost suggests that
>> tests with an operand size prefix also may want adding. Except that's
>> difficult, because of ...
>>
>>> +DECL(tests_rel4)
>>> +disp32:
>>> +        _ call   other_section
>>> +        _ jmp    other_section
>>> +        _ jo     other_section
>>> +        _ jno    other_section
>>> +        _ jb     other_section
>>> +        _ jae    other_section
>>> +        _ je     other_section
>>> +        _ jne    other_section
>>> +        _ jbe    other_section
>>> +        _ ja     other_section
>>> +        _ js     other_section
>>> +        _ jns    other_section
>>> +        _ jp     other_section
>>> +        _ jnp    other_section
>>> +        _ jl     other_section
>>> +        _ jge    other_section
>>> +        _ jle    other_section
>>> +        _ jg     other_section
>>> +        _ xbegin other_section
>>> +
>>> +disp32_rex:
>>> +        _ rex.w call   other_section
>>> +        _ rex.w jmp    other_section
>>> +        _ rex.w jo     other_section
>>> +        _ rex.w jno    other_section
>>> +        _ rex.w jb     other_section
>>> +        _ rex.w jae    other_section
>>> +        _ rex.w je     other_section
>>> +        _ rex.w jne    other_section
>>> +        _ rex.w jbe    other_section
>>> +        _ rex.w ja     other_section
>>> +        _ rex.w js     other_section
>>> +        _ rex.w jns    other_section
>>> +        _ rex.w jp     other_section
>>> +        _ rex.w jnp    other_section
>>> +        _ rex.w jl     other_section
>>> +        _ rex.w jge    other_section
>>> +        _ rex.w jle    other_section
>>> +        _ rex.w jg     other_section
>>> +        _ rex.w xbegin other_section
>> ... vendor differences here. Perhaps the decoder itself would better
>> reject handling of operand-size-prefixed branches.
> 
> Excluding 66-prefix is easy, but excluding rex.w on jumps is hard and
> would require extra logic.

To exclude 66 is all I was suggesting. REX.W isn't treated differently by
the vendors, afaik, likely simply because it's meaningless altogether for
these insns.

>>> +opsize_branch: /* 66-prefixed branches are decoded differently by vendors */
>>> +        _ data16 call   other_section
>>> +        _ data16 jmp    other_section
>>> +        _ data16 jo     other_section
>>> +        _ data16 jno    other_section
>>> +        _ data16 jb     other_section
>>> +        _ data16 jae    other_section
>>> +        _ data16 je     other_section
>>> +        _ data16 jne    other_section
>>> +        _ data16 jbe    other_section
>>> +        _ data16 ja     other_section
>>> +        _ data16 js     other_section
>>> +        _ data16 jns    other_section
>>> +        _ data16 jp     other_section
>>> +        _ data16 jnp    other_section
>>> +        _ data16 jl     other_section
>>> +        _ data16 jge    other_section
>>> +        _ data16 jle    other_section
>>> +        _ data16 jg     other_section
>>> +        _ data16 xbegin other_section
>> Oh, you even cover the case here. For XBEGIN, however, this can only be pure
>> guesswork as to AMD behavior, I suppose.
> 
> Remember that RTM is available on Zen2 if you know which chickenbits to
> clobber.
> 
> I've not tried.  I expect it's more likely that they behave consistently
> than differently.
> 
>> I also don't see how you force which form you want.
> 
> Binutils always produces AMD behaviour.  (As far as I can see.)

By default, yes. Quite some time ago CALL and JMP were covered more
correctly, via the -mamd64 / -mintel64 cmdline options. Not very long ago
I realized we had never extended that to Jcc.

> This is in the negative-tests section, which confirms that
> x86_decode_lite() rejects the byte pattern.
> 
> If Binutils changes behaviour, the test will start failing.

Changing the default behavior seems extremely unlikely to me. Changing
the non default behavior, otoh, has happened (and if need be could
happen again).

Anyway, all of this is becoming moot if 66 was rejected on branches.

>>> --- /dev/null
>>> +++ b/tools/tests/x86-decode-lite/main.c
>>> @@ -0,0 +1,111 @@
>>> +/*
>>> + * Userspace test harness for x86_decode_lite().
>>> + */
>>> +#include <stdio.h>
>>> +
>>> +#include "x86-emulate.h"
>>> +
>>> +static unsigned int nr_failures;
>>> +#define fail(t, fmt, ...)                                       \
>>> +({                                                              \
>>> +    const unsigned char *insn = (t)->ip;                        \
>>> +                                                                \
>>> +    nr_failures++;                                              \
>>> +                                                                \
>>> +    (void)printf("  Fail '%s' [%02x", (t)->name, *insn);        \
>>> +    for ( unsigned int i = 1; i < (t)->len; i++ )               \
>>> +        printf(" %02x", insn[i]);                               \
>>> +    printf("]\n");                                              \
>>> +                                                                \
>>> +    (void)printf(fmt, ##__VA_ARGS__);                           \
>>> +})
>>> +
>>> +struct test {
>>> +    const char *name;
>>> +    void *ip;
>>> +    unsigned long len;
>>> +};
>>> +
>>> +extern const struct test
>>> +/* Defined in insns.S, ends with sentinel */
>>> +    tests_rel0[], /* No relocatable entry */
>>> +    tests_rel1[], /* disp8 */
>>> +    tests_rel4[], /* disp32 or RIP-relative */
>>> +    tests_unsup[]; /* Unsupported instructions */
>>> +
>>> +static inline void run_tests(const struct test *tests, unsigned int rel_sz)
>>> +{
>>> +    printf("Test rel%u\n", rel_sz);
>>> +
>>> +    for ( unsigned int i = 0; tests[i].name; ++i )
>>> +    {
>>> +        const struct test *t = &tests[i];
>>> +        x86_decode_lite_t r;
>>> +
>>> +        /*
>>> +         * Don't end strictly at t->len.  This provides better diagnostics if
>>> +         * too many bytes end up getting consumed.
>>> +         */
>>> +        r = x86_decode_lite(t->ip, t->ip + /* t->len */ 20);
>> For the excess bytes to at least be legitimate to access (not causing UB),
>> shouldn't finish_arr emit enough filler bytes?
> 
> finish_arr is the wrong place, but I've folded in:
> 
> diff --git a/tools/tests/x86-decode-lite/insns.S b/tools/tests/x86-decode-lite/insns.S
> index e52c2934c8d8..dc017016b2d2 100644
> --- a/tools/tests/x86-decode-lite/insns.S
> +++ b/tools/tests/x86-decode-lite/insns.S
> @@ -695,6 +695,13 @@ unsup_insn: /* Instructions that would complicated decode, or shouldn't be used
>  
>  END(tests_unsup)
>  
> +        /*
> +         * For improved diagnostics, we allow some overreading of the
> +         * instruction under test.  Ensure there are good bytes to read.
> +         */
> +overread_padding:
> +        .skip 20
> +
>          /* This is here to cause jmps to use their disp32 form. */
>          .section .text.other_section, "ax", @progbits
>  other_section:

How would this help? run_tests() is never invoked with tests_unsup[] as
argument. And run_tests_unsup() wants to only fetch up to t->len.

>>> --- /dev/null
>>> +++ b/tools/tests/x86-decode-lite/x86-emulate.h
>>> @@ -0,0 +1,27 @@
>>> +#ifndef X86_EMULATE_H
>>> +#define X86_EMULATE_H
>>> +
>>> +#include <assert.h>
>>> +#include <stdbool.h>
>>> +#include <stdint.h>
>>> +#include <stdlib.h>
>>> +#include <string.h>
>>> +
>>> +#include <xen/asm/x86-defns.h>
>>> +#include <xen/asm/x86-vendors.h>
>>> +
>>> +#include <xen-tools/common-macros.h>
>>> +
>>> +#define ASSERT assert
>>> +
>>> +#define printk(...)
>>> +
>>> +#define likely
>>> +#define unlikely
>>> +#define cf_check
>>> +#define init_or_livepatch
>>> +#define init_or_livepatch_const
>>> +
>>> +#include "x86_emulate/x86_emulate.h"
>> Why does this end up being needed?
> 
> Well, this for starters:
> 
> main.c: In function ‘run_tests’:
> main.c:43:9: error: unknown type name ‘x86_decode_lite_t’
>    43 |         x86_decode_lite_t r;
>       |         ^~~~~~~~~~~~~~~~~
> main.c:49:13: error: implicit declaration of function ‘x86_decode_lite’ [-Werror=implicit-function-declaration]
>    49 |         r = x86_decode_lite(t->ip, t->ip + /* t->len */ 20);
>       |             ^~~~~~~~~~~~~~~

Hmm, yes, that should have been obvious, if only I didn't expect decode-lite
to be largely (up to entirely) independent of the core emulator, irrespective
of its placement in the same dir. x86_emulate/x86_emulate.h is a pretty
involved header, which I think would be nice to avoid growing more
dependencies on. Then again it looks as if about every object file already
depends on it (which imo is bad).

Jan


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2026-08-05  6:45 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03  7:20 [PATCH v3 0/5] x86/alternatives: Adjust all insn-relative fields Andrew Cooper
2026-08-03  7:20 ` [PATCH v3 1/5] x86/emul: Introduce x86_decode_lite() Andrew Cooper
2026-08-03  9:14   ` Andrew Cooper
2026-08-03 15:26   ` Jan Beulich
2026-08-04 15:39     ` Jan Beulich
2026-08-04 18:56     ` Andrew Cooper
2026-08-05  6:24       ` Jan Beulich
2026-08-03  7:20 ` [PATCH v3 2/5] tests/x86: Introduce a userspace test harness for x86_decode_lite() Andrew Cooper
2026-08-03 16:03   ` Jan Beulich
2026-08-04 19:37     ` Andrew Cooper
2026-08-05  6:45       ` Jan Beulich
2026-08-03  7:20 ` [PATCH v3 3/5] x86/alternative: Walk all replacements during self tests Andrew Cooper
2026-08-03  7:20 ` [PATCH v3 4/5] x86/alternative: Relocate all insn-relative fields Andrew Cooper
2026-08-03  7:20 ` [PATCH v3 5/5] x86/spec-ctrl: Introduce and use DO_COND_BHB_SEQ Andrew Cooper

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.