当前位置: 首页 > news >正文

二进制安全-汇编语言-03-寄存器(内存访问)

三、寄存器(内存访问)

KEEP DOING

3.1 内存中字的存储

  • 小端存储:低地址存放低位字节,高地址存放高位字节
  • 大端存储:低地址存放高位字节,高地址存放低位字节

8086CPU中,采用小端存储,高8位存放高位字节,低8位存放低位字节

在内存中存放数据20000H(4E20H)在0,1两个内存单元:

0号单元是低地址,存放低位字节20H;

1号单元是高地址,存放高位字节4EH;

小端存储是从左到右,地址也是从左到右递增

字单元

存放一个字型数据(16位)的内存单元,有两个地址连续的内存单元组成。

高地址内存单元中存放字型数据的高位字节,低地址内存单元中存放字型数据的低位字节。

我们将起始地址为N的字单元简称为N地址字单元。

比如一个字单元由2、3两个内存单元组成,则这个字单元的起始地址为2,我们可以说这是2地址字单元。

内存中的字存储

问题3.1

对于图3.1:

(1)0地址单元中存放的字节型数据是多少?

20H

(2)0地址字单元中存放的字型数据是多少?

4E20H

(3)2地址单元中存放的字节型数据是多少?

12H

(4)2地址字单元中存放的字型数据是多少?

0012H

(5)1地址字单元中存放的字型数据是多少?

124EH

3.2 DS和[address]

CPU要读写一个内存单元时,需要给出这个内存单元的地址

在8086PC中,内存地址由段地址偏移地址组成

因此,8086CPU设计一个DS寄存器,来存放要访问数据的段地址

注:Data Segment register 数据段寄存器

mov bx,1000H
mov ds,bx
mov al, [0]

上面的3条指令将10000H(1000:0)中的数据读到al中

mov指令,可完成两种传送:

  • 将数据直接送入寄存器
  • 将一个寄存器中的内容送入另一个寄存器

mov 寄存器名,内存单元地址

重点:段地址+偏移地址

“[…]”表示一个内存单元,“[…]”中的0表示内存单元的偏移地址

有了偏移地址,那么我们就需要有段地址,才能够定位一个内存单元

指令执行时,8086CPU自动获取DS寄存器中的数据为内存单元的段地址

1、如何用mov指令从10000H中读取数据?

10000H用段地址和偏移地址表示为1000:0,我们先将段地址1000H放入ds,然后用moval,[0]完成传送。mov指令中的[...]说明操作对象是一个内存单元,[ 0 ]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds中,指令执行时,8086CPU会自动从ds中取出。

mov bx,1000H
mov ds,bx

若要用mov al,[0] 完成数据从1000:0单元到al的传送,这条指令执行时,ds中的内容应为段地址1000H,所以在这条指令之前应该将1000H送入ds。

2、如何把一个数据送入寄存器呢?

我们以前用类似“movax,1”这样的指令来完成,从理论上讲,我们可以用相似的方式:mov ds,1000H,来将1000H送入ds。可是,现实并

非如此,8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器,所以movds,1000H这条指令是非法的。

那么如何将1000H送入ds呢?

只好用一个寄存器来进行中转,即先将1000H送入一个一般的寄存器,如bx,再将bx中的内容送入ds。

总结:

要给段寄存器赋值,只能通过一个寄存器作为中转赋值,不能直接赋值数据。

段寄存器ds中有段地址了,再给一个偏移地址[xxx]就能够访问到对应的内存单元了。

问题3.2

写几条指令,将al中的数据送入内存单元10000H中,思考后看分析。

mov bx,1000
mov ds,bx
mov [0],al

思考:

先确定好段地址,把段地址赋值给一个寄存器,然后在赋值给ds寄存器,

最后利用[xxx]确定偏移地址,就可以对这个地址进行操作。

3.3 字的传送

前面我们用mov指令在寄存器和内存之间进行字节型数据的传送。

因为8086CPU是16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是说可以一次性传送一个字。

只要在mov指令中给出16位的寄存器就可以进行16位数据的传送了。

比如:

mov ax,1000H
mov ds,bx
mov ax,[0]		;1000:0处的字型数据送入ax
mov [0],cx		;cx中的16位数据送到1000:0处
问题3.3

内存中的情况如图3.2所示,写出下面的指令执行后寄存器ax,bx,cx中的值。

mov ax,1000H
mov ds,ax
mov ax,[0]
mov bx,[2]
mov cx,[1]
add bx,[1]
add cx,[2]

内存情况示意(1)

指令执行后相关寄存器中的内容说明
mov ax,1000Hax=1000H
mov ds,axds=1000H前两条指令的目的就是为了将ds设置为1000H
mov ax,[0]ax=1123H将1000:0处存放的字型数据送入ax,
1000:1单元存放字型数据的高8位:11H
1000:0单元存放字型数据的低8位:23H
所以1000:0处存放的字型数据为1123H
指令执行时,字型数据的高8位送入ah,字型数据的低8位送入al
mov bx,[2]bx=6622H以下同上原理
mov cx,[1]cx=2211H
add bx,[1]bx=2211+6622=8833H
add cx,[2]cx=6622+2211=8833H
问题3.4

内存中的情况如图3.3所示,写出下面的指令执行后内存中的值。

内存情况示意(2)

mov ax,1000H
mov ds,ax
mov ax,11316
mov [0],ax
mov bx,[0]
sub bx,[2]
mov [2],bx

说明

指令执行后相关寄存器中的内容说明
mov ax,1000Hax=1000H
mov ds,axds=1000H将ds设置为1000H
mov ax,11316ax=2C34H十进制11316,十六进制2C34H
mov [0],ax10000H内存单元=34,10001H内存单元=2C
mov bx,[0]bx=2C34H
sub bx,[2]bx=bx-1122H=1B12Hsub减法指令
mov [2],bx10002H内存单元=12,10003H内存单元=1B

3.4 mov、add、sub指令

在前面使用到的mov指令可以做的一些操作:

mov 寄存器,数据			   比如: mov ax,8
mov 寄存器,寄存器			  比如:mov ax,bx
mov 寄存器,内存单元		 比如:mov ax,[0]
mov 内存单元,寄存器		 比如: mov [0],ax
mov 段寄存器,寄存器		 比如:mov ds,ax

接下来尝试:mov 寄存器,段寄存器

mov 寄存器,段寄存器        比如:mov ax,ds可以在dosbox上验证
-r cs 
1000
-r ip
0
a 1000:0
1000:0000 mov bx,1314
1000:0003 mov ds,bx
1000:0006 mov ax,ds验证可以实现

继续尝试:mov 内存单元,寄存器

mov 内存单元,寄存器	比如:mov [0],csmov ax,1000H
mov ds,ax
mov [0],cs和上面类似:在加一个查看某个内存单元的数据的命令
d 1000:0

最后尝试:mov 段寄存器,内存单元

mov ax,1000H
mov ds,ax
mov ds,[0]最后验证是可以的

add和sub指令同mov一样,都有两个操作对象。它们也可以有以下几种形式。

add 寄存器,数据 				 比如:add ax,8
add 寄存器,寄存器			    比如:add ax,bx
add 寄存器,内存单元		    比如:add ax,[o]
add 内存单元,寄存器		    比如:add [o],ax
sub 寄存器,数据			     比如:sub ax,9
sub 寄存器,寄存器				比如:sub ax,bx
sub	寄存器,内存单元		    比如:sub ax,[0]
sub 内存单元,寄存器		  	比如:sub[o],ax

那么add,sub指令可以对ds进行操作吗?

不行,不合法

add ds,ax 段寄存器不能参与算术运算

add ax,ds 段寄存器不能作为源操作数

3.5 数据段

内存的数据其实都是二进制数据,也不存在某个数据段

但是为了方便编写程序,那我们就可以定义某个内存单位区间作为数据段

数据段是逻辑上的概念,真实不存在

比如,将123B0H~123B9H的内存单元定义为数据段。现在要累加这个数据段中的前3个单元中的数据,代码如下。

mov ax,123BH
mov ds,ax								;将123BH送入ds中,作为数据段的段地址
mov al,0								;用al存放累加结果
add al,[0]								;将数据段第一个单元(偏移地址为0)中的数值加到a1中
add al,[1]								;将数据段第二个单元(偏移地址为1)中的数值加到a1中
add al, [2]								;将数据段第三个单元(偏移地址为2)中的数值加到a1中
问题3.5

写几条指令,累加数据段中的前3个字型数据,思考后看分析。

mov ax,123BH
mov ds,ax
mov ax,0
add ax,[0]
add ax,[2]
add ax,[4]

字型数据,每一个字型数据占用两个单元

小结3.1~3.5

1、字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。

2、用mov指令访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS 寄存器中。

3、[address]表示一个偏移地址为address的内存单元。

4、在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。

5、mov、add、sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。

6、可以根据自己的推测,在Debug中实验指令的新格式。

检测点3.1

(1)在Debug中,用 “ d 0:0 1f ”查看内存,结果如下。

0000:0000 70 80 F0 30 EF 60 30 E2-00 80 80 12 66 20 22 60
0000:0010 62 26 E6 D6 CC 2E 3C 3B-AB BA 00 00 26 06 66 88

下面的程序执行前,AX=0,BX=0,写出每条汇编指令执行完后相关寄存器中的值。

  1. 段地址设置
    • mov ax,1AX=0001H
    • mov ds,axDS=0001H,此时数据段基址为00010HDS*16)。‌‌‌‌1
  2. 内存寻址规则
    • 指令中的偏移地址对应物理地址00010H,即内存0000:0010处的数据62 26(低字节在前),故AX=2662H。‌‌
  3. 逐条指令执行结果‌:
    • mov ax,AX=2662H(从0000:0010读取字数据)。‌‌
    • mov bx,BX=E626H(从0000:0011读取字数据)。‌‌
    • mov ax,bxAX=E626H
    • mov ax,AX=2662H‌‌
    • mov bx,BX=D6E6H(从0000:0012读取)。‌‌
    • add ax,bxAX=FD48H2662H+D6E6H,进位截断)。
    • add ax,AX=2C14HFD48H+2ECCH,结果取低16位)。‌‌
    • mov ax,0AX=0000H
    • mov al,AX=00E6H(仅修改AL,AH保持0)。‌‌
    • mov bx,0BX=0000H
    • mov bl,[000C]BX=0026H(从0000:001C读取字节)。
    • add al,blAX=000CHE6H+26H=0CH,AL为8位)
指令执行后相关寄存器中的内容说明
mov ax,1ax=1
mov ds,axds=1将ds设置为1
mov ax,[0000]ax=26620000:0010处的字型数据赋值给ax
mov bx,[0001]bx=E6260000:0011处的字型数据赋值给bx
mov ax,bxax=E626将bx中的数据赋值到ax
mov ax,[0000]ax=26620000:0010处的字型数据赋值给ax
mov bx,[0002]bx=D6E60000:0012处的字型数据赋值给bx
add ax,bxax=FD482662+D6E6=FD48,进位截断
add ax,[0004]ax=2C14FD48+2ECC=2C14,进位截断,取低位
mov ax,0ax=0000ax直接赋值为0
mov al,[0002]ax=00E6ax的低位al=E6,高位ah=0
mov bx,0bx=0bx直接赋值为0
mov bl,[000C]bx=0026bx的低位bl=E6,高位bh=0
add al,blax=000CE6+26=10C,进位截断

(2)内存中的情况如图3.6所示。

1、各寄存器的初始值:CS=2000H,IP=0,DS=1000H,AX=0,BX=0;

2、写出CPU执行的指令序列(用汇编指令写出)。

3、写出CPU执行每条指令后,CS、IP和相关寄存器中的数值。

再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序?

内存情况示意

CS:IP指令各寄存器的值说明
CS=2000H,IP=0,指向2000:0000mov ax,6622HAX=6622H,BX=0,DS=1000H
CS=2000H,IP=3,指向2000:0003jmp 0ff0:0100AX=6622H,BX=0,DS=1000H0ff0x10+0100=10000H
CS=1000H,IP=0,指向1000:0000mov ax,2000HAX=2000H,BX=0,DS=1000H
CS=1000H,IP=3,指向1000:0003mov ds,axAX=2000H,BX=0,DS=2000H
CS=1000H,IP=5,指向1000:0005mov ax,[0008]AX=C389H,BX=0,DS=2000H
CS=1000H,IP=8,指向1000:0008mov ax,[0002]AX=EA66H,BX=0,DS=2000H
CS=1000H,IP=B,指向1000:000B无指令

理解:

内存中的数据其实都是二进制数据0和1,本质上没有区别

但是人类很聪明,将计算机设计成可以分时段,即指令周期,来把数据分为指令和数据

当指令周期中的取指令周期时,把数据当做指令来操作

当指令周期中的执行周期时,把数据当做操作数来操作

这里作者水平有限,让deepseek来帮助一下吧!

再次体会数据和程序的区别:在物理层面,内存中的所有信息都只是二进制数据(0和1),本质上并无区别。

区分它们的关键在于计算机执行指令的时序。计算机体系结构的精妙之处在于,它将指令的执行划分为不同的时钟周期阶段(最典型的是取指周期和执行周期)。

  • 在取指周期(Fetch Cycle): CPU 将特定内存地址的内容解释为指令(操作码),并将其取入指令寄存器。
  • 在执行周期(Execute Cycle): CPU 根据取到的指令,可能再次访问内存,此时从相应地址读取的内容则被解释为该指令的操作数(数据)

因此,内存中的同一串二进制位是“数据”还是“程序”(指令),完全取决于它在指令执行周期中被访问的时机和上下文。是 CPU 当前的“解读意图”(由硬件时序逻辑控制)赋予了它们不同的角色。

3.6 栈

栈不多说,可以类比弹夹压子弹

栈顶:栈中最上面的(有指针,看怎么定义了)

入栈:将一个新元素方到栈顶

出栈:从栈顶取出一个元素

栈最重要的一句话:后进先出,Last In First Out

3.7 CPU提供的栈机制

现今的CPU中都有栈的设计,8086CPU也不例外。8086CPU提供相关的指令来以栈的方式访问内存空间。

这意味着,在基于8086CPU编程的时候,可以将一段内存当作栈来使用。

8086CPU提供入栈和出栈指令,最基本的两个是PUSH(入栈)POP(出栈)

比如,push ax表示将寄存器ax中的数据送入栈中,pop ax表示从栈顶取出数据送入ax。

8086CPU的入栈和出栈操作都是以为单位进行的。

8086CPU的栈操作

这里值得注意的就是:

字型数据要用到两个内存单元来存放,高地址单元放高8位,低地址单元放低8位。

mov ax,0123H push ax为例,01是高位字节,因此放在位于1000FH的内存单元

​ 23是低位字节,因此放在位于1000EH的内存单元

看懂上述栈操作后,我们这里会产生两个疑惑?

(1)CPU怎么知道10000H~1000FH这段空间被当做是栈来使用呢?

(2)push、pop在执行的时候,必须要知道哪个单元是栈顶,CPU又怎么知道栈顶是哪个呢?

因此,与CS:IP的设计思维一致,CS:IP始终指向下一条要执行的指令的位置

那么,任意时刻,SS:SP指向栈顶元素,同样的是栈段寄存器SS和栈寄存器SP(自编的)

push、pop指令执行时,CPU先去从SS和SP中得到栈顶的地址

官方说法:

SS (Stack Segment Register)是 堆栈段寄存器 ,用于存放堆栈的段地址;

SP (Stack Pointer)是 堆栈指针寄存器 ,用于存放栈顶的偏移地址

下面以书中的例子来看一下push、pop在内存中的体现:

push指令的执行过程

push ax 的执行,由以下两步完成。

(1)SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;

(2)将 ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。

思考:

这里其实和CS:IP很像,可以类比来学习。

CS:IP中CS是段地址,就是基础地址,IP呢?是偏移地址,每次执行一条指令,IP都要自增。从而实现“自动执行”

SS:SP中SS是堆栈段地址,也是基础地址,SP呢?是指针寄存器,始终要指向栈顶。

因此,每次push/pop,SP都需要进行调整,从而达到“自动执行”的效果。

从图中我们可以看出,8086CPU中,入栈时,栈顶从高地址向低地址方向增长。

入栈SP-X,出栈SP+X。不用刻意记住,在脑海里构思一个栈就好。

问题3.6

如果将10000H~1000FH这段空间当作栈,初始状态栈是空的,此时,SS=1000H,SP=?思考后看分析。

其实,思考这个问题,我们要回归到SS:SP的核心:始终指向栈顶

如果10000H~1000FH都是栈空间,且栈为空,那么我们就要找到栈空间中最高地址

但是栈为空,说明在这个最高地址的上面就是栈顶:1000FH + 1H = 10010H

栈空的状态

这里是pop指令执行的操作

pop指令的执行过程

这里会有一个思考:为什么这个pop出去了,2266还在内存单元中呢?

其实,这也是计算机的一个设计思维:

出栈的目的只是为了拿到栈中的一个数据,我只要拷贝到这个数据就可以了

如果我要删除这个数据,我还得给这个内存单元赋值,明显不划算

一般计算机采用的就是,写入时把新数据给它覆盖就行了

不禁思考到,很多时候,删除电脑中的数据文件,真的删除了吗?

还只是改变了指向这个文件的指针,毕竟我不指向他了就等于访问不到他了

如果我需要这片空间写数据,我直接覆盖就好了

就凭这个思维,想必会有很多漏洞或者信息暴露出来,

但是作者水平有限,只能思考到这,后续学习希望能碰上这类手法

3.8 栈顶超界的问题

越界的问题就不多说了,多次入栈,多次出栈,很容易造成栈顶超界的问题。

一旦越界,就可能导致覆盖掉了别的程序的代码或数据,影响很严重。

但是,8086CPU并没有设置相应的限制,或者定义栈空间等

这也能理解,当时计算机还在发展初期,在x86汇编中我们会学到保护模式,

那里应该会有限制

因此,我们可以发现8086CPU的两条核心原则:

  • 当前的栈顶在何处
  • 当前要执行的指令是哪一条

3.9 push、pop指令

push、pop指令是可以在寄存器和内存之间传送数据的

push、pop指令的格式:

push 寄存器	;将一个寄存器中的数据入栈
pop 寄存器		;出栈,用一个寄存器接收出栈的数据push 段寄存器   ;将一个段寄存器中的数据入栈
pop 段寄存器	;出栈,用一个段寄存器接收出栈的数据push 内存单元	;将一个内存单元处的字入栈
pop 内存单元	;出栈,用一个内存单元接收出栈的数据

比如:

mov ax,1000H	
mov ds,ax		;内存单元的段地址要放在ds中
push [0]		;将1000:0处的字压入栈中
pop [2]			;出栈,出栈的数据送入1000:2处

指令执行时,CPU要知道内存单元地址,可以在push、pop指令中只给出内存单元的偏移地址,段地址在指令执行时,CPU从ds中获得。

问题3.7

编程,将10000H~1000FH这段空间当作栈,初始状态栈是空的,将AX、BX、DS中的数据入栈。

mov ax,1000H
mov ss,ax				;设置栈的段地址,SS=1000H,不能直接向段寄存器SS中送入数据,所以用ax中转,和ds一样
mov sp,0010H			;设置栈顶的偏移地址,因栈为空,所以sp=0010H。这里设置栈顶地址。
push ax
push bx
push ds
问题3.8

编程:

(1)将10000H~1000FH这段空间当作栈,初始状态栈是空的;

(2)设置AX=001AH,BX=001BH;

(3)将AX、BX中的数据入栈;

(4)然后将AX、BX清零;

(5)从栈中恢复AX、BX原来的内容。

mov ax,1000H
mov ss,ax
mov sp,0010H		;设置栈顶地址
mov ax,001AH
mov bx,001BH
push ax
push bx				;把ax,bx压入栈中
mov ax,0
mov bx,0			;把ax,bx清零
pop bx
pop ax				;这里注意先pop bx,再pop ax,后进先出原则
问题3.9

编程:

(1)将10000H~1000FH这段空间当作栈,初始状态栈是空的;
(2)设置AX=001AH,BX=001BH;
(3)利用栈,交换AX和BX中的数据。

mov ax,1000H
mov ss,ax
mov sp,0010H		;设置栈顶地址
mov ax,001AH
mov bx,001BH
push ax
push bx				;把ax,bx压入栈中
pop ax
pop bx				;先pop ax,即将原先bx的内容赋给ax;再pop bx,即将原先ax的内容赋给bx
问题3.10

如果要在10000H处写入字型数据2266H,可以用以下的代码完成:

mov ax,1000H
mov ds,ax
mov ax,2266H
mov [0],ax补全下面的代码,使它能够完成同样的功能:在10000H处写入字型数据2266H。
要求:不能使用“mov 内存单元,寄存器”这类指令。
这里有三行空行
____________	 
____________
____________mov ax,2266H
push axmov ax,1000H
mov ss,ax
mov sp,2

提供:SS、SP指示栈顶:改变SP后写内存的入栈指令;读内存后改变SP的出栈指令

这就是 8086CPU 提供的栈操作机制。

栈的综述

(1)8086CPU提供了栈操作机制,方案如下.
在SS、SP中存放栈顶的段地址和偏移地址:

​ 提供入栈和出栈指令,它们根据SS:SP指示的地址,按照的方式访问内存单元。

(2)push指令的执行步骤:①SP-SP-2;②向 SS:SP 指向的字单元中送入数据。

(3)pop指令的执行步骤:①从SS:SP指向的字单元中读取数据;②SP=SP+2.

(4)任意时刻,SS:SP指向栈顶元素。

(5)8086CPU只记录栈顶,栈空间的大小我们要自己管理。

(6)用栈来暂存以后需要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反。

(7)push、pop实质上是一种内存传送指令,注意它们的灵活应用。

栈是一种非常重要的机制,一定要深入理解,灵活掌握。

3.10 栈段

栈段与前面的段概念类似,指定一段内存空间作为栈空间(栈段)

将SS:SP指向我们定义的栈段

问题3.11

如果将10000H~1FFFFH这段空间当作栈段,初始状态栈是空的,此时,SS=1000H,SP=?

这和之前的问题3.6类似,但有所不同,栈中是存放字型单元的

起初,我以为栈中最低的位置为1000:FFFF,这里我犯了一个错误就是,把栈中数据当做一个内存单元来看了

但其实不是的,如果在10000H~1FFFFH的栈段中,栈底指向的位置就是1000:FFFE

也就是SS=1000H,SP=FFFEH

如果栈为空栈,那么栈底就为栈中最低位置的下一个字型单元,即SP+2

那么SS=1000H,SP=0000H

总结:栈中存放的都是字型单元

问题3.12

一个栈段最大可以设为多少?为什么?

在8086CPU中,一个栈段的最大大小是64KB

最主要的原因就是:栈指针寄存器SP的位数的限制

SS:SP,SP是用来寻址的,SP是一个16位寄存器,其取值范围为0x0000到0xFFFF

因此,不管SS指向哪一个栈段,栈的可用空间始终由SP的16位偏移量限制

数据、代码、栈段

数据段:段地址DS指定

代码段:CS:IP指定

栈段:SS:SP指定

参考一下下面的代码理解一下:

比如我们将10000H~1001FH安排为代码段,并在里面存储如下代码
mov ax,1000H
mov ss,ax
mov sp,0020H	;初始化栈顶
mov ax,cs
mov ds,ax		;设置数据段段地址
mov ax,[0]
add ax,[2]
mov bx,[4]
add bx,[6]
push ax
push bx
pop ax
pop bx

检测点3.2

(1)补全下面的程序,使其可以将10000H1000FH中的8个字,逆序复制到20000H2000FH中。逆序复制的含义如图3.17所示(图中内存里的数据均为假设)。

逆序复制示意图

mov ax,1000H
mov ds,ax				;设置段寄存器地址mov ax,2000H
mov ss,ax
mov sp,0010H			;设置栈顶地址push [0]
push [2]
push [4]
push [6]
push [8]
push [A]
push [C]
push [E]

(2)补全下面的程序,使其可以将10000H1000FH中的8个字,逆序复制到20000H2000FH中。

mov ax,2000H
mov ds,axmov ax,1000H
mov ss,ax
mov sp,0000Hpop [E]
pop	[C]
pop	[A]
pop	[8]
pop	[6]
pop	[4]
pop	[2]
pop	[0]

这里可以自行画一个堆栈图出来辅助理解,主要是对push、pop两个指令的加深理解,以及栈顶的设置

实验二

1、D命令

我们来回顾一下D命令:“ d 段地址:偏移地址 ” 可以查看指定内存单元的内容

我们尝试来拆解一下D命令,

首先,D命令后面是直接给了一个段地址,

比如,Debug在执行“ d 1000:0 ”这样的命令时,会先把段地址1000H送入段寄存器中

那么我们就会有疑问了,

(1)D命令为什么会自动执行?

​ D命令后面是一段程序

(2)谁来执行这段程序呢?

​ CPU

(3)CPU是从哪里拿到内存单元的段地址?

​ 段地址寄存器

因此,我们得出结论,Debug在处理D命令时,肯定要把段地址送入段寄存器中

段寄存器有四个:CS、DS、SS、ES

CS:IP控制指令执行

SS:SP始终指向栈顶

DS段地址寄存器合适!

ES Extra Segment 附加段寄存器

DS 主要指向程序的数据段,而 ES 在一些特殊操作中提供了额外的段地址信息,

帮助程序更有效地执行某些任务,比如字符串处理、内存操作等。

D命令支持一种格式:“ d 段寄存器:偏移地址 ”

SA:偏移地址

(1)-r ds:1000-r ds:0			;查看从1000:0开始的内存区间中的内容(2)-r ds:1000-d ds:10 18		 ;查看1000:0~1000:18中的内容(3)-d cs:0			;查看当前代码段中的指令代码(4)-d ss:0 			;查看当前栈段中的内容

2、在E、A、U命令中使用段寄存器

在E、A、U这些可以带有内存单元地址的命令中,也可以同D命令一样,用段寄存器表示内存单元的段地址,以下是几个例子。

-r ds
:1000
-e ds:0 11 22 33 44 55 66		;在从1000:0开始的内存区间中写入数据-u cs:0						   ;以汇编指令的形式,显示当前代码段中的代码,0代码的偏移地址-r ds
:1000
-a ds:0						   ;以汇编指令的形式,向从1000:0开始的内存单元中写入指令

3、下一条指令执行了吗?

我们来看一段有意思的程序
在Debug中,用A命令写一段程序:

mov ax,2000
mov sS,ax
mov sp,10			;安排2000:0000~2000:000F为栈空间,初始化栈顶mov ax,3123
push ax
mov ax,3366
push ax				;在栈中压入两个数据

如下图

在这里插入图片描述

我们不禁要问mov sp,10哪去了呢?

其实,这里我们发现,当我们设置了SS的值后,紧接着SP的值也被改变了

一般情况下,T命令执行完一条指令,就会停下来给你显示各个寄存器的状态,还有下一步要执行的指令

但是T命令在执行mov ss,ax 并没有这样,为什么呢?

其实,不只有mov ss,ax,还有 mov ss,bx mov ss,[0] pop ss等指令都会发生上面这种情况

它们有什么共性吗?都是修改栈段寄存器SS

为什么会这样呢?计算机是出于什么考虑这样设计呢?

计算机设计了一个中断机制,具体怎么操作这里就不多说,后面会学到

但是我们可以思考一下,计算机中断(介入)的原因:

这里我们通过mov ss,ax指令修改了SS的值,那么如果SP不及时修改,

就会导致栈段指向原来的地方或是一个随机的地方,

那么就会导致某些需要用到栈段的程序崩溃、系统锁定、数据损坏等后果,

所以修改了SS,那么我们也要让计算机停下来等SP修改后再运行,保持它两是个“原子性”操作

不知道这么说能不能理解,不懂可以到后面学到中断就理解了

现在我们只要知道这一点就可以了:

Debug的T命令在执行修改寄存器SS的指令时,下一条指令也紧接着被执行。

实验任务

(1)使用Debug,将下面的程序段写入内存,逐条执行,根据指令执行后的实际运行情况填空。

mov ax,ffff
mov ds,ax			    ;设置段地址为FFFFHmov ax,2200
mov	ss,ax
mov sp,0100				;设置栈段地址为2200:0100mov ax,[0]				;ax=C0EA
add ax,[2]				;ax=0012
mov bx,[4]				;bx=0012
add bx,[6]				;bx=0012push ax					;sp=__00FEH____ ; 修改的内存单元的地址是_22100H____内容为___0012H__
push bx					;sp=__2F31H____ ; 修改的内存单元的地址是__220FEH___内容为___2F31H__
pop ax					;sp=__00FCH____ ; ax=__0012H____
pop bx					;sp=__0100H____ ; bx=__0012H____push [4]				;sp=__00FEH____ ; 修改的内存单元的地址是__220FEH___内容为__0___
push [6]				;sp=__00FCH____ ; 修改的内存单元的地址是__220FCH___内容为__0___

在这里插入图片描述

(2)仔细观察图3.19中的实验过程,然后分析:为什么2000:0~2000:f中的内容会发生改变?

在这里插入图片描述

其实,关于这段内存单元为什么会被改变?我们可以通过后续的学习来了解

这里我就简单引导一下,毕竟我也刚开始学,很多地方还得看后面,如有疑惑,请多包涵

在这里插入图片描述

我们先来看这里吧,为什么正好CS:IP的数据存放在这呢?计算机把它们存起来是干什么呢?

我们先从CS:IP的作用分析,CS:IP指向的是下一条要执行的指令的位置

我们把他存起来其实是为了方便我们进行其他操作后(中断),再恢复到原来的步骤

一般,我们把这个行为称为:保护现场(保护上下文环境)

这里还会保存标志寄存器中的内容,后面会学到标志寄存器的具体内容,这里就不多赘述

http://www.dtcms.com/a/269036.html

相关文章:

  • cuda编程笔记(6)--流
  • PowerQuery逆透视之二维表转一维表
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | ContentPlaceholder(背景占位)
  • 电动汽车的传导发射仿真
  • navicate如何设置数据库引擎
  • RabbitMQ在SpringBoot中的使用详解
  • 2025光学成像与机器视觉国际会议 (OIMV 2025)
  • 用Python制作华夫图:从零开始
  • ShortGPT: Layers in Large Language Models are More Redundant Than You Expect
  • delphi,c++程序 阻止Win11 用户更改系统时间
  • 电子防抖(EIS)技术概述
  • Springboot 如何加密数据库连接相关配置信息
  • 特伦斯T1节拍器,突出综合优势与用户体验
  • AI建站工具对决:Wegic、腾讯云、Hocoos、Typedream深度测评,谁是国内用户的首选?
  • MySQL Galera Cluster企业级部署
  • 【Python】VSCode:解决模块导入与调试
  • 【音视频】HLS简介与服务器搭建
  • 【LLIE专题】通过预训练模型先验提升暗光增强模型复原效果
  • 安卓10.0系统修改定制化____如何修改固件 去除开机向导 实现开机直接进入桌面
  • C++笔记之开关控制的仿真与实际数据处理优雅设计
  • 基于物联网的城市低洼地段水深报警系统设计
  • 【人工智能学习路线(一)】以SCI为目标学习笔记——Python 编程基础入门
  • 面试总结46-50天
  • Python爬虫图片验证码和滑块验证码识别总结
  • 前端技术博客汇总文档
  • 思考5-10分钟,输出高质量的学术科研报告,谷歌的deepsearch模型太惊艳了!
  • 【最新版】Spring Boot 项目打包部署到服务器
  • 【配置+图解Android各种版本配置】
  • V8 主要版本与对应 ECMAScript 支持
  • 2025 API 开发管理工具 Apipost 与 Apifox 全维度对比