x86: accept LOCK on control register accesses only with ModR/M.reg == 0

Message ID 8b5974f8-7533-44a7-b357-40fd5f5f0db3@suse.com
State New
Headers
Series x86: accept LOCK on control register accesses only with ModR/M.reg == 0 |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Test passed

Commit Message

Jan Beulich July 10, 2026, 6:20 a.m. UTC
  Reportedly (e.g. [1]) the LOCK handling is special to %cr0 / %cr8 only.
Deal with it this way also in assembler and disassembler.

For the assembler also introduce a separate feature indicator: Not all
64-bit CPUs support this insn form; only most AMD (and presumably all
Hygon) ones do. Register names %cr9 ... %cr15 thus become invalid outside
of 64-bit mode altogether (unprefixed forms become ordinary symbol names),
while %cr8's availability outside of 64-bit mode now depends on the new
feature indicator.

For the disassembler don't limit this handling to non-64-bit modes. Use
of LOCK is similarly permitted in 64-bit mode. Instead don't handle LOCK
this way when "intel64" was specified as an option.

[1] https://lists.xen.org/archives/html/xen-devel/2026-07/msg00391.html
  

Comments

Jan Beulich July 10, 2026, 6:29 a.m. UTC | #1
On 10.07.2026 08:20, Jan Beulich wrote:
> Reportedly (e.g. [1]) the LOCK handling is special to %cr0 / %cr8 only.
> Deal with it this way also in assembler and disassembler.
> 
> For the assembler also introduce a separate feature indicator: Not all
> 64-bit CPUs support this insn form; only most AMD (and presumably all
> Hygon) ones do. Register names %cr9 ... %cr15 thus become invalid outside
> of 64-bit mode altogether (unprefixed forms become ordinary symbol names),
> while %cr8's availability outside of 64-bit mode now depends on the new
> feature indicator.
> 
> For the disassembler don't limit this handling to non-64-bit modes. Use
> of LOCK is similarly permitted in 64-bit mode. Instead don't handle LOCK
> this way when "intel64" was specified as an option.
> 
> [1] https://lists.xen.org/archives/html/xen-devel/2026-07/msg00391.html

Argh - the hunks below are also needed. I really don't understand why there
still is this kind of needless duplication in ilp32/. When the expectations
aren't different between the two modes, there really should be only one
instance (that then needs modifying when a testcase is extended).

Jan

--- a/gas/testsuite/gas/i386/ilp32/x86-64-crx.d
+++ b/gas/testsuite/gas/i386/ilp32/x86-64-crx.d
@@ -1,21 +1,4 @@
 #source: ../x86-64-crx.s
 #objdump: -dw
 #name: x86-64 (ILP32) control register related opcodes
-
-.*: +file format .*
-
-Disassembly of section .text:
-
-0+ <_start>:
-[ 	]*[0-9a-f]+:	44 0f 20 c0[ 	]+movq?[ 	]+?%cr8,%rax
-[ 	]*[0-9a-f]+:	44 0f 20 c7[ 	]+movq?[ 	]+?%cr8,%rdi
-[ 	]*[0-9a-f]+:	44 0f 22 c0[ 	]+movq?[ 	]+?%rax,%cr8
-[ 	]*[0-9a-f]+:	44 0f 22 c7[ 	]+movq?[ 	]+?%rdi,%cr8
-[ 	]*[0-9a-f]+:	44 0f 20 c0[ 	]+movq?[ 	]+?%cr8,%rax
-[ 	]*[0-9a-f]+:	44 0f 20 c7[ 	]+movq?[ 	]+?%cr8,%rdi
-[ 	]*[0-9a-f]+:	44 0f 22 c0[ 	]+movq?[ 	]+?%rax,%cr8
-[ 	]*[0-9a-f]+:	44 0f 22 c7[ 	]+movq?[ 	]+?%rdi,%cr8
-[ 	]*[0-9a-f]+:	44 0f 20 c0[ 	]+movq?[ 	]+?%cr8,%rax
-[ 	]*[0-9a-f]+:	44 0f 20 c7[ 	]+movq?[ 	]+?%cr8,%rdi
-[ 	]*[0-9a-f]+:	44 0f 22 c0[ 	]+movq?[ 	]+?%rax,%cr8
-[ 	]*[0-9a-f]+:	44 0f 22 c7[ 	]+movq?[ 	]+?%rdi,%cr8
+#dump: ../x86-64-crx.d
--- a/gas/testsuite/gas/i386/ilp32/x86-64-crx-suffix.d
+++ b/gas/testsuite/gas/i386/ilp32/x86-64-crx-suffix.d
@@ -1,21 +1,4 @@
 #source: ../x86-64-crx.s
 #objdump: -dwMsuffix
 #name: x86-64 (ILP32) control register related opcodes (with suffixes)
-
-.*: +file format .*
-
-Disassembly of section .text:
-
-0+ <_start>:
-[ 	]*[0-9a-f]+:	44 0f 20 c0[ 	]+movq[ 	]+?%cr8,%rax
-[ 	]*[0-9a-f]+:	44 0f 20 c7[ 	]+movq[ 	]+?%cr8,%rdi
-[ 	]*[0-9a-f]+:	44 0f 22 c0[ 	]+movq[ 	]+?%rax,%cr8
-[ 	]*[0-9a-f]+:	44 0f 22 c7[ 	]+movq[ 	]+?%rdi,%cr8
-[ 	]*[0-9a-f]+:	44 0f 20 c0[ 	]+movq[ 	]+?%cr8,%rax
-[ 	]*[0-9a-f]+:	44 0f 20 c7[ 	]+movq[ 	]+?%cr8,%rdi
-[ 	]*[0-9a-f]+:	44 0f 22 c0[ 	]+movq[ 	]+?%rax,%cr8
-[ 	]*[0-9a-f]+:	44 0f 22 c7[ 	]+movq[ 	]+?%rdi,%cr8
-[ 	]*[0-9a-f]+:	44 0f 20 c0[ 	]+movq[ 	]+?%cr8,%rax
-[ 	]*[0-9a-f]+:	44 0f 20 c7[ 	]+movq[ 	]+?%cr8,%rdi
-[ 	]*[0-9a-f]+:	44 0f 22 c0[ 	]+movq[ 	]+?%rax,%cr8
-[ 	]*[0-9a-f]+:	44 0f 22 c7[ 	]+movq[ 	]+?%rdi,%cr8
+#dump: ../x86-64-crx-suffix.d
  

Patch

--- a/gas/doc/c-i386.texi
+++ b/gas/doc/c-i386.texi
@@ -153,6 +153,7 @@  accept various extension mnemonics.  For
 @code{sse4},
 @code{avx},
 @code{avx2},
+@code{altmovcr8},
 @code{lahf_sahf},
 @code{monitor},
 @code{adx},
@@ -1708,9 +1709,9 @@  supported on the CPU specified.  The cho
 @item @samp{.rdrnd} @tab @samp{.f16c} @tab @samp{.avx2} @tab @samp{.bmi2}
 @item @samp{.lzcnt} @tab @samp{.popcnt} @tab @samp{.invpcid} @tab @samp{.vmfunc}
 @item @samp{.monitor} @tab @samp{.hle} @tab @samp{.rtm} @tab @samp{.tsx}
-@item @samp{.lahf_sahf} @tab @samp{.adx} @tab @samp{.rdseed} @tab @samp{.prfchw}
-@item @samp{.smap} @tab @samp{.mpx} @tab @samp{.sha} @tab @samp{.prefetchwt1}
-@item @samp{.clflushopt} @tab @samp{.xsavec} @tab @samp{.xsaves} @tab @samp{.se1}
+@item @samp{.altmovcr8} @tab @samp{.lahf_sahf} @tab @samp{.adx} @tab @samp{.rdseed}
+@item @samp{.smap} @tab @samp{.mpx} @tab @samp{.sha} @tab @samp{.prfchw}
+@item @samp{.prefetchwt1} @tab @samp{.clflushopt} @tab @samp{.xsavec} @tab @samp{.xsaves}
 @item @samp{.avx512f} @tab @samp{.avx512cd} @tab @samp{.avx512er} @tab @samp{.avx512pf}
 @item @samp{.avx512vl} @tab @samp{.avx512bw} @tab @samp{.avx512dq} @tab @samp{.avx512ifma}
 @item @samp{.avx512vbmi} @tab @samp{.avx512_4fmaps} @tab @samp{.avx512_4vnniw}
@@ -1719,7 +1720,7 @@  supported on the CPU specified.  The cho
 @item @samp{.tdx} @tab @samp{.avx_vnni} @tab @samp{.avx512_fp16} @tab @samp{avx512_bmm}
 @item @samp{.avx10.1} @tab @samp{.clwb} @tab @samp{.rdpid} @tab @samp{.ptwrite}
 @item @samp{.ibt} @tab @samp{.prefetchi} @tab @samp{.avx_ifma} @tab @samp{.avx_vnni_int8}
-@item @samp{.cmpccxadd} @tab @samp{.wrmsrns} @tab @samp{.msrlist}
+@item @samp{.cmpccxadd} @tab @samp{.wrmsrns} @tab @samp{.msrlist} @tab @samp{.se1}
 @item @samp{.avx_ne_convert} @tab @samp{.rao_int} @tab @samp{.fred} @tab @samp{.lkgs}
 @item @samp{.avx_vnni_int16} @tab @samp{.sha512} @tab @samp{.sm3} @tab @samp{.sm4}
 @item @samp{.pbndkb} @tab @samp{.user_msr} @tab @samp{.msr_imm} @tab @samp{.avx10.2}
--- a/gas/config/tc-i386.c
+++ b/gas/config/tc-i386.c
@@ -1147,6 +1147,7 @@  static const arch_entry cpu_arch[] =
   SUBARCH (lwp, LWP, ANY_LWP, false),
   SUBARCH (movbe, MOVBE, MOVBE, false),
   SUBARCH (cx16, CX16, CX16, false),
+  SUBARCH (altmovcr8, ALTMOVCR8, ALTMOVCR8, false),
   SUBARCH (lahf_sahf, LAHF_SAHF, LAHF_SAHF, false),
   SUBARCH (ept, EPT, ANY_EPT, false),
   SUBARCH (lzcnt, LZCNT, LZCNT, false),
@@ -11655,6 +11656,7 @@  build_modrm_byte (void)
   if (flag_code != CODE_64BIT && (i.rex & REX_R))
     {
       gas_assert (i.types[!i.tm.opcode_modifier.regmem].bitfield.class == RegCR);
+      gas_assert (i.op[!i.tm.opcode_modifier.regmem].regs->reg_num == 0);
       i.rex &= ~REX_R;
       add_prefix (LOCK_PREFIX_OPCODE);
     }
@@ -17001,10 +17003,11 @@  static bool check_register (const reg_en
     }
 
   if (((r->reg_flags & (RegRex64 | RegRex)) || r->reg_type.bitfield.qword)
-      && (!cpu_arch_flags.bitfield.cpu64
+      && flag_code != CODE_64BIT
+      && (!cpu_arch_flags.bitfield.cpualtmovcr8
 	  || r->reg_type.bitfield.class != RegCR
-	  || dot_insn ())
-      && flag_code != CODE_64BIT)
+	  || r->reg_num != 0
+	  || dot_insn ()))
     return false;
 
   if (r->reg_type.bitfield.class == SReg && r->reg_num == RegFlat
--- a/gas/testsuite/gas/i386/cr-err.l
+++ b/gas/testsuite/gas/i386/cr-err.l
@@ -2,7 +2,7 @@ 
 .*:[0-9]+: Error: .\(%cr0\). is not a valid base/index expression
 .*:[0-9]+: Error: .\(%cr7\). is not a valid base/index expression
 .*:[0-9]+: Error: .\(%cr8\). is not a valid base/index expression
-.*:[0-9]+: Error: .\(%cr15\). is not a valid base/index expression
+.*:[0-9]+: Error: bad register name `%cr15.*'
 .*:[0-9]+: Error: .\(%db0\). is not a valid base/index expression
 .*:[0-9]+: Error: .\(%db7\). is not a valid base/index expression
 .*:[0-9]+: Error: .\(%dr0\). is not a valid base/index expression
@@ -12,7 +12,6 @@ 
 .*:[0-9]+: Error: .\(cr0\). is not a valid base/index expression
 .*:[0-9]+: Error: .\(cr7\). is not a valid base/index expression
 .*:[0-9]+: Error: .\(cr8\). is not a valid base/index expression
-.*:[0-9]+: Error: .\(cr15\). is not a valid base/index expression
 .*:[0-9]+: Error: .\(db0\). is not a valid base/index expression
 .*:[0-9]+: Error: .\(db7\). is not a valid base/index expression
 .*:[0-9]+: Error: .\(dr0\). is not a valid base/index expression
@@ -22,7 +21,6 @@ 
 .*:[0-9]+: Error: .\[cr0\]. is not a valid base/index expression
 .*:[0-9]+: Error: .\[cr7\]. is not a valid base/index expression
 .*:[0-9]+: Error: .\[cr8\]. is not a valid base/index expression
-.*:[0-9]+: Error: .\[cr15\]. is not a valid base/index expression
 .*:[0-9]+: Error: .\[dr0\]. is not a valid base/index expression
 .*:[0-9]+: Error: .\[dr7\]. is not a valid base/index expression
 .*:[0-9]+: Error: .\[tr0\]. is not a valid base/index expression
--- a/gas/testsuite/gas/i386/x86-64-crx.d
+++ b/gas/testsuite/gas/i386/x86-64-crx.d
@@ -11,6 +11,8 @@  Disassembly of section .text:
 [ 	]*[0-9a-f]+:	44 0f 20 c7[ 	]+movq?[ 	]+?%cr8,%rdi
 [ 	]*[0-9a-f]+:	44 0f 22 c0[ 	]+movq?[ 	]+?%rax,%cr8
 [ 	]*[0-9a-f]+:	44 0f 22 c7[ 	]+movq?[ 	]+?%rdi,%cr8
+[ 	]*[0-9a-f]+:	f0 0f 20 c1[ 	]+movq?[ 	]+%cr8,%rcx
+[ 	]*[0-9a-f]+:	f0 44 0f 20 c1[ 	]+lock movq?[ 	]+%cr8,%rcx
 [ 	]*[0-9a-f]+:	44 0f 20 c0[ 	]+movq?[ 	]+?%cr8,%rax
 [ 	]*[0-9a-f]+:	44 0f 20 c7[ 	]+movq?[ 	]+?%cr8,%rdi
 [ 	]*[0-9a-f]+:	44 0f 22 c0[ 	]+movq?[ 	]+?%rax,%cr8
--- a/gas/testsuite/gas/i386/x86-64-crx.s
+++ b/gas/testsuite/gas/i386/x86-64-crx.s
@@ -5,6 +5,9 @@  _start:
 	movq	%rax, %cr8
 	movq	%rdi, %cr8
 
+	lock; mov %cr0, %rcx
+	lock; mov %cr8, %rcx
+
 .att_syntax noprefix
 	movq	cr8, rax
 	movq	cr8, rdi
--- a/gas/testsuite/gas/i386/x86-64-crx-suffix.d
+++ b/gas/testsuite/gas/i386/x86-64-crx-suffix.d
@@ -11,6 +11,8 @@  Disassembly of section .text:
 [ 	]*[0-9a-f]+:	44 0f 20 c7[ 	]+movq[ 	]+?%cr8,%rdi
 [ 	]*[0-9a-f]+:	44 0f 22 c0[ 	]+movq[ 	]+?%rax,%cr8
 [ 	]*[0-9a-f]+:	44 0f 22 c7[ 	]+movq[ 	]+?%rdi,%cr8
+[ 	]*[0-9a-f]+:	f0 0f 20 c1[ 	]+movq[ 	]+%cr8,%rcx
+[ 	]*[0-9a-f]+:	f0 44 0f 20 c1[ 	]+lock movq[ 	]+%cr8,%rcx
 [ 	]*[0-9a-f]+:	44 0f 20 c0[ 	]+movq[ 	]+?%cr8,%rax
 [ 	]*[0-9a-f]+:	44 0f 20 c7[ 	]+movq[ 	]+?%cr8,%rdi
 [ 	]*[0-9a-f]+:	44 0f 22 c0[ 	]+movq[ 	]+?%rax,%cr8
--- a/opcodes/i386-dis.c
+++ b/opcodes/i386-dis.c
@@ -13297,7 +13297,11 @@  OP_C (instr_info *ins, int dummy ATTRIBU
       USED_REX (REX_R);
       add = 8;
     }
-  else if (ins->address_mode != mode_64bit && (ins->prefixes & PREFIX_LOCK))
+  else if (/* Only %cr0 -> %cr8 is dealt with this way (and also really only on
+	      most AMD hardware).  */
+	   ins->modrm.reg == 0
+	   && (ins->prefixes & PREFIX_LOCK)
+	   && ins->isa64 != intel64)
     {
       ins->all_prefixes[ins->last_lock_prefix] = 0;
       ins->used_prefixes |= PREFIX_LOCK;
--- a/opcodes/i386-gen.c
+++ b/opcodes/i386-gen.c
@@ -392,6 +392,7 @@  static bitfield cpu_flags[] =
   BITFIELD (TBM),
   BITFIELD (Movbe),
   BITFIELD (CX16),
+  BITFIELD (AltMovCr8),
   BITFIELD (LAHF_SAHF),
   BITFIELD (EPT),
   BITFIELD (Rdtscp),
--- a/opcodes/i386-opc.h
+++ b/opcodes/i386-opc.h
@@ -135,6 +135,8 @@  enum i386_cpu
   CpuMovbe,
   /* CMPXCHG16B instruction support required.  */
   CpuCX16,
+  /* MOV to/from %cr8 instruction support required (in 32-bit mode).  */
+  CpuAltMovCr8,
   /* LAHF/SAHF instruction support required (in 64-bit mode).  */
   CpuLAHF_SAHF,
   /* EPT Instructions required */
@@ -470,6 +472,7 @@  typedef union i386_cpu_flags
       unsigned int cputbm:1;
       unsigned int cpumovbe:1;
       unsigned int cpucx16:1;
+      unsigned int cpualtmovcr8:1;
       unsigned int cpulahf_sahf:1;
       unsigned int cpuept:1;
       unsigned int cpurdtscp:1;