EverydayOneCat
E D M
一、寄存器(内存访问)
1.内存中字的存储
在内存中存储时, 由千内存单元是字节单元(一个单元存放一个字节), 则一个字要用两个地址连续的内存单元来存放, 这个字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。
对千这个字单元来说,0号单元是低地址单元,1号单元是高地址单元, 则字型数据4E20H 的低位字节存放在0 号单元中, 高位字节存放在1号单元中。我们提出字单元的概念: 字单元, 即存放一个字型数据(16位)的内存单元, 由两个地址连续的内存单元组成。高地址内存单元中存放字型数据的高位字节, 低地址内存单元中存放字型数据的低位字节。
我们将起始地址为N 的字单元简称为N 地址字单元。比如一个字单元由2 、3 两个内存单元组成, 则这个字单元的起始地址为2, 我们可以说这是2 地址字单元。
1地址字单元中存放的字型数据是多少?
答:1地址字单元, 即起始地址为1的字单元, 它由1号单元和2号单元组成, 用这两个单元存储一个字型数据, 高位放在2 号单元中, 即:12H, 低位放在1 号单元中,即:4EH, 它们组成字型数据是124EH, 大小为:4686。
任何两个地址连续的内存单元, N号单元和N+1号单元, 可以将它们看成两个内存单元, 也可看成一个地址为N的字单元中的高位字节单元和低位字节单元。
2.DS和[address]
CPU要读写一个内存单元的时候, 必须先给出这个内存单元的地址, 在8086PC中,内存地址由段地址和偏移地址组成。8086CPU中有一个DS寄存器, 通常用来存放要访问数据的段地址。比如我们要读取10000H单元的内容, 可以用如下的程序段进行。
1 | mov bx,lOOOH |
上面的3条指令将10000H(1000:0)中的数据读到al中。
下面详细说明指令mov al, [0]的含义:
使用mov 指令将一个内存单元中的内容送入一个寄存器中。从哪一个内存单元送到哪一个寄存器中呢?在指令中必须指明。寄存器用寄存器名来指明, 内存单元则需用内存单元的地址来指明。显然, 此时mov 指令的格式应该是:mov 寄存器名, 内存单元地址。
mov 指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0, 它的段地址默认放在ds 中, 指令执行时, 8086CPU 会自动从ds 中取出。
8086CPU 不支待将数据直接送入段寄存器的操作, ds 是一个段寄存器, 所以mov ds, 1 OOOH 这条指令是非法的。
那么如何将1000H 送入ds 呢?只好用一个寄存器来进行中转, 即先将1000H 送入一个一般的寄存器, 如bx, 再将bx 中的内容送入ds。
问题:写儿条指令, 将al 中的数据送入内存单元IOOOOH 中。
1 | mov bx,1000H |
3.字的传送
前面我们用mov 指令在寄存器和内存之间进行字节型数据的传送。因为8086CPU 是16 位结构, 有16 根数据线, 所以, 可以一次性传送16 位的数据, 也就是说可以一次性传送一个字。只要在mov 指令中给出16 位的寄存器就可以进行16 位数据的传送了。比如:
1 | mov bx,1OOOH |
4.数据段
对千8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。我们可以将一组长度为N(N64KB)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。
比如用123B0H~123B9H这段内存空间来存放数据,我们就可以认为,123B0H~123B9H这段内存是一个数据段,它的段地址为123BH, 长度为10个字节。
将一段内存当作数据段,是我们在编程时的一种安排,可以在具体操作的时候, 用ds 存放数据段的段地址, 再根据需要, 用相关指令访问数据段中的具体单元。
将123BOH-123B9H 的内存单元定义为数据段。现在要累加这个数据段中的前3 个单元中的数据, 代码如下:
1 | mov ax,123BH |
写几条指令, 累加数据段中的前3个字型数据,注意: 一个字型数据占两个单元, 所以偏移地址是0 、2、4。可以修改以上代码:
1 | mov ax,[O] ;用ax存放累加结果,将数据段第一个字(偏移地址为0) 加到ax中 |
5.小结+习题1~4
5.1问题1
内存中的情况如图3.2 所示, 写出下面的指令执行后寄存器ax,bx,cx 中的值。
我们通过debug来一步步看:
1 | ;修改内存中的内容: E命令 |
列表分析:
5.2问题2
内存中的情况如图3.3 所示, 写出下面的指令执行后内存中的值, 思考后看分析。
mov ax1000H
mov ds,ax
mov ax,11316
mov [0],ax
mov bx, [0J
sub bx, [2]
mov [2],bx
5.3小结
(1)字在内存中存储时, 要用两个地址连续的内存单元来存放, 字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。
(2)用mov指令访问内存单元, 可以在mov指令中只给出单元的偏移地址, 此时, 段地址默认在DS 寄存器中。
(3) [ address]表示一个偏移地址为address的内存单元。
(4)在内存和寄存器之间传送字型数据时, 高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。
(5) mov、add、sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。
(6)可以根据自己的推测, 在Debug中实验指令的新格式。
5.4习题3
在DEBUG中,用 “D 0:0 lf” 查看内存,结果如下:
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,写出每条汇编指令执行完后相关寄存器中的值
5.5习题4
各寄存器的初始值: CS=2000H, IP=O, DS=IOOOH, AX=O, BX=O;
@ 写出CPU 执行的指令序列(用汇编指令写出).
@ 写出CPU 执行每条指令后, CS 、IP 和相关寄存器中的数值。
© 再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据, 哪些是程序?
6.CPU提供的栈机制
栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于, 最后进入这个空间的数据, 最先出去。栈的这种操作规则被称为:LIFO(Last InFirst Out,后进先出)。
现今的CPU中都有栈的设计,8086CPU也不例外。8086CPU提供相关的指令来以栈的方式访问内存空间。这意味着,在基千8086CPU编程的时候,可以将一段内存当作栈来使用。
8086CPU 提供入栈和出栈指令,最基本的两个是PUSH(入栈) 和POP(出栈)。比如,push ax表示将寄存器ax 中的数据送入栈中,pop ax表示从栈顶取出数据送入ax。8086CPU的入栈和出栈操作都是以字为单位进行的。
CPU也有相应的寄存器来存放栈顶的地址,8086CPU中,有两个寄存器, 段寄存器SS 和寄存器SP,栈顶的段地址存放在SS 中, 偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素。push 指令和pop指令执行时,CPU从SS 和SP中得到栈顶的地址。
push ax的执行,由以下两步完成。
(1) SP=SP-2, SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
(2)将ax 中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。
pop ax 的执行过程和push ax 刚好相反, 由以下两步完成。
(I)将SS:SP 指向的内存单元处的数据送入ax 中;
(2) SP=SP+2, SS:SP 指向当前栈顶下面的单元, 以当前栈顶下面的单元为新的栈顶。
我们可以看出:8086CPU 中, 入栈时, 栈顶从高地址向低地址方向增长。
问题:如果将IOOOOH-1 OOOFH 这段空间当作栈, 初始状态栈是空的, 此时, SS=IOOOH,SP=?
任意时刻, SS:SP 指向栈顶元素, 当栈为空的时候, 栈中没有元素,也就不存在栈顶元素, 所以SS:SP 只能指向栈的最底部单元下面的单元, 该单元的偏移地址为栈最底部的字单元的偏移地址+2, 栈最底部字单元的地址为1000:000E, 所以栈空时, SP=0010H。
这里要注意,出栈其实并不是将数据删除,而是修改了栈顶的地址,接下来有入栈操作时会覆盖原来的数据
7.栈顶超界的问题
我们现在知道, 8086CPU用SS和SP指示栈顶的地址, 并提供push和pop指令实现入栈和出栈。可是, 如何能够保证在入栈、出栈时, 栈顶不会超出栈空间?
我们通过实践发现,当栈顶超出了栈空间,他还是会继续出栈入栈,把数据传出去。栈顶超界是危险的, 因为我们既然将一段空间安排为栈, 那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等, 这些数据、代码可能是我们自己程序中的, 也可能是别的程序中的(毕竟一个计算机系统中并不是只有我们自己的程序在运行)。但是由千我们在入栈出栈时的不小心, 而将这些数据、代码意外地改写, 将会引发一连串的错误。
利用这一点,黑客可以知道他想要的数据,比如他设置一个栈,然后不断地出栈,因为溢出了还是会继续把数据出栈,通过这种可以寻找到他想要的数据(尽管加密了)
8086CPU不保证我们对栈的操作不会超界。这也就是说,8086CPU只知道栈顶在何处(由SS:SP指示), 而不知道我们安排的栈空间有多大。这点就好像CPU只知道当前要执行的指令在何处(由CS:IP 指示), 而不知道要执行的指令有多少。从这两点上我们可以8086CPU的工作机理, 它只考虑当前的情况: 当前的栈顶在何处、当前要执行的指令是哪一条。
我们在编程的时候要自己操心栈顶超界的问题, 要根据可能用到的最大栈空间, 来安排栈的大小, 防止入栈的数据太多而导致的超界;执行出栈操作的时候也要注意, 以防栈空的时候继续出栈而导致的超界。
8.push、pop 指令
push和pop指令是可以在寄存器和内存(栈空间当然也是内存空间的一部分, 它只是一段可以以一种特殊的方式进行访问的内存空间。)之间传送数据的。
push和pop 指令的格式可以是如下形式:
push 寄存器 ;将一个寄存器中的数据入栈
pop 寄存器 ;出栈, 用一个寄存器接收出栈的数据
当然也可以是如下形式:
push 段寄存器 ;将一个段寄存器中的数据入栈
pop 段寄存器 ;出栈, 用一个段寄存器接收出栈的数据
push和pop也可以在内存单元和内存单元之间传送数据, 我们可以:
push 内存单元 ;将一个内存字单元处的字入栈(注意:栈操作都是以字为单位)
pop 内存单元 ;出栈, 用一个内存字单元接收出栈的数据
指令执行时, CPU要知道内存单元的地址, 可以在push、pop指令中只给出内存单元的偏移地址, 段地址在指令执行时, CPU从ds中取得。
9.小结+习题6~8
9.1习题1
编程:
(1)将10000H-1000FH 这段空间当作栈, 初始状态栈是空的;
(2)设置AX=001AH, BX=001BH;
(3)将AX、BX 中的数据入栈;
(4) 然后将AX、BX清零;
(5) 从栈中恢复AX、BX原来的内容。
1 | mov ax,1000H |
从上面的程序我们看到, 用栈来暂存以后需要恢复的寄存器中的内容时, 出栈的顺序要和入栈的顺序相反, 因为最后入栈的寄存器的内容在栈顶, 所以在恢复时, 要最先出栈。
9.2习题2
如果要在10000H 处写入字型数据2266H,补全下面的代码(要求: 不能使用“ mov 内存单元, 寄存器” 这类指令。)
3条汇编指令……
mov ax,2266H
push ax
分析:我们来看需补全代码的最后两条指令, 将ax中的2266H压入栈中, 也就是说, 最终应由push ax将2266H写入10000H处。push ax 是入栈指令, 它将在栈顶之上压入新的数据。一定要注意: 它的执行过程是, 先将记录栈顶偏移地址的SP寄存器中的内容减2, 使得SS:SP指向新的栈顶单元,然后再将寄存器中的数据送入SS:SP指向的新的栈顶单元。
解答:
1 | mov ax,1000H |
push和pop指令同mov指令不同, CPU执行mov指令只需一步操作, 就是传送, 而执行push、pop指令却需要两步操作。,
执行push时, CPU的两步操作是: 先改变SP , 后向SS:SP 处传送。
执行pop 时, CPU 的两步操作是: 先读取SS:SP处的数据, 后改变SP。
注意, push, pop等栈操作指令, 修改的只是SP。也就是说, 栈顶的变化范围最大为: 0-FFFFH。
改变SP后写内存的入栈指令;读内存后改变SP的出栈指令。这就是8086CPU提供的栈操作机制。
9.3小结
(1) 8086CPU提供了栈操作机制, 方案如下。在SS、SP中存放栈顶的段地址和偏移地址;提供入栈和出栈指令, 它们根据SS:SP指示的地址, 按照栈的方式访问内存单元。
(2) push指令的执行步骤:1、SP=SP-2 ; 2、向SS:SP指向的字单元中送入数据。
(3) pop指令的执行步骤:1、从SS:SP指向的字单元中读取数据; 2、SP=SP+2。
(4) 任意时刻,SS:SP指向栈顶元素。
(5) 8086CPU 只记录栈顶, 栈空间的大小我们要自己管理。
(6) 用栈来暂存以后需要恢复的寄存器的内容时, 寄存器出栈的顺序要和入栈的顺序相反。
(7) push、pop实质上是一种内存传送指令, 注意它们的灵活应用。
10.栈段
对千8086PC机, 在编程时, 可以根据需要, 将一组内存单元定义为一个段。我们可以将长度为N(N64KB)的一组地址连续、起始地址为16的倍数的内存单元, 当作栈空间来用, 从而定义了一个栈段。
将一段内存当作栈段, 仅仅是我们在编程时的一种安排, CPU并不会由千这种安排, 就在执行push、pop等栈操作指令时自动地将我们定义的栈段当作栈空间来访问。如何使得如push、pop等栈操作指令访问我们定义的栈段呢?就是要将SS:SP指向我们定义的栈段。
问题:如果将1OOOOH~ 1 FFFFH 这段空间当作栈段, 初始状态栈是空的, 此时,SS=1000H, SP=?
分析:如果将1OOOOH~ 1 FFFFH这段空间当作栈段, SS=1000H, 栈空间为64KB, 栈最底部的字单元地址为1000:FFFE。任意时刻, SS:SP指向栈顶单元, 当栈中只有一个元素的时候, SS=1000H , SP= FFFEH。栈为空, 就相当于栈中唯一的元素出栈, 出栈后,SP=SP+2。
SP原来为FFFEH, 加2后SP=0, 所以, 当栈为空的时候, SS=1000H, SP=O。
换一个角度看, 任意时刻, SS:SP指向栈顶元素, 当栈为空的时候, 栈中没有元素,也就不存在栈顶元素, 所以SS:SP只能指向栈的最底部单元下面的单元, 该单元的地址为栈最底部的字单元的地址+2。栈最底部字单元的地址为1000:FFFE, 所以栈空时,SP=0000H。
一个栈段最大可以设为多少?为什么?
答:首先从栈操作指令所完成的功能的角度上来看, push、pop等指令在执行的时候只修改SP, 所以栈顶的变化范围是0-FFFFH, 从栈空时候的SP=0, 一直压栈, 直到栈满时SP=0; 如果再次压栈, 栈顶将环绕, 覆盖了原来栈中的内容。所以一个栈段的容量最大为64KB。
一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是。关键在于CPU中已寄存器的设即CS、IP、SS、SP、DS的指向。
二、第一个程序
1.一个源程序从写出到执行的过程
第一步: 编写汇编源程序。
第二步: 对源程序进行编译连接。
可执行文件包含两部分内容。
• 程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的
数据)
• 相关的描述信息(比如, 程序有多大、要占用多少内存空间等)
第三步:执行可执行文件中的程序。
2.源程序
下面就是一段简单的汇编语言源程序。
1 | assume cs:codesg |
2.1伪指令
1、segment……ends
codesg segment ;定义一个段, 段的名称为“ codesg”, 这个段从此开始
codesg ends ;名称为“ codesg” 的段到此结束
一个有意义的汇编程序中至少要有一个段, 这个段用来存放代码
2、end
end 是一个汇编程序的结束标记, 编译器在编译汇编程序的过程中, 如果碰到了伪指令end, 就结束对源程序的编译。
注意, 不要搞混了end和ends, ends是和segment 成对使用的, 标记一个段的结束,ends的含义可理解为“ end segment”。我们这里讲的end的作用是标记整个程序的结束。
3、assume
这条伪指令的含义为“ 假设“ 。它假设某一段寄存器和程序中的某一个用segment… ends定义的段相关联。通过assume说明这种关联, 在需要的情况下, 编译程序可以将段寄存器和某一个具体的段相联系。
在程序的开头, 用assumecs:codesg将用作代码段的段codesg和CPU中的段寄存器cs联系起来。
2.2源程序中的“ 程序”
用汇编语言写的源程序, 包括伪指令和汇编指令, 我们编程的最终目的是让计算机完成一定的任务。源程序中的汇编指令组成了最终由计算机执行的程序。
以后可以将源程序文件中的所有内容称为源程序, 将源程序中最终由计算机执行、处理的指令或数据, 称为程序。程序最先以汇编指令的形式存在源程序中, 经编译、连接后转变为机器码, 存储在可执行文件中。
2.3标号
汇编源程序中, 除了汇编指令和伪指令外, 还有一些标号, 比如“ codesg” 。一个标号指代了一个地址。比如codesg 在segment 的前面, 作为一个段的名称, 这个段的名称最终将被编译、连接程序处理为一个段的段地址。
2.4程序返回
一个程序结束后, 将CPU的控制权交还给使它得以运行的程序,我们称这个过程为: 程序返回。那么, 如何返回呢?应该在程序的末尾添加返回的程序段。
mov ax,4c00H
int 21H
这两条指令所实现的功能就是程序返回。
3.汇编程序从写出到执行的过程
结语
小母牛系列歇后语:
1、小母牛拿别针——别牛逼
2、小母牛追公牛——牛逼极了
3、小公牛哭小母牛——牛逼死了
4、小母牛骑摩托——牛逼轰轰
5、借小母牛给我耙田——让我也牛逼一把
6、一头母牛和十头公牛——牛逼惨了
7、小母牛探亲——牛逼到家了
8、两只小母牛倒立——比较牛逼
9、赶小母牛拉犁——跟着牛逼走
10、抓到小母牛的后腿跟——离牛逼不远了
11、小母牛周游世界——走到哪儿牛逼到哪儿
12、小母牛哭老母牛——牛逼死了
13、小母牛背降落伞——牛逼满天飞
14、小母牛排队——一个牛逼跟着一个牛逼
15、老母牛来月经——牛逼坏了
16、一群小母牛过独木桥——牛逼一个接一个
17、小母牛踩刹车——停止牛逼
18、小母牛到南极——牛逼到了极点
19、小母牛荡秋千——牛逼过去,牛逼过来
20、小母牛过生日——牛逼大了
21、小母牛坐钢锯——巨牛逼
22、小母牛坐电线——牛逼带闪电
23、小母牛掉进酒缸里——醉牛逼
24、小母牛掉到酒缸里——最牛逼
25、小母牛抽烟——牛逼的够呛呀
26、小母牛竖蜻蜓——挺牛逼
27、小母牛坐飞机——牛逼上天了
28、小母牛拿大顶——牛逼冲天
29、小母牛放屁——吹牛逼
30、小母牛蹦迪——左一个牛逼右一个牛逼
31、小母牛打手机——无限牛逼
32、小母牛长高了——牛逼大了
33、小母牛迎风劈叉——吹牛逼
34、克隆小母牛——照样牛逼
35、小母牛上蒸笼——真牛逼
36、俩小母牛盘腿对坐——比较牛逼
37、小母牛没有性病——好牛逼
38、小母牛进栏——牛逼到家了
39、这可真是三班的小母牛一一没有一班牛逼啊