汇编教程6:内存访问
刚才在群里面发布了我的文章地址,希望他们来看看文章,多给点意见,某君却以为我是挂马的朋友,不敢相信啊。意见就不发表了,还有希望看了我写的文章一定要发表个意见啊,雁过留痕嘛。主要是想把文章写的更好些,更适合别人的口味,这写文章和写程序类似。
拿着别人的程序一边看一边改,还一边骂,如果是改自己的程序就骂自己。所以还是一气呵成比较好!多给点意见在我后续的文章没有发表出来的时候改进!
现在还记得和CS:IP吗?如果不记得就看看 寄存器CPU工作原理2 是的,它是指示了CPU当前要执行的指令,当然在内存中不只有指令对吧,还有数据!是不是已经想到了呢?是的CPU里面也有指示当前是数据的寄存器…可以肯定你现在一定是想着我把这寄存器的名字告诉你,但是在告诉你这个寄存器之前、我们还需要先了解点东西:
在8086的CPU中,我们是用一个16位的寄存器来存储一个字,高8位存放高位字节,低8位存放低位字节,这个在前面已经很详细的说明了。但是问题现在就来了,在内存中存储时,一个内存单元只能够存储一个字节,那么改怎么办呢?非常简单的可以想到:用2个内存单元来存储一个字节。
那么在用内存单元存储的时候还有点细节需要说明下,首先内存单元是连续的、其次这个字的排列方式是一一对应的,高位对高位,低位对低位。比如现在有2个内存单元0和1那么数据42E2H就是这样存放的。(后面加H表示是16进制的)42H就存放在1单元中,E2就存放在0单元中,当然我还见过介绍相反的存储的CPU,不过在中国不是很普遍,所以就不用了解了。
那么知道了内存中存储字的方式后就可以开始写几条读取指令了,回想起物理地址的给出方法,N * 16 + M。是的和CS:IP一样,不过换了2个寄存器,在8086中,我们用CS:IP指向当前的指令,用DS段寄存器指向数据的段地址,百说不如一练,我们来看看CPU是咋样读取内存中的数据的。
现在我们要读取内存2000处的内容,把他当作数据那么我想代码应该是这样的:
mov bx, 2000
mov ds, bx
mov al [ 0 ]
我们首先把2000赋值给了bx为什么不直接mov ds 2000呢?因为intel不给这样弄。我也没有办法,所以必须先用一个寄存器中转下,也就是说前两条指令的意思就是把ds赋值为2000。 再看看mov al [ 0 ],括号[]表示内存单元的偏移地址,我们已经知道了,CPU必须要段地址加偏移地址才可以定位一个内存单元,那么这里的内存的偏移地址就是[]里面的值。好啦,偏移地址有了,那么段地址在哪里呢?详细的到,它就是DS,所以上面3句代码的意思就是把内存2000:0处的数据读到al寄存器中。
看了上面的代码你肯定有一个问题了,我们用的是8位的寄存器al,如果是ax呢?其实ax也是一样的用,请看下面的代码:
mov bx, 1000
mov ds, bx
mov ax, [ 0 ] ‘就这样,和上面没有区别,
16位的寄存器使用方法和8位的是一样的,不过这里要注意低8位和高8位了 、这就是为什么我在前面要先讲下高8位,低8位的区别了,毛主席说了实践才是硬道理,自己好好用debug试验下就明白了。
我们可以在内存中读取数据,同样的我们也可以给内存单元赋值:
mov bx 1000
mov ds, bx
mov [0], ax
这个道理也是一样的,上面的代码就是把ax的数据赋值给1000:0。是的只是mov换了下位置而已。很简单。
好了,到此为止,我们知道了CPU无论在任何时候都把CS:IP指向的代码当成指令,在任何时候都把DS:[]中的代码当成数据。了解这个以后离开始写“hello World”就不远了。
我们再总结下MOV指令,这个指令被称为传送指令,这个指令有以下几种形式:
mov 寄存器, 数据 ‘比如 mov ax, 1
mov 寄存器,寄存器 ‘比如 mov ax, bx
mov 寄存器 内存单元 ‘比如 mov ax, [0]
mov 内存单元 寄存器 ‘比如 mov [0], ax
mov 段寄存器 寄存器 ‘比如 mov ds, ax
相同的还有add和sub指令,它们的操作数都和mov是一样的,add是加法指令,sub是减法指令。
接下来我们就要讨论下栈了,什么是栈呢??栈也是一些存储空间,不过去有个特性,,就是后进先出,最后面放进去的数据在拿的时候却是最先出来。好比我们有个存放东西的桶,最后放进去的东西刚好是在上面所以最先被拿出来。
现在几乎所有的CPU都有”栈”,8086也是不例外的,所以我们在编程的过程中就可以把一段内存当作栈来使用。
8086有两个指令一个是入栈push和出栈pop。push和pop总是成对出现的,比如想要把寄存器ax的值入栈,那么应该这样:push ax 。pop ax,表示从栈顶取出数据送入ax。
需要特别说明的是,8086的入栈和出栈都是以字为单位进行的,也就是2个字节。
那么现在我们已经知道了push和pop是出栈和入栈的指令,那么我们如何向CPU申请一段空间做为栈使用呢?回忆我们前面讲的,我们是怎么让一条指令得到执行呢?哦对啦是让CS:IP指向这条指令,那么很明显,我们要把一段内存空间作为栈来使用我们应该怎么弄呢??非常简单在8086里面有两个寄存器是专门用来存放栈地址的,SS段寄存器和SP寄存器。
通过上面的讲解我们就知道了栈这玩意了。同时我们又知道了CPU一个天大的秘密:在任意时刻:SS:SP都指向栈顶这个秘密是非常重要的,知道了这个我们就可以看看在执行push ax的时候CPU都做了些什么??
push ax这条指令执行的时候CPU总共会做这些事:
1:sp寄存器-2
2:将ax中的内容送入ss:sp指向的内存单元处(注意高位低位的排列方式)同时ss:sp此时指向新的栈顶内存单元。
大概就是这些内容了,如果你有不明白的我想是这个:为什么先要让sp-2?,这个其实很容易理解,因为ax入栈要2个字节来存放,sp-2以后才可以指向新的栈顶对吧。那么我们由此也可以得出结论,栈是从高处向低处增长的,我这样说你可能很难明白,我改这样说吧:如果现在我们把1000:0-1000:f这段内存当作栈来使用,那么最先存进去的是在1000:f处,然后向1000:0处增长。
说起来比较麻烦,其实现在的32位汇编都把这些封装了,我们都不大需要了解这些,只要我们想用栈的时候用就是咯,这里主要是让大家了解下,你可能要问我为什么要栈这玩意呢??很简单,如果你反汇编过程序你就知道了,为什么在调用其他子程序的时候前面有很多的push,那就是栈,为的就是用栈来传递参数,所以我们是非常要必要了解下这个东西的。如果你以为到这里就完了的话,那么就错了,在8086中还有个需要注意的地方。
假设我们现在要使用1000:0-1000:f作为栈的时候,在栈里一个元素也没有的时候那么ss:sp的值是多少呢?你可能会认为是1000:f,但是我告诉你错了,ss:sp的值应该是1000:10才对,也就是1000:f下面一个元素。为什么会这样呢?这个想想也是比较容易理解的,ss:sp只有指向栈顶最高位再低一位才合乎逻辑对吧,就是我们算时间一样12号到16号总共有多少天?如果是16-12是不对的,应该再减一个1才对。。
那么执行pop ax的时候CPU又做了些什么呢?刚好和push ax相反:
1:将ss:sp指向的内容送入ax中(注意高低位排列)
2:sp寄存器+2 ss:sp指向当前栈顶下面的单元,把ss:sp-2作为新的栈顶。
pop和push是配套出现的,所以pop的注意事项和push其实是一样的。但是pop和push还有个非常值得注意的地方,比如说我们现在想把1000:0-1000:f作为栈使用,那么我们可以让SS:SP指向栈顶1000:f-1也就是1000:10处(计算机中是16进制的),那么CPU知道了我们的栈顶,可是CPU怎么我们要在什么时候结束呢?比如我们想要把1000:0-1000:f这段栈空间使用完了后让CPU通知下我们,或者用哪个寄存器告诉CPU栈已经用完了那该多好啊。但是我不得不难过的告诉你 ,CPU只知道我们的栈顶在哪里它可不管你分配了多少空间,比如现在我们现在的1000:0-1000:f做为栈使用,没存放一个数据sp-2当存放满了以后也就是到了栈顶已经指向了1000:0处的时候如果你还要放数据进去,CPU照收,这样就是和我们的想法不同,如果我们在1000:0以下的内存空间中放了重要的数据的话,那么就会被覆盖、这就是传说中的栈顶越界。
你别以为就这样就完了,相同的问题还发生在pop上,如果你把最后一个元素出栈以后继续指向pop操作的话,那么我们就会得到一些不是我们期望中的数据,所以我们在编程的时候要特别小心这个栈顶越界的问题,要根据可能用到的最大栈空间来安排,防止入栈的数据太多而发生越界,同时在出栈的时候也要注意,别要在栈内已经空空如也的时候继续执行出栈操作,这是非常愚蠢的。
样刚才我们提到了push ax 那么push可以操作数可以是些什么呢?那么向这些指令都是正确的:
push 寄存器 ‘比如push ax
push 内存单元 ‘比如push [ 0 ](注意咯,还记得上面说的吗?段地址在DS中,偏移地址是[]里面的数字)
push 段寄存器 ’比如push CS (push还可以操作段寄存器)
pop的操作指令和push是一样的,push有上面pop就有什么,只是相反而已。
这篇也算写完了,这是非常难过的一篇,我非常怕写的你看不明白,也非常怕我有哪里讲错的地方、我还害怕你失去学习下去的信心,其实不难,只是我这里不好画图,昨天我弄了几张图片想更好的说明下,可是我上传了半天也没有弄好,这WP博客系统也是个麻烦事,算了,希望你能够好好体会,同时多用debug做试验,接下来的课程是比较轻松的了,我们开始向学习其他语言一样开始编写程序了。在学习编写程序之前我还是要打击下你:希望你一定要把这篇和前面几篇弄懂才好,不然后面的学习起来是非常痛苦的,如果你有什么不明白的话可以给我发邮件,agan88@foxmail.com。 同时希望你多提意见,哪里没有明白一定要说。
最后还是要告诉你,下去多练下debug。对你有好处的。