HALICERY

free-time coding, hardware dev, articles

Top
Home 8042 Blogs About
Home IntelEssential 64-bit programming IntelEssential64special

Last modified: Fri Jul 3 21:20:10 UTC+0200 2026 © A. Tarpai


MOV in 64-bit mode

2-operand MOV in 64-bit mode

Opcodes based on the original 8086 MOV instructions

Move data between register and register/memory. In two directions (opcode d-bit), byte- or word movement (opcode w-bit). Short form moves data between accumulator and absolute 16-bit mem offset.

8086
                                       opcode
                                       88..8B
+---------+       +---------+        _________    _________ _ _ _ _ _
|   REG   | <---> | REG/MEM |       |1000_10dw|  |M_REG_R/M|  DISP   |
+---------+       +---------+
                                       opcode
                                       A0..A3
+---------+       +---------+        _________    _________  _________
|  ACCU   | <---> |   MEM   |       |1010_00dw|  |  DISP   ||  DISP   |
+---------+       +---------+

Short-form means without MODRM byte (operand encoded in the opcode itself).

64-bit mode moves all operand sizes. The default operand size for move is 32-bit.

Move data between register and register/memory:

         opcode
         88..8B
       _________    _________ _ _ _ _ _
      |1000_10d0|  |M_REG_R/M|  DISP   |      mov byte
       _________    _________ _ _ _ _ _
  66h |1000_10d1|  |M_REG_R/M|  DISP   |      mov word
       _________    _________ _ _ _ _ _
      |1000_10d1|  |M_REG_R/M|  DISP   |      mov dword
       _________    _________ _ _ _ _ _
  48h |1000_10d1|  |M_REG_R/M|  DISP   |      mov qword


Short-form, move data between accumulator and absolute mem offset:

         opcode
         A0..A3
       _________    ______  ______  ______  ______  ______  ______  ______  ______
      |1010_00d0|  | DISP || DISP || DISP || DISP || DISP || DISP || DISP || DISP |   mov byte
       _________    ______  ______  ______  ______  ______  ______  ______  ______
  66h |1010_00d1|  | DISP || DISP || DISP || DISP || DISP || DISP || DISP || DISP |   mov word
       _________    ______  ______  ______  ______  ______  ______  ______  ______
      |1010_00d1|  | DISP || DISP || DISP || DISP || DISP || DISP || DISP || DISP |   mov dword
       _________    ______  ______  ______  ______  ______  ______  ______  ______
  48h |1010_00d1|  | DISP || DISP || DISP || DISP || DISP || DISP || DISP || DISP |   mov qword


       The only instruction supporting a true 64-bit absolute offset value
       (in all other cases offsets are maximum 32 bits, sign-extended to 64-bit).

Note the short-form honors addr-size prefix:

           _________    ______  ______  ______  ______
  67h     |1010_00d0|  | DISP || DISP || DISP || DISP |   mov byte
           _________    ______  ______  ______  ______
  67h 66h |1010_00d1|  | DISP || DISP || DISP || DISP |   mov word
           _________    ______  ______  ______  ______
  67h     |1010_00d1|  | DISP || DISP || DISP || DISP |   mov dword
           _________    ______  ______  ______  ______
  67h 48h |1010_00d1|  | DISP || DISP || DISP || DISP |   mov qword

The 8086 short-form addressing mode heritage extended to 8-byte [memory displacement]. Eg. the same opcode, A1, means different sized displacements:

8086 or 16-bit address-size:

  A1 06 00  mov ax, [6]

386 or 32-bit address-size:

  A1 06 00 00 00  mov eax, [6]

64-bit mode (NASM syntax):

  A1 06 00 00 00 00 00 00 00  mov eax, [qword 6]

Operation

Straightforward operation in 64-bit:

W=0 moves byte between 8-bit reg and reg/mem

W=1 moves default operand-size 32- or 64-bit with REX prefix. 16-bit move with 66h prefix. Register destination zeroes HI for 32-bit move:

W=1

Default opcode moves DWORD between reg and mem with REG HI zeroed:

+----+----+----+----+----+----+----+----+        +----+----+----+----+
| 00   00   00   00 |        R32        |  <---  |      M32/R32      |
+----+----+----+----+----+----+----+----+        +----+----+----+----+


+----+----+----+----+----+----+----+----+        +----+----+----+----+
| ..   ..   ..   .. |        R32        |  --->  |        M32        |
+----+----+----+----+----+----+----+----+        +----+----+----+----+


REX.W moves QWORD between reg and reg/mem:

+----+----+----+----+----+----+----+----+        +----+----+----+----+----+----+----+----+
|                  R64                  |  <-->  |                M32/R32                |
+----+----+----+----+----+----+----+----+        +----+----+----+----+----+----+----+----+

MOV immediate in 64-bit mode

Opcodes based on the original 8086 MOV instructions

Move immediate data to register/memory. Byte- or word movement (opcode w-bit). Short form moves immediate data to register.

                                       opcode
                                       C6..C7
+---------+       +---------+        _________    _________ _ _ _ _ _    _________  _________
| REG/MEM | <---- |  IMMED  |       |1100_011w|  |M_000_R/M|  DISP   |  |  DATA   || DATA w=1|
+---------+       +---------+

                                       opcode
                                       B0..BF
+---------+       +---------+        _________    _________  _________
|   REG   | <---- |  IMMED  |       |1011_wreg|  |  DATA   || DATA w=1|
+---------+       +---------+

Short-form means without MODRM byte (operand encoded in the opcode itself).

64-bit mode

Move immediate data to register/memory:

         opcode
         C6..C7
       _________    _________ _ __ __ _    _______
      |1100_0110|  |M_000_R/M|  DISP   |  |  DATA |                             mov byte
       _________    _________ _ __ __ _    _______  _______
  66h |1100_0111|  |M_000_R/M|  DISP   |  |  DATA ||  DATA |                    mov word
       _________    _________ _ __ __ _    _______  _______  _______  _______
      |1100_0111|  |M_000_R/M|  DISP   |  |  DATA ||  DATA ||  DATA ||  DATA |  mov dword
       _________    _________ _ __ __ _    _______  _______  _______  _______
  48h |1100_0111|  |M_000_R/M|  DISP   |  |  DATA ||  DATA ||  DATA ||s DATA |  mov sign-extended dword!


Short-form, move immediate data to register:

         opcode
         B0..BF
       _________    _______
      |1011_0reg|  |  DATA |                                                                 mov byte
       _________    _______  _______
  66h |1011_1reg|  |  DATA ||  DATA |                                                        mov word
       _________    _______  _______  _______  _______
      |1011_1reg|  |  DATA ||  DATA ||  DATA ||  DATA |                                      mov dword
       _________    _______  _______  _______  _______  _______  _______  _______  _______
  48h |1011_1reg|  |  DATA ||  DATA ||  DATA ||  DATA ||  DATA ||  DATA ||  DATA ||  DATA |  mov qword

       The only instruction that supports a 64-bit immediate value

MOV immediate to register 64-bit mode operation W=1

The most notable difference is that R/M-form cannot encode 64-bit immediate value (just as all other instructrions with immediate operand).

I.e. with REX.W these two instructions operate differently:

64-bit move 32/64-bit immediate to register:


Both defaults to 32-bit immediate move with REG HI zero:

  +----+----+----+----+----+----+----+----+       +----+----+----+----+
  | 00000000000000000          R32        |  <--  |        I32        |
  +----+----+----+----+----+----+----+----+       +----+----+----+----+

   C7 C0 FE FF FF FF    mov  eax, -2                R/M form*
      B8 FE FF FF FF    mov  eax, -2                Short form

   * no assembler would generate this form for reg move - used for mem32 move


REX.W immediate move R/M form:

  +----+----+----+----+----+----+----+----+       +----+----+----+----+
  | sssssssssssssssss        R64/M64      |  <--  |s       I32        |
  +----+----+----+----+----+----+----+----+       +----+----+----+----+

   48 C7 C0 FE FF FF FF     mov  rax, -2           Sign-extend (same as to 64-bit mem)


REX.W immediate move Short-form:

  +----+----+----+----+----+----+----+----+       +----+----+----+----+----+----+----+----+
  |                  R64                  |  <--  |                  I64                  |
  +----+----+----+----+----+----+----+----+       +----+----+----+----+----+----+----+----+

   48 B8 89 67 45 23 01 EF CD AB    mov  rax, 0xABCDEF0123456789

   The only instruction that supports a 64-bit immediate value

Clever assembler and/or programmer can then chose between:

8-bit MOV immediate to register in 64-bit mode

Opcode bit w=0.

64-bit move 8-bit immediate to register:


Short form     r/m form*
8-bit immed    8-bit immed
to reg8        to reg8

B0..B7 i8      C6 MODRM i8

B0 01          C6 C0 01*           mov    al,1
B1 01          C6 C1 01*           mov    cl,1
B2 01          C6 C2 01*           mov    dl,1
B3 01          C6 C3 01*           mov    bl,1
B4 01          C6 C4 01*           mov    ah,1
B5 01          C6 C5 01*           mov    ch,1
B6 01          C6 C6 01*           mov    dh,1
B7 01          C6 C7 01*           mov    bh,1

REX            REX

40 B0 01*      40 C6 C0 01*        mov    al,1
40 B1 01*      40 C6 C1 01*        mov    cl,1
40 B2 01*      40 C6 C2 01*        mov    dl,1
40 B3 01*      40 C6 C3 01*        mov    bl,1
40 B4 01       40 C6 C4 01*        mov    spl,1
40 B5 01       40 C6 C5 01*        mov    bpl,1
40 B6 01       40 C6 C6 01*        mov    sil,1
40 B7 01       40 C6 C7 01*        mov    dil,1

REX.B          REX.B

41 B0 01       41 C6 C0 01*        mov    r8b,1
41 B1 01       41 C6 C1 01*        mov    r9b,1
41 B2 01       41 C6 C2 01*        mov    r10b,1
41 B3 01       41 C6 C3 01*        mov    r11b,1
41 B4 01       41 C6 C4 01*        mov    r12b,1
41 B5 01       41 C6 C5 01*        mov    r13b,1
41 B6 01       41 C6 C6 01*        mov    r14b,1
41 B7 01       41 C6 C7 01*        mov    r15b,1

* no assembler would encode this form for reg move - but all tested

Notes on MOV, XCHG and NOP in 64-bit mode

Both MOV and XCHG is-a move and should follow REG HI zeroed. But xchg eax, eax (90) is the NOP instruction since the 8086 and is not supposed to change CPU-state. So what happens in 64-bit mode? Testing:

48 83 CB FF          or   rbx, -1       rbx = ffffffff_ffffffff
      8B DB          mov  ebx, ebx      rbx = 00000000_ffffffff

48 83 CB FF          or   rbx, -1       rbx = ffffffff_ffffffff
      87 DB          xchg ebx, ebx      rbx = 00000000_ffffffff  <-- it is a mov!

Results are identical for rbx. Consider with eax:

48 83 C8 FF          or   rax, -1       rax = ffffffff_ffffffff
      8B C0          mov  eax, eax      rax = 00000000_ffffffff

48 83 C8 FF          or   rax, -1       rax = ffffffff_ffffffff
         90          xchg eax, eax      rbx = ffffffff_ffffffff  <-- "xchg eax, eax" is NOP since 8086

So NOP is-a-NOP in 64-bit mode too. Interesting.

Another use: these 2-byte opcodes can be used to zero REG-HI.

64-bit PUSH

All implicit stack movement is 64-bit, the stack is qword-stack:

   63                      0
   |                       |
   +-----+-----+-----+-----+
   |                       |
   +-----+-----+-----+-----+
   |                       | <-- RSP
   +-----+-----+-----+-----+
   |                       |

The stack pointer is 64-bit RSP and there is no B-bit and stuff.

In 64-bit mode, push is either a 64-bit qword move (default) or a 16-bit word move (66h) – decrementing RSP by 8 or 2.

For 16-bit push use:

There is no 32-bit push in 64-bit mode and REX.W 48h has no effect (every reg/mem push is already 64-bit move).

64-bit PUSH immediate

The old 186/286 opcodes: 68 (push operand-size) and 6A (push sign-extended byte).

In 64-bit mode:

There is no 64-bit immediate push.

64-bit immediate qword push:


                DWORD                             BYTE
            +-----+-----+                         +--+
        68  |s          |                     6A  |s |
            +-----+-----+                         +--+
                  |                                 |
                  v                                 v
+-----+-----+-----+-----+    +-----+-----+-----+--+--+
| sssssssss             |    | sssssssssssssssssss   | <- RSP - 8
+-----+-----+-----+-----+    +-----+-----+-----+--+--+    QWORD move



64-bit immediate word push:


                    WORD                          BYTE
                  +-----+                         +--+
           66 68  |     |                  66 6A  |s |
                  +-----+                         +--+
                     |                              |
                     v                              v
+-----+-----+-----+-----+    +-----+-----+-----+--+--+
| . . . . . . . . |     |    | . . . . . . . . |sss  | <- RSP - 2
+-----+-----+-----+-----+    +-----+-----+-----+--+--+    WORD move


Examples:

                  ml64 syntax        ml64 disassembly

68 F4 01 00 00    push 500           push 1F4h
68 34 12 CD AB    push 0ABCD1234h*   push 0FFFFFFFFABCD1234h
6A FF             push -1            push 0FFFFFFFFFFFFFFFFh

66 68 F4 01       pushw 500          push 1F4h
66 68 CD AB       pushw 0ABCDh       push 0ABCDh
66 6A FF          pushw -1           push 0FFFFh

* NASM gives warning

PUSH reg short form

Default opcode means 64-bit register push. 66h can push 16-bit register part to RSP - 2. No sign-extension here.

   50  push rax       ; RSP = RSP - 8
66 50  push ax        ; RSP = RSP - 2

REX.B for other registers:

41 53    push r11

PUSH r/m

Push [mem]: default opcode means 64-bit push from memory location, so RAX.W has no meaning. NB: 64-bit memory read (watch eg. APIC registers).

66h will push 16-bit word from memory to RSP - 2.

ml64 syntax:

   FF 35 45 00 00 00      push qword ptr [p (07FF6D304105Ch)]   ; uses rip-rel
66 FF 35 01 00 00 00      push  word ptr [p (07FF647B62319h)]   ; rsp = rsp - 2

NASM syntax:

   FF 34 25 06 00 00 00   push qword [6]   ; uses SIB Direct Memory
66 FF 34 25 06 00 00 00   push  word [6]

Push register r/m form, hand-made code disassembly:

   FF F0   push rax
66 FF F0   push ax

FAR in 64-bit mode

Analyzing direct control transfers only (far-CALL or a far-JMP instruction without using a call gate) and all CPL=0. This is mainly about how the cpu works and interprets code bytes.

Using traditional instructions, CPU can pull a far target address from memory to jump:

* Far immediate is illegal in 64-bit mode.

JMP FAR memory indirect in 64-bit mode

CPU can load RIP from memory in all operand sizes (Intel only) with RIP HI zeroed. CS is always WORD.

64-bit mode JMP FAR memory indirect and how JMP FAR loads RIP:


                                DEFAULT
 66h jmp far word [mem]         jmp far dword [mem]            REX.W jmp far qword [mem]

                                                          /            |
                                                    AMD! /             | Intel only
                                                        /              |
                                                       /               v
                                                      /
 READS 2                        READS 3              v         READS 5
 MEMORY WORDS                   MEMORY WORDS                   MEMORY WORDS
+-----+-----+                  +-----+-----+-----+            +-----+-----+-----+-----+-----+
|  IP |  CS |                  |    EIP    |  CS |            |          RIP          |  CS |
+-----+-----+                  +-----+-----+-----+            +-----+-----+-----+-----+-----+
   |                                 |                                    |
   | load CS                         | load CS                            | load CS
   | load RIP                        | load RIP                           | load RIP
   v                                 v                                    v

+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
| 000000000000000       |      | 000000000             |      |                       | RIP
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
 JMP to <64K                    JMP to <4GB                    JMP to full 64-bit
 virtual address                virtual address                virtual address



NASM examples:

66 FF2C25[69010000]  jmp far word [sorry]

   FF2C25[69010000]  jmp far dword [sorry]

48 FF2C25[69010000]  jmp far qword [sorry]
48 FF2C25[69010000]  jmp far [sorry]*

* NB. NASM defaults to QWORD and that is for Intel only

AMD:
It is not like REX JMP FAR instruction fails, but the offset read is not qword but dword (the default).
REX prefix is simply ignored by AMD for direct far call: 48FF2C25[69010000] with EIP:CS works. These were all tested.

CALL FAR memory indirect in 64-bit mode

The instruction works in all operand sizes.

  1. Push CS and operand size RIP
  2. Jump as far jmp based on operand size. NB: Intel/AMD difference again!
64-bit mode CALL FAR memory indirect with 64-bit stack view and how CALL FAR pushes and loads RIP:


                                DEFAULT
 66h call far word [mem]        call far dword [mem]           REX.W call far qword [mem]


+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
| xxxxxxxxxxxxxxx       |      | xxxxxxxxx             |      |                       | RIP
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
 x: RIP HI lost                 x: RIP HI lost

                  |                              |                              |
 push word CS     |             push dword CS    |             push qword CS    |
 push word RIP    |             push dword RIP   |             push qword RIP   |
                  |                              |                              |
                  v                              v                              v

                                                              63                      0
                                                              +-----+-----+-----+-----+
63          15          0      63                      0      |                    CS | +8
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
|     |     |  CS |  IP |      |        CS |    EIP    |      |          RIP          | <-- rsp
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
      rsp = rsp - 4                  rsp = rsp - 8                  rsp = rsp - 16


           |                             |              /              |
           |                             |        AMD! /               | Intel only
           |                             |            /                |
           v                             v           /                 v
                                                    /
 READS 2                        READS 3            v           READS 5
 MEMORY WORDS                   MEMORY WORDS                   MEMORY WORDS
+-----+-----+                  +-----+-----+-----+            +-----+-----+-----+-----+-----+
|  IP |  CS |                  |    EIP    |  CS |            |          RIP          |  CS |
+-----+-----+                  +-----+-----+-----+            +-----+-----+-----+-----+-----+
   |                                 |                                    |
   | load CS                         | load CS                            | load CS
   | load RIP                        | load RIP                           | load RIP
   v                                 v                                    v

+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
| 000000000000000       |      | 000000000             |      |                       | RIP
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
 JMP to <64K                    JMP to <4GB                    JMP to full 64-bit
 virtual address                virtual address                virtual address

The jump mechanism is the same as for far jmp based on operand size – so this is a little tricky on AMD:

REX prefix is partly ignored: REX CALL FAR pushes 64-bit CS and RIP, but, similar to REX JMP FAR instruction, the offset read is not qword but dword (default). Eg. 48FF1C25[6D010000] call far [mem] with EIPCS works. The 64-bit callee, located at <4GB virtual address, should far return by retfq the stack frame is 64-bit.

Note that far calls are not really meant to be executed as direct control transfers; that's what indirect call gates are for. They hold full 64-bit addresses and work the same on both AMD and Intel.

RET FAR

Both Intel and AMD can pop RIP from stack in all operand sizes – so there is no hardware difference here.

64-bit mode RETF with 64-bit stack view and how RETF loads RIP:


                                                          63                      0
                                                          +-----+-----+-----+-----+
63                      0    63                      0    |                 |  CS | +8
+-----+-----+-----+-----+    +-----+-----+-----+-----+    +-----+-----+-----+-----+
|     |     |  CS |  IP |    |     |  CS |    EIP    |    |          RIP          | <- rsp
+-----+-----+-----+-----+    +-----+-----+-----+-----+    +-----+-----+-----+-----+
     rsp = rsp + 4                rsp = rsp + 8                rsp = rsp + 16

      retfw                       retfd*                        retfq
      66 CB                       CB                            48 CB

      pop word RIP                pop dword RIP                 pop qword RIP
      pop word CS                 pop dword CS                  pop qword CS

                     |                         |                      |
                     v                         v                      v

+-----+-----+-----+-----+    +-----+-----+-----+-----+    +-----+-----+-----+-----+
| 000000000000000       |    | 000000000             |    |                       | RIP
+-----+-----+-----+-----+    +-----+-----+-----+-----+    +-----+-----+-----+-----+
 Returns to <64K              Returns to <4GB              Returns to any 64-bit
 virtual address              virtual address              virtual address


* In 64-bit the default operand size is 32-bit for RETF

IRET operation in 64-bit mode

Interestingly, iret in 64-bit mode can pull up parameters in all operand sizes (don't know why, the hardware makes 64-bit stack frames only when LME=1).

SS:SP/ESP/RSP is unconditionally pop-ed by all iret in 64-bit mode (but NOT when legacy 16/32-code is running).

These were all tested by manually creating the following 16/32/64-bit stack frames, and successfully returned from 64-bit code (see GitHub):

64-bit stack view for IRET and how IRET loads RIP, RSP and RFLAGS:

                                                              63                      0
                                                              +-----+-----+-----+-----+
                                                              |                    SS | +32
                                                              +-----+-----+-----+-----+
                               63                      0      |       RETURN RSP      | +24
                               +-----+-----+-----+-----+      +-----+-----+-----+-----+
63                      0      |           |        SS |      |         RFLAGS        | +16
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
|                 |  SS |      |    ESP    |  EFLAGS   |      |                    CS | +8
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
|  SP |FLAGS|  CS |  IP |      |        CS |    EIP    |      |          RIP          | <-- rsp
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+

          iretw                          iret*                          iretq
          66 CF                          CF                             48 CF
      pops 5 words                   pops 5 dwords                  pops 5 qwords

                     |                           |                        |
                     v                           v                        v

+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
| 000000000000000       |      | 000000000             |      |                       | RIP
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
 Returns to <64K                Returns to <4GB                Returns to any 64-bit
 virtual address                virtual address                virtual address

                     |                           |                        |
                     v                           v                        v

+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
| ...............  FLAGS|      | .........    EFLAGS   |      |                       | RFLAGS
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
 Bits 15..0 restored            Bits 31..0 restored            Bits 63..0 restored

                     |                           |                        |
                     v                           v                        v

+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
| 000000000000000       |      | 000000000             |      |                       | RSP
+-----+-----+-----+-----+      +-----+-----+-----+-----+      +-----+-----+-----+-----+
 Stack pointer to <64K          Stack pointer to <4GB          Stack pointer to any
 virtual address                virtual address                64-bit virtual address


* In 64-bit the default operand size is 32-bit for IRET

This was investigated for any software stack-switch for bare metal programming.

Interrupt-stack in 64-bit mode

Because PE must be set in 64-bit mode, only the IDT calling mechanism works and any other gate than a 64-bit interrupt gate or a 64-bit trap gate causes general-protection exception (#GP).

Interrupt-stack frame creation in 64-bit mode

When LME=1, hardware makes 64-bit stack frames for all exceptions and interrupts, including INT N, regardless of 16/32/64-bit code running (when LMA=1).

Eg. INT N: any 66h, REX prefix ignored - tried.

This also means that all interrupt handlers must be 64-bit code – and return by iretq (there is no way to encode iretq in 16/32-bit code).

In long mode, the return-program stack pointer (SS:RSP) is also pushed unconditionally for any 16/32/64-bit code transfer.

           63                      0
           |                       |
           +-----+-----+-----+-----+
           |                       | <-- rsp before <------------+
           +-----+-----+-----+-----+                             |
           |    possibly align     | +40                         |
           +-----+-----+-----+-----+                             |
           |                    SS | +32                         |
           +-----+-----+-----+-----+                             |
           |       RETURN RSP      | +24  ------>----------------+
           +-----+-----+-----+-----+                 points here
           |         RFLAGS        | +16
           +-----+-----+-----+-----+
           |                    CS | +8
           +-----+-----+-----+-----+
           |          RIP          | <-- rsp after: in interrupt handler
           +-----+-----+-----+-----+

64-bit Interrupt-stack frame detailed operation:

1. save current, possibly un-aligned rsp (50% chance)
2. align new rsp by masking with FFFF_FFFF_FFFF_FFF0h (= dummy qword push) to 16-byte boundary
3. start pushing qwords..

4. Make the far jmp.

64-bit qword REX.W IRET:

Fetch next instruction from RIP.

RETURN RSP

The 64-bit interrupt-stack frame is double-qword-aligned. The CPU therefore saves the original RSP before aligning the stack pointer, then push. The 64-bit stack frame is 40- or 48 bytes. On iret RSP is pop-ed and stack is restored.

RETURN SS

Although SS means almost nothing in 64-bit mode, hw will check the correctness of the descriptor beyond the P-bit (tested). Not on push but on iret: PE=1 and the cpu will attempt to fetch a valid descriptor in the legacy sense, when the pop-ed SS is non-null – to possibly return to 16/32-bit legacy code as well.

But 64-bit allows to pop null-selector on iret, i.e. zero value to load into SS. I used this for some bare metal tests with all CPL=0 (GitHub). See details in AMD/Intel docs.

NEAR in 64-bit mode

NEAR RET

For RET, default operand size is 64-bit and nothing can change that (66h, REX ignored - tried)

CPU pops a qword and replaces RIP.

How RET loads RIP

          63                      0
          +-----+-----+-----+-----+
          |          RIP          | <- RSP
          +-----+-----+-----+-----+
                     ret

                 pop qword RIP
                      |
                      v
          +-----+-----+-----+-----+
          |                       | RIP
          +-----+-----+-----+-----+

Any prefix ignored. Makes sense: near calls are only between the same 64-bit code.
Far calls could mean between 16/32/64-bit code and all types of RETF supported.

NEAR JMP/CALL rel32

For E9 JMP, operand size is 32-bit signed value and nothing can change that (66h, REX ignored - tried). EA target calculation is similar to RIP-relative:

  63                  31                  0
  +----+----+----+----+----+----+----+----+
  | sssssssssssss <-- |s                  | sign-extended DWORD
  +----+----+----+----+----+----+----+----+
  |            RIP of next instr          |
  +----+----+----+----+----+----+----+----+
_______________________________________________________________________
  63                                      0
  +----+----+----+----+----+----+----+----+
  |                                       |
  +----+----+----+----+----+----+----+----+
                     RIP

jmp  rel32
call rel32

NEAR JMP/CALL register indirect

For [FF] [R 2/4 M] register indirect default operand size is 64-bit, register replaces RIP and nothing can change that (66h, REX ignored - tried).


+------+------+------+------+        +------+------+------+------+
|                           |  <---  |            RIP            |
+------+------+------+------+        +------+------+------+------+
             RIP                                register

jmp  [reg]
call [reg]

NEAR JMP/CALL memory indirect

For [FF] [R 2/4 M] default operand size is 64-bit, cpu reads a qword from memory and replaces RIP. Nothing can change that (66h, REX ignored - tried).

                                     4 WORDS: m64
+------+------+------+------+        +------+------+------+------+
|                           |  <---  |            RIP            |
+------+------+------+------+        +------+------+------+------+
             RIP

jmp  [mem]
call [mem]