寄存器是 CPU 内部用于暂存数据、地址以及指令执行过程中相关信息的高速存储单元,它们在汇编编程中起着至关重要的作用。不同的处理器架构下,寄存器的种类、数量和功能会有所不同
通用寄存器 | |||
---|---|---|---|
64位 | 32位 | 16位 | 8位 |
RAX | EAX | AX | AL |
RCX | ECX | CX | CL |
RDX | EDX | DX | DL |
RBX | EBX | BX | BL |
RSP | ESP | SP | AL |
RBP | EBP | BP | CH |
RSI | ESI | SI | DH |
RDI | EDI | DI | BH |
AL
、BL
、CL
、DL
为低 8 位寄存器,AH
、BH
、CH
、DH
分别对应AX
、BX
、CX
、DX
这几个 16 位寄存器的高 8 位(例如AX
由AH
和AL
组成)。它们常用于存放临时的字节数据,比如在进行 8 位的算术运算(如 “ADD AL, 5”,将立即数 5 与AL
寄存器中的 8 位数值相加)、逻辑运算(如 “AND AL, 0xF0”,对AL
中的值按位与操作)以及数据传输(如 “MOV BL, [SI]”,把SI
指向的内存单元中的 8 位数据传送到BL
寄存器)等操作时使用。AL
存放一个操作数,结果的低 8 位存回AL
,高 8 位存放在AH
中;对于字乘法(操作数为 16 位),AX
存放一个操作数,结果的低 16 位存回AX
,高 16 位存放在另一个寄存器(如DX
,在特定乘法运算场景下配合使用)中。同时,在输入输出操作中也常使用AX
来传递数据等。BX
中,结合元素的索引(可以是其他寄存器或立即数),通过一定的寻址方式(如 “MOV AX, [BX + SI]”,SI
存放索引值,这里就是把数组中对应元素的数据传送到AX
寄存器)就能访问数组中的各个元素,也用于指向内存中的数据结构等,方便数据的读写操作。CX
中存放的就是重复操作的次数,每执行一次循环或者数据串操作,CX
的值会自动减 1,直到CX
变为 0,操作结束。AX
配合存放结果(如上述乘法场景中存放高 16 位数据)外,在除法运算中也有相应作用。比如在无符号除法 “DIV” 指令执行时,对于字除法(操作数为 16 位),被除数放在DX
和AX
组成的 32 位数据中(DX
存放高 16 位,AX
存放低 16 位),商存回AX
,余数存放在DX
中,并且在一些输入输出端口操作等方面也会用到它。SI
指向要复制、移动的源数据所在的内存起始位置,方便数据从源地址处进行相应操作,是数据传输过程中确定源数据来源的重要寄存器。SI
相对应,用于指定数据串操作指令中目的操作数的内存地址。比如在 “MOVS” 系列指令执行时,DI
指向目标数据要存放的内存起始位置,确保数据能准确复制、移动到相应的目标地址处,实现数据在内存不同位置间的转移等操作。BP
以及相对BP
的偏移量来访问函数的参数、局部变量等数据在堆栈中的位置。例如,在一个函数中,若局部变量存放在相对于BP
偏移量为-4
的堆栈位置,可通过 “MOV AX, [BP - 4]” 指令来获取该局部变量的值并存放到AX
寄存器中,它帮助构建了函数内部数据在堆栈中的组织框架。SP
的值会相应地减小或增大(以字节为单位,16 位数据压入或弹出时,SP
变化 2 个字节;32 位数据压入或弹出时,SP
变化 4 个字节等),用于精确控制堆栈中数据的存储和访问顺序,保证堆栈按照 “后进先出” 的原则正常运作。EBX
寄存器中的 32 位数值与EAX
寄存器中的 32 位数值相加,结果存回EAX
)、逻辑运算、数据传输以及内存寻址等操作时会使用它们。在函数调用中,参数传递、返回值存放等也常涉及到这些 32 位寄存器,并且在 32 位系统环境下的编程中,它们是主要的操作对象,能满足更复杂的数据处理和内存管理需求。RAX
作为累加器寄存器用于存放算术和逻辑运算结果以及函数返回值;RBX
可充当稳定的基址寄存器用于内存寻址;RCX
在循环等操作中作为计数器;RDX
配合RAX
在乘法、除法等运算中处理高位数据;RSI
和RDI
分别用于数据串操作中的源地址和目的地址指示;RBP
管理函数栈帧结构;RSP
控制堆栈指针,这些寄存器为 64 位环境下的高精度计算、大容量内存访问以及复杂的程序逻辑控制等提供了有力支持。32 位寄存器是 32 位 CPU 中的重要组成部分,以下是其主要类型及介绍:
EIP 用于存放下次将要执行的指令在代码段的偏移量 。
如 EFLAGS,包含进位标志 CF、奇偶标志 PF、辅助进位标志 AF、零标志 ZF、符号标志 SF、溢出标志 OF 等,用于反映运算结果的相关状态 。
工具:OD动态调试器
mov可以在寄存器之间、寄存器与内存之间、立即数与寄存器或内存之间传送数据。
我们可以才看到0019ff70的数值传给了ebx寄存器。
mov eax,[19ff70]
也可以把eax寄存器的值给edx寄存器。
mov ebx,eax
也是可以把寄存器的值给某个地址。
mov [19ff74],eax
也可以给个立即数,我这里给了个1
mov eax,1
movsd可以复制内存4个字节到内存
是传送双字(32 位)它主要用于在内存的两个区域之间复制数据,每次操作传送一个双字。例如,在将一个数组中的双精度浮点数复制到另一个数组时很有用。
前面这个dword代表的就是4个字节,
movd [19ff74],eax
下面我们还没有执行,我们可以看到0019ff90的数值都是0。
执行后面我们可以看到数值都被复制下来了,寄存器的地址会加4个字节。
movsw可以复制内存2个字节到内存
用于传送字(16 位)串,每次操作会从源地址向目标地址传送一个 16 位的数据单元。
原来的esi是0019ff74,edi是0019ff96,复制后都加了2个字节。
我们可以看到,FCC9复制下来了。
movsw edi,esi
用于传送字节(8 位)串。它每次传送一个字节的数据,主要用于处理字节类型的数据复制
movsb可以复制内存1个字节到内存
movsb edi,esi
默认是0,执行一条dword指令就会加4字节,修改为1,执行一条dword指令就会减去4字节。
在 x86 架构下,常见的STOS
指令格式有STOSB
、STOSW
和STOSD
,它们的区别主要在于每次操作向内存中存储的数据大小不同:
STOSB
(Store Byte String): STOSB
或者 STOS BYTE PTR [EDI]
等(不同汇编器的具体语法可能略有差异)。AL
寄存器中取出一个字节的数据,并将其存储到由EDI
寄存器指向的内存单元中。然后,根据方向标志DF
(Direction Flag)的值来自动更新EDI
寄存器的值,若DF = 0
(正向操作,默认通常为此设置),EDI
会自动增加 1;若DF = 1
(反向操作),EDI
会自动减少 1,以指向下一个(或上一个)要操作的内存单元。AL
寄存器中存放的字节数据是 0x30
,EDI
指向内存地址为 0x1000
的单元,执行 STOSB
指令后,会将 0x30
存入内存地址 0x1000
处,若 DF = 0
,EDI
会变为 0x1001
,准备好下一次存储操作指向新的内存单元。STOSW
(Store Word String): STOSW
或者 STOS WORD PTR [EDI]
等。AX
寄存器中取出一个字(16 位)的数据,存储到EDI
指向的内存单元中。同样根据DF
的值来更新EDI
寄存器,若DF = 0
,EDI
会自动增加 2;若DF = 1
,EDI
会自动减少 2。AX
寄存器中的值为 0x1234
,EDI
指向内存地址 0x2000
,执行 STOSW
指令后, 0x1234
会被存入 0x2000
地址处,在 DF = 0
时,EDI
随后变为 0x2002
。STOSD
(Store Double Word String): STOSD
或者 STOS DWORD PTR [EDI]
等。EAX
寄存器中取出一个双字(32 位)的数据,存储到EDI
指向的内存单元中,再依据DF
来更新EDI
指针,若DF = 0
,EDI
增加 4;若DF = 1
,EDI
减少 4。EAX
寄存器的值为 0x11223344
,EDI
指向内存地址 0x3000
,执行 STOSD
指令后, 0x11223344
存入 0x3000
处,当 DF = 0
时,EDI
变为 0x3004
。下面我们执行后,eax的值11223344会存储到edi指向的内存单元。
stos edi
没执行前:
执行后:
DF = 0
,EDI
增加 4。
ADD
指令的核心功能是将两个操作数相加,并将结果存放在目标操作数中。它能够实现不同类型数据的加法运算,比如字节、字、双字等不同长度的数据相加,具体取决于所使用的操作数的类型和寄存器、内存单元的设定。
ADD
目标操作数, 源操作数。例如:ADD AX, BX
,这里AX
是目标操作数,BX
是源操作数,指令会将BX
中的值与AX
中的值相加,然后把结果存回AX
中。寄存器与立即数相加
下面eax寄存器的数值00000000加一个立即数10,最后得出来的值存到目标操作数。
add eax,10
下面我们执行后,我们可以看到eax变成00000010了。
寄存器与寄存器相加
eax原来是00000010,ecx是00000020,相加后就是00000030,存放目标操作数。
add eax,ecx
内存单元与寄存器相加
下面,内存单元的数值是00000010,eax的数值是00000030,相加后是00000040,存放到目标操作数,就是这个内存单元的地址。
执行后, 内存单元的数值是00000040。
下面都和add加法的使用方法一样
“SUB”指令用于从目标操作数中减去源操作数,并将所得的结果存放在目标操作数中,以此实现对不同类型数据(如字节、字、双字等)的减法运算,具体的数据长度取决于所选用的操作数类型以及对应的寄存器、内存单元设定。
格式为:“SUB” 目标操作数, 源操作数。例如:“SUB ECX, EDX”,其中“ECX”是目标操作数,“EDX”是源操作数,该指令会用“ECX”中的值减去“EDX”中的值,然后把结果存回“ECX”中。
“And”指令对两个操作数按位进行逻辑与运算,其运算规则是:只有当参与运算的两个对应位都为1时,结果位才为1;只要对应的两位中有一位为0,结果位就为0。运算结束后,结果会存放在目标操作数中(如果目标操作数是寄存器,就覆盖寄存器原来的值;如果是内存单元,则更新内存单元中的数据)。
格式:“AND” 目标操作数, 源操作数。例如:“AND EAX, EBX”,这里“EAX”是目标操作数,“EBX”是源操作数,指令会对“EAX”和“EBX”中的每一位按上述逻辑与运算规则进行操作,然后把运算结果存回“EAX”中。
“OR”指令按位对两个操作数进行逻辑或运算,其运算规则为:只要参与运算的两个对应位中有一位为1,结果位就为1;只有当两个对应位都为0时,结果位才为0。运算结束后,结果会存放在目标操作数中(如果目标操作数是寄存器,就覆盖寄存器原来的值;如果是内存单元,则更新内存单元中的数据)。
格式:“OR” 目标操作数, 源操作数。例如:“OR EAX, EBX”,这里“EAX”是目标操作数,“EBX”是源操作数,指令会对“EAX”和“EBX”中的每一位按上述逻辑或运算规则进行操作,然后把运算结果存回“EAX”中。
“XOR”指令按位对两个操作数进行逻辑异或运算,其运算规则为:当参与运算的两个对应位的值不同(即一个为0,另一个为1)时,结果位的值为1;当两个对应位的值相同(都为0或者都为1)时,结果位的值为0。运算结束后,结果会存放在目标操作数中(如果目标操作数是寄存器,就覆盖寄存器原来的值;如果是内存单元,则更新内存单元中的数据)。
格式:“XOR” 目标操作数, 源操作数。例如:“XOR EAX, EBX”,这里“EAX”是目标操作数,“EBX”是源操作数,指令会对“EAX”和“EBX”中的每一位按上述逻辑异或运算规则进行操作,然后把运算结果存回“EAX”中。
NOT取反
“NOT” 指令的功能是将操作数的每一位进行取反,也就是把操作数中的 0 位变为 1,1 位变为 0,从而得到取反后的结果。该结果会存放在目标操作数所在的位置(如果目标操作数是寄存器,就覆盖寄存器原来的值;如果是内存单元,则更新内存单元中的数据)。需要注意的是,“NOT” 指令是单操作数指令,它只作用于一个指定的操作数,不像 “AND”“OR”“XOR” 等逻辑运算指令需要两个操作数进行运算。
格式:“NOT” 操作数。例如:“NOT EAX”,这里 “EAX” 就是操作数,指令会对寄存器 “EAX” 中的每一位按位取反,然后把取反后的结果存回 “EAX” 中。
这讲的堆栈是操作系统在程序启动的时候分配好的空间,让程序执行的时候用的。
从低部开始到顶部结束。
ESP:堆栈指针寄存器,用于指向堆栈顶部,在 32 位平台上,ESP 每次减少 4 字节 。
我们可以查看ESP的地址,esp是用于指向堆栈顶部。
往上哪些00000000都是没有用过的,所以需要往下执行汇编,来用push压入堆栈使用。
修改堆栈指针ESP寄存器
“PUSH” 指令属于堆栈操作指令,其核心功能是将指定的操作数压入到堆栈(Stack)中。堆栈在计算机内存中是一块特定的存储区域,它按照 “后进先出”(Last In, First Out,简称 LIFO)的原则来管理数据,就好比一个只允许从一端放入和取出物品的容器,最后放入的物品会最先被取出来。
下面,push把eax压入到堆栈中,然后esp更新地址,也就是0019ff74就会减4就会指向0019ff70这个地址存放的就是push后的数据。
执行后,我们可以看到eax的11111111压入到了堆栈0019ff70中。
下面我们也可以将堆栈中的某个内存,进行push压入到堆栈。
0019ff94这个地址的数值是9999999,我们可以看到也是压入到了堆栈中,然后esp更新地址,就是减4指向刚刚压入的堆栈地址。
pop指令与push指令紧密相关,它主要用于从堆栈中弹出数据。
POP
指令的核心作用是将堆栈顶部的数据取出,并传送到指定的操作数中,同时会相应地调整堆栈指针,使堆栈指针指向新的堆栈顶部位置,以保证堆栈数据结构遵循 “后进先出”(LIFO)的原则进行操作。
格式:POP
操作数。操作数可以是寄存器或者内存单元,例如:POP EAX,表示从堆栈中弹出数据存入寄存器EAX中;POP [0x1234]
,则是把堆栈中的数据弹出并存入内存地址为0x1234
的单元中。
pop堆栈顶部的数据取出,存放到eax寄存器中,然后esp加4,更新地址使堆栈顶部的指针指向新的堆栈顶部位置。
执行后,把0019ff74地址的值存放到eax寄存器中了。然后esp加4,更新地址使堆栈顶部的指针指向新的堆栈顶部位置。
原来的0019ff74地址有数值不用担心,push新的数据会覆盖掉的。
也是可以把堆栈顶部的数据取出,存放到堆栈的某个内存地址中。