jhanker 发表于 2020-7-2 09:36:39

x86-64位汇编语言简介

一、x86-64的寄存器
x86-64较x86-32多了8个通用寄存器,而且,每个通用寄存器都是64位宽,它们是:
rax,rbx,rcx,rdx,rsi,rdi,rsp,rbp
r8,r9,r10,r11,r12,r13,r14,r15
同时,x86-64全面支持x86-32和x86-16的通用寄存器:
eax,ax,al,ah,
ebx,bx,bl,bh,
....
而且,还对传统的edi,esi做了改进:
edi ,32位
di,16位
dil ,8位,在传统的x86机器中,di是不可按照8位来访问的,但在x86-64下可以。
同样esi也可以按照8位来访问。一个很特别的寄存器 rip,相当于x86-32的eip. 在x86-32是不可直接访问的,如mov eax,eip是错的,但在x86-64位下却可以,如 mov,rax,qword ptr 是对的。而且,它除了是个程序计数器外,也是个“数据基地址”,有此可见,它现在是身兼两职!为什么在x86-64位下要用rip做访问数据的基地址呢?因为,在x86-64下,DS,ES,CS,SS都没有实际意义了,也就是说,它们不再参与地址计算,只是为了兼容x86-32。FS,GS还是参与地址计算,它们两个和x86-32的意义相同。

二、x86-64的汇编
x86-64的汇编和x86-32的没有多大的区别。添加了新寄存器和指令。
写64位汇编代码时,可以用8、16、32、64位寄存器,如:
push   rdi
sub   rsp, 48               ;
mov   r10, rcx
; Line 36
mov   rdi, rdx
xor   eax, eax
mov   ecx, 512            
rep stosb
; Line 43
movsxd   r8, DWORD PTR
mov   QWORD PTR , rdx
mov   r9, QWORD PTR
mov   rdx, QWORD PTR
mov   rcx, QWORD PTR
call   fs_read_disk
; Line 47
mov   ecx, 1
cmp   eax, ecx
cmovne   ecx, eax
mov   eax, ecx
; Line 52
add   rsp, 48               
pop   rdi
ret   0
再如:
$L1818:
; Line 2398
mov   al, BYTE PTR
cmp   al, 32               
jne   SHORT $L1819

mov   BYTE PTR , 0
$L1819:

add   r8d, 1
movsxd   rdx, r8d
xor   eax, eax
mov   rcx, r12
mov   rdi, rbx
repne scasb
not   rcx
sub   rcx, 1
cmp   rdx, rcx
jb   SHORT $L1818

但,有点值得注意,当操作传统的32位寄存器时,那么,整个64位寄存器都会受到影响,如:
mov eax,0ah
那么,rax也等于000000000000000ah

再如:
mov rcx,0aaaaaaaaaaaaaaaah
(此时ecx等于0aaaaaaaah)
mov ecx,0ddddddddh
(此时,rcx等于00000000ddddddddh,高32位受到了影响).

规则:
Example 1: 64-bit Add:
Before:RAX =0002_0001_8000_2201
RBX =0002_0002_0123_3301
ADD RBX,RAX ;48 is a REX prefix for size.
Result:RBX = 0004_0003_8123_5502

Example 2: 32-bit Add:
Before:RAX = 0002_0001_8000_2201
RBX = 0002_0002_0123_3301
ADD EBX,EAX ;32-bit add
Result:RBX = 0000_0000_8123_5502
(32-bit result is zero extended)

Example 3: 16-bit Add:
Before:RAX = 0002_0001_8000_2201
RBX = 0002_0002_0123_3301
ADD BX,AX ;66 is 16-bit size override
Result:RBX = 0002_0002_0123_5502
(bits 63:16 are preserved)

Example 4: 8-bit Add:
Before:RAX = 0002_0001_8000_2201
RBX = 0002_0002_0123_3301
ADD BL,AL ;8-bit add
Result:RBX = 0002_0002_0123_3302
(bits 63:08 are preserved)

三、指令集变化小结:
当然,这里说的都是最基本的东西,是针对通用寄存器言的。其实,x86-64对FPU(数学处理单元)和MMX,SSE,SSE2都做了很大的改进。然而,对写OS来说,我们最关心的还是通用寄存器

1.地址宽度和操作数宽度前缀
   64位模式中,缺省的地址宽度是64位,缺省的操作数宽度是32位。地址宽度和操作数宽度前缀允许32位和64位数据和地址在指令序列中混用。下表(1-7)显示了在IA-32e模式下需要指令前缀地址宽度。注意,在64位模式下不支持16位地址。在敬爱内容和传统模式下,地址宽度函数的功能和在IA-32传动架构中一样。

下表(1-8)显示了66H指令前缀和REX.W前缀的有效组合来指定IA-32e操作模式下的操作数宽度问题。
    在64位模式下, 缺省的操作数宽度是32位,REX前缀包括4位域来指定16个不同的值。REX前缀的W位域指定为REX.W。REX.W=1时前缀表明操作数位64为操作数。注意,软件依然能使用操作数宽度66H前缀来切换到16位操作宽度。然而如果同时用REX.W和66H前缀,REX.W的优先权要高。
    在SSE/SSE2/SSE3 SIMD指令的情况下,66H, F2H和F3H前缀作为操作码扩展,并被认为是指令的一部分。在这些情况下,有效的REX.W前缀和66H代码扩展前缀之间没有相互关系。

2.REX前缀
    REX前缀是64位模式下引入的新的指令前缀字节,他作以下工作:
•指定新的GPRs和SSE寄存器
•指定64位代码宽度
•指定扩展的控制寄存器(只给系统软件使用)

    不是所有的指令都需要REX前缀。这个前缀只在指令引用扩展的寄存器或使用64位操作数的时候才有必要。如果该前缀放在不需要的地方将会被忽略。
一个指令只能有一个REX前缀。这个前缀一旦使用,就必须直接放在操作码字节或两字节操作码扩展前缀之前。 其他位置的REX前缀将被忽略。
    包含有REX前缀的指令依然要遵循传统的15字节的指令宽度的限制。下图描述了REX前缀如何符合指令的字节次序的。

3.控制和调试寄存器的新编码
   在64位模式下,有为控制机存器和调试寄存器指定的附加的编码。当ModRM寄存器的域编码一个控制或调试寄存器的时候,REX.R位被用来修改这些域。这些编码允许处理器访问CR8-CR15和DR8-DR15。
   在64位模式中附加了一个控制寄存器(CR8)。CR8成为任务优先级寄存器(TPR)。在IA-32e技术的首次实现的时候,CR9-CR15和DR8-DR15都没有实现,对它们的访问将引起无效代码异常(#UD)。

4.新的指令
    下面的新指令在带有64位扩展的64位模式下被引入。
•SWAPGS 指令
•SYSCALL and SYSRET 指令
•CDQE 指令
•CMPSQ 指令
•CMPXCHG16B 指令
•LODSQ 指令
•MOVSQ 指令
•MOVZX(64-bits) 指令
•STOSQ 指令

5.堆栈指针
    在64位模式,堆栈指针为64位。堆栈大小不是像兼容模式或传统模式中那样靠SS段描述符中的某位来控制,也不通过指令前缀来指示。
    对隐式堆栈引用将忽略地址大小的指示。除远分支以外,所有隐式引用RSP的指令在64位模式下缺省为64位操作数。影响到的指令包括:PUSH, POP, PUSHF, POPF, ENTER, 和LEAVE。使用这些指令在64位模式下将不可能产生32位堆栈值的压栈和退栈。如果使用66H操作数前缀,将支持16位的压栈和退栈。
    当寄存器RAX-RSP被用作操作数的时候,64位模式缺省的操作尺寸无需REX前缀作为这些指令的先导。如果式R8-R15作为操作数,则REX依然是需要的。这是因为前缀在访问新扩展寄存器中是需要的。

6.分支转移
   64位扩展技术扩充2个分支机制来适应64位线性地址空间的分支。他们是:
•64位模式下近分支转移被重新定义
•在64位模式和兼容模式下,64位调用门描述符定义成远调用

   64位模式下,所有近分支转移(CALL, RET, JCC, JCXZ, JMP 和 LOOP)被强迫为64位。这些指令被更新为提供64位的RIP值而无需REX前缀。下面的近转移被有效的操作数宽度所控制:
•指令指针的宽度的截断
•由于CALL或RET引起的退栈压栈或退栈的大小
•由于CALL或RET而引起的堆栈指针增加或减少的大小
•间接转移操作数大小

    在64位模式下,以上的所有操作都被强制为64位而不管操作数前缀(操作数大小的前缀被忽略)。然而相对转移的位移区域依然受到32位的限制;近转移的地址大小没有被强制为64位。
    地址大小影响到JCXZ和LOOP中RCX的大小;他们也影响到内存间接转移的地址计算。这样的地址缺省是64位,但是他们可以通过地址宽度前缀转换到32位宽度。
    软件会用远转移来改变优先级。传统IA-32结构提供调用门机制来允许软件去从一个优先级转到另一个优先级,尽管调用门也可以不改变优先级而只是做转移。当调用门使用的时候,直接或间接的选择器指针会指向一个门描述符(指令重的便宜被忽略)目的代码段的偏移可以从调用门描述符中获得。IA-32e模式重新定义了32位调用门描述符的类型值,使其成为64位调用门描述符,并扩展64位描述符使其能够容纳64位的偏移。64位模式调用门描述符允许远转移访问有效的线性地址空间的任何地方。这些调用门也控制代码段选择器(CS),允许转换到特权级和缺省尺寸并作为门转换的结果。
    因为通常情况下是指定32位的,唯一在64位模式下指定完全64位绝对RIP的是间接分支转移,由于这个原因,直接远分支转移被从64位模式的指令集中删除了。
    IA-32e模式扩充了SYSENTER和SYSEXIT指令的语义,以便他们操作在64位存储空间。IA-32e也引入了两个新的指令:SYSCALL和SYSRET,他们只在64位模式有效。

四、存储组织

1.64位模式下的地址计算
    在64位模式(如果没有地址大小的转变),有效地址计算的大小是64位的。一个有效地址计算使用一个64位的基和索引寄存器以及符号扩展变换成64位。
    对于64位模式下平面地址空间,线性地址等同于有效地址。在使用FS和GS段的非0为基的事务中,这个规则不被使用。在64位模式下,有效地址成分被加进来,并且有效地址在加64位基地址之前被缩短。地址映射模式在64位模式时,基地址从不会被缩短。
    在IA-32e模式下,指令指针被扩展到64位来支持64位代码偏移。64位指令指针在调用中将值赋给RIP。下表描述了RIP、EIP和IP之间的不同。

    通常,替换和直接在64位模式下不被扩展到64位。他们在有效地址计算中依然被限制在32位和符号扩展。然而,在64位模式提供了MOV指令的64位替换和直接形式的支持。
    所有的在IA-32e模式下的16位和32位地址计算用0扩展来形成64位地址。地址计算搜现是缩短到当前模式的有效地址宽度,就像地址宽度前缀的指定那样。其结果是用0扩展得到完全的64位地址宽度。因为这个,16位和32位应用程序运行在兼容模式只能存取64位模式有效地址的低4GB。同样,在64位模式产生一个32位地址只能访问64位模式有效地址的低4GB。

2.规范的寻址
    一个规范形式的地址有地址位63直到更有效的实现位,宏结构设置其为全1或全0。
    IA-32e模式定义一个64位的线性地址,但实现的时候支持的位数要少些。第一个具有64位扩展技术的IA-32e结构的处理器将支持48位线性地址。这意味着规范的地址必须将位63到位48全填0或全填1,填0还是填1要看位47是0还是1。
    尽管实现并不用先行地址的全部64位,他们需要检查位63知道更有效的实现位来看是否地址是规范形式。如果一个线性存储引用不是规范形式,该实现将会产生一个异常。在很多情况下,会产生一个一般保护异常(#GP)。然而,在显示或隐式对战应用的情况下,会产生一个堆栈错(#SS)。隐式堆栈引用指令包括PUSH/POP指令和使用RSP/RBP寄存器来作为缺省堆栈段寄存器的指令。在这些情况下,一个规范错误式#SF,如果一个指令使用RSP/RBP作为基寄存器并且有段超越给出一个非SS段,将引起一个一般保护错误(#GP)的规范错误。隐式堆栈引用包括所有PUSH/POP类型指令和任何使用RSP或RBP作为一个基寄存器。规范地址形式的检查将在特权检查之后页面和边界检查之前完成。
页: [1]
查看完整版本: x86-64位汇编语言简介