街机模拟游戏逆向工程(HACKROM)教程:[6]68K汇编简介

发布时间:2024年01月18日

以下说明来自维基百科,可以大概了解一下关于m68000汇编

摩托罗拉68000型中央处理器,或称MC68000,680x0,m68000,m68k,68k;是由美国摩托罗拉公司(Motorola)的半导体部门(现已独立成为飞思卡尔公司(Freescale))出品的一款16/32位CISC(复杂指令集)微处理器。

地址总线

68000地址总线为24位,故支持16MB最大物理内存。在使用32位长字对地址进行存储和计算时,高位的一个字节会被自动忽略。这种设计使得其具备相当的向前兼容性,可以直接运行为后续的纯32位CPU编写的软件。也因此,根据现今的定义,68000应称得上是一款32位CPU。摩托罗拉使用32位内部总线的目的在于希望能够在68000上编写可以被将来的后续产品直接使用的软件,而相关指令不必作位数上的调整。

然而,程式工程师还是有可能编写出无法与后续产品兼容的软件。倘若这种24位软件丢弃高位字节,或将该字节用作寻址以外的目的,它就有可能在32位68K系列CPU上运行失败。这就是说,对于希望支持向前兼容的软件,必须始终使用32位长字寻址,并且将最高位字节置零。
内部寄存器

68000包含8个32位通用寄存器(D0-D7),及8个32位地址寄存器(A0-A7)。最后一个地址寄存器,即A7,也作为标准栈指针使用,在编程中可以使用SP作为同义词。这组寄存器在规模上恰到好处:既可以对中断快速反应(只有十多个寄存器要保存),也有足够的寄存器来进行快速计算。

尽管两种寄存器并存有时会比较麻烦,但在实践中并非难于掌握。据称,这还使得CPU的设计者们可以通过对地址寄存器组使用辅助计算单元,从而实现较高程度的并行机制。

存储内容高位字节在前(Big Endian模式),与x86相反。
状态寄存器

68000比较、算术和逻辑操作会在状态寄存器SR的低端字节(又称CCR)中设置一些标志位,以供之后的条件跳转使用。这些标志位是:得零(Z)、进位(C)、溢出(V)、扩展(X)、负数(N)。尽管许多时候值是相同的,X与C依然是两个不同的标志位。这就允许算术、逻辑和移位操作的多余位与逻辑控制/连接造成的进位区别开。
指令集

68000的指令集基本上是正交的。大部分指令被划分成操作和地址模式两部分,并且大部分地址模式都对几乎全部指令可用。这种近似正交性在编程人员当中毁誉参半。

编程者会清楚地发现,他/她所书写的指令可能被汇编成几种不同的二进制操作码。这实际上是一种不错的妥协:一方面,在便利性上与纯粹的正交指令系统相仿;一方面,CPU设计者可以有更多的自由来设计操作码表。

对于一台16位时代的机器而言,由56条指令构成的最小指令集仍显巨大。此外,许多指令和寻址模式会在指令后边加入地址/寻址模式码。

许多设计者确信MC68000体系结构应基于成本考量使用较精简的指令码,特别是使用编译器自动生成时。这种认识为对其设计上的成功加分不少,并且使之成为一种经久不衰的体系结构。这一信条持续地保证了整个系列指令集的设计优势,直到ARM体系结构引入同样精简的Thumb指令集。


标准寻址模式

68000提供多种寻址模式,并统称为有效寻址(EA)。在CPU参考手册中,经常会有诸如MOVE <ea>,<ea>这样的表记方式。这表示在目的操作数和源操作数上可分别使用一种(但通常不是全部)有效寻址。

??? 寄存器直接寻址
??????? 数据寄存器直接寻址,如D0。
??????? 地址寄存器直接寻址,如A0。通常不使用A7。

??? 寄存器间接寻址
??????? 简单间址,如(A0)。先取得A0所存储的地址,再在内存中该地址处取出数据。
??????? 自增间址,如(A0)+。只能用于源操作数域。先取得A0所存储的地址,再在内存中该地址处取出数据,然后A0的内容(地址)自增一定长度(因指令后缀而定)。这个操作其实相当于x86中的出栈指令POP(注意栈的方向和内存方向相反)。在这里,A0被用作一个用户自定义的栈指针,与系统使用的A7/A7'不同。
??????? 自减间址,如-(A0)。可用于源或目的操作数域。先将A0的内容(地址)自减一定长度(因指令后缀而定);然后取得A0所存储的地址,再在内存中该地址处存入数据,然后这个操作其实相当于x86中的入栈指令PUSH。[1]
??????? 偏移间址,如2AFF(A0)。前边的偏移量为16位。实际取得的地址为(A0)+2AFF。
??????? 加索引的偏移间址,如E3(A0, D0)。前边的偏移量为8位。实际取得的地址为(A0)+(D0)+E3。

??? 程序计数器位移
??????? 长位移,如2AFF(PC)。前边的偏移量为16位。PC变为(PC)+2AFF。
??????? 短位移,如E3(PC, D0)。前边的偏移量为8位。PC变为(PC)+(D0)+E3。

??? 绝对内存寻址
??????? 直接使用内存地址值,如$4000,表示目标地址在内存地址为4000处。注意$表示后边所跟数字为16进制,%表示2进制。
??????? 在实际编程中,可以使用代码中的标号来充当内存地址值。汇编器会自动翻译为数字地址。
??????? 注意不应与立即数混淆。使用立即数时应在值前再加一个#。$4000表示16进制地址4000;#$4000表示16进制立即数4000;4000表示10进制地址4000;#4000表示10进制立即数4000。

指令后缀

大部分指令都有表示操作长度的后缀:.B、.W或.L,分别表示这一操作在字节(8比特)上、字(16比特)上或长字(32比特)上进行。运算的过程和结果都会受到后缀的影响。在运算过程中,只有属于操作长度范围的部分才会参与运算。比方说,执行MOVE.B D2,D1会把D1的最低一字节复制到D2的最低一字节处,而两者的其余字节均不受影响。对于CCR,各标志位的值会由操作长度的最高有效位决定。如果某次ADD.B使得结果的第7Bit位为1,则CCR的N位会置1;如果某次ADD.L使得结果的第7Bit位为1,并且第15Bit不为1,则CCR的N位不会置1。在后一种情况下,CCR的N位只受第15Bit,即一个字的最高有效位影响。另一个例子是在自增/自减寻址中,自增/自减的长度因操作后缀而异。如果操作为MOVE.B,则自增/自减1;W为2;L为4。
常用指令

大部分68000的指令都是二元的。目的操作数在前,源操作数在后。

??? 移动指令
??????? MOVE:标准移动指令。另有一些其它移动指令供选择:MOVEA(移动到地址寄存器An,不会影响CCR,但后缀不可为.B)、MOVEQ(移动一个8位数到目标寄存器,因为可将数值直接写入指令故称快速移动)、MOVEM(移动寄存器组到指定堆栈,多用于中断/子程序处理的第一步)等。
??????? LEA:移动一个地址值到目标寄存器。LEA $2000,A0表示将A0的值设为16进制的2000。应注意此时不加立即数标志#。
??????? LINK/ULNK:建立/取消堆栈帧。这个指令用于翻译高级语言的函数调用。LINK An, #-x可将An作为函数栈指针,并SP所指向的用户栈内取得x大小的空间存储局部变量,之后SP仍将指向栈顶,而An指向栈顶+x的位置。x的值在编译时由编译器自动算出。

??? 算数指令:ADD(加)、SUB(减)、MULU /MULS(无/有符号乘)、DIVU/DIVS(无/有符号除)、NEG (取补)、及CMP(类似于减但只会影响标志位而不改变操作数)。算数指令也多成组提供。对于ADD,还有ADDA(加地址,不会影响标志位)、ADDI(加立即数)、ADDQ(快速加,加数不大于8以便于直接放在操作码中,比x86的INC指令书写麻烦但功能更强)、ADDX(影响进位符,用于大数运算)等。

??? BCD码算数指令:ABCD(加)和SBCD(减)。

??? 移位指令
??????? 逻辑移位(移位后用0补充):LSL(左移)、LSR(右移)
??????? 算数移位(移位后用原来最高/最低有效位补充):ASL(左移)、ASR(右移)
??????? 循环移位(移位后用所移动位补充):ROL(左移)、ROR(右移)
??????? 循环扩展移位(移位至CCR的X位,同时用之前的X位补充):ROXL(左移)、ROXR(右移)

??? 位操作指令:BSET(置1)、BCLR (清0)和BTST (如果测试位在改变前为0则将CCR的Z设为1)。

??? 多任务处理:TAS(测试并置位)。这个指令用于实现信号灯等同步机制。

??? 流程控制:JMP(无责任跳转)、 JSR (跳转至子例程)、BSR (按相对地址跳转至子例程,多用于跳转至用户定义例程)、RTS (从子例程返回)、RTE (从异常/中断中返回)、 TRAP(自陷,即软中断)、CHK(检查地址是否越界,如是则触发异常)。
??????? JMP指令只是纯粹跳转,不会将下一条指令地址压栈;JSR和BSR会将下一条指令压栈。
??????? RTS将栈指针指向内容弹出给PC。

??? 条件测试并跳转:Bcc系列指令。cc定义了所测试的条件位。常用的如:
??????? BNE:不等于时跳转。
??????? BEQ:等于时跳转。
??????? BRA:无条件跳转。

在上一章中,我们已经接触了部份M68000的汇编指令 和 MAME的DEBUG调试器的指令 ,这两部份对模拟游戏的逆向工程都十分重要,尤其是M68000的汇编指令。我们以上一章中所接触到的汇编指令sub.w? D0,($6c,A2)开始,来一步步学习M68000的汇编

sub.w D0,($6c,A2)

我们先把这条命令分解,来介绍这条命令各部份的作用,为了方便说明,我们从这条命令分解后从后往前来解析:

1、($6c,A2)??????? - 目的操作数,大部份指令都会有"目的操作数"和"源操作数"两个部份

??????? 目的操作数的内容会随着执行指令而改变的操作数,也就是说,在该指令运行后,该操作数的值会产生不同的改变。$为指定该数值为16进制数值,A2为说明指向A2寄存器。

2、D0??????????????? ? -源操作数,大部份指令都会有"目的操作数"和"源操作数"两个部份

??????? 源操作数为只用作读取的操作数,源操作数不会因为指令的执行而发生改变

3、.w?????????????????? -指令后缀,大部分指令都有表示操作长度的后缀

??????? 指令后缀有三种长度,分别是 B、W、L

??????? .B??????? - 这一操作长度为字节(8比特)

??????? .W??????? -这一操作的长度为双字节(16比特)

??????? .L??????? - 这一操作的长度为四字节(32比特)

运算的过程和结果都会受到后缀的影响。在运算过程中,只有属于操作长度范围的部分才会参与运算。

4、sub ??????????????? -指令,指令是用来说明该指令的作用,如该命令的作用为 "目的操作数-原操作数 (结果)置入 目的操作数"

我们用一个命令的实例来更加详细地说明指令的工作原理:

MOVE.B D2,D1

该指令的目的操作数为D1寄存器,源操作数为D2寄存器,操作长度为字节,指令为传送指令,作用是把数据从源操作地复制到目的操作地。

我们假设该指令的源操作数D2寄存器的值为$16(十进制为:22),然后假设目的操作数D1寄存器的值为FFFF FFFF。那么,当该行指令执行后,目的操作数D1寄存器的值会改变为:FFFF FF16。

我们可以用一个68000汇编程序的集成开发环境(EASy68K)来测试指令的运行结果:

EASy68K下载地址:EASy68K Home

可以选择安装版和绿色解压版。

安装完成后,我们选择运行EDIT68K.exe来启动编辑器

我们把测试代码插入到 * Put program code here的后面

*-----------------------------------------------------------
* Title      :
* Written by :
* Date       :
* Description:
*-----------------------------------------------------------
    ORG    $1000
START:                  ; first instruction of program

* Put program code here
    move.l  #$FFFFFFFF,d1       *d1寄存器值设为$FFFFFFFF
    move.l  #$16,d2             *d2寄存器值设为$16
    move.b  d2,d1               *d2寄存器的值 单字节长度 复制 到d1寄存器

    SIMHALT             ; halt simulator

* Put variables and constants here

    END    START        ; last line of source

?点击编译按钮:

?

?点击Execute执行该程序:

可以观察到,程序当前是停在1000地址处,该指令为:

move.l #$FFFFFFFF,d1

我们先观察到当前D1寄存器的值为0000000

然后,我们用步进来只运行一行代码,来测试该行指令是否会对D1寄存器进行改变:

点击一次步进按钮,并观察D1寄存器:

可以观察到,D1寄存器已更改为FFFFFFFF,同时,程序停止在1002地址处。我们再按一次步进按钮,同时观察该行代码的目的操作地D2寄存器:

可以观察到,D1寄存器已更改为00000016,程序停止在1004处。再次步进,同时观察该行代码的目的操作地D1寄存器:

可以观察到,D1寄存器已更改为FFFFFF16 ,因为该行代码的操作长度为字节,所以,这行代码只改变了最后两个数值。

我们把这行代码做一个改变,把该行代码的操作长度改为双字节:

*-----------------------------------------------------------
* Title      :
* Written by :
* Date       :
* Description:
*-----------------------------------------------------------
    ORG    $1000
START:                  ; first instruction of program

* Put program code here
    move.l  #$FFFFFFFF,d1       *d1寄存器值设为$FFFFFFFF
    move.l  #$16,d2             *d2寄存器值设为$16
    move.W  d2,d1               *d2寄存器的值 单字节长度 复制 到d1寄存器

    SIMHALT             ; halt simulator

* Put variables and constants here

    END    START        ; last line of source

我们重复之前的操作,测试观察最终D1寄存器的值:

可以观察到,和之前的结果不同,这次运行的结果为:FFFF0016,是因为这次的指令操作长度比之前的指令由单字节增加到了双字节,所以,这次的指令的影响范围也增加到了两个字节。

下一章开始,我们会开始介绍更多的M68000指令

文章来源:https://blog.csdn.net/m0_50114967/article/details/135444078
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。