x64汇编介绍
大多数的资料还是介绍x86汇编,而现在的程序大多是64位了。
寄存器变动
增加了64位寄存器 R8-R15, 扩展了原来已有的寄存器RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP。原来的代码指针寄存器EIP也扩展成64位的RIP(RIP执行下一条代码)。RSP作为栈指针。
同时寄存器可以分为多个宽度来访问,比如RAX寄存器,可以使用RAX来访问64位的数据,EAX访问低位的32位数据,AX访问低位的16位数据,AH访问AX中高8位,AL访问AX中低8位。而对于64位中新增加的寄存器R8-R15,R8(访问64位)、R8D(低32位)、R8W(低16位)、R8B(低8位)。
RFLAGS寄存器保存各种操作的标志位,它是x86中EFLAGS的64位扩展。
常用标志位:
符号 | 位数 | 名称 | 设置 |
---|---|---|---|
CF | 0 | Carry | 当发生进位或者借位时 |
PF | 2 | Parity | 运算得到的结果中1的个数为偶数时置为1 |
AF | 4 | Ajust | 辅助进位 |
ZF | 6 | Zero | 结果为0时置1 |
SF | 7 | Sign | 结果为负时置1 |
OF | 11 | Overflow | 溢出时置1 |
DF | 10 | Direction | 字符串操作方向,置1时为增加方向 |
浮点数单元(FPU)包含8个寄存器FPR0-FPR7,每个寄存器是80-bit,其中可以使用MMX0-MMX7范围这些寄存器的64-bit。
指令基础
指令结构:指令 [目的操作],[源操作]
寻址指令
直接数赋值:ADD EAX, 14
,直接将14存放到EAX中。
寄存器之间:ADD R8L, AL
,将8位的AL中的值存放到R8L中。
间接存放:使用公式直接数[放大因子 * 索引值 + 基础值]
,比如MOV R8W, 1234[8*RAX+RCX]
等于取地址8*RAX+RCX+1234地址的值到R8W中。当目标寄存器的宽度与内存宽度不符合时,需要指定怎么取值,比如`MOV ECX, dword ptr [RBX+RDI]
Opcode | Meaning | Opcode | Meaning |
---|---|---|---|
MOV | Move to/from/between memory and registers | AND/OR/XOR/NOT | Bitwise operations |
CMOV* | Various conditional moves | SHR/SAR | Shift right logical/arithmetic |
XCHG | Exchange | SHL/SAL | Shift left logical/arithmetic |
BSWAP | Byte swap | ROR/ROL | Rotate right/left |
PUSH/POP | Stack usage | RCR/RCL | Rotate right/left through carry bit |
ADD/ADC | Add/with carry | BT/BTS/BTR | Bit test/and set/and reset |
SUB/SBC | Subtract/with carry | JMP | Unconditional jump |
MUL/IMUL | Multiply/unsigned | JE/JNE/JC/JNC/J* | Jump if equal/not equal/carry/not carry/ many others |
DIV/IDIV | Divide/unsigned | LOOP/LOOPE/LOOPNE | Loop with ECX |
INC/DEC | Increment/Decrement | CALL/RET | Call subroutine/return |
NEG | Negate | NOP | No operation |
CMP | Compare | CPUID | CPU information |
操作系统相关
理论上64位操作系统可以寻址64位地址,实际上目前的芯片使用低48位,因此寻址范围是0~0x7fff ffffffff。地址范围00x7fff ffff ffff 和地址范围0xffff 8000 0000 0000 ~ 0x ffff ffff ffff ffff作为虚拟地址空间。其他的空间不使用,因为完整的64位内存需要很大的page table。许多系统使用0xffff 8000 0000 0000 ~ 0x ffff ffff ffff ffff作为内核地址范围,而00x7fff ffff ffff作为用户空间地址范围。
调用惯例
Microsoft x64调用惯例使用fastcall:
- RCX、RDX、R8、R9用于从左到右的整数、指针参数。
- XMM0、XMM1、XMM3用于浮点参数。
- 剩下的参数从左到右push到栈中。
- 调用者在调用前需要存储RCX、RDX、XMM0等用于存放参数的寄存器。
- 调用者在调用后清理分配的栈空间。
- 浮点数返回值存放在XMM0中、整数返回值存放在RAX中。
- 当返回值过大时,返回值存放在栈中,由调用者分配空间,返回值地址存放在RCX中。
- 栈16字节对齐。
- 被调用者需要自己保存RAX、RBP、RDI、RSI、R12、R14、R15.
GNU C的调用惯例cdecl:
- 从右到左push参数到栈中。
- 64位系统下前6个整数、指针参数使用RDI、RSI、RDX、RCX、R8、R9传入。
- 函数调用者保存RBP、恢复RBP。
- 返回地址保存在[RSP]中,第一个变量存放在[RSP+8],依次存放。
- 栈指针RSP必须16字节对齐。
例子
main这个符号是默认的入口函数,所以不用在gcc里面更改入口,extern printf表示这是外部符号,在链接时重定向。RDI传入printf函数的第一个参数,RSI传入第二个参数。
1 | global main |
编译链接执行:
1 | nasm -felf64 libcall.S && gcc -no-pie libcall.o && ./a.out |
在Ubuntu 18.04 x64的环境下。
-no-pie
:关闭内核加载程序时,随机化虚拟地址的功能。PIE(Position Independent Executables)用于库函数固化的结果(an output of the hardened package build process),一个PIE二进制程序和它的依赖会被加载到虚拟内存的随机地址,使得那些覆盖return地址的攻击变得困难。
参考
内容大多来自《Introduction to x64 Assembly》这本书
- 本文链接:https://dowob.cn/2019/09/13/x64%E6%B1%87%E7%BC%96%E4%BB%8B%E7%BB%8D/
- 版权声明:本站所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!