寄存器
这回我们来看寄存器。CPU是由运算器,控制器,寄存器这些个器件组成。
运算器主要负责数据的各种数据的处理工作,比如算数运算,逻辑运算;
控制器是根据给出的相应的机器指令,发出对应的具体控制信号驱动硬件工作。它有指令译码的工作,就是根据指令寄存器的内容,产生相应的微指令;加法指令的执行可分为:取指,计算地址,取操作数和加法运算四步,每一步都由一组微操作实现.这一组能同时执行的微操作就构成一条微指令。
寄存器则是CPU中存储数据的部件。对于一个汇编程序员,CPU中最重要的部件就是寄存器。通过对各个寄存器数据的改变,完成对于CPU的控制。不同的CPU,寄存器的结构,数量是不同的。比如,8086CPU中有14个寄存器,名称分别是:AX,BX,CX,DX,CS,DS,ES,SS,IP,SP,BP,SI,DI,PSW。我们不在这里一一介绍每个寄存器的功能,以后用到什么寄存器,就学习什么。大体上我会按照书上的顺序来写这些寄存器。
上述这些个器件,全部集成到了一个CPU芯片中。CPU中数据的传输通过内总线进行。前面一篇提到了总线,那个总线是指外总线,是用来连接CPU和主板上的其他部件的。
--------------------------------------------------------------------------------------------------------
8086CPU的寄存器都是16位的寄存器,也就是说,一个寄存器最多可以存储2个字节的数据。通用寄存器有AX,BX,CX,DX这四个。他们用来存放一般的数据。
AX 称为累加器,常用于存放算术逻辑运算中的操作数,另外所有的I/O指令都使用累加器与外设接口传送信息 BX 称为基址寄存器,常用来存放访问内在时的基地址,
CX 称为计数寄存器,在循环和串操作指令中用作计数器 DX 称为数据寄存器,在寄存器间接寻址中的I/O指令中存放I/O端口的地址
另外,在做双字长乘除法运算时,DX 与AX合起来存放一个双字长数(32位),其中DX存放高16位,AX存放低16位.
我们以AX为例看一下寄存器的内部逻辑结构。
可以看出,一个16位的寄存器,一共可以存放16bit的数据。
一个16位的寄存器,可以看作是由2个8位的寄存器组成的。比如
AX寄存器分别当做两个8位的寄存器来使用,一个叫AL,一个叫AH(L表示low H表示High);分别存储16位数据中的低8位和高8位。同时16位寄存器也可以直接被当做8位的来使用。就好像高级语言中,我们可以用int型数据来存放一个short型的。
之所以把一个16位的寄存器分成两个8位的看,是因为8086的上一代CPU是8位的。为了兼容,使原来的程序经过很小的改动既可以在新的CPU上运行。
我们来看寄存器中存储上数据后是什么样的。
如果给一个16位的寄存器,其表示的数字的值最大就是每一位全存1,转换成16进制就是FFFFH;最小就是全存0,相应的是0000H。(数字最后一个字母H表示16进制,如果最后一个字母是B表示2进制,如果什么都不加就是10进制)
各个进制之间的表示的转换我就不在这里敖述了。
8086CPU一次可以处理2种尺寸的数据:一个是字,一个是字节。一个字是由2个字节构成,分别称为低位字节,高位字节。
我们现在来看几条汇编指令来熟悉一下刚才说过的寄存器操作。
上面这个比较好理解。
下面我们看一个特别一点的例子。
MOV AX,8226H
MOV BX,AX
ADD AX,BX
我们问这个时候AX中存放的数据是多少?
我们发现执行最后一条指令ADD之后,这个结果的数据应该是1044CH。但是我们的AX寄存器只能存放16位的数据,这个结果是1044CH已经超出了这个范围。最高位的1不能放在AX中保存,所以AX中最后存放的数字是044CH。在随后的知识中我们会知道,这个加法造成的寄存器溢出,最后由PSW程序状态字中的一个位来记录。
再来看一个例子。
图中表的空白的问题。思考一下。
前两行指令没有任何问题,简单的赋值;接着add al,bl 就是一个8位的加法,也没有在低位产生溢出这种情况;然后是add ah,bl ,add bh,al也是简单的加法;mov ah,0赋值ax高位为0;add al,85H, AX寄存器的数据变成了00C5H;
接着最后一句,问题就来了,add al,93H,我们发现执行这句加法之后,al会产生溢出,得到158H的结果,但是这个在al低位的溢出会不会进到ah中的最低一位呢?答案是不会进位到ah中。CPU把ah,al两个8位寄存器当做独立的没有关系的两个寄存器。al加法产生进位并不会影响ah中值的变化。
如果最后一句执行的语句是add ax,93H 那么AX中的值就是0158H。所以我们的结果的值是和我们的操作对象是匹配的。就是说我们给出的数据宽度,和结果的数据宽度的大小是一致的。
而且,我们进行数据传输和运算的时候,两边的数据宽度要一致。比如mov ax,bx,两边的数据宽度都是16位;如果我们写 mov ax, bl 那么这就是一条错误的语句,原因就是操作对象数据长度不一致。
做一下书上的检测点2.1
(1)写出每条汇编指令执行后,相关寄存器的值。
我做的结果给在这里AX=F4A3H;BX=31A3H;CX=3123H;AX=6246H;AX=826CH;AX=6246H;AX=826CH;
AX=04D8H;AX=0482H;AX=6C82H;AX=D882H;AX=D888H;AX=D810H;AX=3123H.
题里面有上面讲到的溢出的问题。比较简单。
(2)用学过的汇编指令用最多4条指令,编程计算2的四次方。
思路:2的四次方就是2*2*2*2;而乘以2就是数据翻倍。
我们之前用的 add ax,ax就是使ax的数据的翻倍。
首先我们要用mov ax,2来给ax赋初值。
接着就是连续3个add ax,ax,即可完成数据的3次翻倍,也就是2的4次方。
正好4条指令。
--------------------------------------------------------------------------------------------------------
下面我们说一下物理内存地址的问题。
CPU访问内存的时候要给出内存的地址。8086CPU有20位的地址总线,也就是说它的寻址能力是1MB。但是我们之前说过,8086CPU是16位的机器,也就是说,在内部,它能够传输的地址位数最高是16位,寻址范围只有64KB。
为了访问整个1MB的内存,8086CPU采用使用2个16位数据形成一个物理地址的方法。
这里书上讲的很明白。
图上,其他部件提供两个16位数据,一个叫做段地址,一个叫偏移地址。然后两个数据被送到一个地址加法器中,做运算,形成一个20位的物理地址。之后根据这个地址,做下一步的操作。
我们来看一下这个加法器内部是怎么做这个加法的,怎么把段地址和偏移地址结合成物理地址的。
简单的说,物理地址=段地址*16+偏移地址。
我们知道10进制的一个数乘以10就是这个数左移一位,同样,2进制乘以2就是左移一位,,16进制乘以16就是左移一位。
比如,要形成物理地址123C8H,可以提供一个段地址1230H,在提供一个偏移地址00C8H。
根据上面的式子,1230H*16+00C8H=123C8H,1230H*16,就是把1230H左移一位,得到12300H,然后加上00C8H,得到结果123C8H。
我们说形成一个物理地址,可以用不同的段地址和偏移地址。这个道理也很简单。
进一步看这个地址形成的方式。CPU形成一个物理地址,是根据一个基地址,和一个偏移地址来确定这个最后地址的。用一个式子看就是:物理地址=基地址+偏移地址。这里基地址就是段地址*16。
关于段地址,这里还要说明一点就是,我们的内存并没有被划分成一个一个的段。分段这个概念只是CPU对于内存的一个管理方法。以基地址为开始地址,然后根据偏移地址,找到相应物理地址。
偏移地址是16位的,所以根据一个确定的基地址,最多可以寻找到64KB的内存单元,所以一个段最多是64KB这么大。
在8086中我们表示一个物理地址时,用“段地址:偏移地址”这种形式。比如,表示物理地址21F60H,我们说数据存在内存2000:1F60单元中。或者数据存在2000H段中的1F60地址中。
检测点2.2
(1)给定段地址0001H,仅通过变化偏移地址寻址,CPU的寻址范围为()到()。
(2) 有一数据存放在内存20000H单元中,现在给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小的为(),最大的为()。
思路:(1)就是利用那个物理地址形成的式子嘛,没啥难度。
最低地址=0001H*16+0000H=00010H+0000H=00010H;
最高地址=0001H*16+FFFFH=00010H+FFFFH=1000fH;
(2)第一感觉,这是一个反向的计算,就是逆着用那个公式,把SA当做已知
20000H=SA*16+0000H;20000H=SA*16+FFFFH;
容易算出第一个SA=2000H;第二个SA=10001H/16,我们发现10001H右移一位,得到1000H会丢失1,这个地址无法再算出20000H。所以,地址最小为1001H。
--------------------------------------------------------------------------------------------------------
我们来看段寄存器。
所谓段寄存器,就是CPU在访存的时候,可以提供段地址寄存器。8086CPU中有CS,DS,ES,SS这四个段寄存器。
CS(Code Segment):代码段寄存器; DS(Data Segment):数据段寄存器; SS(Stack Segment):堆栈段寄存器; ES(Extra Segment):附加段寄存器。
我们现在只看CS。
CS,IP 是CPU中最关键的两个寄存器。CS是代码段寄存器,IP是指令指针寄存器。
8086CPU,任意时刻,设CS中内容为M,IP中的内容N,8086CPU将从M*16+N单元开始,读取一条指令并执行。
8086CPU的工作过程简要入下:
(1)从CS:IP 指向的内存地址读出一条指令,存入指令缓冲器。
(2)IP=IP+指令的长度,从而指向下一条指令。
(3)指令执行。转到(1)继续。
这里有一个问题,CPU如何确定指令的长度?比如,mov ax,0123H占3个字节;mov ax,bx占2个字节。取指令的时候怎么确定要取多长的字节作为一个指令?
这里根据查的一些资料,我的理解是,这是由操作码决定的。和计组里指令编码那块有点关系。
所有的x86指令的第一个字节都是操作码(有的第二个字节也是操作码),操作码包含了3部分信息,1.作什么操作,2。操作数(或寄存器)是什么。3.操作数(或者寄存器)的宽度是多少,字节,字还是双字。知道了操作码,就可以确定当前指令的长度,进而可以确定下一条指令的地址:
注意,在汇编语言中,相同的指令其操作码并不一定相同,如 指令 mov ax,[si+4], mov ax,[si+1234] 粗看出来,功能,寄存器都是一样。但操作码并不相同,它们的操作码分别是,8B44 和8B84,下面是在debug下反汇编的结果。
138E:0100 8B4404 MOV AX,[SI+04]
138E:0103 8B843412 MOV AX,[SI+1234]
取指令的时候先取一个字节,分析这个操作码,确定指令长度,然后再根据这个长度取完这条指令。
然后我们来看如何修改CS,IP这两个寄存器的值。
我们知道通用寄存器,AX,BX这些,可以用MOV指令,来改变寄存器的值。其实大部分寄存器都可以用MOV指令来修改其中的值。
但是CS这样的段寄存器不可以用MOV指令来修改。原因书上说是CPU不提供这样的功能。我记得有一个说法是段寄存器这样的数据和内存没有直接的线路相连。好像挺有道理。
我们修改CS和IP要用到一条汇编指令JMP。若要同时修改CS和IP,可以用“JMP 段地址:偏移地址”这样的指令。
比如我们要执行JMP 2AE3:3 ,执行之后,CS变为2AE3H,IP变为0003H。
若只修改IP内容,则JMP 某一合法寄存器。比如,JMP AX,此时AX存放0010H,则IP被修改成0001H。
显然,我们要想让内存中装入的一段程序执行,必须把CS,IP指向该内存的地址。
检测点2.3
下面的3条指令执行后,CPU修改几次IP?都是在什么时候?最后IP中的值是多少?
MOV AX,BX
SUB AX,AX
JMP AX
解答:CPU修改IP一共有2种情况:一个是正常执行,IP=IP+指令长度;另一个就是使用跳转指令JMP。
这里一共有3条指令。前两条MOV SUB都是一般的指令,所以IP修改2次,然后是JMP指令,取指令结束后,取到了JMP AX这条指令,IP再加。然后执行这条指令完成跳转,IP又改变一次,由于AX值这时为0,所以IP的值最后也为0。
--------------------------------------------------------------------------------------------------------
来看实验1的内容。这个实验主要目的是熟悉debug这个工具。
debug是一个dos下的调试工具。可以查看CPU中各寄存器的内容,内存的情况,和机器码级跟踪程序的运行。
我们主要用到6条debug中的命令。我把书上的贴过来。
进入debug。
我们通过cmd即可进入debug。也可以用command。这两个的区别,就是cmd是基于win NT的,它的功能比command要多,支持中文。而command则是一个运行在虚拟dos下的东西。
书上对于这几个指令的介绍很全,我就不在这里敖述。如果学过汇编可以容易想起来不少这些个命令到底怎么用。
我这里主要看实验任务。
(1)把一段汇编程序写入内存并执行
这里可以用e命令,看着机器码一个一个修改内存;也可以用a命令,把一条一条的汇编指令打入内存。
执行的时候要注意CS:IP的指向。
我用A命令,把汇编指令一条一条打入内存。
我要执行它,必须使CS,IP指向这段代码的开始地址0B4E:0100.
我使用r命令,来改变cs,ip的值。
寄存器的值已经修改好。r命令查看寄存器内容,显示的最后一行就是cs:ip的存储数据,对应有一条汇编指令,这个指令就是cs,ip指向的指令,也就是下一条要执行的质量。
我们用t命令一步一步执行。并用r命令观察各条指令执行之后,各个寄存器的变化,主要观察cs,ip。比较简单。
(2)写入三条指令到2000:0开始的内存单元。用这三条指令计算2的8次方
mov ax,1;
add ax,ax;
jmp 2000:0003。
然后执行就行。要计算2的8次方最后ax中的结果就是0100H。
(3)查看ROM的生产日期,在FFF00H~FFFFFH中。并试图改变它。
我用d命令查看该位置内存。第二次用d则看到了最后一行有一个日期格式的字符串。
按理说这个内容应该不能被修改,因为这是ROM区域。
这段日期是从FFF5H~FFFCH,内存中方的就是ASC码。我们试图修改它。用e命令。
我这里就给就改成功了,我也很纳闷,不是ROM么?看图。
我说是不是我用CMD的原因,换成command结果依然是这样,可以修改。我觉得这算个bug吧,我的系统是win 7.
(4)最后一个是该显存的。
想内存B8100H中写入数据。
可以看到屏幕上出现了几个彩色的图案。
原因就是内存空间A0000H到BFFFFH属于显存区域。修改显存的值,会立刻反应到屏幕上。我们可以再修改几个值。=。=。
--------------------------------------------------------------------------------------------------------
我不知道大家看了我的文章以后什么感觉。觉得这样写是不是很啰嗦?
我自我感觉这样写有点过于依赖于书上的顺序。和我预期想写这样的blog感觉不一样。
我还是想是不是换一种方式来写。比如直接把我在书上看到的问题一个一个写出来,写出我的思路。而不是我现在把我觉得是知识点的东西罗列出来。这样就需要读者也和我一起在学这本书。
希望听到你们的感受和建议~~
我会尽快更新下一篇
来源:https://www.cnblogs.com/vanie/archive/2011/03/03/1969497.html