请选择 进入手机版 | 继续访问电脑版

电玩龙资讯台

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 1950|回复: 3

Genesis M68000 游戏程序开发指北 - M68000汇编指路牌

[复制链接]
发表于 2015-3-3 00:55:10 | 显示全部楼层 |阅读模式
1.Genesis基础知识
[本文翻译自www.hacking-cult.org的MD Programming部分,作者drx,某些部分作了更详细解释和修正]
[发表于意志之路,转载请保留这第一第二行中括号内内容,暂不可用作商业用途]


第一节 Genesis基础知识


-处理器
Genesis/Mega主机(不含32X与SegaCD)由一个摩托罗拉68000(以后简称作M68k或68k)处理器作为其CPU,处理游戏程序(音乐部分由Z80处理器负担,后面会提到)。

68K是一块16位的处理器,在当时家用机里面是很不错的,所以Genesis I型机上还凸印着大大的"16-BIT"字样。本文主要讲述用68k的汇编语言在这台主机上进行开发,当然你如果喜欢像20来年前的技术人员一样直接用机器码写,我是不会拦你的,本文多少对你也有些帮助。汇编语言虽然比机器编码好接受些,而且能让周围人觉得你比写机器码的弟兄/(姐妹?!)头脑正常些,但它仍然不算很有趣,而且在只懂高级程序语言的人群中,你依然会被归入头脑“有问题”的群体。接着看下去之前,请最好先具备一些计算机高级语言(如C/C++/Pascal,Java/C#也行,不过面向对象概念我们几乎用不着,不要嘲笑过去的技术缺乏此类概念,它们运行的时间代价和空间代价可要比现在的新东西低得多),另外最好有一门任意CPU的汇编基础,8080/8086/Z80/6502/ARM都可以,如果有M68k系列的汇编基础,那再好不过,大可以跳过这个入门篇,直接转向深入篇了解Genesis主机特性相关的内容。

-内存
Genesis主机含有64K RAM,就是内存啦,用于存放任意数据。另有64K显存专属于VDP(Video Display Processor,处理图像的专用CPU,即现在所说的GPU) ,也可以理解为一块64K显存的显卡(是不是很迷你:目),其用途是存放调色盘,角色精灵的属性,图像和其他背景卷轴数据。

Genesis的内存地址映射规则如下:
$000000-$3FFFFF - ROM(就是游戏卡带,由此看出不做特殊硬件技术处理的话最多支持32MBit就是4M大小的卡,注意超过16MBit卡存档处理也不同)
$400000-$9FFFFF - Sega官方保留
$A00000-$A0FFFF - Z80处理器用
$A10000-$A10FFF - I/O
$A11000-$A11FFF - 控制器(包括手柄、鼠标等各种输入设备)
$B00000-$BFFFFF - Sega官方保留

$C00000-$C0001F - VDP
$C00020-$DFFFFF - VDP镜像
$E00000-$FFFFFF - RAM(其中$E00000-$FEFFFF官方文档标为不可访问,有效访问区域为$FF0000-$FFFFFF,而实际上如果访问$E00000-$FEFFFF,得到的是间隔$10000的循环数据,也就是和$FF0000-$FFFFFF一样的数据)

-数据类型
M68K汇编变成中涉及到三种数据类型,字节(Byte)、字(Word,=2Byte)、双字(LongWord,=4Byte)。没有所谓的有符号数无符号数之分,正负号的处理由汇编程序自身或高级语言编译器负担。

-寄存器
Genesis含8个数据寄存器(D0=D7)8个地址寄存器(A0-A7),16个都是LongWord类型,一般地址寄存器开发时只用A0-A6,因为A7由Genesis系统本身用于存放栈顶指针。数据寄存器一般用于存放实际的数据,地址寄存器间则一般用于存放RAM中的地址,供间接访问内存数据用。

-数据存放结构
如果是学X86汇编的,要特别注意,68K系列以Big Endian方式存放数据,所以$12345678在内存中是$12,$34,$56,$78这样的顺序,而32位X86却是$78,$56,$34,$12这样的顺序。值得注意的是同一台机体中的Z80是以Little Endian方式,就是刚才说的X86这种存储顺序。

-总结

这些内容是些基础特性,以后会经常用到,即使记不住,以后用到时多回过头来翻翻就会很快接受。但如果绝大多数都看不懂,那最好先拿起《IBM-PC汇编语言程序设计》(沈美明、温冬婵编)这本书看掉前5章。


[SONIC3D 2007年9月12日21点15分]

回复

使用道具 举报

 楼主| 发表于 2015-3-3 00:55:31 | 显示全部楼层
提个问题:将MD_Rom用工具(IDA)反汇编出来后,看到的是80x86的汇编吧?还是有专门有反汇编MD_rom到68K汇编的程序?
用IDA的推荐方法:
先下载IDA5:http://gzu.3322.org/download.asp?downid=2&id=1135
然后下载附件里的idc脚本
genesis_ida.rar (1.85 KB)  genesis_ida.rar (1.85 KB)
下载次数: 12

2007-9-19 22:01
这个脚本原本是从KModGens作者的站点上下的,稍微又改了一点
下载后,解压到IDA目录下的idc目录

1.运行idag.exe,选择New
2.然后关闭弹出的向导,这样就可以只看到IDA空的项目界面
3.直接拖放一个bin格式的rom到ida主界面上(smd格式的不可以,要先转成bin)
4.弹出的Load a new file对话框中央有一个Process type,默认是Intel 80x86,往下拉选择Motorola series:68000
5.点一下这个下拉框右边的Set按钮(这个一定要按,否则下拉框的选择是没用的,还是当80x86的bin文件处理)
6.点OK,然后弹出一个Disassembly memory organization对话框,不用改,之后会用那个idc自动改,直接点OK
7.稍候会弹出ida无法识别入口地址的框,直接点OK,然后会出现几乎没有经过任何处理的反编译结果。。
8.点"File"菜单下的"idc file",然后打开刚才解压在idc目录里的genesis.idc
9.然后你就会发觉多了一个浮动工具条,上面两个按钮,第一个是修改genesis.idc脚本,第二个是执行该脚本,你在打开这个脚本的时候,已经执行了,把反编译窗口往下拉一点就可以看到,ida已经从md rom header指定的程序入口开始反编译了。
10.你可以继续对这个反编译结果加注释,更改Label名,但是如果做好这些操作以后,千万不要再点那个浮动工具条上的"执行脚本"按钮,否则你的所有手动修改就都没了。。。。我的魂斗罗将近2周的反编译结果就是这样被结果掉的。。。。我点了以后还点了保存。。。郁闷

附加:
11.ida自动的反编译不能完全识别所有分支,比如魂斗罗里很多case分支的汇编写法人工优化很多,都不是直接跳转,这种情况下就需要人工读懂代码,然后到真正跳转的目标代码地址,光标点在那一行,然后按"Edit"菜单下的"Code",或者(最好是)按快捷键C
12.ida处理未知数据默认按byte方式,有些可能为字符串的他就自动按string处理,但有时候它的智能判断是错的,或者有时我们不想让数据按byte显示,而是按word或long显示
如果需要让按byte或按string显示的数据按word或long显示,只要把光标放到那个数据位置上,然后按D键就可以在三种数据显示格式间循环切换
如果想让某些数据按ascii字符串显示,只要按A就可以
如果想让某些数据按Unicode或者UTF-8之类的复杂字符编码格式显示,一定要到"Edit"菜单下选择String子菜单下的操作

应该这些已经很详细了,最后再说一点,IDA5反编译好以后有时会显示框图式反编译结果,如果你不是想看逻辑框图,那就在上面右击,选Text View

如果还需要更详细的IDA使用教程,我以后另外再写,让我先把MD的汇编开发教程写完。。。。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-3-3 00:55:58 | 显示全部楼层
第二节 征途由此开始


我们就要开始踏上Genesis编程的征途了,总要先储备些东西给之后漫漫长路使用吧,所以首先要学会的就是搬运东西,或者在程序的世界中称作搬运和增减数据。

这一节中你会学到以下指令:
move 相当于高级语言中的赋值号“=”
add    相当于高级语言中的加号“+”
sub    相当于高级语言中的减号“-”  

-准备工作
下载一个SNASM68k或者jas(我个人喜欢用jas,见附件,含2种汇编器的编译方法和示例代码),他们是68K的汇编程序,可以把你的汇编代码转为68K机器码,也就是Genesis可以运行的东西。
附件下载
jas_using.rar (42.65 KB)  jas_using.rar (42.65 KB)
jas及其使用方法
下载次数: 9

2007-9-19 20:48
snasm68k_using.rar (294.15 KB)  snasm68k_using.rar (294.15 KB)
snasm68k及其使用方法
下载次数: 8

2007-9-21 12:21

-指令入门
Move可以复制数据(无论是直接给定的还是放在寄存器或RAM里的数据)到指定的寄存器或RAM地址里。

语法:move source,destination
例子:
     move.w #2,($FFFFFE10).w
     move (a6),d3
     move.b ($FFFFFE12).w,(a5)+

是不是已经晕了?即使你用过X86汇编,可能也会对这三行东西有些搞不清。
首先,是跟在这条指令后的.w和.b,他们是什么?其实它们主要用来指定move指令操作的长度,也就是复制多少长的数据,如果不指定,默认一般是.l型的,其中.b是byte/.w是word/.l是longword。

举些好懂的例子:
move.b #1,($FFFFFE10).w

执行前 - FFFFFE10: 05 06 07 08
执行后 - FFFFFE10: 01 06 07 08

move.w #1,($FFFFFE10).w
执行前 - FFFFFE10: 05 06 07 08
执行后 - FFFFFE10: 00 01 07 08

move.l #1,($FFFFFE10).w
执行前 - FFFFFE10: 05 06 07 08
执行后 - FFFFFE10: 00 00 00 01
应该明白了吧。

#1就是立即值或者说常量1,#后面除了可以跟10进制数字外,还可以跟其它的,比如'SEGA',这就是我为什么称它为立即值而不是立即数的原因,尽管本质上它们还是数字。
#5     十进制 5
#$20   十六进制 20 - 十进制 32
#%1001 二进制 1001 - 十进制 9
#@777  八进制 777  - 十进制 511
#'SEGA' - ascii字符串(这里可以用些小技巧,优化内存拷贝的速度。因为一个ascii字符就是一个字节,所以使用move时,所以1个字符时可以用.b类型,2个字符可以用.w,4个字符可以用.l,而3个字符可以用1个.b加1个.w,明白我的意思吗)。

($FFFFFE10).w是直接地址,或者叫绝对地址,看过前一章节的应该知道这是RAM地址(最前面的FF先不要去管)。
值得注意的是RAM内的地址一般用.w访问,而ROM内的地址一般用.l访问。极少数游戏有用.w方式访问ROM的,但这往往造成一些难以查觉的bug;另一点要注意的是不要往ROM地质范围内写入数据,在模拟器里结果未知,但在真正Genesis上会造成当机。

再举些例子加深理解:
move.b #1,($FFFFFE12).w
复制数字1(以字节方式)到RAM中的$FFFFFE12地址上
move.l ($12346).l,d0
从地址$12346(ROM中)复制一个longword型数据到d0寄存器
move.w (a0)+,d0
把a0寄存器存放的值作为地址,从该地址里复制一个word型数据到d0寄存器,之后将a0里的值增加2(2字节,即word的长度)。



是不是看出我接下去想说的了?没错是寄存器。
之前提到了共16个寄存器,都是longword型,也就是可以存4字节,8个作为地址寄存器(a0-a7),8各数据寄存器(d0-d7)。
地址寄存器一般存放指针,也就是某个实际数据所处的地址(可以在ROM里也可以在RAM里,甚至可以是其他I/O端口)。本质上它们和数据寄存器一样,甚至多了数据自增功能,所以他们也可以用作数据寄存器,但是a7除外,他被系统用作栈指针(我以后解释),所以除非你很自信,否则不要盲目改动它,会造成堆栈混乱。

move.b ($FFFFFE12).w,d0     ;从$FFFFFE12地址复制值到d0寄存器(分号后的是注释,汇编代码里要注释的话就是这种格式)
move.b d0,($FFFFFE13).w     ;把d0的数据复制到$FFFFFE13地址中去
这两句所作的操作其实就等同于
move.b ($FFFFFE12).w,($FFFFFE13).w

但用寄存器的话有一个好处就是你可以在不改变源数据的情况下改变寄存器里的值,然后把它存到另一个地方去。起到了临时变量的作用,而且相比用RAM作临时变量的存放点,寄存器要快得多,原因参见任何一本计算机系统结构书 。

move.b ($FFFFFE12),d0 ;复制$FFFFFE12中的值到d0(注意那个括号后面没有.w,自己汇编一下看看有什么区别)
add.b #2,d0 ;d0中的值加2
;其他后续操作。。。。

新指令来了,ADD。它可以往寄存器或RAM里增加数值。

语法:

add source,destination
例子:
     add.b   #2,($FFFFFE12).w     ;将$FFFFFE12中的值增加2
     add.l   (a0),a0                    ;取出a0中的值作为地址,把该地址中的值加到a0中去
     add     d0,d0                      ;把d0中的值加到d0中去 (d0 = d0 * 2)

a0代表一个值,也就是存放在里面的值;(a0)代表存放在"以a0值为地址"的值。很拗口,但是如果你有其他CPU汇编的基础,那就很好理解了,因为就是间接寻址嘛!
假设:
$FFFFFE12: 01 ;$FFFFFE12处的值为01
则有:
a0:   $FFFFFE12    ;address
(a0): 01               ;value at address $FFFFFE12

地址寄存器特有的自增操作使你可以作延后自增和超前预减:
move.b (a0)+,d0     ;指令执行后a0自增1(取决于move指令的操作长度)
move.w -(a0),d0     ;指令执行前a0预减2
举例:
move.l (a0)+,d0
执行前: a0: $00012345
       d0: $00000000
       $000012345: $FFFFFFFF
执行后: a0: $00012349
       d0: $FFFFFFFF
       $000012345: $FFFFFFFF

加法介绍完了,那接下来肯定是减法操作,指令是SUB,用法和ADD几乎一样。
举例:
sub #5,a0                       ;简单的说就是a0=a0-5
sub.w (a0),($FFFFFE12).w
sub.b d0,d1

接下去要讲比对和跳转操作,挑战一点点开始了。。。                              

[SONIC3D 2007年9月19日19点17分]

http://www.ad1874.cn/bbs/attachment.php?aid=85
http://www.ad1874.cn/bbs/attachment.php?aid=116

[待续]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-3-3 00:57:37 | 显示全部楼层
第三节:这条路不对,我要绕过去

本章附件:68000指令参考手册
68000.pdf (822.2 KB) 68000.pdf (822.2 KB)
下载次数: 24
2007-9-20 13:55



我们经常会碰到走到岔路路的情形,生活中如此,RPG游戏中也如此,在看过攻略或撞过墙以后,我们肯定会知道到底该往哪条分支路线走。这些决策都是在我们脑中完成的,那怎样让68K CPU完成类似的操作,让我们的程序流程按着一定的条件走我们想走的分支路线呢?简单的概括就是一个比较和跳转的过程。

在这一节中,我会穿插强化一下上一节讲的内容,所以请务必看熟上两小节内容,至少是大部分内容。

MOVE指令的各种形式:
MOVE指令有很多其他形式,比如MOVEA, MOVEM, MOVEP和MOVEQ,它们对于数据的传送方式各有不同,编译后生成的二进制机器码也不同,一般在编译时,编译器会选择合适的形式,但有些时候你必须在写代码的时候就按情况手工指定(编译器毕竟不如人那么万能)。。

===
MOVEA是复制一个地址值到地址寄存器:
语法:movea   address,a0-a7
举例:
movea   #$FF0000,a0     ;执行后,a0里存储的就是$FF0000,以后用(a0)就可以读出FF0000地址里的数据
movea   (a0)+,a5           ;执行后,把FF0000里存的一个数复制到a5,然后a0自增
movea   d0,a3               ;把d0里的值复制到a3

注意我之前讲过的,不要去用a7(并不是不能用,只是在你了解它用途前最好先不要动它,我之后会讲解他的实际用途)

===
插入一个知识点:Label,中文一般译作标签
和basic还有c语言差不多,他的格式类似于:
           .....
           bra    Label    ;跳转到下面的Label处,然后继续执行,如果是用过C,那你可以理解为goto语句
           .....
Label:
           .....
就是这样,Label没有什么太多可说的,但你在之后的汇编开发中经常要用到。

===
跳转指令(本节重点开始了):
刚刚在讲Label的例子时用到了bra,他的全称是branch,就是无条件跳转
实际上跳转指令还有很多,因为他们打头都为B,所以一般被称为Bcc,cc就是以一个68000状态寄存器的值来进行跳转的条件
比如有:
BEQ         Branch if equal        如果相等则跳转
BNE         Branch if not equal   如果不相等则跳转
具体的下一节会详细讲解,不过仅根据这两个我们就可以看到,实际这些跳转指令和他们的英文全称只见很容易联系起来,因而数量虽然比较多但也不会太难记。
不过撇开这些有条件的跳转,我们先来仔细看看无条件跳转,无条件跳转有两种:
BRA   这个就是我们刚才Label例子里用的,一个短跳转
       BRA的参数是一个16位的偏移量,也就是说刚才例子里那个bra Label实际对程序来说只是bra #$????(此处????是一个16进制数)
      实际CPU执行时遇到BRA #$FF就往后跳过FF个字节(256字节)的地址然后执行那个地址里的指令
       bra.b的跳转范围在-127...127,bra.w的跳转范围在-32 768...32 768
JMP   这个是长跳转,和BRA的区别就是他的参数是一个32位的绝对地址,也就是可以从$00000000一直到$FFFFFFFF
      远大于Genesis的实际内存范围,所以用这个指令可以在Genesis系统的任意部分跳来跳去,也就可以做很多很强的事情
       比如说,把卡带ROM里的一部分代码解压到系统内存,然后一个JMP,跳到内存的指定地址开始执行
       最后再一个JMP跳会卡带ROM的范围继续执行卡带里的程序等等
       这不是瞎想出来的,Sonic3里就有这样的代码,另外在Sega Game Can这个Sega CD游戏中,那些小游戏也是这样处理的。
你肯定会有疑问,JMP既然可以在全部范围内跳转,为什么不都用JMP来写代码?关键就在于生成的代码大小,因为参数是32位的,所以就编译后生成的机器码来说,JMP的要比BRA多将近一半,对于容量有限的卡带,字节数就是金钱,就是成本,区别就在这里。

比较了BRA和JMP,不得不提另一组B和J打头的指令,BSR和JSR:
BSR    Branch to SubRoutine  跳转到子程序,如果你写过basic,那很好理解,就是GoSub了,它比BRA多做了一步,就是在跳转前先保留下一条指令的地址,然后让CPU去执行那段分支程序,一旦遇到rts指令,就返回到那条保留好的地址继续执行。这样就相当去调用了一段子程序后,返回原先的调用点,继续执行下去。
JSR    Jump to SubRoutine  看过BRA和JMP的区别,不难猜到这条指令的含义吧。没错,就是接受参数的不同,BSR范围有限,而JSR可以在全机的整个内存访问范围内跳转。

例子:
jsr   $FFFFFFF0
jsr   Label
jsr   a0

搞清楚这两组功能类似的B打头和J打头的指令还是必要的,尤其是在对rom做一些小hack的时候,插入或修改了一些自己的代码以后,你必须搞清改用B跳还是J跳引导原程序走到你写的代码上去。搞错了,执行是会出错的。

===
a7寄存器派什么用?

上一节我说了不要随便动a7寄存器,那他派什么用处呢?刚才那段BSR的介绍里里我提到了BSR比BRA多了一步保存返回地址的操作,这个返回地址必然是保存在一个内存区域里,这个区域叫做栈,随便找本汇编书都可以把栈的概念讲得很透彻,但我还是简单概括一下。栈就像一卷用来放硬币牛皮纸小卷(见过新包装好的一卷卷的1元硬币吗,没见过的话去超市问问超市阿姨。。。),硬币一个一个堆叠进去的话,如果不用暴力,那么你想取出硬币时必须再一个一个取出来,最后放进去的最先取出,也就是所谓的后进先出。

而a7这个寄存器就是用来保存栈顶地址指针的寄存器(你可以想象成那卷硬币的开口处),每存入一个数据前,a7里的数据自减一次。举例来说,系统初始时,a7指向$1000000,这是Genesis的内存末端,我们执行了一个涉及栈操作的指令后(比如BSR),某个longword型数据要入栈,那么a7=a7-4,a7就变成了$FFFFFC,然后把那个数据写入(a7)也就是写入$FFFFFC,这样入栈过程就完成了,要出栈时(比如BSR所跳到的SubRoutine结束,执行rts时),只要直接读取(a7),然后a7=a7+4就可以了。根据入栈和出栈数据的长度不同,a7的自增或自减的数也不同。

那么a7到底可不可以改动呢?当然你硬要改也是可以的,毕竟一卷硬币你用刀把中间划开也是可以从中取出硬币的。但请记得在改动a7以前,把他的值保存在内存的某个你指定的地方,以便恢复,否则栈一旦乱了,你的所有程序很有可能都会乱套(99.99%的程序在执行时要用到栈空间来存放临时数据)。

===
先前讲完了无条件跳转,那肯定还要讲有条件跳转
那有一个问题就是条件的来源是什么?
条件在高级语言中一般就是if之后的括号里的东西,比如
if (var1>0) then {do something;}
这里var1>0就是条件,如果var1比0大,那么条件为真,如果var1小于等于0那么条件为假
这个比较过程在汇编里不能直接用XXX>YYY来写,而要用cmp也就是compare来做比较:
    movea.l    var1,a0   ;把var1这个地址复制到a0
    cmp.b      (a0),0     ;比较a0地址里的数,也就是var1地址处的数和0的大小,比较方法是用后一个数减前一个数
    ble.w       Label1     ;如果关系是小于,也就是说后一个数小于前一个,就跳到Label1处去
                                ;这里我用ble.w,因为我假设中间省略的代码大于127,用ble.b不够,实际应用中,如果距离短,也可用ble.b
    ......
Label1:
    ......
var1:
    dc.b      $4          ;定义一个数字等于4,因为它紧接着标签var1,所以它的地址实际就是var1

上面这段代码有新内容,也有我们已经学过的
先看最后一行这个dc.b,他表示define code,in byte也就是手工定义一个字节的机器码,自然也就可以作为手工定义数据的方法,我们定义了$4在这个内存区域。你也可以用dc.w,dc.l定义word和longword数据,更可以用dc.b 'SEGA'的方式定义四个字节的字符串。
第二行的cmp.b的操作是把后一个操作数减去前一个操作数,然后把一定的结果保存下来(保存在哪里下一节讲),然后第三行的条件跳转指令ble读取这个保存下来的结果,然后根据情况决定是否跳转。
整个代码的逻辑就比较清楚了 ,这一例中,它的执行顺序和结果是:先把var1这个地址复制到a0,然后将0减去var1这个地址里的值4,发觉是小于关系,符合ble的小于则跳转的条件,然后CPU就跳到Label1处开始执行代码了。
其他所有条件跳转的机制和ble是一样的,只是条件内容本身不太一样而已,下一节讲完那个保存比较结果的东西之后,我就会详细列出所有条件跳转指令,大家也可以先下载附件里的68000指令参考手册第10页自己翻翻,如果能看懂,那是再好不过了。

======
这一节重要的内容基本都结束了,那就顺便把开头时还没讲的几个move指令也讲掉吧。

===
MOVEQ和MOVE没啥大区别,它的意思是move quick,执行速度比较快,但每次只能复制1字节
例如:
moveq #0,d0
执行后,清空了整个d0寄存器,因为当你用moveq复制数据到寄存器时,他会置整个寄存器的位
如果你用move.l,完成同样的工作就需要更多的代码空间。

与MOVEQ类似的还有ADDQ和SUBQ,这两个请查阅附件里的68000指令参考手册自行学习(你可以用Ctrl+F搜索addq和subq,很快就能找到)。

===
MOVEM指令,即move mutiple,这个可能比较复杂些,但也不是太难懂。
他是一个批量复制的操作,我直接举些例子吧,这样更好懂。

MOVEM.L D0-D7/A0-A6,$1234                 ;把d0,d1,d2,d3,d4,d5,d6,d7,a0,a1,a2,a3,a4,a5,a6寄存器里的内容
                                                         ;按longword型存放到$1234开始的内存地址中去
                                                         ;就是依次存放到$1234,$1238,$123C.......中去
MOVEM.L (A5),D0-D2/D5-D7/A0-A3/A6
MOVEM.W (A7)+,D0-D5/D7/A0-A6
MOVEM.W D0-D5/D7/A0-A6,-(A7)
以上三个请自己理解一下,其实MOVEM的特点就是一个参数为寄存器列表,另一个参数为单个的内存地址,然后把寄存器列表的数据依次存到单个内存地址开始的区域,或是从这个区域复制数据依次放到寄存器列表。

那它有什么实际应用呢?
一个最好的应用就是在进入SubRoutine时保存现场(就是保存所有寄存器状态),然后在这个SubRoutine返回前恢复现场。
例子,大家看一下这段有问题的代码:
        move.b        #$10,d0
        bsr              Subroutine
        move.b        d0,d1
        ...
Subroutine:
        ...
        moveq         #0,d0
        moveq         #0,d1
        moveq         #0,d2
        ...
        rts
在这个代码里,有一个Subroutine子程序,他用到了d0,而在进入这个子程序前,d0里是保存着东西的,假设这个东西并不想让Subroutine更改,而是想在Subroutine执行后再使用的,那么这段代码就不符合要求,Subroutine把d0改为了0,并且返回时没有恢复d0的原有数值$10,使得外层代码在以后用到d0时获得了错误的数据,继而可能引发错误的结果,甚至当机。

那在寄存器有限的情况下,如何解决这种情况?
1.你可以打电话给M公司,大骂他们设计cpu时留了太少的寄存器。。。。但如果他们的客服比腾逊客服的态度更好些的话,因该会告诉你,你现在在看网页的这台电脑的cpu,寄存器也并不比68000多太多。。。。。实际上14个32位寄存器对于16bit的游戏机来说足够多了。。。
2.从你的程序道具箱里调出MOVEM,然后选择“使用”。(无限次道具,免装备  :目)
        move.b        #$10,d0
        bsr              MovemSubroutine
        move.b        d0,d1
        ...
MovemSubroutine:
        movem        d0-d2,-(sp)            ;sp和a7是等价的,你也可以写成movem        d0-d2,-(a7)
        ...
        moveq         #0,d0
        moveq         #0,d1
        moveq         #0,d2
        ...
        movem         (sp)+,d0-d2
        rts
这样在Subroutine的开始处,对自己要用到的寄存器内容先进行了保存,然后在结束返回前进行了恢复,这样就不会影响到外部程序了。

MOVEM的参数组合可能性实在太多了,更多使用情况参照附件里的68000指令参考手册35页

今天就先写到这里,下一次讲解条件比较的结果到底存放在哪个“宝箱”里,还有就是所有的Bcc条件跳转指令


[SONIC3D 2007年9月20日13点50分]

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|手机版|Archiver|电玩龙资讯台 天空联盟- SKY LEAGUE

GMT+8, 2018-10-15 21:04 , Processed in 0.087890 second(s), 16 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表