注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

疾风之狼博客

 
 
 

日志

 
 

[转载]汇编教程 - 知识准备  

2007-04-14 13:57:29|  分类: NES资料 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

汇编教程 - 知识准备

  导向汇编的圣堂

你的文曲星中塞了写什么东西呢?我想必不可少的有PC-TOOLS。大家想用BASIC

写出来吗?但是如果仅用纯BASIC时无法达到的。真正要成为LEE一样的高手,你

对象,同时

亦是一个很方便的工具。因为,PC-TOOLS是目前我所见过的文曲星上的汇编写成

的最大的一个软件。而且编程风格良好,代码井然有序。下面,进入正题。

其实你的确已经写过一些低级代码了。不是有一个播放音乐的例子吗?就是通过

POKE一段机器码然后CALL来实现的。不过,那还不能称为汇编,而是机器码。下

面我就帮你把几个可能另人迷惑的概念讲一下:

机器码:对于未经过训练的人来说,是天书。对于通天高手来说,是文章。对于

CPU来说,是唯一的信号。所以说机器码是给CPU来阅读的,其实并不适合人来读

。并且你对于电脑或者文曲星的一切操作最终都表现在机器码上。一切的一切都

,什么编程语言,什么样的编程方法

,什么样的操作。机器码是一切一切的基础。目前文曲星编程界能达到如此化境

的高手屈指可数,而且我仍然未能身列其中。至于机器码到底是什么样子,以后

再说。

汇编:是一种快速读写机器码的方法。使用短小的助记符比如LDA来代替机器码。

所以写起来要轻松许多,而且读起来更加容易明白什么意思。所以汇编码是给人

读的,但是机器就不能直接读了。所以需要有一个翻译的过程,最终仍然是编程

机器码给CPU阅读。本教程主要使用汇编代码,不过仍然需要大家了解知道机器码

。如果有必要,还是要自己尝试写机器码的,毕竟文曲星上用汇编不如电脑上这

么方便。

高级语言:相对于汇编和机器码而言。他们两者合称为低级语言。意思并不是说

功能差,而是贴近硬件的原因。所以,相对的高级就是与硬件不是太直接相关,

更多的照顾人的思维。比如GVBASIC就是一种高级语言。C也是一种高级语言。文

曲星可以使用这两种语言来写程序。GVBASIC一般直接在星星上写,用GVBASIC助

手。而C则一般用电脑写,工具是WINIDE,也就是那个GGV说的开发环境。C和

BASIC虽然同为高级语言,但是也有些不同点。还记得我前面的机器码是一切一切

的基础吗?那么BASIC和C也要变称机器码才能够运行罗。对,而且两者的不同点

也就在这里。BASIC是运行的时候边运行边翻译,所以速度不行而且以源代码的形

式发布。C则是先翻译好然后运行,所以速度要好得多,而且功能也要强大得多,

并且可以保留源代码。高级语言不是这里的重点,所以就略提这么一点。

所以说导向汇编也就是导向令人生畏的低级语言。一定程度上是广大GVBASIC爱好

者难以接受的。不过,姑且一试。而且,我写这个教程的目的也在于此——让更

多的文曲星FANS能体会到究级编程的乐趣。如果你不明白前面讲的三个概念,一

点关系都没有以后也会慢慢体会到的。

工欲善其事,必先利其器

此乃古训也。首先来看看写汇编可以倚仗的利器有那些:

1、PC-TOOLS。主要用它的调试工具以及编程资料。

2、WQXHEX。sun写的超级精简的分析以及反汇编工具。当然如果你不怕麻烦,用

来写机器码也是可以的。有BASIC,COM,内核三个版本供你选择。推荐你使用徐

洪海集成,我作成EXE文件的“汇编专用系统升级”。右键+热键启动(何为热键

?上面一排都是)

3、XASM 3.5。徐洪海写的汇编工具。也是写汇编的一个最佳办法,我没有用过改

装过的可以汇编的PC-TOOLS,不过想来操作也不方便。

然后是电脑上可以使用的工具

1、HOTLINK中的紧急修复。你要写汇编,写机器码。死机,开不了机都是难免的

。有了这个工具就可以减少99%的问题,目前我还没有失败过。据徐洪海说,只

要不是硬件问题,都可以用这个东西修复。

2、WINIDE。咦?这个家伙不是写C的工具吗?的确,不过由于这是GGV出的编程工

具,仔细研究,可以发现许多系统内部的知识。比如系统函数列表就是从这里出

来的。而且,我怀疑GGV的所有的程序都是用它写的。

3、文曲星汇编器。到目前为止,最新的版本是4.5。由于该软件的作者就是我,

所以我可以提供100%的技术支持。主要功能是在电脑上用汇编写BIN,COM文件,

有标签,向导等减轻难度的特色,有条件就用这个写。而且,可以直接在PC上调

试。源代码开放。

本章取自C++程序设计轻松入门的附录,我辛苦输入的

二进制和十六进制

1、引言

读者很早以前就学习了算术的基础知识。每当看到数字145,读者不用怎么想就会

立即想到“一百四十五”。理解二进制和十六进制需要读者重新考察数字145,不

要把它看成数,而是把它看成某个数的编码。

让我们从小的开始:考察数三和“3”之间的关系。数字3只是胡乱写在纸上的一

个字;而数三则是一个概念。数字是用来表示数的。

如果我们认识到三、3、|||、]|[和***都可以用来表示三这个概念的时,它们之

间的区别也就显而易见了。

在十进制数学中,我们用数字0,1,2,3,4,5,6,7,8,9来表示所有的数字。数字时如

何表示的呢?我们可以用A来表示十;也可以用]||||||||[来表示这一思想。罗马

数字使用的是X。我们所使用的阿拉伯数字系统利用数字之间的关联的位置来表示

值。的一列(最右面)用来表示1位上的数字,下一列用来表示10位上的数字。因

此,数十五表示15——也就是1个10和5个1。

从中形成某些准则,并推广如下:

a、十进制使用数字0~9

b、列是10的幂:1,10,100等

c、如果第三列是100,两列能构成的最大数是99。更一般的说,用n列可以表示0

到(10^n-1)之间的数。因此用三列可以表示0到(10^3-1)即0~999。

2、其他数制

我们使用十进制并不是巧合;我们有10个手指头。但也可以假想其他不同的数制

。使用十进制中归纳的准则,八进制可以描述如下:

a、八进制使用数字0~7。

b、列是8的幂:1,8,64等。

c、用n列可以表示0到(8^n-1)之间的数。

为区分用不同数制写成的数,把数制作为下标写在数字的后面。十进制的数十五

就写成15(10下标),读作十进制的十五。因此如果用八进制表示15(10下标),

就可以写成17(8下标)。它读作八进制的十七。注意也可以把它读作“十五”,

因为它仍然那表示十五这个数。

为什么是17呢?因为1表示1个8,7表示7个1。1个8加上7个1等于十五。考虑十五

个星号:

********** *****

一般是把它分成两组,一组十个星号,另一组五个星号。这就表示成十进制的15

(1个十和5个1)。也可以把星号作如下分组:

******** *******

即八个星号和七个星号,这就表示了八进制的17。即1个八和7个1。

3、关于数制

数十五和十进制中表示为15,在九进制中就表示为16(9下标),在8进制中表示

为17(8下标),在七进制表示为21(7下标)。为什么是21?在7进制中,没有数

字8,要表示十五,需要2个7和1个1。如何推广这一过程?要把十进制数换成7进

制数,考虑不同的列:在7进制中它们分别是1,7,49,343等。为什么是这些列

呢?他们分别表示70,71,72,73。自己这样作一个表格:

4 |3 |2 |1

7^3 |7^2 |7^1 |7^0

343 |49 |7 |1

第一行是列数,第二行是7的幂。第三行是每个数字所代表的值。

要把十进制数转换成7进制,过程如下:考察该数,决定先用哪一列。比如:如果

数是200,显然第四列(343)是0,可以不用考虑。

要想知道有多少个49,用200除以49。答案是4,因此把4放在第3列,再考察余数

:4。4中没有7,因此再7那一列放0。4中有4个1,因此在1那一列放4。答案是404

(7下标)。

要把数968转换成6进制:

5 |4 |3 |2 |1

6^4 |6^3 |6^2 |6^1 |6^0

1296 |216 |36 |6 |1

968中没有1296,因此第5列是0。968除以216商是4,余数是104。第4列是4。104

除以36商是2,余数是32。第3列是2。32除以6商是5,余数是2。因此答案是4252

(6下标)。

5 |4 |3 |2 |1

6^4 |6^3 |6^2 |6^1 |6^0

1296 |216 |36 |6 |1

0 |4 |2 |5 |2

当从另一个数制(如6进制)转换成十进制时则比较简单。可以按如下乘法:

4×216=864

2×36 =72

5×6 =30

2×1 =2

968

3、1二进制

二进制是这一思想的极端扩展。二进制中只有两个数字:0和1。各列分别是:

列:8 |7 |6 |5 |4 |3 |2 |1

幂:2^7 |2^6 |2^5 |2^4 |2^3 |2^2 |2^1 |2^0

值:128 |64 |32 |16 |8 |4 |2 |1

要把88转换成二进制,过程是一样的:没有128,所以第8列是0。

88中有一个64,所以7列是1,24是余数。24中没有32,因此6列是0。

24中有一个16,因此5列是1。余数是8。8中有一个8,所以4列是1。没有余数,所

以其余列都是0。

01011000

为了检验答案,我们再把它转换回去:

1×64=64

0×32=0

1×16=16

1×8 =8

0×4 =0

0×2 =0

0×1 =0

88

3、2为什么使用二进制?

二进制的力量在于它与计算机需要的表示的内容对应非常清楚。计算机根本就不

知道字母、数字、指令或程序。在计算机的核心只有电路,在给定的接点上,要

么有大量电荷或者几乎没有。

为使逻辑清楚,工程师并不把它作为相对的刻度处理:“少量电荷,有些电荷,

较多电荷,大量电荷,巨多电荷”;而是把它作为二进制刻度处理:“足够电荷

或不足电荷”。他们甚至连足够或不足都不说,而把它简化为“是或否”。是或

否,或者真或假,可以表示成1或0。习惯上用1表示真或是,但这仅是习惯而已;

用1表示假或否一样方便。

一旦从直觉上作出这样一个巨大的飞跃,二进制的力量就显而易见的:用1和0就

可以表示每个电路的基本事实:有电或者没有。计算机所知道的只是“有还是没

有?”有就是1;没有就是0。

3、3位,字节和四位元组

一般决定用1和0表示真和假,二进制位(BITS)就变得非常重要。因为早期计算

机一次传递8位,开始使用8位数字(称为字节)编写代码是非常自然的。

注:半个字节(4位)称为四位元组!

使用8位数字可以表示256个不同的值。为什么?考察一下各列:如果所有8位都设

置为1,值是255。如果都不设置(所有位都是0),值是0。0~255是256个可能的

状态。

3、4什么是K?

这是因为210(1024)月等于103(1000)。这一个巧合真是太好了,没有人愿意

错过它,因此计算机科学家利用一千的科技前缀kilo把210字节称为1K或1千字节

类似的,1024×1024(1048576)与一百万非常接近,碧烫烫烫烫烫烫烫烫烫烫烫

添示1M或者1兆字节;1025兆字节称为10亿个字节。

4、二进制数

计算机使用1和0的不同组合编码它们所能进行的任何操作。机器指令编码成一系

列的1和0,由基本电路解释执行。1和0的任意组合可由计算机科学家翻译成数字

,但如果认为这些数字由本质含义则是错误的。

例如,Intel80x86芯片把位模式10010101解释成指令。我们当然也可以把它翻译

成十进制数(149),但单个数字是没有什么意义的。

数字有时是指令,有时是值,有时是代码。一个重要的标准代码集是ASC][。在

ASC][中每个字母和标点都用一个7位二进制数表示。例如:小写字母a表示为

01100001。虽然我们可以把它翻译成数97(64+32+1),但这不是个数。在这种

意义下,它就是人们所说的ASC][中97表示的字母a;但实际上97的二进制表示

01100001是字母a的编码,十进制97只是人们为方便而使用的。

5、十六进制

因为二进制数难于阅读,人么就寻求较为简便的表示方法。把二进制数转换成十

进制需要较多的数字处理,但把二进制转换成十六进制是非常简便的,其中有捷

径可循。

为理解这一点,首先必须理解十六进制。在十六进制中有十六个数字:0,1,2,

3,4,5,6,7,8,9,A,B,C,D,E和F。后六个数字是随意的。选择字母A到F

是因为我们在键盘上容易表示。十六进制的各列分别是:

4 |3 |2 |1

16^3 |16^2 |16^1 |16^0

4096 |256 |16 |1

要从十六进制转换成十进制,可以作乘法。因此用数字F8C表示

F×256=15×256=3840

8×16 = =128

C×1 =12×1 =12

3980

把数字FC翻译成二进制最好是先翻译成十进制,然后翻译成二进制:

F×16=15×15=240

C×1 =12×1 =12

252

把252转换成二进制需要下图:

列:9 |8 |7 |6 |5 |4 |3 |2 |1

幂:2^8 |2^7 |2^6 |2^5 |2^4 |2^3 |2^2 |2^1 |2^0

值:256 |128 |64 |32 |16 |8 |4 |2 |1

没有256

1个128剩124

1个64剩60

1个32剩28

1个16剩12

1个8剩4

1个4剩0

11111100

因此二进制答案就是1111 1100

现在如果我们把这个二进制数看成是两组四位数字,就可以作一个奇妙的变换。

右边一组是1100。它是十进制中的12;十六进制中是C。

左边一组是1111,十进制中是15,十六进制中是F。

因此我们有

1111 1100

F C

把这两个十六进制数放在一起就是FC,它是1111 1100的实际值。这一简便方法总

是可行的。读者可以把任意长度的二进制数拿来按4分组,把每组翻译成十六进制

数,然后把这些十六进制数放在一起,结果就是十六进制数。下面是一个非常大

的数:

1011 0001 1101 0111

各列分别是1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384和32768

1×1=1

1×2=2

1×4=4

0×8=0

1×16-16

0×32=32

1×64=64

1×128=128

1×256=256

0×512=0

0×1024=0

0×2048=0

1×4096=4096

1×8192=8192

0×16384=0

1×32768=32768

总计: 45527

它把转换成十六进制需要十六进制图表:

65535 4096 256 16 1

45527中没有65535,因此第一列就是4096。有11个4096(45056),余数是471。

471中有一个256。215中有13个16(208),余数是7。因此十六进制数是B1D7。

检查:

B(11)×4096=45056

1 ×256 =256

D(13)×16 =208

7 ×1 =7

总计: 45527

简便方法是把原来的二进制数1011000111010111拿来,每四位分成一组:1011

0001 1101 0111。然后把每四个分别转化成十六进制数:

1011=B

1×1=1

1×2=2

0×4=0

1×8=8

总计 11

0001=1

1×1=1

0×2=0

0×4=0

0×8=0

总计 1

1101=D

1×1=1

0×2=0

1×4=4

1×8=8

总计 13

0111=7

1×1=1

1×2=2

1×4=4

0×8=0

最后的十六进制数:B1D7

  数字决定一切

上次的进制转换有些夸张,篇幅太长了。你可能也已经开始抱怨,怎么还没有开

始写具体的代码啊。急死人了。你急我也急啊,输入这些字很累的。学完本课,

再学一下XASM的使用就开始了,仓促上正只能使你感到左右碰壁。

说明白了,这次就是要让你建立起EVERYTHING IS DIGIT的概念。DIGIT就是数字

的意思。程序是数字,字符是数字。图象也是数字。声音还是数字。只要可以存

放再电脑中或者文曲星中的都是数字。只不过,这些数字通过不同的方法得到解

读,最终有不同的呈现。而那些解读用的程序以及其使用的方法的组成都是数字

数字不一定要是1,2,3,4……。相信你看完进制那部分后一定有这个概念了。

实际放入wqx的是以二进制表示的。就是用一堆元件的开与关来表示。运行也只不

过是基于一堆数字对另一堆数字进行0->1,1->0等的操作,没什么神秘的,但就是

产生了那么丰富的效果。

还记得我说过机器码是一切一切的基础吗?机器码是什么呢?就是二进制数。几

个0,几个1加到一起就成了机器码,cpu就*这些数字来使唤。当然我们要读起来

就不会用二进制,十六进制就是用来干这个事的。比如EA,这么一个字节就表示

一条机器码,意思是NOP,无操作(就是歇着去吧,没你事)。而有些机器码不只

一个字节,比如A9 FF,就是A9和FF两个字节。其中第一个表示何种操作,FF是提

供的参数(比如前前面是叫你去买酒,后面就注明是买啤酒)。长度还有三个字

节的,只不过参数用了两个字节。另外,机器码的参数一般称为操作数。

而汇编呢就是把机器码的第一个字节换成有一定含义的英文字母,比如NOP来代替

EA。NO OPERATION显然要直观得多。而对后面得操作数也作了一些标识,用来区

别不同得类型和语义。不用多想,混个眼熟就行。

好,机器码已经变成数字了。而程序就是机器码的组合(相当于把一些字词连缀

成篇)。所以,很自然,一个完整的程序可以用0和1打印出来。

那么,那些英文字母,单词,句子,中文也是数字吗?当然是啦。表示的方法是

用某个数字与之一一对应,这种对应过程称之为编码。最前面我们不是用过CHR$

吗?那不就是把一个数字换成一个字符吗?字符的编码目前有很多标准,不过都

有一个基本的集合。那就是对英文字母,数字等的编码。由于这个标准是美国人

定的,所以称之为ASCII。叫什么不重要啦。用这个表,可以用数字来表示字母,

当然读的时候也是要用这个表。还有一点,“0”也是字符,他的ASCII码是30而

不是0,类似的“1”为31……

一般情况下一个字节就可以表示一个字符了,0~FF共有256种可能,也就是可对

应256个字符,对于26个字母的英文来说,足矣。而当情况变为中文这样的语言上

来时,就复杂了。256远远不够,常用汉字少说也有3000。所以,为了解决这个问

题,华人就想出用2个字节来表示一个汉字的办法。256×256=65536,这样就够

了。但是由于历史原因,出现了两种编码方案。一种时我们用的GB码,一种时台

湾同胞用的BIG5码。而这两种编码方案又根本没有什么对应关系,再加上GB码只

支持2312个汉字,导致了汉字编码上的混乱。

类似的,图象声音也是用数字来表现的,它们有它们各自的编码标准。但无论如

何,最终表现都是0和1。对于我们来读来写,都是十六进制数。下一次,将讲述

如何用XASM来查看这些数字,以及初步写几行汇编代码。

  学用XASM

前面提过一些工具,好比给了你一个工具箱,当你要钉钉子的时候,不能用刨子

。当你要弹墨线时,不需要用螺丝起子。所以,我们需要选择一下合用的工具。

由于要在文曲星上进行汇编,合用的只有PCTOOLS7.4和XASM。不过,由于作者徐

洪海不善于宣传,XASM3.5居然找不到了。可能你在看到这篇文章的时候,你已经

可以在WQXSTAR.HAS.IT找到它了,如果寻觅不到,也可以向mo2mo@163.com索取。

这是一个bin文件,启动后是软件声明,不用管他。进去后的界面对于不曾用过它

的人来说有些恐怖。其实,也是可以看懂的。最大的那一块是数据显示区,下面

一杠加一行空的是命令输入位置,右边显示的是当前状态。现在,不可能一下全

部明白,也不需要知道那么多。

先教你一招,D。D命令相当于BASIC中的PEEK。只不过是取出从D的位置开始列举

一屏,而且使用的是十六进制。比如D 4000 00。中间的空格是不需要你来输入的

。然后按“=”号执行这一行命令。显示出来的是4000 00处的值。当然4000 00

是地址。怎么和以前不一样?是啊,使用了十六进制嘛。还是不对!那个00是干

什么的?呃,这个下次再说,OK?执行之后,那一块黑压压的十六进制数字区域

就发生了变化,其现在所显示的就是从4000 00开始的值,左边的-地址也说明了

这一点。

再教你第二招,E。E和D不同,D是列举值,也就是数字。而E则是编辑,写入数字

。和POKE有些类似,不过比POKE强大的多,可以用E 2000 00试试。不过很快就会

发现两个问题。有时按“=”号没有用,没有B键。第一个问题是因为十六进制必

须一个字节,一个字节的输入也就是要输入偶数个数字。第二个问题很简单,用.

代替B。注意,不要乱用E,会死机的。

再支一招G。G干什么呢?PPC的第三个,Call干什么的,G就是干什么的。G E06F

00看看,再用CAll写一行看看,是不是一样的。注意一下进制就可以了。

最后按一下F1,是不是数字变乱码了。再试试D 1FC8。是不是可以看到密码了?

想想为什么。再按一下F3,再想。(提示:看看右边的提示,想想什么是GB,

Big5,ASCII)

  汇编教程 - 开始编程

  第一行汇编

进入XASM,按P。地址2000~20FF处被清为FF,并且把当前地址变成2000。然后按

A。这就可以开始汇编了。你先照着作就是了。

JSR$E06F

按“=”号

RTS

按“=”号

跳出。然后G 2000 00。这就写了一个完整的程序了。虽然与G E06F 00效果差不

多。但确实是我们自己写的代码起的效果,而不是用某个工具的某个命令。下面

来解释一下:

JSR是指令名,是机器码20的助记符而$E06F就是加了些修饰的操作数,用$表示这

是地址。RTS是机器码60的助记符,每个程序均需用这个指令结束,否则死机。相

当初,我照着一个6502指令集写第一个程序的时候,就是不知道要用RTS,死机一

个下午。

再按P,按A。输入:

JSR$E1C2 等号

JSR$E09F 等号

RTS

没有什么新指令不过G 2000 00会有不同的感受

出现了一个查找画面JSR$E1C2是显示画面,JSR$E09F是等待按键(相当于INKEY$

),以防止图画一下子就没有了。

如果你用的是PC1000,用JSR$E0E4试试,是不是很神奇。

这只是简单的,功能与CALL,与G差不多。只不过这里用来做一个引子,让你好热

身一下。哦,记住了JSR相当于CALL,RTS返回系统。

  扩展地址的概念

现在来解释为什么D 4000 00要加那个00。还记得原来计算闪存大小吗?那时,算

得只有32K,而实际有512K。32K与512K的关系就相当于一页书的容量和一本书的

容量的关系。而那个00就是用来标识页码的。相应的有D 4000 01,你会发现D

4000 00 ~D 4000 0F的内容各不相同。而D 4000 10则与D 4000 00是相同的,这

是为什么呢?

很简单512K/32K=16,所以只有16页的闪存,即00~0F。那么为什么要用一个字

节两个数字来表示呢?这不是浪费吗?好,先来思考一个问题?按下英汉/汉英,

出现了英汉字典,你有没有想过这些内容是放在哪里的?

原来我们是这样划分的$0000~$3FFF为ram,这个没问题。当扩展了页码的概念后

,注意ram是不分页的即D 2000 00 与 D 2000 01是一样的。接下来$4000~$BFFF

是闪存,而$C000~$FFFF是rom,同样这里的rom也是不分页的。其中,没有发现这

些wqx内置程序的地方,是不是觉得有些神秘?我想你也猜不到它们躲在哪里,直

接告诉你们好了。$4000~$BFFF就是它们的居所,载体不是闪存而是rom。那么矛

盾出来了。闪存与rom的地址怎么会重叠?机器怎么区分我们要的是rom还是闪存

呢?你按一下F2,就会发现D 4000 01的内容和没有按下的时候发生了变化(怎么

不用D 4000 00举例?因为它很特殊)。变化的原因就是你从闪存切换到rom。而

这个rom与$C000~$FFFF的rom不同,它非常大所以也分了页。页数也非常多,以至

于00~0F都不够表示,所以页码使用了一个字节,即00~FF。

怎么用页码,怎么用闪存/rom。我们已经了解了xasm操作上的问题,而实际上cpu

获知我们的命令是通过查看$0000与$000A两个ram地址处的值。目前,我们仅需要

了解一下,还不需要自己去操作这两个地址。

  重新审视变量

变量是一个什么样的概念?在basic中,肯定没有仔细想过这个问题,随手提出来

用就是了。变量用来存放数制或者字符串,而且其内容是可以改变的。那么汇编

中呢?

首先告诉你,汇编中没有变量这个概念。啊?奇怪了是吧。但是我们手中有对硬

件的绝对控制,basic可以做到的,汇编一定可以做到。变量到哪里去了呢?其实

变量就是对应了内存中的某个值,而变量就标识了存放的地址。而在汇编中,直

接操作内存,所以省去了变量这一步。

然后你是不是想要把某个值存入内存中呢?也就是像POKE那样,但是你找不到这

样的指令:

mov $2000,FF

以实现把FF存入$2000的愿望(这条指令只是在文曲星等8位机上没有,如果你有

兴趣研究现代的CPU指令集,你会发现mov的)。那么用什么指令可以达到目的呢

?用下面这一段:

LDA #$FF

STA $2000

RTS

告诉我LDA,STA是什么?呃,这个,不知道。我不是问你它们是干什么用的,是问

你这个是什么。助记符嘛,用来代替A9,8D这样的机器码的。#$FF呢?参数。#不

是代表地址而是真正的数值。$2000就是目标地址。RTS结束程序。好,问题集中

到了LDA与STA是干什么上了。

你可以想想,当你妈不准你对着汤碗直接喝汤的时候,你会怎么办?我想头脑正

常的人都会用汤勺。然后送入嘴中。现在FF好比汤,$2000好比嘴,那么LDA与STA

呢?自然就是舀汤与喝汤两个动作。而勺子呢?勺子就是寄存器A。好极,汇编的

重量级主演——寄存器正式登场。寄存器A,就是LDA(LOAD A)和STA(STORE A)中

的A,现在好理解了吧。先把FF塞入A中,再从A中存入$2000,实现了POKE

2000,255的过程。

这里寄存器用作了暂存容器,而实际上汇编指令就是在寄存器,内存中打转,所

以还有许多其他用途,而寄存器数量也不只一个,有A,X,Y等好几个,分别也有不

同的作用。

  进一步学习寄存器

寄存器就是CPU内部的一些元件,一个寄存器可以存放一个字节的数值。因为一个

字节是八位二进制数,所以文曲星使用的是8位的cpu。寄存器有几种:

⒈通用:A,X,Y

⒉栈:S

⒊指令指针:P

⒋标志寄存器

最常用的是A,X,Y,以及标志寄存器。目前先掌握A,X,Y的使用。

LDA #$01

ADC #$02

STA $2050

RTS

猜猜这一段是干什么的。你输入后执行,可以按R查看运行后寄存器(Register)

的状态。发现A是03,所以应该已经知道ADC是一条干什么的指令了。这里也就演

示了寄存器的另一个重要作用,提供操作数,这里是提供被加数。用D 2050 00查

看结果是否真的被保存了。

然后你可以设想一下了,从两个地址分别载入值,相加,再存回去就是一个加法

程序了:

LDA $2050

ADC $2051

STA $2052

RTS

先用E 2050 00把$2050,$2051放入两个不太大的数字,然后运行后查看$2052,和

A,看看是不是如你所愿。然后用两个较大的数字,再看看。

然后你会发现,比如FF+01=00,这就叫绕回现象,本来应该是100,但由于寄存器

仅有八位(二进制),二位(十六进制),所以1就被挤出了,仅存入00。一个形

象的比喻是汽车上公里表到底之后就会回到00000,基本是一个道理。

寄存器在这里有点像试管。从试剂瓶中去处试剂,然后进行各种操作,用作反应

容器。可见,寄存器承担了一个多么重要的角色。

  汇编教程 - 接着来学

    跳转  

何为跳转?用过GOTO不,那就是了。不过,由于行号概念的消失,取而代之的就

是地址了,当然这个地址可以是RAM,闪存,ROM。先来考虑一下,无跳转时程序

的执行。

首先,当你给文曲星接上电源之后,就有程序在运行了。那是文曲星的系统,也

就是操作系统(win98是什么?操作系统)。我们自己的程序使用G+地址来启动

,此时cpu位于我们指定的那个地方载入指令执行。如何来表达cpu位于某个地址

这个概念?这就要谈谈cpu内部的一个特殊的寄存器P。P中就放着这个地址,而且

总是放着cpu将要前往的目的地的地址。执行一个,P往后移动一些,执行一个,

再移动一些,直到我们的程序RTS为止(此时P中并不是空了,而是回到系统了,

不需我们操心)。

那么跳转就是要改变这种一个压一个的执行顺序。涉及到三个指令:JSR,JMP,RTS

。你会惊异地发现三个中的两个我们都已经用过,而那个JMP和GOTO几乎一样,而

JSR似乎和GOSUB有些像。它们的作用就是改变寄存器P,使得cpu到处跑动。

JSR是那种fetch的动作。去了之后还要回来的。比如JSR $2500,那么在执行了

$2500处的一段代码之后cpu会回到JSR $2055这条指令所处的内存地址的下一行。

这也就是我们用JSR $E06F会回来的原因。

RTS不是控制程序的结束吗?但是你想想,程序结束了你的星星就关机了,黑屏了

吗?没有,是回到XASM了。其实G 就相当于JSR。执行了我们的程序还要返回的。

而RTS就是告诉cpu回去吧,程序结束了。

JMP与GOTO很像,也是有去无回。JMP到某个地址与这个地址中的内容接到调用处

的后面效果是一样的。由于我们JSR $E06F后没有什么,实质性的代码,可以把

JSR $E06F与RTS并为JMP $E06F。想想,省去的RTS到哪里去了?

下面再看一个例子:

在$2000处按A

JSR $2500

DEX

RTS

在$2500处,按A。

LDX #$20

RTS

怎么到$2000与$2500?用D呀。

G 2000 00。然后用R查看寄存器,会发现X不是20而是1F。当然20-1=1F(不是19

哦!十六进制嘛)。

虽然LDX没有讲过,但如果你不知道是干什么的也就不用看下去了。与LDA差不多

嘛。DEX从结果也应猜出,就是减一。然后你应已经明白$2500处的代码如何被调

用,如何返回,假如$2500处的RTS去掉则死机,与程序没有用RTS结束是一样的。

现在要进一步的看看机器码,用D 2000 00,十六进制数值为:

200025CA60

先来作一个划分:200025 CA 60

20,CA,60是指令。分别为JSR,DEX,RTS而0025则是JSR的参数。如果你不用A,用

E 2000 00,输入这些数字也是一样的。

比较奇怪的是0025与$2500正好相反,这是机器码的规定——高八位在后,低八位

在前,特别要注意。当你开始用机器码写程序的时候这个是经常犯的错误。

然而我们用E 2000 00把20改成4C。再运行一下,你会发现X变成20,而不是1F。

DEX没有执行。这是怎么回事呢?

用D 2000 00回到代码,按U。可以把机器码变回汇编代码(也是所谓的反汇编)

。你会发现第一行因为我们把20变成4C,现在成了JMP $2500。你现在想想为何

JMP 下面的指令为何没有被执行。

 

汇编教程——栈

 

首先要把栈和客栈区别开来,虽然也不能说没有关系(客栈是暂时休息的地方,

栈是数据暂时保存的地方),但是差别还是很大。

栈是一个计算机中很重要的概念,它是一段连续的内存区域用来保存临时的数据

。而且对于数据的进入和弹出有特别的规定。为什么需要栈呢?问得好。

就是给一个规范的存储临时数据的地点。比如你可以用LDA和STA结合起来达到把

数据储存在内存的某一个角落,你可以把重要的数据(比如密码校检地址)放在

那,也可以临时的数据(比如你觉得寄存器不够用,先要空出一个来)。这样或

许没有提供一个专门的地点放临时数据好。而且看到push和pop这样也比较规整,

另外jsr的返回地址等也是放在栈中。

说实话栈并不是一个很好理解,很好用的东西。有时候我们习惯堆栈连称,我认

为这不是规范的叫法,因为堆实际上是heap,就是自由使用的内存的意思。而栈

则是stack,是内存中的一块连续区域(其实栈也不是哪里飞来的一块空间,还是

在ram中,对应地址在6502这样的环境中是$0100~$01FF)。所以还是规范一下称

呼,说栈就可以了,不要说堆栈。

我前面说过栈有一个特别的规定,那就是先进后出,后进先出。这个就是因为栈

是一个连续的内存中的区域。而把数据加入和数据从里面取出是有一些规矩的。

先引入一个新的寄存器先,栈顶寄存器(S)。栈顶,顾名思义是记录栈的顶部的

寄存器。那么用这个寄存器的值,加上栈底部的值($0100)就可以得出当前栈顶

处的位置。而这个位置被指出的意义就在于,我们所有能够对栈进行的操作都是

对栈顶的操作。也就是你不可以直接操作放在栈最底部底值(也就是最先进入的

值),也不可以从中间随机抽取出几个值来操作。不行!都是要在栈顶的。操作

的指令有:

PHA 把A寄存器的值压入(输入)栈中

PHP 把P(标志寄存器)的值压入栈中

PLA 把栈顶处的值取出并放入A寄存器中

PLP 把栈顶处的值取出并放入P寄存器中

这些就是我们可以对栈(本身)进行的所有操作(还有对栈顶寄存器的操作)。

分析这些指令也就知道为什么栈是一个先进后出,后进先出的东西。因为你只有

取出了栈顶,栈顶寄存器的值才会加一,从而使下面一个数据成为栈顶。而你加

入(压入)了一个数据,栈顶寄存器的值也就会指向你新压入的值。对了,为什

么是加一?因为栈顶寄存器是这样的,当其值为FF的时候,说明栈是空的没有东

西。是FE的时候,说明栈中有一个值,存放在$01FF处。也就是说这是一个倒过来

的概念,不过不用你管,php,pha这些指令会自动改变栈顶寄存器。你用下面的

指令就好了:

LDA #$6

PHA ;此时栈顶处为6

LDA #$8

PHA ;此时栈顶处为8

PLA ;此时栈顶处为6,A中为8

PLA ;此时栈顶处为原来操作前的值,A中为6

不过要记住一定要一个PH配套一个PL。因为栈中存放了一些对于cpu来说重要的数

据,不可以在你的程序中任意破坏(也就是jsr的返回地址)。如果破坏了栈,八

成死机。

TXS (Transfer X to Stack ptr) $9A 2

TSX (Transfer Stack ptr to X) $BA 2

这两个指令分别把栈顶寄存器的值放入A寄存器中和从A寄存器中取出。目前我们

还不需要使用这些值,但是如果要编写比较高级的程序就可能需要用这两个指令

来重整栈。比如PCTOOLS入口处就进行了栈顶保存的工作。

 

抱歉:由于本来对栈并不是很了解,在询问了sun很多问题后,才跌跌撞撞的写完

了本篇,如果看到了云里雾里我也没有办法,自己分析一些实际的例子或许有比

较深刻一些的理解。

 

寄存器

 

首先纠正一个错误,程序指针(当前执行位置的寄存器)习惯的代号是PC,而不

是P。

上次讲到了跳转,但是那样的跳转并不是健全的,试想你可能需要在程序执行到

某一个地方,根据用户的输入跳转到不同的地方。这个比顺序执行要求要高,而

且简单的使用jmp或者jsr都无法满足这样的jmp on condition(条件跳转)的要

求。这个就是高级的跳转——有条件跳转。我们来想一下程序的执行过程,就像

一棵大树,主轴就是那个树干,然后有一些跳转,而这些跳转就想主轴两侧的分

支。所以我们可以用brnch(树枝)这样的单词来形象的形容跳转的概念,而条件

跳转的几个指令的汇编符号就是b**(**代表其他字母),也就是brnch on ***的

意思。那么我们还有一个问题,这样的分支的根据是什么,也就是cpu凭什么去这

不是去那。

也许你会说,凭我们的输入呀。对,我们的输入一般用系统函数(文曲星内部的

一些可以供我们调用的机器码,放在$E006之后)获取,存放在寄存器或者内存中

。但是实际上cpu并没有强大到这种程序,也没有提供什么直接比较两个数是否相

等的指令。这些都算比较高等的逻辑,并不适合cpu。那么cpu可以给出的是上一

步的状态,然后这些条件跳转的指令就是根据上一步计算的状态来决定下一步的

程序流向(很形象的比喻,程序也有程序流的说法)。而上一步计算的状态就是

由标志寄存器各个位的是0还是1来表示的。所以下面主要讲一讲标志寄存器的必

要知识,这方面我也不是非常厉害,如果由错误之处还请高手们纠正。

先引用一段徐洪海写的教程中关于标志寄存器的描述:

————————————————————————

标志寄存器P

┏━━━━━━━━━━━━━━━┓

┃N ┃V ┃ ┃ B ┃D ┃I ┃Z ┃C┃

┗━━━━━━━━━━━━━━━┛

N:负数标志。指令执行完毕后,若结果大于 $7f那么N标志位被置1,若结果为正

,N=0。

V:溢出标志。指令执行完毕后若结果溢出,则V=1。

B:BRK指令标志。此位被置1表示由于执行了BRK指令而使程序被中止。

D:十进制运算标志。此位置0使6502做二进制运算,置1做十进制运算。

I:中断标志。此位置0表示允许中断,置1表示禁止中断,但非屏蔽中断不在次范

围。

Z:零标志。指令执行完后,若结果为0,Z=1。

C:进位标志。若最高位产生进位,则C=1

————————————————————————

这些就是关于标志寄存器的基本情况,你每一步的计算或者操作有可能就会改变

这些标志的状态,比如你用6去和A寄存器比较,而6正好又是A的值,那么标志寄

存器的Z那个位置就会变成0,那么就有一个条件跳转指令可以根据A的状态前往不

同地方。如:

LDA #$6

CMP #$6

BEQ $xxxx(代表某个地址)

BEQ的意思就是Brnch on equal(相等的时候跳转)。实际的操作是判断标志寄存

器的Z(也就是第七)位,是否为0。常用的这样的条件分支指令有:

————————————————————————

BPL (Brnch on PLus) $10

BMI (Brnch on MInus) $30

BVC (Brnch on oVerflow Clear) $50

BVS (Brnch on oVerflow Set) $70

BCC (Brnch on Carry Clear) $90

BCS (Brnch on Carry Set) $B0

BNE (Brnch on Not Equal) $D0

BEQ (Brnch on EQual) $F0

————————————————————————

这几个都很常用,具体的使用需要在实际的练习和工作中才可以领会,有时候用

那个都是不一定的,需要一些经验进行判断。其中有一个比较重要的标志位,那

就是C,也就是Carry。很多时候会这个标志会和jsr,rts联合起来用。C标志位成

为那个子程序(也就是jsr进入的那一段代码)的返回值,比如那个是一个判断密

码是否正确的子程序,这时C标志位就可以用来标记密码是否输入正确了。那么这

个操作可以用某个数学运算来达到,比如你故意使得产生进位,但是你可以直接

用指令操作这些标志位的状态(是0还是1)。那就是:

————————————————————————

CLC--清除进位标志C 0-C

SEC--置位进位标志C 1-C

CLD--清除十进制运算标志D 0-D

SED--置位十进制运算标志D 1-D

CLV--清除溢出标志V 0-V

SEI--置位中断禁止标志I 1-I

CLI--清除中断禁止标志I 0-I

————————————————————————

利用这些标志位,利用这些操作标志的指令,利用这些根据标志位进行操作的分

支指令,我们可以把if,把while把for等循环以及判断语句用汇编表达出来,这

些都很有模式。在实际的应用过程中都不难理解的。

条件转移指令

这类指令共有八条,都是采用相对寻址方式的两字节指令,

各种条件转移指令的功能都是根据指令中要求的条件是否满足来决定是进行分文

转移或是

继续执行下一条指令,若转移则转移的目标地址用转移步长的形式存放在指令机

器码的第二字节中。条件转移指令虽是根据P寄存器中各标志位状态来确定程序是

否转移,但它本身的执行却不影响P寄存器状态。条件转移指令的重要性在于它们

使计算机有了 判断功能,即可以根据具体条件的满足与否来决定执行哪一段程序

分支。

如:030E: LDA $06

0310 :BEQ $0342

0312 : AND #$0F

……

0342:ADC $6000

执行到BEQ$0342这条指令时,如果前一条指令LDA $06执行 结果由06单元取入A的

数是八位全0码而使标志位z=1,则BEQ $0342执行之后就转移到0342处去执行

ADC$6000,若是06单 元中的数不是0,则LDA$06执行结果z=0,那么BEQ$0342 执

行之后将继续顺序执行下一条指令AND#$0f,可见程序由 于BEQ$0342的执行而产

生了两个分支。此例中BEQ$ 0342的 第一个指令字节所在地址是0310,而转移的

目标地址是0342,所 以BEQ $0342的机器码指令为F030两字节,F0是操作码,30是

转移步长。BEQ$0342执行后不论是否转移,都不影响P寄存 器中各标志位,而维

持它们的原有状态.

。是FE的时候,说明栈中有一个值,存放在$01FF处

  评论这张
 
阅读(259)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017