----------------
title : x86-64 명령어 레퍼런스 읽는 법
path : /X86-64 명령어 레퍼런스
publish_date : 2020-09-09
----------------

```lec-info
본 내용은 [Intel® 64 and IA-32 Architectures Software Developer’s Manual](https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.html) 의 제 2 권에 내용에서 가져왔으며, 이 글을 작성하는데 사용한 문서는 2020년 9월 현재 가장 최신 버전인 2016년 9월 버전 입니다.
```

인텔 64 비트 아키텍쳐의 명령어의 경우 아래와 같은 꼴을 가지고 있습니다.

![](/img/inst/op.png)

간단히 살펴보자면, 명령어 맨 앞에 임의의 순서로 위치할 수 있는 명령어 접두사(instruction prefix)들이 있고 그 다음에 최대 3 바이트를 차지하는 실제 명령어 (Opcode) 가 옵니다. 그 뒤로, 필요에 따라 어떠한 레지스터와 메모리 주소값을 사용할지 알려주는 ModR/M 과 SIB(Scale-Index-Base) 부분이 각각 1 바이트 씩 차지할 수 있고, 그 뒤로 주소 변위값 (Displacement) 과 명령어 실행 시 필요한 데이터가 위치한 부분이 1, 2, 4 바이트 중 하나를 차지할 수 있습니다. 

여기서 반드시 있어야 하는 부분은 실제 명령어 부분 (Opcode) 이고, 나머지 부분들은 필요에 따라 있을 수도, 없을 수 도 있습니다.


### 명령어 접두사 종류

명령어 접두사들은 크게 4 가지 그룹으로 구분할 수 있고, 명령어 구성 시 각 그룹에 속해 있는 접두사 하나씩만을 사용해야 합니다. 또한 각기 다른 그룹에 속한 접두사들을 명령어 앞에 임의의 순서로 배치해도 상관 없습니다.

1. 첫 번째 그룹: Lock 과 반복 접두사. 
   * `LOCK` 의 경우 0xF0 으로 인코딩 됩니다.
   * `REPNE`/`REPNZ` 접두사의 경우 0xF2 로 인코딩 됩니다. 참고로 `REPNZ`(Repeat Not Zero) 접두사의 경우 오직 문자열과 I/O 명령에만 사용할 수 있습니다.
   * `REP` 와 `REPE`/`REPZ` 는 0xF3 으로 인코딩 됩니다. 이들도 마찬가지로 문자열과 I/O 명령에만 사용할 수 있습니다. 참고로 0xF3 은 `POPCNT`, `LZCNT`, `ADOX` 명령어들의 필수 접두사 입니다.
2. 두 번째 그룹: Segment 재정의 접두사와 분기 힌트
   * 0x2E : CS 세그먼트 재정의
   * 0x36 : SS 세그먼트 재정의
   * 0x3E : DS 세그먼트 재정의
   * 0x26 : ES 세그먼트 재정의
   * 0x64 : FS 세그먼트 재정의
   * 0x65 : GS 세그먼트 재정의
   * 0x2E : 분기를 택하지 않는다 (`Jcc` 계열 명령어들에만 사용)  
   * 0x3E : 분기를 택한다 (마찬가지로 `Jcc` 명령에들에만 사용) 
3. 세 번재 그룹: 피연산자(Operand) 크기 재정의 (0x66)
4. 네 번재 그룹: 주소 크기 재정의 (0x67) 

`LOCK` 접두사의 경우 멀티 프로세서 환경에서, 공유되는 메모리에 독점적인 접근을 강제하는 접두사 입니다.

0xF2 와 0xF3 과 같은 반복 접두사의 경우, 문자열의 각 원소에 대해서 명령어를 반복적으로 수행하라는 의미가 됩니다. 반복 접두사의 경우 문자열과 I/O 명령 (`MOVS`, `CMPS`, `SCAS`, `LODS`, `STOS`, `INS`, `OUTS`) 에만 사용할 수 있습니다. 

분기 힌트 (0x2E, 0x3E) 접두사의 경우, CPU 에게 어떠한 분기를 택할 가능성이 높을지 알려주는 접두사 이며, 오직 조건 분기 명령어 (`Jcc`) 에만 사용해야 합니다. 그 외에 명령어들에 분기 힌트 접두사를 사용한다면 의도하지 않은 결과를 나타낼 수 있습니다.

### 실제 명령어(Opcode)

실제로 어떤 명령을 수행할 지 그 정보를 담고 있는 부분을 Opcode 라고 합니다. Opcode 의 경우 크기가 1, 2, 3 바이트가 될 수 있으며, 필요에 따라 3 비트 정도 더 ModR/M 영역을 침범해서 사용할 수 도 있습니다. 

Opcode 의 크기가 2 바이트 이상일 경우 보통 Opcode 의 첫 번째 바이트에는 Escape 코드 (0x0F) 가 오고 추가적으로 맨 앞에 특정 접두사 (0x66, 0xF2, 0xF3 중 하나) 가 올 수 있습니다. 예를 들어서 CVTDQ2PD 의 경우 0xF3 0x0F 0xE6 으로 인코딩 됩니다. 

### ModR/M 과 SIB

대부분의 명령어들은 레지스터나 메모리에 위치해 있는 데이터를 참조하게 되는데, ModR/M 을 통해서 명령어의 레지스터나 메모리의 주소값을 어떤 식으로 표현할지 정할 수 있습니다.

ModR/M 에는 아래와 같은 필드들이 있습니다.

* mod 부분과 r/m 부분을 합쳐서 총 32 가지의 가능한 값들을 조합할 수 있는데 이 들은 각각 8 개의 레지스터들과 24개의 주소값 연산 방식에 대응됩니다.
* reg/opcode 필드를 통해 레지스터 번호를 나타내던지 아니면 앞서 말했듯이 Opcode 를 위해 제공하는 3 비트의 역할을 수행할 수 도 있습니다.
* r/m 필드의 경우 피연산자로 사용할 레지스터를 지칭하거나, mod 필드와 같이 사용할 경우 주소 접근 방식을 나타낼 수 있습니다. 

참고로 ModR/M 에서 제공하는 주소값 표현 방식 중에선 때론 ModR/M 만으로는 부족한 경우가 있는데 이를 위해 필요한 추가적인 정보는 SIB 필드를 통해 전달됩니다. SIB 필드에는 다음과 같은 정보들이 들어갑니다. 

* scale : 얼마나 곱할 것인지 (scale factor)
* index : 인덱스 레지스터의 번호
* base : 베이스 레지스터의 번호 

#### ModR/M 과 SIB 를 통한 주소값 표현 형태

ModR/M 과 SIB 의 값들이 어떠한 주소값 표현 형태에 대응되는지 아래 3 개의 표를 보면알 수 있습니다. 

먼저 표 2-1 과 2-2 에는 mod 과 r/m 필드의 값에 따라서 어떠한 방식으로 실제 메모리 주소값을 접근할 것인지에 대해서 설명해놓고 있습니다. 예를 들어서 ModR/M 의 값이 0x7D 이라고 가정해봅시다. 표 2-2 를 참조한다면, Effective Address 로 `[EBP] + disp8` 과 레지스터로 DI, EDI, MM7, XMM7 중 하나를 택할 수 있음을 알 수 있습니다. disp8 은 앞서 말한 주소값 변위로, 명령어의 맨 마지막 부분에 인코딩 됩니다.

예를 들어서 다음과 같은 명령어를 생각해봅시다. 해당 명령어는 주소값 `ebp - 0x14` 에 위치한 데이터를 edi 레지스터로 복사합니다. 

```asm
mov    DWORD PTR [ebp-0x14],edi
```

`mov` 에 대응되는 명령어가 0x89 이라고 하면, 위 명령어는 0x89 0x7D 0xEC 로 인코딩 됨을 알 수 있습니다. 왜냐하면 0x7D 가 `[EBP] + disp8` 와 EDI 를 참조하는 ModR/M 이기 때문이죠. 그리고 마지막에 disp8 에 해당하는 -0x14 가 2 의 보수 표현법을 통해 변환되어서 0xEC 로 들어갑니다.

그렇다면 아래 명령어는 어떨까요?

```asm
mov    ebp, esp
```

위 명령어의 경우 둘 다 레지스터 이므로 테이블의 마지막 열에 해당하는 부분입니다. 위 경우 *Effective Adress* 에 해당하는 부분에 EBP 가 오고 레지스터에 ESP 가 오므로 테이블 2-2 에 따르면 0xE5 로 ModR/M 이 인코딩 됨을 알 수 있죠.

![](/img/inst/table2-1.png)

![](/img/inst/table2-2.png)

표 2-2 를 잘 보면 `[--][--]` 로 표현된 부분이 있습니다 (Mod 00 과 10 부분에서). 이 들의 경우 SIB 를 참조하라는 의미가 되겠습니다. 예를 들어서 아래와 같은 명령어를 살펴봅시다.

```asm
mov    eax,DWORD PTR [ebp+eax*4-0x30]
```

표 2-3 을 보면 EAX 에 4 를 곱하는 작업에 대응 되는 것이 SS 가 10 에 Index 가 000 임을 알 수 있죠. 또한, 해당 결과를 다시 EBP 에 더하는데, 2-3 표 밑에 Note 를 읽어보면, 해당 상황이 레지스터가 `[*]` 이고 Mod 가 01 이 되어야 함을 알 수 있습니다. 

또한 그 결과를 EAX 에 대입하므로, 결과적으로 ModR/M 은 0x44 이고 SIB 는 0x85 가 되어야겠죠. 마찬가지로 disp8 에 해당하는 부분은 명령에 맨 끝에 붙어 오는데 이 경우 -0x30 의 보수 표현인 0xD0 가 됩니다.

결과적으로 위 명령어는 8b 44 85 D0 로 인코딩 됩니다.

![](/img/inst/table2-3.png)

### IA-32e 모드에서의 명령어

IA-32e 모드에는 두 가지 모드들이 있는데

* 호환 모드 (Compatibility Mode) : 64 비트 운영체제가 옛날의 보호 모드 시절의 프로그램들을 실행시킬 수 있게 한다.
* 64 비트 모드 : 64 비트 운영체제가 64 비트 메모리 공간을 사용하도록 작성된 프로그램을 실행시킬 수 있게 한다.

#### REX 접두사 

64 비트 모드에서의 명령어들의 경우 REX 접두사로 구분됩니다. REX 접두사의 역할로
* GPR 과 SSE 레지스터들을 나타냄
* 64 비트 피연산자의 크기를 나타냄
* Extended control 레지스터를 나타냄

를 들 수 있습니다.

참고로 모든 명령어들이 64 비트 모드에서 실행시키기 위해 REX 접두사를 사용해야 하는 것은 아닙니다. REX 접두사가 필요한 경우는 명령어에서 64 비트 레지스터들을 참조하던지, 피연산자의 크기가 64 비트 이던지 등의 경우 밖에 없습니다. 참고로 불필요하게 추가된 REX 접두사는 그냥 무시됩니다.

REX 접두사의 경우 명령어 Opcode 바로 **앞**에 위치해야 합니다. 이전에 말했듯이 일부 Opcode 들의 경우 필수적으로 와야 하는 접두사들이 있었는데, 이들 역시 REX 접두사 바로 앞에 와야 합니다. 예를 들어서 CVTDQ2PD 명령어의 경우 REX 접두사가 F3 과 0F E6 사이에 와야겠죠.

![](/img/inst/fig2-3.png)


### Opcode 인코딩 방식

#### VEX 접두사가 붙지 않는 명령어들의 경우

* **REX.W** : `REX` 접두사의 사용 유무에 따라서 operand 의 크기나 명령어의 구조가 바뀔 수 있다. 자세한 내용은 `REX` 에 관한 설명 페이지를 참조
* **/digit** : ModR/M 의 r/m 필드만을 나타낸다 (레지스터)
* **/r** : ModR/M 값
* **cb, cw, cd, cp, co, ct** : 1 (cb), 2 (cw), 4 (cd), 6 (cp), 8 (co), 10 (ct) 바이트 값들로, 코드 세그먼트 레지스터의 값으로 사용되던지, 코드의 offset 으로 사용된다. 
* **ib, iw, id, io** : 1 (ib), 2 (iw), 4 (id) 8 (io) 바이트 짜리 명시적 데이터(immediate operand)로 사용된다. 여기서 명시적 데이터란, 명령어 안에 박혀 있는 상수값들 (예를 들어서 `add eax, 123` 에서의 123) 들을 의미합니다. 명시적 데이터를 부호 있는 정수로 해석할지 아니면 부호 없는 정수로 해석할지의 여부는 opcode 를 통해 결정되며, 데이터의 경우 *하위 바이트* 가 먼저 배치됩니다. (리틀 엔디언)
* **+rb, +rw, +rd, +ro** : Opcode 의 하위 3 비트를 통해서 레지스터 피연산자를 지칭하는데 사용합니다. 이 경우 ModR/M 필드는 사용되지 않습니다. 64 비트가 아닐 경우 레지스터가 8 개 이므로. 3 비트를 통해서 레지스터들을 충분히 표현할 수 있지만, 64 비트 모드의 경우 레지스터의 개수가 16 개 이므로, Opcode 하위 3 비트와 함께, REX 접두사의 b 필드 1 비트까지 총 4 비트를 이용해서 어떤 레지스터를 택할지 표현합니다. 
* **+i** : FPU 레지스터들 중에서 어떤 `ST(i)` 를 취할 지 나타낸다. 

#### VEX 접두사가 붙는 명령어들의 경우

벡터 관련 명령어들의 경우 VEX 접두사가 붙게 됩니다. VEX 접두사는 아래와 같이 2 바이트 혹은 3 바이트 형태로 인코딩 되어 있습니다.

![caption=VEX 접두사 인코딩 방식들](/img/inst/vex.png)

각각의 부분에 대해 간단하게 살펴보자면;

* `VEX.L` : 벡터의 크기가 128 비트(L = 0)인지, 256 비트(L = 1)인지를 명시합니다.
* `VEX.R`, `VEX.X`, `VEX.B`: 각각 대응되는 REX 접두사 필드들의 반전 버전. 예를 들어서 `REX.R` 이 0 이라면 `VEX.R` 은 1 이고, `REX.X` 가 1 이라면 `VEX.X` 는 0 이다.
* `VEX.mmmmm` : 값에 따라서 Opcode 앞에 특정 바이트들이 있는 듯한 효과를 낼 수 있다.
  * 00001 : Opcode 앞에 0x0F 가 있다고 간주된다.
  * 00010 : Opcode 앞에 0x0F 0x38 가 있다고 간주된다.
  * 00011 : Opcode 앞에 0x0F 0x3A 가 있다고 간주된다.
  * 그 외의 값들은 사용되지 않고 설정시 #UD 가 발생할 수 있다.
* `VEX.pp` : 앞선 mmmmm 필드와 유사한데, 특정 SIMD 접두사가 붙어있는듯한 효과를 낼 수 있다.
  * 00 : 없음
  * 01 : 0x66
  * 10 : 0xF3
  * 11 : 0xF2
* `VEX.W` : 설정되어 있을 경우 피연산자 크기가 64 비트인 것으로 해석한다.

본 x86-64 명령어 레퍼런스는 아래와 같은 형태로 VEX 접두사를 사용하고 있는 명령어들의 인코딩 방식을 서술하고 있습니다.

`VEX.[NDS/NDD/DDS].[128,256].[66,F2,F3].0F/0F3A/0F38.[W0,W1,WIG] opcode [/r] [/ib,/is4]`

위 형태에서 `[ ]` 안에 들어오는 것들은 이들 중 하나가 옵션으로 올 수 있다는 의미 입니다. 예를 들어서 `VPADDUSB` 명령어의 경우

`VEX.NDS.256.66.0F.WIG DC /r`

와 같이 인코딩 될 수 있습니다. 

* `VEX` : VEX 접두사가 필요하다는 것을 의미 합니다. VEX 접두사는 3 바이트 형태 (첫 번째 바이트가 0xC4) 혹은 2 바이트 형태 (첫 번째 바이트가 0xC5) 가 가능합니다. 2 바이트 형태의 경우 `VEX.mmmmm`, `VEX.W`, `VEX.X`, `VEX.B` 필드를 인코딩 할 필요가 없는 명령어들에만 사용할 수 있습니다.
  * `NDS, NDD, DDS` : `VEX.vvvv` 필드가 피연산자 레지스터를 인코딩 하고 있음을 의미합니다. 
     * `VEX.NDS` : `VEX.vvvv` 가 첫 번째 소스 레지스터(source register) 를 인코딩 하고 있습니다. 참고로 소스 레지스터의 내용은 변경되지 않습니다.
     * `VEX.NDD` : `VEX.vvvv` 가 ModR/M 의 reg 필드를 통해 인코딩 되지 않는 목적 레지스터(destination register) 를 인코딩 하고 있습니다.
     * `VEX.DDS` : 3 개의 인자를 받는 명령어에서 `VEX.vvvv` 가 두 번째 소스 레지스터를 인코딩 하고 있습니다. 이 때 첫 번째 소스 레지스터의 값은 연산 결과로 덮어 씌어집니다.
     * 만일 `NDS`, `NDD`, `DDS` 가 모두 아니라면 `VEX.vvvv` 는 반드시 0b1111 이어야만 합니다. 
  * `128,256` : 명령어 인코딩 설명에서 `VEX.256` 이나 `VEX.128` 이 써 있다면 아래와 같이 해석해야 합니다.
    * `VEX.256` 이 명시되어 있을 경우 `VEX.L` 필드는 반드시 1 이 되어야 합니다. 만일 해당 명령어의 `VEX.L = 0` 으로 할 경우 (1) `VEX.128` 버전이 정의되었을 경우 프로세서가 `VEX.128`버전을 따르던지 (2) `VEX.128` 이 정의되어 있지 않다면 #UD 예외가 발생합니다.
    * `VEX.128` 이 명시되어 있지만 `VEX.256` 버전이 정의되어 있지 않다면 반드시 `VEX.L` 을 0 으로 인코딩 해야 하며, 그렇지 않을 경우 프로세서에서 #UD 예외가 발생합니다.
    * `VEX.LIG` 가 명시되어 있을 경우 `VEX.L` 은 무시됩니다. 대부분의 VEX 인코딩 된 SIMD 부동 소수점 명령들이 여기에 해당됩니다.  
    * `VEX.LZ` 가 명시되어 있을 경우 `VEX.L` 은 반드시 0 이어야 하고 그렇지 않을 경우 #UD 예외가 발생합니다. 
  * `66,F2,F3` : 명시되어 있을 경우 대응되는 `VEX.pp` 의 값을 설정합니다. 예를 들어서 0x66 의 경우 `pp` 의 값은 `00` 입니다.
  * `0F,0F3A,0F38` : 명시되어 있을 경우 대응되는 값이 `VEX.mmmmm` 에 설정됩니다. 예를 들어서 0x0F 의 경우 `mmmmm` 은 0b00001 입니다.
  * `W0` : `VEX.W=0`
  * `W1` : `VEX.W=1`
  * `WIG`: `VEX.mmmmm` 이 필요 없는 경우 2 바이트 VEX 접두사 형태를 사용할 수 있습니다. 3 바이트 접두사를 사용한다면 `VEX.W` 필드 값이 무시됩니다. 
* `opcode` : 명령어 Opcode.
* `/is4` : 소스 레지스터를 나타냅니다. 64 비트 모드에서는 상위 4 비트 (`imm8[7:4]`)를, 32 비트 모드에서는 3 비트 `imm8[6:4]` 만을 사용해서 어떠한 레지스터를 사용하는지 나타냅니다. 참고로 하위 4 비트에는 명령어 특이적인 정보를 인코딩 하고 있을 수 있습니다.

#### EVEX 접두사가 붙는 경우

EVEX 접두사의 경우 4 바이트 형태로 인코딩 되는데, VEX 와 상당히 유사합니다. 다만, 벡터 크기로 최대 512 비트 까지 지정할 수 있습니다. 


### 명령어 설명을 읽는 방법

명령어 레퍼런스의 *Instruction* 부분을 보면 명령어 문법이 나와 있습니다. 예를 들어서 `MOV` 를 보자면

![caption=MOV 명령어 설명 예시](/img/inst/mov.png)

와 같이 나와 있는데, 여기서 `MOV r/m32,r32` 에 해당 하는 부분을 어떻게 해석할지 알려드리겠습니다.

* **rel8** : 현재 명령어로 부터 최대 128 바이트 이전 혹은 127 바이트 이후 까지의 주소.
* **rel16, rel32** : 현재 명령어가 포함되어 있는 코드 세그먼트 안의 상대 주소값. `rel16` 은 피연사자 크기가 16 비트인 명령어들에, `rel32` 는 피연산자 크기가 32 비트인 명령어들에 적용된다. 
* **ptr16:16, ptr16:32** : *far pointer* 로 보통 현재 명령어의 코드 세그먼트와 다른 세그먼트를 의미한다. : 왼쪽에 있는 값이 코드 세그먼트 레지스터 값을 지칭하고, 오른쪽에 있는 값은 해당 세그먼트에서의 오프셋을 의미한다. 위의 `rel16` 과 `rel32` 처럼 `ptr16:16` 의 경우 명령어 피연산자 크기가 16 비트인 곳에서 사용하며, `ptr16:32` 의 경우 32 비트 피연산자를 가지는 명령어에서 쓰인다. 
* **r8** : 1 바이트 짜리 범용 레지스터를 의미 (`AL`, `CL`, `DL`, `BL`, `AH`, `CH`, `DH`, `BH`, `BPL`, `SPL`, `DIL`, `SIL`). 64 비트 모드에 경우 추가적으로 `R8L` 부터 `R16L` 까지 가능.
* **r16** : 2 바이트 짜리 범용 레지스터를 의미 (`AX`, `CX`, `DX`, `BX`, `SP`, `BP`, `SI`, `DI`). 64 비트 모드의 경우 추가적으로 R8 부터 R15 까지 가능.
* **r32** : 4 바이트 짜리 범용 레지스터를 의미 (`EAX`, `ECX`, `EDX`, `EBX`, `ESP`, `EBP`, `ESI`, `EDI`). 64 비트 모드의 경우 추가적으로 R8D 부터 R15D 까지 가능.
* **r64** : 8 바이트 짜리 범용 레지스터를 의미 (`RAX`, `RBX`, `RCX`, `RDX`, `RDI`, `RSI`, `RBP`, `RSP`, `R8`–`R15`). 이들은 64비트 모드에서만 사용 가능하다.
* **imm8** : 1 바이트 짜리 명시적 데이터 (immediate value). `imm8` 의 경우 부호 있는 정수를 의미하며, -128 부터 127 까지의 값을 표현할 수 있다. 참고로 만일 `imm8` 이 1 바이트 보다 큰 피연산자를 가지는 명령어와 사용될 경우, 명시적 데이터는 부호를 유지한채 해당 크기로 확장이 된다. (쉽게 말해 0b11000000 은 0b11111111 11000000 로 확장되며 0b00110000 은 0b00000000 00110000 으로 확장된다.)
* **imm16** : 2 바이트 짜리 명시적 데이터. -32,768 부터 32,767 까지의 정수를 표현한다. 
* **imm32** : 4 바이트 짜리 명시적 데이터. -2,147,483,648 부터 2,147,483,647 까지의 정수를 표현한다. 
* **imm64** : 8 바이트 짜리 명시적 데이터. -9,223,372,036,854,775,808 부터 9,223,372,036,854,775,807 까지의 정수를 표현한다. 
* **r/m8** : 1 바이트 짜리 피연산자로, 1 바이트 범용 레지스터나 (`r8` 의 레지스터들), 1 바이트 메모리 데이터를 의미한다. 
* **r/m16** : 2 바이트 짜리 피연산자로, 2 바이트 범용 레지스터나 (`r16` 의 레지스터들), 2 바이트 메모리 데이터를 의미한다. 
* **r/m32** : 4 바이트 짜리 피연산자로, 4 바이트 범용 레지스터나 (`r32` 의 레지스터들), 4 바이트 메모리 데이터를 의미한다.
* **r/m64** : 8 바이트 짜리 피연산자로, 8 바이트 범용 레지스터나 (`r64` 의 레지스터들), 8 바이트 메모리 데이터를 의미한다.
* **m** : 16- 혹은 32- 혹은 64 비트 짜리 메모리 데이터를 의미한다.
* **m8** : DS:(E)SI, ES:(E)DI 로 표현되는 1 바이트 짜리 메모리를 나타낸다. 주로 배열의 이름을 나타내는데 사용된다. 64 비트의 경우 RSI 또는 RDI 레지스터로 표현된다. 
* **m16** : DS:(E)SI, ES:(E)DI 로 표현되는 2 바이트 짜리 메모리를 나타낸다. 주로 문자열 연산에서 사용된다. 64 비트의 경우 RSI 또는 RDI 레지스터로 표현된다. 
* m32 : DS:(E)SI, ES:(E)DI 로 표현되는 4 바이트 짜리 메모리를 나타낸다. 주로 문자열 연산에서 사용된다. 64 비트의 경우 RSI 또는 RDI 레지스터로 표현된다.
* **m64** : 메모리 상의 8 바이트 데이터를 표현한다.
* **m128** : 메모리 상의 16 바이트 데이터를 표현한다.
* **m16:16, m16:32 & m16:64** : Far pointer 로 표현된 메모리 데이터로, : 왼쪽에 있는 값은 포인터의 세그먼트 셀렉터를, 오른쪽 값은 해당 세그먼트 안의 오프셋을 의미한다.
* **m16&32, m16&16, m32&32, m16&64** : 메모리 데이터 쌍을 나타내며, & 왼쪽과 오른쪽에 메모리 데이터의 크기를 나타낸다. 예를 들어서 m16&32 는 16 비트 와 32비트 메모리 데이터 쌍을 의미한다. m16&16 과 m32&32 의 경우 `BOUND` 명령어에서 사용되는데, 배열 인덱스의 상한과 하한을 나타내기 위해서 사용된다. m16&32 와 m16&64 의 경우 `LIDT` 와 `LGDT` 명령어에서 사용되는데, 등록할 `GDTR` 과 `IDTR` 레지스터의 주소와 limit 필드 값을 로드하기 위해 사용된다.
* **moffs8, moffs16, moffs32, moffs64** : `MOV` 명령어에서만 사용되는데, 현재 세그먼트 베이스로 부터의 오프셋을 표현한다. 이 때 해당 명령어의 경우 ModR/M 은 사용되지 않는다.
* **Sreg** : 세그먼트 레지스터
* **m32fp, m64fp, m80fp** : 메모리 상에 위치한 단일 정밀도(float), 배정밀도(double), 확장 배정밀도 (long double) 부동 소수점 데이터를 의미한다. 이 들은 x87 FPU 부동 소수점 명령어에서 사용된다. 
* **m16int, m32int, m64int** : 메모리 상에 위치한 2 바이트, 4 바이트, 8 바이트 정수 데이터로, 위와 마찬가지로 x87 FPU 부동 소수점 명령어에서 사용된다. 
* **ST or ST(0)** : FPU 레지스터 스택의 가장 최상단에 위치한 원소.
* **ST(i)** : FPU 레지스터 스택에서 최상단으로 부터 `i` 번째 원소로 `i` 의 값으로 0 부터 7 까지가 가능하다.
* **mm** : 64 비트 MMX 레지스터로 `MM0` 부터 `MM7` 까지 가능하다. 
* **mm/m32** : MMX 레지스터의 하위 32 비트 혹은 32 비트 메모리 데이터.
* **mm/m64** : MMX 레지스터 혹은 64 비트 메모리 데이터.
* **xmm** : 128 비트 XMM 레지스터로 `XMM0` 부터 `XMM7` 까지 있다. 64 비트 모드에서는 `XMM8` 부터 `XMM15` 까지 추가적으로 사용할 수 있다.
* **xmm/m32**: 128 비트 XMM 레지스터 혹은 32 비트 메모리 데이터.
* **xmm/m64** : 128 비트 XMM 레지스터 혹은 64 비트 메모리 데이터.
* **xmm/m128** : 128 비트 XMM 레지스터 혹은 128 비트 메모리 데이터.
* **<XMM0>**: XMM0 레지스터가 사용됨을 의미.
* **ymm** : 256 비트 YMM 레지스터로 `YMM0` 부터 `YMM7` 까지 있다. 64 비트 모드에서는 `YMM8` 부터 `YMM15` 까지 추가적으로 사용할 수 있다.
* **m256** : 32 바이트 메모리 데이터. 이는 AVX 명령어들에만 사용된다.
* **ymm/m256** : YMM 레지스터나 256 비트 메모리 데이터.
* **<YMM0>**: YMM0 레지스터가 암묵적으로 사용되었음을 의미.
* **bnd** : 128 비트 바운드 레지스터. BND0 부터 BND3 까지 가능하다. 
* **mib** : SIB 주소 표현 형태를 사용하는 메모리 데이터로, 인덱스 레지스터가 주소값 계산에 사용되지 않으며 scale 역시 무시된다. 오직 베이스 와 변위 값만 유효 주소값을 계산하는데 사용된다. 
* **m512** : 64 바이트 메모리 데이터
* **zmm/m512** : ZMM 레지스터나 512 비트 메모리 데이터.
* **{k1}{z}** : 쓰기 마스크 (write mask) 레지스터로 64 비트 k 레지스터로 k1 부터 k7 까지 사용 가능하다. 쓰기 마스크는 오직 EVEX 접두사가 있을 경우에만 사용 가능하다. 마스크 방식으로 마스크 되지 않는 부분의 값을 유지하는 병합 마스크 (merge mask) 가 있고, 해당 부분을 아예 0 으로 지워버리는 zeroing mask 방식이 있다. 어떤 식으로 마스크를 할 지는 EVEX.z 필드의 비트값에 따라 달라진다.
* **{k1}** : 위와는 다르게 `{z}` 가 없다. 이 경우 병합 마스크 방식만 지원하고 zeroing mask 는 지원하지 않는다.
* **k1** : 마스크 레지스터를 지칭 (k0 부터 k7 까지 가능)
* **mV** : 벡터 메모리 데이터를 나타내며, 해당 데이터의 크기는 명령어에 따라 달라진다.
* **vm32{x,y,z}** : VSIB 메모리 주소값 방식을 사용한 벡터 메모리 데이터 배열들. 메모리 주소 배열은 베이스 레지스터, scale, 벡터 인덱스 레지스터와 XMM (vm32x), YMM (vm32y), ZMM (vm32z) 레지스터들에 들어 있는 32 비트 주소값을 통해 결정된다.
* **vm64{x,y, z}** : 위와 비슷하지만 이 경우 64 비트 주소값을 읽는다.
* **zmm/m512/m32bcst** : ZMM 레지스터나 512 비트 메모리 데이터나, 32 비트 메모리 주소값에 불러온 벡터 데이터.
* **zmm/m512/m64bcst** : ZMM 레지스터나 512 비트 메모리 데이터나, 64 비트 메모리 주소값에 불러온 벡터 데이터.
* **<ZMM0>** : `ZMM0` 레지스터가 암묵적으로 사용되었음을 의미.

따라서 다시 `MOV r/m32,r32` 를 어떻게 해석하는지 보자면, `r/m32` 의 경우 32 비트 범용 레지스터 혹은 32 비트 메모리 데이터를 의미하고, `r32` 는 그냥 32 비트 범용 레지스터를 의미하기 때문에

```asm
        mov     DWORD PTR [rbp-20], edi
```

와 같은 형태의 명령어를 의미하게 되겠습니다. 실제로도 Opcode 로 이에 대응하는 0x89 가 사용되었습니다.

### 명령어 호환 여부

명령어 레퍼런스의 *64/32 bit Mode Support* 열을 보면 해당 명령어가 64/32 비트 환경에서 호환되어 있는지 나와 있습니다. 

#### 64 비트 호환 여부

'/' 로 구분된 왼쪽 부분에 써 있습니다. (예를 들어서 V/I 라면 V).

* **V** (혹은 **Valid**) : 지원 됨
* **I** : 지원 안됨
* **N.E.** : 64 비트 모드에서 해당 명령어를 인코딩 할 수 가 없다.
* **N.P.** : REX 접두사가 해당 명령어에 영향을 주지 않는다.
* **N.I.** : Opcode 가 64 비트 모드에서 새로운 명령어로 취급된다.
* **N.S.** : 해당 명령어가 주소 재정의 접두사가 필요로 하는데 64 비트 모드에서 지원이 되지 않는다. 만일 주소 재정의 접두사를 64 비트 모드에서 사용하였을 경우 어떤 결과가 나올지는 프로세서마다 다르다.

#### 호환 모드 (Compatibility mode) 지원 여부

* **V** : 지원 됨
* **I** : 지원 안됨
* **N.E.** : Intel 64 명령어를 인코딩 할 수 없다.