Regression of GP-7018: operands are swapped in mov instruction
Author: hochwasserCreated Sep 14, 2026Updated Sep 14, 2026
LabelsType: BugFeature: Processor/x86Status: Internal
Describe the bug Commit f9ed6f4 "GP-7018: Fixed register opsize for mov to/from segment registers" introduced a swap of the two operands in a mov instruction. Before the commit, the code reads:
start
3c0f:03d9 b8 8e 3d MOV AX,0x3d8e
3c0f:03dc 8e d8 MOV DS,AX
3c0f:03de c7 06 02 MOV word ptr [0x2],0x0
00 00 00
After the commit:
start
3c0f:03d9 b8 8e 3d MOV AX,0x3d8e
3c0f:03dc 8e d8 MOV AX, DS
3c0f:03de c7 06 02 MOV word ptr [0x2],0x0
00 00 00
To Reproduce
- Install Ghidra 12.1.2
- Load a real-mode application with a mov instruction that sets a segment register with a normal register
- Note the operand order
- Install Ghidra 12.1.3
- Load the same application from 2.
- Note the operand order => the order is swapped
Expected behavior The mov instructions has first the segment register then the normal register
Environment (please complete the following information):
- OS: Debian testing
- Java Version: 25.0.4.1
- Ghidra Version: 12.1.3
- Ghidra Origin: official GitHub distro
Additional context I tried swapping the segment and register in the ia.sinc for the lines the commit changed. This seems to work. The change is:
--- ia.sinc 2026-09-14 17:24:44.977196694 +0200
+++ ia_fixed.sinc 2026-09-14 16:52:03.758250858 +0200
@@ -4007,10 +4007,10 @@
:MOV Rmr64,Sreg is $(LONGMODE_ON) & vexMode=0 & opsize=2 & byte=0x8c; mod=3 & Rmr64 & Sreg { Rmr64 = zext(Sreg); }
@endif
:MOV Sreg,m16 is vexMode=0 & byte=0x8e; (mod != 3 & Sreg) ... & m16 { Sreg = m16; }
-:MOV Rmr16,Sreg is vexMode=0 & opsize=0 & byte=0x8e; mod=3 & Rmr16 & Sreg { Sreg = Rmr16; }
-:MOV Rmr32,Sreg is vexMode=0 & opsize=1 & byte=0x8e; mod=3 & Rmr32 & Sreg { Sreg = Rmr32(0); }
+:MOV Sreg,Rmr16 is vexMode=0 & opsize=0 & byte=0x8e; mod=3 & Rmr16 & Sreg { Sreg = Rmr16; }
+:MOV Sreg,Rmr32 is vexMode=0 & opsize=1 & byte=0x8e; mod=3 & Rmr32 & Sreg { Sreg = Rmr32(0); }
@ifdef IA64
-:MOV Rmr64,Sreg is $(LONGMODE_ON) & vexMode=0 & opsize=2 & byte=0x8e; mod=3 & Rmr64 & Sreg { Sreg = Rmr64(0); }
+:MOV Sreg,Rmr64 is $(LONGMODE_ON) & vexMode=0 & opsize=2 & byte=0x8e; mod=3 & Rmr64 & Sreg { Sreg = Rmr64(0); }
@endif
:MOV AL,moffs8 is vexMode=0 & byte=0xa0; AL & moffs8 { AL=moffs8; }
:MOV AX,moffs16 is vexMode=0 & opsize=0 & byte=0xa1; AX & moffs16 { AX=moffs16; }
But I don't know if this is enough.
Source: NationalSecurityAgency/ghidra