X64(64位)汇编指令与机器码转换原理
本文属于《 X86指令基础系列教程》之一,欢迎查看其它文章。
1 64位寻址形式下的ModR/M字节
x64指令机器码组成:
REX prefix组成:
ModR/M组成:
1.1 寻址方式
ModR/M字节具体值,组成情况,如下图所示:
ModRM.mod、ModRM.r/m和REX.B三者确定一种寻址方式,一共有64种寻址方式。
1.2 寄存器编号
ModRM.reg与REX.R位,合并扩展为4位Rrrr,表示X64架构下这16个寄存器编号,具体编号如下:
r8(/r) r16(/r) r32(/r) r64(/r) mm(/r) | AL AX EAX RAX MM0 | CL CX ECX RCX MM1 | DL DX EDX RDX MM2 | BL BX EBX RBX MM3 | SPL SP ESP RSP MM4 | BPL BP EBP RBP MM5 | SIL SI ESI RSI MM6 | DIL DI EDI RDI MM7 | r8b r8w r8d r8 | r9b r9w r9d r9 | r10b r10w r10d r10 | r11b r11w r11d r11 | r12b r12w r12d r12 | r13b r13w r13d r13 | r14b r14w r14d r14 | r15b r15w r15d r15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Rrrr | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
2 汇编指令转机器码
我们举几个立即数操作的例子:
mov cl, 12h
mov cx, 1234h
mov ecx, 12345678h
mov rcx, 1122334455667788h
然后,把表格中每一类寻址方式,都举一个例子,进行描述,如下:
序号 | 寻址方式 | Mod | R/M | REX.B | 汇编例子 | 含义 |
---|---|---|---|---|---|---|
1 | [r8] | 00 | 000 | 1 | mov rcx,[r8]与mov [r8],rcx | (rcx)=((r8))与((r8))=(rcx) |
2 | [SIB] | 00 | 100 | 任意 | mov rcx,[r8+r9*2] | (rcx)=((r8)+(r9)*2) |
3 | [disp32] / [rip+disp32] | 00 | 101 | 任意 | mov rcx,[00001000h] | (rcx)=(00001000h) / (rcx)=((rip)+00001000h) |
4 | [r8+disp8] | 01 | 000 | 1 | mov rcx,[r8+10h] | (rcx)=((r8)+10h) |
5 | [SIB+disp8] | 01 | 100 | 任意 | mov rcx,[r8+r9*2+10h] | (rcx)=((r8)+(r9)*2+10h) |
6 | [r8+disp32] | 10 | 000 | 1 | mov rcx,[r8+00001000h] | (rcx)=((r8)+00001000h) |
7 | [SIB+disp32] | 10 | 100 | 任意 | mov rcx,[r8+r9*2+00001000h] | (rcx)=((r8)+(r9)*2+00001000h) |
8 | r8 | 11 | 000 | 1 | mov r9,r8 | (r9)=(r8) |
9 | r9 | 11 | 001 | 1 | mov r8,r9 | (r8)=(r9) |
因此,一共有13个汇编指令转机器码的例子,接下来,依次来讲解。
2.1 mov rcx, 1122334455667788h
- mov cl, 12h
mov cl, 12h
表示将立即数12h存储到8位
寄存器cl中。
查询指令手册,与MOV r8,imm8
指令相符,其操作码为B0+ rb,rb表示目的操作数cl寄存器编号。
当前指令cl编号为1,因此操作码为B0+1=B1。
因此指令的机器码组成,如下所示:
汇编指令 | 操作码 | 立即数 | 机器码 |
---|---|---|---|
mov cl, 12h | B1 | 12 | B112 |
- mov cx, 1234h
mov cx, 1234h
表示将立即数1234h存储到16位
寄存器cx中。
查询指令手册,与MOV r16,imm16
指令相符,其操作码为B8+ rw,rw表示目的操作数cx寄存器编号。
当前指令cx编号为1,因此操作码为B8+1=B9。
由于在X64下,默认操作数宽度是32位,需要添加指令前缀66H,“反转”选择16位宽度的操作数。
因此指令的机器码组成,如下所示:
汇编指令 | 指令前缀 | 操作码 | 立即数 | 机器码 |
---|---|---|---|---|
mov cx, 1234h | 66 | B9 | 1234 | 66B93412 |
- mov ecx, 12345678h
mov ecx, 12345678h
表示将立即数12345678h存储到32位
寄存器ecx中。
查询指令手册,与MOV r32,imm32
指令相符,其操作码为B8+ rd,rd表示目的操作数ecx寄存器编号。
当前指令ecx编号为1,因此操作码为B8+1=B9。
由于在X64下,默认操作数宽度是32位,刚好合适,因此不需添加指令前缀66H。
因此指令的机器码组成,如下所示:
汇编指令 | 操作码 | 立即数 | 机器码 |
---|---|---|---|
mov ecx, 12345678h | B9 | 12345678 | B978563412 |
- mov rcx, 1122334455667788h
mov rcx, 1122334455667788h
表示将立即数1122334455667788h存储到64位
寄存器rcx中。
查询指令手册,与MOV r64,imm64
指令相符,其操作码为REX.W + B8+ rd,rd表示目的操作数rcx寄存器编号。
当前指令rcx编号为1,因此操作码为B8+1=B9。
REX.W说明需要REX前缀,REX.W=1表示操作位宽为64位,因此REX=48h。
因此指令的机器码组成,如下所示:
汇编指令 | REX前缀 | 操作码 | 立即数 | 机器码 |
---|---|---|---|---|
mov rcx, 1122334455667788h | 48 | B9 | 1122334455667788h | 48 B9 88 77 66 55 44 33 22 11 |
2.2 mov rcx,[r8]与mov [r8],rcx
- mov rcx,[r8]
mov rcx,[r8]
表示将r8寄存器中地址指向的内存单元,存储到rcx中。
查询指令手册,与MOV r64,r/m64
指令相符,其操作码为REX.W + 8B /r,/r表示这条指令具有ModR/M字段。
当前指令寻址方式属于《X64指令基本格式》中“第一种,无SIB字节的内存寻址”。
因此,尝试推导ModR/M值。ModRM.mod、ModRM.r/m、REX.B三者确定一种寻址方式,如下图:
第一步,在源和目的操作数中,以内存寻址操作数为坐标点,反推ModRM.mod、ModRM.r/m、REX.B。
这里内存寻址操作数为[r8],故ModRM.mod=00,ModRM.r/m=000,REX.B=1。
第二步,在源和目的操作数中,以寄存器操作数,反推ModRM.reg与REX.R。
这里寄存器操作数为rcx,其寄存器编号Rrrr=0001,故REX.R=0,ModRM.reg=001。
REX.W说明需要REX前缀,REX格式为0100WRXB,W表示操作数宽度,W=1表示64位,X位默认为0,因此REX=01001001=49h。ModRM=00001000=08h。
因此指令的机器码组成,如下所示:
汇编指令 | REX前缀 | 操作码 | ModRM | 机器码 |
---|---|---|---|---|
mov rcx,[r8] | 49 | 8B | 08 | 498B08 |
- mov [r8],rcx
mov [r8],rcx
表示将rcx寄存器内容,存储到r8寄存器中地址指向的内存单元中。
查询指令手册,与MOV r/m64,r64
指令相符,其操作码为REX.W + 89 /r,/r表示这条指令具有ModR/M字段。
当前指令寻址方式属于《X64指令基本格式》中“第一种,无SIB字节的内存寻址”。
因此,尝试推导ModR/M值。ModRM.mod、ModRM.r/m、REX.B三者确定一种寻址方式,如下图:
第一步,在源和目的操作数中,以内存寻址操作数为坐标点,反推ModRM.mod、ModRM.r/m、REX.B。
这里内存寻址操作数为[r8],故ModRM.mod=00,ModRM.r/m=000,REX.B=1。
第二步,在源和目的操作数中,以寄存器操作数,反推ModRM.reg与REX.R。
这里寄存器操作数为rcx,其寄存器编号Rrrr=0001,故REX.R=0,ModRM.reg=001。
REX.W说明需要REX前缀,REX格式为0100WRXB,W表示操作数宽度,W=1表示64位,X位默认为0,因此REX=01001001=49h。ModRM=00001000=08h。
因此指令的机器码组成,如下所示:
汇编指令 | REX前缀 | 操作码 | ModRM | 机器码 |
---|---|---|---|---|
mov [r8],rcx | 49 | 89 | 08 | 498908 |
发现没有,源操作数与目的操作数,交换传输方向后,仅操作码发生变化。也就是说,寄存器与内存之间传递数据的方向,是靠操作码来分辨的,与REX前缀、ModRM无关。
2.3 mov rcx,[r8+r9*2]
mov rcx,[r8+r9*2]
表示将r8寄存器中地址+r9寄存器中地址*2,指向的内存单元,存储到rcx中。
查询指令手册,与MOV r64,r/m64
指令相符,其操作码为REX.W + 8B /r,/r表示这条指令具有ModR/M字段。
当前指令寻址方式属于《X64指令基本格式》中“第三种,带SIB字节的内存寻址”,如下图所示:
此场景下REX.B与SIB.base合并,不再与ModRM.r/m合并。
因此,尝试推导ModR/M值。ModRM.mod、ModRM.r/m两者就可以确定[SIB]内存寻址方式,如下图:
第一步,以内存寻址操作数为坐标点,反推ModRM.mod、ModRM.r/m。
这里内存寻址操作数为[r8+r9 * 2],故ModRM.mod=00,ModRM.r/m=100。
第二步,以寄存器操作数,反推ModRM.reg与REX.R。
这里寄存器操作数为rcx,其寄存器编号Rrrr=0001,故REX.R=0,ModRM.reg=001。
第三步,SIB寻址[r8+r9 * 2],根据effective_address = scale * index + base + offset
地址计算公式,得出base为r8,index为r9,scale为2,无offset。
REX.B与SIB.base拼成的Bbbb,表示基地址寄存器(r8)的编号,Bbbb=1000,故REX.B=1,SIB.base=000。
REX.X与SIB.index拼成的Xxxx,表示索引寄存器(r9)的编号,Xxxx=1001,故REX.X=1,SIB.index=001。
scale机器码编码,如下所示:
scale | Encoded value |
---|---|
1 | 00 |
2 | 01 |
4 | 10 |
8 | 11 |
scale为2,因此对应机器码为01。
REX格式为0100WRXB,REX.W=1表示操作数宽度为64位。
我们基于上述这些位,得出REX=0100 1011=4Bh,Opcode=8Bh,ModRM=00001100=0Ch,SIB=01001000=48h。
因此指令的机器码组成,如下所示:
汇编指令 | REX前缀 | 操作码 | ModRM | SIB | 机器码 |
---|---|---|---|---|---|
mov rcx,[r8+r9*2] | 4B | 8B | 0C | 48 | 4B8B0C48 |
2.4 mov rcx,[00001000h]
mov rcx,[00001000h]
表示将00001000h地址指向的内存单元,存储到rcx中。
查询指令手册,与MOV r64,r/m64
指令相符,其操作码为REX.W + 8B /r,/r表示这条指令具有ModR/M字段。
当前指令寻址方式属于《X64指令基本格式》中“第三种,带SIB字节的内存寻址(mod!=11 & r/m=100)”,一定有疑问,这指令看起来,跟SIB一般形式不像,我们通过查看其机器码,发现的确属于第三种场景。
因此,尝试推导ModR/M值。ModRM.mod、ModRM.r/m两者确定一种内存寻址方式,如下图:
第一步,在源和目的操作数中,以内存寻址操作数为坐标点,反推ModRM.mod、ModRM.r/m。
这里内存寻址操作数为[00001000h],故ModRM.mod=00,ModRM.r/m=100。
第二步,在源和目的操作数中,以寄存器操作数,反推ModRM.reg与REX.R。
这里寄存器操作数为rcx,其寄存器编号Rrrr=0001,故REX.R=0,ModRM.reg=001。
第三步,我们知道SIB地址形式,通常是这样effective_address = scale * index + base + offset
,但是对于当前指令mov rcx,[00001000h]
,如果要匹配的话,那么base和index寄存器不存在,scale为1,offset=00001000h,因此SIB=25h,如下所示:
REX.B与SIB.base拼成的Bbbb,表示基地址寄存器(base)的编号,Bbbb=0101,故REX.B=0,SIB.base=101。
REX.X与SIB.index拼成的Xxxx,表示索引寄存器(index)的编号,Xxxx=0100,故REX.X=0,SIB.index=100。
REX.W说明需要REX前缀,REX格式为0100WRXB,W表示操作数宽度,W=1表示64位,因此REX=01001000=48h。ModRM=00001100=0Ch。
因此指令的机器码组成,如下所示:
汇编指令 | REX前缀 | 操作码 | ModRM | SIB | 偏移 | 机器码 |
---|---|---|---|---|---|---|
mov rcx,[00001000h] | 48 | 8B | 0C | 25 | 00001000 | 488B0C2500100000 |
本例子,正是属于RIP寻址方式中,红框所示的场景。
至于mod=00,r/m=101的场景,留待以后再讨论。
2.5 mov rcx,[r8+10h]
mov rcx,[r8+10h]
表示将r8寄存器中地址+10h,指向的内存单元,存储到rcx中。
查询指令手册,与MOV r64,r/m64
指令相符,其操作码为REX.W + 8B /r,/r表示这条指令具有ModR/M字段。
当前指令寻址方式属于《X64指令基本格式》中“第一种,无SIB字节的内存寻址”。
因此,尝试推导ModR/M值。ModRM.mod、ModRM.r/m、REX.B三者确定一种寻址方式,如下图:
第一步,在源和目的操作数中,以内存寻址操作数为坐标点,反推ModRM.mod、ModRM.r/m、REX.B。
这里内存寻址操作数为[r8+10h],故ModRM.mod=01,ModRM.r/m=000,REX.B=1。
第二步,在源和目的操作数中,以寄存器操作数,反推ModRM.reg与REX.R。
这里寄存器操作数为rcx,其寄存器编号Rrrr=0001,故REX.R=0,ModRM.reg=001。
REX.W说明需要REX前缀,REX格式为0100WRXB,W表示操作数宽度,W=1表示64位,X位默认为0,因此REX=01001001=49h。ModRM=01001000=48h。
因此指令的机器码组成,如下所示:
汇编指令 | REX前缀 | 操作码 | ModRM | 偏移 | 机器码 |
---|---|---|---|---|---|
mov rcx,[r8+10h] | 49 | 8B | 48 | 10 | 498B4810 |
2.6 mov rcx,[r8+r9*2+10h]
mov rcx,[r8+r9*2+10h]
表示将r8寄存器中地址+r9寄存器中地址*2+10h,指向的内存单元,存储到rcx中。
查询指令手册,与MOV r64,r/m64
指令相符,其操作码为REX.W + 8B /r,/r表示这条指令具有ModR/M字段。
当前指令寻址方式属于《X64指令基本格式》中“第三种,带SIB字节的内存寻址”,如下图所示:
此场景下REX.B与SIB.base合并,不再与ModRM.r/m合并。
因此,尝试推导ModR/M值。ModRM.mod、ModRM.r/m两者就可以确定[SIB+disp8]内存寻址方式,如下图:
第一步,以内存寻址操作数为坐标点,反推ModRM.mod、ModRM.r/m。
这里内存寻址操作数为[r8+r9 * 2+10h],故ModRM.mod=01,ModRM.r/m=100。
第二步,以寄存器操作数,反推ModRM.reg与REX.R。
这里寄存器操作数为rcx,其寄存器编号Rrrr=0001,故REX.R=0,ModRM.reg=001。
第三步,SIB寻址[r8+r9 * 2+10h],根据effective_address = scale * index + base + offset
地址计算公式,得出base为r8,index为r9,scale为2,offset为10h。
REX.B与SIB.base拼成的Bbbb,表示基地址寄存器(r8)的编号,Bbbb=1000,故REX.B=1,SIB.base=000。
REX.X与SIB.index拼成的Xxxx,表示索引寄存器(r9)的编号,Xxxx=1001,故REX.X=1,SIB.index=001。
scale机器码编码,如下所示:
scale | Encoded value |
---|---|
1 | 00 |
2 | 01 |
4 | 10 |
8 | 11 |
scale为2,因此对应机器码为01。
REX格式为0100WRXB,REX.W=1表示操作数宽度为64位。
我们基于上述这些位,得出REX=0100 1011=4Bh,Opcode=8Bh,ModRM=01001100=4Ch,SIB=01001000=48h。
因此指令的机器码组成,如下所示:
汇编指令 | REX前缀 | 操作码 | ModRM | SIB | 偏移 | 机器码 |
---|---|---|---|---|---|---|
mov rcx,[r8+r9*2+10h] | 4B | 8B | 4C | 48 | 10 | 4B8B4C4810 |
2.7 mov rcx,[r8+00001000h]
与mov rcx,[r8+10h]类似,不再赘述。
2.8 mov rcx,[r8+r9*2+00001000h]
与mov rcx,[r8+r9*2+10h]类似,不再赘述。
2.9 mov r9,r8与mov r8,r9
- mov r9,r8
mov r9,r8
表示将r8寄存器内容,存储到r9中。
查询指令手册,与MOV r64,r/m64
指令相符,其操作码为REX.W + 8B /r,/r表示这条指令具有ModR/M字段。
当前指令寻址方式属于《X64指令基本格式》中“第二种,寄存器到寄存器的寻址(无内存操作数,mod=11)”。
因此,尝试推导ModR/M值。ModRM.mod、ModRM.r/m、REX.B三者确定一种寻址方式,如下图:
当寄存器与寄存器间传送数据时:
第一步,以源操作数为寻址方式,反推ModRM.mod、ModRM.r/m、REX.B。
这里源操作数为r8,故ModRM.mod=11,ModRM.r/m=000,REX.B=1。
第二步,以目的操作数,反推ModRM.reg与REX.R。
这里目的操作数为r9,其寄存器编号Rrrr=1001,故REX.R=1,ModRM.reg=001。
REX.W说明需要REX前缀,REX格式为0100WRXB,W表示操作数宽度,W=1表示64位,X位默认为0,因此REX=01001101=4Dh。ModRM=11001000=C8h。
因此指令的机器码组成,如下所示:
汇编指令 | REX前缀 | 操作码 | ModRM | 机器码 |
---|---|---|---|---|
mov r9,r8 | 4D | 8B | C8 | 4D 8B C8 |
- mov r8,r9
mov r8,r9
表示将r9寄存器内容,存储到r8中。
查询指令手册,与MOV r64,r/m64
指令相符,其操作码为REX.W + 8B /r,/r表示这条指令具有ModR/M字段。
当前指令寻址方式属于《X64指令基本格式》中“第二种,寄存器到寄存器的寻址(无内存操作数,mod=11)”。
因此,尝试推导ModR/M值。ModRM.mod、ModRM.r/m、REX.B三者确定一种寻址方式,如下图:
当寄存器与寄存器间传送数据时:
第一步,以源操作数为寻址方式,反推ModRM.mod、ModRM.r/m、REX.B。
这里源操作数为r9,故ModRM.mod=11,ModRM.r/m=001,REX.B=1。
第二步,以目的操作数,反推ModRM.reg与REX.R。
这里目的操作数为r8,其寄存器编号Rrrr=1000,故REX.R=1,ModRM.reg=000。
REX.W说明需要REX前缀,REX格式为0100WRXB,W表示操作数宽度,W=1表示64位,X位默认为0,因此REX=01001101=4Dh。ModRM=11000001=C1h。
因此指令的机器码组成,如下所示:
汇编指令 | REX前缀 | 操作码 | ModRM | 机器码 |
---|---|---|---|---|
mov r8,r9 | 4D | 8B | C1 | 4D 8B C1 |
mov r9,r8 : 4D 8B C8
mov r8,r9与mov r9,r8的机器码,仅有ModR/M不一样,其他均一致。
我们可以看出,当寄存器与寄存器之间进行数据操作时,ModR/M值决定了,寄存器写入的方向。
参考文档: