寄存器
早期的 x86 CPU
只有 8 个寄存器(eax
、ebx
、ecx
、edx
、esi
、edi
、ebp
、esp
),而且每个都有不同的用途。
寄存器名称 | 含义 | 用途 | 包含寄存器 |
---|---|---|---|
eax |
累加(Accumulator )寄存器 |
常用于乘除法和函数返回值 |
ax (ah 、al ) |
ebx |
基址(Base )寄存器 |
常以它为基址来访问内存 |
bx (bh 、bl ) |
ecx |
计数器(Counter )寄存器 |
常做字符串和循环操作中的计数器 |
cx (ch 、cl ) |
edx |
数据(Data )寄存器 |
常用于乘除法和 I/O 指针 |
dx (dh 、dl ) |
esi |
来源索引(Source Index )寄存器 |
常用于内存数据指针和源字符串指针 | si |
edi |
目的索引(Destination Index )寄存器 |
常用于内存数据指针和源字符串指针 | di |
ebp |
基址指针(Base Pointer )寄存器 |
只做堆栈指针,可以访问堆栈内任意地址,经常用于中转 ESP 中的数据,也常以它为基址来访问堆栈;不能用于算术运算与数据传送 |
bp |
esp |
堆栈指针(Stack Pointer )寄存器 |
只做堆栈的栈顶指针,不能用于算术运算与数据传送 | sp |
栈帧中,最重要的是帧指针
ebp
和栈指针esp
,有了这两个指针,我们就可以刻画一个完整的栈帧。
x86-64
中,所有寄存器都是 64 位,相对 32 位的 x86
来说,标识符发生了变化,比如:从原来的 eax
变成了 rax
。为了向后兼容性,eax
依然可以使用,不过指向了 rax
的低 32 位。
比如在 64 位 CPU 上:
-
rax
是 64 位的寄存器 -
rax
是rax
的低 32 位 -
ax
是rax
的低 16 位 -
ah
是ax
的高 8 位。 -
al
是ax
的低 8 位。
它们的对照关系如下:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
|=================================RAX=================================|---8个字节
|===============EAX===============|---4个字节
|======AX=======|---2个字节
|==AH===|-----------1个字节
|===AL==|---1个字节
通过 gdb 的 p /x $reg
命令打印寄存器的值。
(gdb) p /x $rax
$16 = 0x555555554870
(gdb) p /x $eax
$12 = 0x55554870
(gdb) p /x $ax
$13 = 0x4870
(gdb) p /x $ah
$14 = 0x48
(gdb) p /x $al
$15 = 0x70
汇编指令
1. 数据传送指令
指令 | 名称 | 语法 | 描述 |
---|---|---|---|
mov |
数据传送指令 | mov dest, src |
将数据从 src 移动到 dest
|
push |
入栈指令 | push src |
将源操作数 src 压入堆栈 |
pop |
入栈指令 | pop dest |
从栈顶弹出数据到 dest
|
1.1 mov
指令
mov
指令是最常见的数据传送指令,类似于高级语言中的赋值语句。
mov
指令可以实现寄存器与寄存器之间、寄存器与内存之间、寄存器与立即数、内存与立即数的数据传递。
需要注意的是,内存与内存无法直接传递数据,目的操作数不能为立即数。
mov
指令的用法示例如下:
mov eax, 12345678h
mov eax, [00401000h]
mov eax, ebx
mov [00401000h], 12345678h
mov [00401000h], eax
1.2 push
和 pop
指令
push
指令和 pop
指令互为相反的操作指令。
-
push
指令的功能是将操作数压入堆栈。 -
pop
指令的功能是将栈顶的操作数弹出。
push
指令把一个 32 位的操作数送入堆栈,该操作致使 esp
寄存器的值减 4。
esp
寄存器始终指向栈顶。堆栈的方向是由高地址向低地址进行延伸,也就是执行的 push
次数越多,esp
寄存器指向的地址越小。
在 32 位平台上,每执行一次 push
指令,esp
指向的地址都减小 4 字节。
pop
指令把 esp
指向地址(栈顶)中的值送入寄存器或内存中,然后 esp
指向的地址加 4 字节。
执行的 pop
指令越多,esp
寄存器指向的地址越大。
(2)算术运算指令
指令 | 名称 | 语法 | 描述 |
---|---|---|---|
add |
加法指令 | add dest, src |
在 dest 基础上加 src
|
sub |
减法指令 | sub dest, src |
在 dest 基础上减 src
|
inc |
加 1 指令 | inc dest |
在 dest 基础上加 1 |
dec |
减 1 指令 | dec dest |
在 dest 基础上减 1 |
(3)逻辑运算指令
指令 | 名称 | 语法 | 描述 |
---|---|---|---|
not |
取反运算指令 | not dest |
把操作数 dest 按位取反 |
and |
与运算指令 | and dest, src |
把 dest 和 src 进行与预算之后送回 dest
|
or |
与运算指令 | or dest, src |
把 dest 和 src 进行或预算之后送回 dest
|
xor |
与运算指令 | xor dest, src |
把 dest 和 src 进行异或预算之后送回 dest
|
(4)循环控制指令
指令 | 名称 | 语法 | 描述 |
---|---|---|---|
loop |
计数循环指令 | loop label |
使 ecx 寄存器的值减 1,当 ecx 寄存器的值不为 0 的时候跳转至 lable ,否则执行 loop 之后的语句 |
(5)转移指令
指令 | 名称 | 语法 | 描述 |
---|---|---|---|
jmp |
无条件转移指令 | jmp label |
无条件跳转至标号为 lable 的位置 |
call |
过程调用指令 | call label |
直接调用 lable
|
je |
条件转移指令 | je label |
zf = 1 时跳转至标号为 lable 的位置 |
jne |
条件转移指令 | jne label |
zf = 0 时跳转至标号为 lable 的位置 |