首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【汇编指令1】解锁计算机底层操作的核心密码,从基础指令开启编程智慧之门,洞察数据处理与程序流程掌控奥秘,以简洁代码诠释高效运算逻辑,于数字世界构建强大功能基石,引领深入理解计算机运行机制新征程

【汇编指令1】解锁计算机底层操作的核心密码,从基础指令开启编程智慧之门,洞察数据处理与程序流程掌控奥秘,以简洁代码诠释高效运算逻辑,于数字世界构建强大功能基石,引领深入理解计算机运行机制新征程

作者头像
逆向-落叶
发布2024-12-25 10:17:00
发布2024-12-25 10:17:00
44200
代码可运行
举报
文章被收录于专栏:C++C++
运行总次数:0
代码可运行

寄存器

寄存器是 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

通用寄存器
  • 8 位通用寄存器(可扩展使用,在 16 位、32 位、64 位模式下有不同表现形式)
    • AL、BL、CL、DL、AH、BH、CH、DH:这些都是 8 位的通用寄存器,其中ALBLCLDL为低 8 位寄存器,AHBHCHDH分别对应AXBXCXDX这几个 16 位寄存器的高 8 位(例如AXAHAL组成)。它们常用于存放临时的字节数据,比如在进行 8 位的算术运算(如 “ADD AL, 5”,将立即数 5 与AL寄存器中的 8 位数值相加)、逻辑运算(如 “AND AL, 0xF0”,对AL中的值按位与操作)以及数据传输(如 “MOV BL, [SI]”,把SI指向的内存单元中的 8 位数据传送到BL寄存器)等操作时使用。
  • 16 位通用寄存器(在 32 位和 64 位模式下也可按特定规则使用)
    • AX、BX、CX、DX
      • AX(Accumulator Register):累加器寄存器,是很多算术和逻辑运算的默认操作数和结果存放位置。例如,在执行无符号乘法指令 “MUL” 时,对于字节乘法(操作数为 8 位),AL存放一个操作数,结果的低 8 位存回AL,高 8 位存放在AH中;对于字乘法(操作数为 16 位),AX存放一个操作数,结果的低 16 位存回AX,高 16 位存放在另一个寄存器(如DX,在特定乘法运算场景下配合使用)中。同时,在输入输出操作中也常使用AX来传递数据等。
      • BX(Base Register):基址寄存器,常作为内存寻址时的基地址寄存器。比如在访问数组元素时,若数组起始地址存放在BX中,结合元素的索引(可以是其他寄存器或立即数),通过一定的寻址方式(如 “MOV AX, [BX + SI]”,SI存放索引值,这里就是把数组中对应元素的数据传送到AX寄存器)就能访问数组中的各个元素,也用于指向内存中的数据结构等,方便数据的读写操作。
      • CX(Count Register):计数寄存器,在循环操作、重复的数据串操作等场景中发挥关键作用。例如,在使用带有 “REP” 前缀的指令(如 “REP MOVSB” 用于复制字节串)时,CX中存放的就是重复操作的次数,每执行一次循环或者数据串操作,CX的值会自动减 1,直到CX变为 0,操作结束。
      • DX(Data Register):数据寄存器,除了在乘法运算中与AX配合存放结果(如上述乘法场景中存放高 16 位数据)外,在除法运算中也有相应作用。比如在无符号除法 “DIV” 指令执行时,对于字除法(操作数为 16 位),被除数放在DXAX组成的 32 位数据中(DX存放高 16 位,AX存放低 16 位),商存回AX,余数存放在DX中,并且在一些输入输出端口操作等方面也会用到它。
    • SI、DI
      • SI(Source Index Register):源索引寄存器,主要用于数据串操作指令中指示源操作数的内存地址位置。例如在执行 “MOVS” 系列指令(如 “MOVSB” 复制字节串、“MOVSW” 复制字串等)时,SI指向要复制、移动的源数据所在的内存起始位置,方便数据从源地址处进行相应操作,是数据传输过程中确定源数据来源的重要寄存器。
      • DI(Destination Index Register):目的索引寄存器,与SI相对应,用于指定数据串操作指令中目的操作数的内存地址。比如在 “MOVS” 系列指令执行时,DI指向目标数据要存放的内存起始位置,确保数据能准确复制、移动到相应的目标地址处,实现数据在内存不同位置间的转移等操作。
    • BP、SP
      • BP(Base Pointer Register):基址指针寄存器,常被用于函数调用过程中管理函数的栈帧结构。在函数内部,可以通过BP以及相对BP的偏移量来访问函数的参数、局部变量等数据在堆栈中的位置。例如,在一个函数中,若局部变量存放在相对于BP偏移量为-4的堆栈位置,可通过 “MOV AX, [BP - 4]” 指令来获取该局部变量的值并存放到AX寄存器中,它帮助构建了函数内部数据在堆栈中的组织框架。
      • SP(Stack Pointer Register):堆栈指针寄存器,它始终指向堆栈的顶部位置,随着数据的压入(通过 “PUSH” 指令)和弹出(通过 “POP” 指令),SP的值会相应地减小或增大(以字节为单位,16 位数据压入或弹出时,SP变化 2 个字节;32 位数据压入或弹出时,SP变化 4 个字节等),用于精确控制堆栈中数据的存储和访问顺序,保证堆栈按照 “后进先出” 的原则正常运作。
  • 32 位通用寄存器(在 64 位模式下也有相应的兼容使用方式)
    • EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP:这些 32 位通用寄存器是对应 16 位通用寄存器的扩展,功能上与 16 位寄存器类似,但能处理 32 位的数据。例如,在进行 32 位的算术运算(如 “ADD EAX, EBX”,将EBX寄存器中的 32 位数值与EAX寄存器中的 32 位数值相加,结果存回EAX)、逻辑运算、数据传输以及内存寻址等操作时会使用它们。在函数调用中,参数传递、返回值存放等也常涉及到这些 32 位寄存器,并且在 32 位系统环境下的编程中,它们是主要的操作对象,能满足更复杂的数据处理和内存管理需求。
  • 64 位通用寄存器(主要用于 64 位架构编程)
    • RAX、RBX、RCX、RDX、RSI、RDI、RBP、RSP:如前文所述,它们在 64 位架构下功能更加强大且应用广泛。例如,RAX作为累加器寄存器用于存放算术和逻辑运算结果以及函数返回值;RBX可充当稳定的基址寄存器用于内存寻址;RCX在循环等操作中作为计数器;RDX配合RAX在乘法、除法等运算中处理高位数据;RSIRDI分别用于数据串操作中的源地址和目的地址指示;RBP管理函数栈帧结构;RSP控制堆栈指针,这些寄存器为 64 位环境下的高精度计算、大容量内存访问以及复杂的程序逻辑控制等提供了有力支持。

32位的通用寄存器

32 位寄存器是 32 位 CPU 中的重要组成部分,以下是其主要类型及介绍:

数据寄存器

  • EAX:累加寄存器,常用于运算,是乘、除、输入 / 输出等操作的常用寄存器 。
  • EBX:基地址寄存器,可作为存储器指针来使用 。
  • ECX:计数寄存器,在循环和字符串操作中用于控制循环次数 。
  • EDX:数据寄存器,在乘、除运算时可作为默认操作数参与运算,也可存放 I/O 的端口地址

变址寄存器

  • ESI:在内存操作指令中常作为 “源地址指针” 使用 。
  • EDI:在内存操作指令中作为 “目的地址” 使用 。

指针寄存器

  • ESP:堆栈指针寄存器,用于指向堆栈顶部,在 32 位平台上,ESP 每次减少 4 字节 。
  • EBP:基址指针寄存器,常被用作高级语言函数调用的 “框架指针” 。

指令指针寄存器

EIP 用于存放下次将要执行的指令在代码段的偏移量 。

标志寄存器

如 EFLAGS,包含进位标志 CF、奇偶标志 PF、辅助进位标志 AF、零标志 ZF、符号标志 SF、溢出标志 OF 等,用于反映运算结果的相关状态 。


汇编指令

工具:OD动态调试器

mov指令传送数据

mov可以在寄存器之间、寄存器与内存之间、立即数与寄存器或内存之间传送数据。

我们可以才看到0019ff70的数值传给了ebx寄存器。

代码语言:javascript
代码运行次数:0
运行
复制
mov eax,[19ff70]

也可以把eax寄存器的值给edx寄存器。

代码语言:javascript
代码运行次数:0
运行
复制
mov ebx,eax

也是可以把寄存器的值给某个地址。

代码语言:javascript
代码运行次数:0
运行
复制
mov [19ff74],eax

也可以给个立即数,我这里给了个1

代码语言:javascript
代码运行次数:0
运行
复制
mov eax,1
movsd内存区域复制数据(32位)

movsd可以复制内存4个字节到内存

是传送双字(32 位)它主要用于在内存的两个区域之间复制数据,每次操作传送一个双字。例如,在将一个数组中的双精度浮点数复制到另一个数组时很有用。

前面这个dword代表的就是4个字节,

代码语言:javascript
代码运行次数:0
运行
复制
movd [19ff74],eax

下面我们还没有执行,我们可以看到0019ff90的数值都是0。

执行后面我们可以看到数值都被复制下来了,寄存器的地址会加4个字节。


movsw内存区域复制数据(16位)

movsw可以复制内存2个字节到内存

用于传送字(16 位)串,每次操作会从源地址向目标地址传送一个 16 位的数据单元。

原来的esi是0019ff74,edi是0019ff96,复制后都加了2个字节。

我们可以看到,FCC9复制下来了。

代码语言:javascript
代码运行次数:0
运行
复制
movsw edi,esi

movsb内存区域复制数据(8位)

用于传送字节(8 位)串。它每次传送一个字节的数据,主要用于处理字节类型的数据复制

movsb可以复制内存1个字节到内存

代码语言:javascript
代码运行次数:0
运行
复制
movsb edi,esi
修改 标志寄存器中的D值

默认是0,执行一条dword指令就会加4字节,修改为1,执行一条dword指令就会减去4字节。


stos指令将EAX的值存储到【EDI】指向的内存单元

在 x86 架构下,常见的STOS指令格式有STOSBSTOSWSTOSD,它们的区别主要在于每次操作向内存中存储的数据大小不同:

  • STOSB(Store Byte String)
    • 格式:STOSB 或者 STOS BYTE PTR [EDI]等(不同汇编器的具体语法可能略有差异)。
    • 操作:从AL寄存器中取出一个字节的数据,并将其存储到由EDI寄存器指向的内存单元中。然后,根据方向标志DF(Direction Flag)的值来自动更新EDI寄存器的值,若DF = 0(正向操作,默认通常为此设置),EDI会自动增加 1;若DF = 1(反向操作),EDI会自动减少 1,以指向下一个(或上一个)要操作的内存单元。
    • 示例:假设AL寄存器中存放的字节数据是 0x30EDI指向内存地址为 0x1000 的单元,执行 STOSB 指令后,会将 0x30 存入内存地址 0x1000 处,若 DF = 0EDI 会变为 0x1001,准备好下一次存储操作指向新的内存单元。
  • STOSW(Store Word String)
    • 格式:STOSW 或者 STOS WORD PTR [EDI]等。
    • 操作:从AX寄存器中取出一个字(16 位)的数据,存储到EDI指向的内存单元中。同样根据DF的值来更新EDI寄存器,若DF = 0EDI会自动增加 2;若DF = 1EDI会自动减少 2。
    • 示例:假如AX寄存器中的值为 0x1234EDI指向内存地址 0x2000,执行 STOSW 指令后, 0x1234 会被存入 0x2000 地址处,在 DF = 0 时,EDI 随后变为 0x2002
  • STOSD(Store Double Word String)
    • 格式:STOSD 或者 STOS DWORD PTR [EDI]等。
    • 操作:从EAX寄存器中取出一个双字(32 位)的数据,存储到EDI指向的内存单元中,再依据DF来更新EDI指针,若DF = 0EDI增加 4;若DF = 1EDI减少 4。
    • 示例:若 EAX 寄存器的值为 0x11223344EDI 指向内存地址 0x3000,执行 STOSD 指令后, 0x11223344 存入 0x3000 处,当 DF = 0 时,EDI 变为 0x3004

    下面我们执行后,eax的值11223344会存储到edi指向的内存单元。

代码语言:javascript
代码运行次数:0
运行
复制
stos edi

没执行前:

执行后:

DF = 0EDI增加 4。


add加法和一些运算符

ADD指令的核心功能是将两个操作数相加,并将结果存放在目标操作数中。它能够实现不同类型数据的加法运算,比如字节、字、双字等不同长度的数据相加,具体取决于所使用的操作数的类型和寄存器、内存单元的设定。

  • 格式ADD 目标操作数, 源操作数。例如:ADD AX, BX,这里AX是目标操作数,BX是源操作数,指令会将BX中的值与AX中的值相加,然后把结果存回AX中。

寄存器与立即数相加

下面eax寄存器的数值00000000加一个立即数10,最后得出来的值存到目标操作数。

代码语言:javascript
代码运行次数:0
运行
复制
add eax,10

下面我们执行后,我们可以看到eax变成00000010了。


寄存器与寄存器相加

eax原来是00000010,ecx是00000020,相加后就是00000030,存放目标操作数。

代码语言:javascript
代码运行次数:0
运行
复制
​​​​​​​add eax,ecx

内存单元与寄存器相加

下面,内存单元的数值是00000010,eax的数值是00000030,相加后是00000040,存放到目标操作数,就是这个内存单元的地址。

执行后, 内存单元的数值是00000040。

下面都和add加法的使用方法一样

sub减法

“SUB”指令用于从目标操作数中减去源操作数,并将所得的结果存放在目标操作数中,以此实现对不同类型数据(如字节、字、双字等)的减法运算,具体的数据长度取决于所选用的操作数类型以及对应的寄存器、内存单元设定。

格式为:“SUB” 目标操作数, 源操作数。例如:“SUB ECX, EDX”,其中“ECX”是目标操作数,“EDX”是源操作数,该指令会用“ECX”中的值减去“EDX”中的值,然后把结果存回“ECX”中。

and与运算

“And”指令对两个操作数按位进行逻辑与运算,其运算规则是:只有当参与运算的两个对应位都为1时,结果位才为1;只要对应的两位中有一位为0,结果位就为0。运算结束后,结果会存放在目标操作数中(如果目标操作数是寄存器,就覆盖寄存器原来的值;如果是内存单元,则更新内存单元中的数据)。

格式:“AND” 目标操作数, 源操作数。例如:“AND EAX, EBX”,这里“EAX”是目标操作数,“EBX”是源操作数,指令会对“EAX”和“EBX”中的每一位按上述逻辑与运算规则进行操作,然后把运算结果存回“EAX”中。

OR或运算

“OR”指令按位对两个操作数进行逻辑或运算,其运算规则为:只要参与运算的两个对应位中有一位为1,结果位就为1;只有当两个对应位都为0时,结果位才为0。运算结束后,结果会存放在目标操作数中(如果目标操作数是寄存器,就覆盖寄存器原来的值;如果是内存单元,则更新内存单元中的数据)。

格式:“OR” 目标操作数, 源操作数。例如:“OR EAX, EBX”,这里“EAX”是目标操作数,“EBX”是源操作数,指令会对“EAX”和“EBX”中的每一位按上述逻辑或运算规则进行操作,然后把运算结果存回“EAX”中。


XOR异或运算

“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压入堆栈使用。

【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】将堆栈顶部的数据存储到寄存器/内存

pop指令与push指令紧密相关,它主要用于从堆栈中弹出数据。

基本功能

POP指令的核心作用是将堆栈顶部的数据取出,并传送到指定的操作数中,同时会相应地调整堆栈指针,使堆栈指针指向新的堆栈顶部位置,以保证堆栈数据结构遵循 “后进先出”(LIFO)的原则进行操作。

格式POP 操作数。操作数可以是寄存器或者内存单元,例如:POP EAX,表示从堆栈中弹出数据存入寄存器EAX中;POP [0x1234],则是把堆栈中的数据弹出并存入内存地址为0x1234的单元中。

pop堆栈顶部的数据取出,存放到eax寄存器中,然后esp加4,更新地址使堆栈顶部的指针指向新的堆栈顶部位置。

执行后,把0019ff74地址的值存放到eax寄存器中了。然后esp加4,更新地址使堆栈顶部的指针指向新的堆栈顶部位置。

原来的0019ff74地址有数值不用担心,push新的数据会覆盖掉的。


也是可以把堆栈顶部的数据取出,存放到堆栈的某个内存地址中。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-12-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 寄存器
    • 通用寄存器
  • 32位的通用寄存器
    • 数据寄存器
    • 变址寄存器
    • 指针寄存器
    • 指令指针寄存器
    • 标志寄存器
  • 汇编指令
    • mov指令传送数据
      • movsd内存区域复制数据(32位)
      • movsw内存区域复制数据(16位)
      • movsb内存区域复制数据(8位)
      • 修改 标志寄存器中的D值
    • stos指令将EAX的值存储到【EDI】指向的内存单元
    • add加法和一些运算符
    • sub减法
    • and与运算
    • OR或运算
    • XOR异或运算
  • 关于堆栈和堆栈的指令
    • 【push】指令向堆栈压入数据
      • 注意事项
    • 【pop】将堆栈顶部的数据存储到寄存器/内存
      • 基本功能
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档