汇编基础
64位编程
之前介绍的一直都是32位的程序,64位和32位有很多相似的地方,但仍有许多的不同:
比如64位MASM 11.0不支持INVOKE伪指令,而且不需要在程序前写出.model,.stack,.386的
寄存器
64位模式下,操作数的默认大小是32位,并且有8个通用的寄存器。但是给每条指令加上REX(寄存器扩展)前缀后,操作数也可达64位。通用寄存器的数量也增加到了16个:32位模式下的寄存器再加上8个有标号的寄存器,R8~R15
- 64位模式下,单条指令不能同时访问寄存器的高字节,如AH,BH,CH以及新寄存器的低字节DIL
- 64模式下,32位的EFLAGS被64位的RFLAGS取代。这两个寄存器共享低32位,而RFLAGS的高32位是不使用的
- 32位和64位拥有相同的状态标志位
加法减法
1 | ;数组求和 |
需要注意的是操作数的大小,当操作数只使用部分寄存器的时候,其余位置不会被修改
同时,使用OFFSET运算符将产生64位地址,必须用64位寄存器或者变量来保存
64位调用规范
- 如果想要调用自己写的子程序或者Irvine64链接库的子程序,则程序员需要在自己的程序顶部用PROTO伪指令来指定所有在本程序外同时又会被调用的过程
1 | ExitProcess PROTO ;Windows API |
- 由于地址长为64位,因此CALL指令把RSP(堆栈指针)寄存器的值减去8
- 长度不足64位的参数不进行零扩展,因此其高位的值是不确定的
- 如果返回值的长度小于或者等于64位,那么它必须放在RAX寄存器中
- 主调者要负责在堆栈中分配至少32字节的影子空间,以便被调用的子程序可以选择将寄存器保存在这个区域中
- 调用子程序时,堆栈指针(RSP)必须对齐16字节边界。CALL指令将8字节的返回地址压入堆栈,因此,主调程序出了把堆栈指针减去32以便存放寄存参数之外,还要减去8
- 大于64的返回值存放于运行时的堆栈,由RCX指出其位置
1 | ExitProcess PROTO |
64位模式下的布尔指令
大多数情况下,64位模式中的64位指令与32位模式中的操作是一样的。比如,如果源操作数是常数,长度小于32位,而目的操作数使一个64位的寄存器或者内存操作数,那么目的操作数的所有位都会受到影响
1 | .data |
我们发现如果操作数是32位常数或寄存器,那么目的操作数,只有低32位会受到影响
这个同样适用于内存操作数,显然32位操作数是一种特殊的情况,需要和其他大小的操作数分开考虑