文章目录
- 简介
- 产生背景
- 应用领域
- 语言学习
- EASy68K
- 帮助文档
- IDE使用
- 编程语言
- comments
- labels
- 开始标签
- 指令标签
- 位置标签
- opcode 操作码
- 常用操作码
- 数据传送
- 算术运算
- 逻辑运算
- 控制流
- 分支跳转
- 地址跳转
- 子程序跳转
- 位操作
- 比较
- 堆栈操作
- IO操作码
- 其他操作码
- directives 指令
- DC指令
- EQU 指令
- SET 指令
- DS 指令
- 其他指令
- 寄存器
- 程序计数器(PC)
- 状态寄存器(SR)
- 状态寄存器的结构
- 状态寄存器的作用
- 示例
- 数据寄存器(D)
- D 寄存器的特点
- D 寄存器的使用场景
- 示例
- 地址寄存器(A)
- A 寄存器的特点
- A 寄存器的使用场景
- 示例
- 堆栈寄存器(SS)
- 堆栈操作
- std函数模拟
- 案例(9*9乘法表)
简介
68000 汇编语言是为 Motorola 68000 微处理器设计的低级编程语言。68000 微处理器于 1979 年推出,因其强大的性能和灵活的架构而广泛应用于多种计算机系统和嵌入式设备中。以下是对 68000 汇编语言的背景、应用领域以及学习它的好处的详细介绍。
产生背景
-
技术进步:
- 68000 微处理器是 16 位架构,具有 32 位的地址总线,能够寻址高达 4GB 的内存。这使得它在当时的微处理器中具有较高的性能和灵活性。
- 其设计采用了复杂指令集计算(CISC)架构,支持多种寻址模式和丰富的指令集。
-
市场需求:
- 1970 年代末和1980年代初,个人计算机和嵌入式系统的需求迅速增长。68000 微处理器因其性能和成本效益被广泛采用。
- 许多知名的计算机系统(如 Apple Macintosh、Atari ST 和 Sega Genesis)都使用了 68000 处理器。
应用领域
-
个人计算机:
- 68000 微处理器被用于早期的个人计算机,如 Apple Macintosh 和 Atari ST。这些系统的操作系统和应用程序通常使用 68000 汇编语言进行开发。
-
嵌入式系统:
- 68000 处理器也被广泛应用于嵌入式系统,如工业控制、汽车电子和消费电子产品。
-
游戏机:
- Sega Genesis 和其他游戏机使用 68000 处理器,许多经典游戏都是用 68000 汇编语言编写的,学习后可以做一些hackrom的实战。
-
实时系统:
- 由于其高效的性能,68000 处理器在实时系统中也得到了应用,如医疗设备和航空航天系统。
语言学习
EASy68K
EASy68K 是一个 68000 结构化汇编语言集成开发环境(IDE)。EASy68K 允许您在 Windows PC 或 Wine 上编辑、汇编和运行 68000 程序。无需额外的硬件。EASy68K 是一个开源项目,根据 GNU 通用公共使用许可分发。
使用easy68k方便我们学习整套68000的编程和调试,学习这件基础知识,对我们hackrom或者逆向的基础。
下载地址:http://www.easy68k.com/files/SetupEASy68K.exe
安装完成后的目录结构
帮助文档
helm.chm提供了完整的编程和调试工具以及68k语言的学习入门资料,可以直接从该文档入手。
IDE使用
打开EDIT68K.exe,菜单file->new x68 source file 。
在source里面实现一个功能,打印helloworld,并从空值台输入一个字符串并打印。
关于指令,标签,寄存器其他相关的内容请移步后续章节。
源代码
*-----------------------------------------------------------
* Title :
* Written by :
* Date :
* Description:
*-----------------------------------------------------------ORG $1000 ;告诉编译器代码从1000位置开始,不指定默认从0开始
START: ; first instruction of program* 将text字符串地址写给A1lea text,A1* 将14号task print 给d0,并执行,14号任务自动获取A1地址的数据并打印move #14,D0trap #15* 执行2号任务,从输入流获取输入,自动写入到A1move #2,D0trap #15* 打印A1地址内容move #14,D0trap #15* Put program code here
*-----------------------------------------------------------
*HELLO:这是一个标签,标识字符串数据的起始位置。
*DC.B:这是一个伪指令,表示“定义常量(Define Constant)”,后面的 .B 表示定义的是字节(Byte)数据。
*'Hello World':这是一个字符串常量,表示字符数组。每个字符占用一个字节。
*$D:这是一个十六进制常量,表示一个字节的值。$D 的十进制值是 13,通常表示回车符(Carriage Return)。
*$A:这是一个十六进制常量,表示一个字节的值。$A 的十进制值是 10,通常表示换行符(Line Feed)。
*0:这是一个字节的值,表示字符串的结束符(null terminator),在 C 语言中常用来标识字符串的结束。
*-----------------------------------------------------------
text dc.b 'helloworld',0SIMHALT ; halt simulator* Put variables and constants hereEND START ; last line of source
点击工具栏运行按钮(如果由错误会有提示,根据情况修正)
会弹出一个确认框
点击execute
绿色圈圈点击变成红色可下断点,F9运行,F8 stepover,F7 stepinto,点击运行可调试。
在view可打开内存窗口,栈窗口等
编程语言
汇编语言程序由以下部分组成:
- labels 标签 - 用户创建的名称,用于标记程序中的位置。
- opcode 操作码 - 微处理器可以执行的特定指令,比如ADD,MOVE等。
- operands 操作数 - 某些指令所需的附加数据,比如#1表示10进制立即数1,$1表示16进制的1。
- directives 指令 - 发给汇编器的命令,比如ORG $1000,告诉编译器,代码的开始位置,代码段不占用空间,类似于c语言的宏,编译阶段使用。
- macros 宏 - 用户创建的源代码集合,可以在编写程序时轻松重用。
- comments 注释 - 用户创建的文本字符串,用于记录程序。
- 寄存器:汇编语言编程需要与微处理器进行直接交互。68000 微处理器包含八个数据寄存器 D0 到 D7。数据寄存器是通用的,可以视为 8 位、16 位或 32 位的整数变量。还有八个地址寄存器 A0 到 A7,地址寄存器的长度为 32 位。它们通常用于引用变量。状态寄存器(SR)包含状态标志,用于指示比较的结果。
以下是一个例子
comments
在 Motorola 68000(68k)汇编语言中,注释用于帮助程序员理解代码的功能和逻辑。68k 汇编语言的注释格式如下:(*或者;开头的为注释)
* Date
TRAP #15 ;将3任务执行,自动打印D1的内容
labels
标签用于通过名称标识程序中的位置或内存位置。需要位置的指令或指令可以使用标签来指示该位置。标签通常在行的第一列开始,必须以空格、制表符或冒号结束。如果使用冒号,它不会成为标签的一部分。如果标签没有在第一列开始,则必须以冒号结束。标签的前 32 个字符是有效的。标签有两种类型:全局标签和局部标签。
全局标签可以在程序的任何地方被引用。因此,全局标签必须是唯一的名称。全局标签应以字母开头,后面可以跟字母、数字或下划线。局部标签可以在程序中重复使用。局部标签必须以点 ‘.’ 开头,后面可以跟字母、数字或下划线。全局标签定义了局部标签的边界。当定义局部标签时,只有在遇到下一个全局标签之前,才能从局部标签上方或下方的代码中引用它。汇编器通过将局部标签名称附加到前面的全局标签并用冒号 ‘:’ 替换点来创建局部标签的唯一名称。结果名称的前 32 个字符是有效的。
开始标签
标签可以用来指定程序的起始位置。如果标签 START 指定了程序的起始位置,那么 END 指令的写法如下:
START: Start of programcodeEND START
指令标签
标签常常放在某个指令前用来表示,定义变量,标签指向存储数据的首地址。
DC - DC 指令指示汇编器将后续的值放入当前内存位置。该指令有三种形式:DC.B 用于字节数据,DC.W 用于字(16 位)数据,DC.L 用于长(32 位)数据。定义常量指令不应与 C++ 中声明常量混淆。
例如
ORG $1000 start of the data region
depart DC.B 'depart.wav',0 stores as a NULL terminated string in consecutive bytes DC.L $01234567 the value $01234567 is stored as a long wordDC.W 1,2 two words are stored as $0001 and $0002DC.L 1,2 two long words are stored as $00000001 and $00000002
depart 就是一个label是这块内存区域的首地址。
内存结果
00001000 64 65 70 61 72 74 2E 77 61 76 00
0000100C 01234567
00001010 0001 0002
00001014 00000001 00000002
其他关于指令标签的用法参考,也可以到指令章节:
位置标签
可以定义一些位置标签,当进行特殊操作时,可以通过控制流opcode跳转到位置标签
实现一个从0,end_index的循环打印
ORG $1000
START: ; first instruction of program* Put program code here
* 实现一个从0,end_index的循环打印move #1,D1
t:move #3,D0TRAP #15 ;将3任务执行,自动打印D1的内容add.b #1,d1 ;让d1+1CMP #end_index,d1 ;比较d1和end_index的值BNE t ;如果不相等继续跳转到t label执行SIMHALT ; halt simulator* Put variables and constants here
end_index equ 10END START ; last line of source
opcode 操作码
在 68K 汇编语言中,操作码(opcode)是指令的核心部分,定义了要执行的操作。以下是一些常用的 68K 操作码及其功能:
常用操作码
注意大部分操作码都可以添加结尾.W表示字(2个字节16位).L表示双字(4个字节32位),.B(1个字节8位)
数据传送
- `MOVE`:将数据从一个位置移动到另一个位置。- 例:`MOVE.W D0, D1`(将 D0 的值移动到 D1)
- `MOVEA`:将地址从一个位置移动到另一个位置。- 例:`MOVEA.L A0, A1`(将 A0 的地址移动到 A1)
算术运算
- `ADD`:将两个操作数相加。- 例:`ADD.W D0, D1`(将 D0 的值加到 D1)
- `SUB`:从一个操作数中减去另一个操作数。- 例:`SUB.W D1, D0`(从 D0 中减去 D1)
- `MULS`:有符号乘法。- 例:`MULS D0, D1`(将 D0 和 D1 相乘,结果存储在 D1)
- `DIVS`:有符号除法。- 例:`DIVS D0, D1`(将 D1 除以 D0,结果存储在 D1)
逻辑运算
- `AND`:按位与运算。- 例:`AND.W D0, D1`(D1 与 D0 按位与)
- `OR`:按位或运算。- 例:`OR.W D0, D1`(D1 与 D0 按位或)
- `EOR`:按位异或运算。- 例:`EOR.W D0, D1`(D1 与 D0 按位异或)
- `NOT`:按位取反。- 例:`NOT.W D0`(D0 的值取反)
控制流
常用如下:
- `BRA`:无条件跳转。- 例:`BRA label`(跳转到指定标签)
- `BEQ`:如果相等则跳转。- 例:`BEQ label`(如果零标志位被设置,则跳转)
- `BNE`:如果不相等则跳转。- 例:`BNE label`(如果零标志位未设置,则跳转)
- `JSR`:跳转到子程序。- 例:`JSR subroutine`(跳转到子程序并保存返回地址)
- `RTS`:从子程序返回。- 例:`RTS`(返回到调用子程序的地址)
分支跳转
该指令将在程序中引发分支,如果某些标志被设置。共有十五种检查标志的方法。每种方法都有一个由两个字母组成的符号,用于替换 “cc” 在 “Bcc” 中。
- BCC:分支如果进位标志清除 - 当 C 标志为 0 时分支。
- BCS:分支如果进位标志设置 - 当 C 标志为 1 时分支。
- BEQ:分支如果相等 - 当 Z 标志为 1 时分支。
- BNE:分支如果不相等 - 当 Z 标志为 0 时分支。
- BGE:分支如果大于或等于 - 当 N 和 V 相等时分支。
- BGT:分支如果大于 - 当 N 和 V 相等且 Z=0 时分支。
- BHI:分支如果高于 - 当 C 和 Z 都为 0 时分支。
- BLE:分支如果小于或等于 - 当 Z=1 或 N 和 V 不同时分支。
- BLS:分支如果小于或相同 - 当 C=1 或 Z=1 时分支。
- BLT:分支如果小于 - 当 N 和 V 不同时分支。
- BMI:分支如果负 - 当 N=1 时分支。
- BPL:分支如果正 - 当 N=0 时分支。
- BVC:分支如果溢出标志清除 - 当 V=0 时分支。
- BVS:分支如果溢出标志设置 - 当 V=1 时分支。
- BRA:无条件分支 - 始终分支。
上面这些opcode根据标志触发跳转,只能跳转到label,注意进入label后会往下执行,和函数调用不一样,函数调用会返回,继续执行之前代码的下一行,这个不会,是直接跳转过去不回来了。
例子:
ORG $1000
START: ; first instruction of program* Put program code here
input:move.b #4,d0TRAP #15CMP #0,d1BNE input ;如果不等于0跳转到input标签,继续让输入数字BEQ exit ;如果等于0直接退出labelexit:SIMHALT ; halt simulator* Put variables and constants hereEND START ; last line of source
地址跳转
JMP(跳转)用于将程序控制转移到一个有效地址。它实际上相当于 MOVE.L xxx, PC,因为它将程序计数器更改为一个有效地址(计算得出)。
注意JMP是无条件跳转,相对于B开头的跳转,他也支持 JMP label的语法,同时他也支持直接JMP 地址的跳转。
ORG $1000
START: ; first instruction of program* Put program code here
input:move.b #4,d0TRAP #15CMP #0,d1BNE input ;如果不等于0跳转到input标签,继续让输入数字BEQ exit ;如果等于0直接退出labelexit:LEA quit,a0 ;=0跳转到这里后,将quit的地址给到a0,JMP直接跳转到地址,相当于:move.l a0,PC(这是伪代码)JMP (a0) ;如果想跳转到a0的下一个地址,可以1(a0) 或者n(a0),当然也可以直接JMP quitquit:SIMHALT ; halt simulator* Put variables and constants hereEND START ; last line of source
子程序跳转
JSR/BSR(跳转到子例程)与 JMP(无条件跳转)类似,但在跳转之前,JSR 会将跳转指令后面的地址压入栈中,这样可以通过 RTS(返回子例程)指令返回,也就相当于调用函数,函数执行完了,执行代码的下一行。
BSR适合同一代码段里的label直接调用,是相对掉哟个,JSR适合指定一个绝对地址调用(比如JSR $5000) ,但是实际上两个可以互相替换,没啥区别。
ORG $1000
START: ; first instruction of program* Put program code here
input:JSR input_notion ;JSR执行完后会自动执行下一行代码,B开头的跳过去就不回来了move.b #4,d0TRAP #15CMP #0,d1BNE input ;如果不等于0跳转到input标签,继续让输入数字BEQ exit ;如果等于0直接退出
input_notion: ;屏幕上输出提示语MOVE #14,D0LEA INPUT_STR,A1TRAP #15RTS ;注意返回了会运行调用这个函数的下一行
confirm_exit *屏幕上输出确认提示语MOVE #14,D0LEA CONFIRM_STR,A1TRAP #15RTS
exit:JSR confirm_exitmove.b #4,d0TRAP #15CMP #0,d1BEQ quitBNE input
quit:SIMHALT ; halt simulator* Put variables and constants here
INPUT_STR: dc.b 'please input number(exit=0):',0
CONFIRM_STR: dc.b 'confirm exit(:exit=0,not=1):',0END START ; last line of source
效果
位操作
- `SHL`:左移。- 例:`SHL.W #1, D0`(D0 左移 1 位)
- `SHR`:右移。- 例:`SHR.W #1, D0`(D0 右移 1 位)
- `ROL`:循环左移。- 例:`ROL.W #1, D0`(D0 循环左移 1 位)
- `ROR`:循环右移。- 例:`ROR.W #1, D0`(D0 循环右移 1 位)
比较
- `CMP`:比较两个操作数。- 例:`CMP.W D0, D1`(比较 D0 和 D1 的值)
堆栈操作
- `PUSH`:将数据压入堆栈。- 例:`PUSH.W D0`(将 D0 的值压入堆栈)
- `POP`:从堆栈弹出数据。- 例:`POP.W D0`(从堆栈弹出值到 D0)
IO操作码
TRAP #15 被用于触发 I/O. 不同的io流任务存储在: D0.
参考chm:
常用的输入输出任务:
- 14: 将A1地址对应的字符串输出 以0结尾结束。
- 13:将A1地址对应的字符串输出 以0结尾结束,加上\r\n换行。
- 2: 从控制台获取一个字符串回车后存储在A1地址中 0结尾。
- 4:读取一个数字写入D1.L中。
例子
START ORG $1000 Program load address.move #14,D0 ;设置14号任务打印A1地址字符串lea text,A1 ;获text地址到A1trap #15 ;激活任务SIMHALT text dc.b 'Hello World',0 ;0表示字符串结束END START End of source with start address specified.
其他操作码
关于更加详情的指令参考chm
directives 指令
指令是汇编器需要遵循的指令。它们占据源代码行中的第二个字段,与指令操作码占据的位置相同,但指令并不是 68000 操作码。 “DC” 和 “DCB” 是唯一会导致数据被添加到输出文件中的指令。指令还可以用于控制宏的汇编、条件汇编和结构化语法。
在以下描述中,选项项用方括号 [] 表示。用斜体显示的项应替换为适当的语法。
Usage:
[label] directive[.size] [data,data,...]^ ^ ^\_________________\_________\_____ varies by directive
DC指令
- 全称:Define Constant(定义常量)
- 用途:用于定义并初始化数据常量。
DC
指令可以用于定义一个或多个初始值,这些值会被存储在程序的输出文件中。 - 内存分配:
DC
指令会在程序的内存中分配实际的存储空间,并将指定的值写入该空间。 - 示例:
使用语法:
Usage:
[label] DC.size data,data,...
例子:
VALUE1 DC 10 ; 定义常量 VALUE1,值为 10
VALUE2 DC 20, 30 ; 定义常量 VALUE2,值为 20 和 30
- 特性:
- 定义的值在程序运行时是不可更改的。
- 实际在内存中占用空间。
注意下面的代码修改地址的值是非法的,常量无法修改
START: ; first instruction of programlea usercount,A0move.b 20,(A0) ;修改A0地址的常量这是非法的。
* Put program code hereSIMHALT ; halt simulator* Put variables and constants hereORG $1200
usercount dc.b 10,20dc.w 23
EQU 指令
-
全称:Equate(等于)
-
用途:用于定义一个符号并将其与一个值关联。
EQU
定义的值在整个程序中是不可更改的,通常用于定义常量或符号地址,类似于c语言的#define在预编译将对应引用的地方替换为值。 -
内存分配:
EQU
不会在内存中分配实际的存储空间。它只是创建一个符号,所有使用该符号的地方都会被替换为其定义的值。 -
示例:
MAX_SIZE EQU 100 ; 定义常量 MAX_SIZE,值为 100
-
特性:
- 一旦定义,
EQU
的值不能被修改。 - 不占用内存空间,编译时进行替换
- 一旦定义,
ORG $1000 ; 程序起始地址
START: ; 将立即数 10 移动到 D0 寄存器; 定义常量
MAX_COUNT EQU 2 ; 定义 MAX_COUNT 为 100
START_VALUE EQU 1 ; 定义 START_VALUE 为 10MOVE.B #10, D0ADD.B #MAX_COUNT, D0 ; 将 MAX_COUNT (100) 加到 D0SUB.B #START_VALUE, D0 ; 将 START_VALUE (10) 从 D0 中减去SIMHALT ; 停止模拟器ORG $1200 ; 数据段起始地址END START
SET 指令
-
用途:用于定义一个符号并赋予一个初始值,但与
DC
不同的是,SET
定义的值是可更改的。SET
通常用于在程序运行时动态地改变值。 -
示例:
COUNT SET 0 ; 定义符号 COUNT,初始值为 0 COUNT SET COUNT + 1 ; 重新定义 COUNT,值为 COUNT + 1
-
内存分配:
SET
指令并不分配实际的存储空间来存储值,而是定义一个符号,允许在程序中动态地改变该符号的值。
DS 指令
-
全称:Define Space(定义空间)
-
用途:用于定义一块未初始化的内存空间。
DS
指令只分配内存,但不初始化这些内存的值,随时可改。 -
示例:
BUFFER DS 256 ; 定义一个大小为 256 字节的缓冲区
-
内存分配:
DS
指令会在输出文件中分配指定大小的内存空间,但这些空间的初始值是未定义的(通常是随机值或零,具体取决于系统)。
定义一个100字节的空间,可以理解为数组,将MULT_TABLE数字第一个位置设置为:12
ORG $1000
START: ; first instruction of program* Put program code heremove.B #0,D0LEA MULT_TABLE, A0MOVE.B #12,(A0, D0)SIMHALT ; halt simulatorORG $1200
* Put variables and constants hereMULT_TABLE: ; 乘法表的存储位置DS.B 10 * 10 ; 预留 10x10 的空间END START ; last line of source
其他指令
参考chm
寄存器
程序计数器(PC)
程序计数器(有时在不同的体系结构中也称为指令指针或指令地址寄存器)保存下一条将要执行的指令的内存地址。每当 CPU 执行一条指令时,PC 的值会自动更新,以指向下一条指令。
更新机制:在大多数情况下,PC 在指令执行后自动加一(或加上指令的长度),以指向下一条指令的地址。
编写一个简单程序 运行,默认会从start:的写一条语句开始,PC寄存器指向初始代码的地址(注意有效的代码时左侧绿色点点的,其他都是指令或者注释)
按下F8执行到下一条
我这里将usercount的地址指向A0 ,同时加了ORG $1200从1200这个地址写入。点击A0的地址可以查看内存:
状态寄存器(SR)
在 68k(Motorola 68000)架构中,状态寄存器(SR,Status Register)是一个重要的寄存器,用于存储处理器的状态信息和控制标志。状态寄存器的内容影响程序的执行流程,特别是在条件跳转和中断处理时。以下是对 68k 状态寄存器的详细介绍:
状态寄存器的结构
68k 的状态寄存器是一个 16 位的寄存器,包含多个标志位。主要的标志位包括:
-
N(Negative):
- 表示最近一次运算的结果是否为负数。
- 如果结果的最高位(符号位)为 1,则 N 标志被设置。
-
Z(Zero):
- 表示最近一次运算的结果是否为零。
- 如果结果为 0,则 Z 标志被设置。
-
V(Overflow):
- 表示最近一次运算是否发生了溢出。
- 溢出通常发生在有符号数运算中,当结果超出可表示的范围时,V 标志被设置。
-
C(Carry):
- 表示最近一次运算是否产生了进位或借位。
- 在加法运算中,如果产生了进位,C 标志被设置;在减法运算中,如果发生了借位,C 标志也会被设置。
-
I(Interrupt Mask):
- 这是一个 3 位的中断屏蔽位,控制中断的响应。
- I0、I1 和 I2 位用于设置中断优先级,值越大,响应的中断优先级越低。
-
T(Trace):
- 这是一个单个位,用于启用或禁用跟踪模式。
- 当 T 位被设置时,处理器将在每个指令执行后产生一个中断,适用于调试。
-
S(Supervisor):
- 这是一个单个位,指示当前处理器是否处于特权模式(超级用户模式)。
- 当 S 位被设置时,处理器处于超级用户模式,允许执行特权指令。
状态寄存器的作用
- 条件跳转: 状态寄存器中的标志位用于条件跳转指令(如
BEQ
、BNE
等),根据运算结果的状态决定程序的执行路径。 - 中断处理: 中断标志位控制中断的响应,允许或禁止特定级别的中断。
- 运算结果的状态: 通过检查 N、Z、V 和 C 标志,程序可以根据运算结果的状态做出相应的处理。
示例
以下是一个简单的示例,展示如何使用状态寄存器的标志位:
MOVE.L #5, D0 ; 将 5 加载到 D0MOVE.L #3, D1 ; 将 3 加载到 D1SUB.L D1, D0 ; D0 = D0 - D1,结果为 2; 检查 Z 标志BEQ zero_result ; 如果 Z 标志为 1,跳转到 zero_result; 检查 N 标志BPL positive_result ; 如果 N 标志为 0,跳转到 positive_resultzero_result:; 处理结果为零的情况; ...positive_result:; 处理结果为正的情况; ...
数据寄存器(D)
在 68000(68k)架构中,D 寄存器(数据寄存器)是用于存储数据和操作数的寄存器。68k 处理器有 8 个数据寄存器,分别为 D0 到 D7。
D 寄存器的特点
-
数量:
- 68k 处理器有 8 个数据寄存器,编号为 D0 到 D7。
-
大小:
- 每个 D 寄存器的大小为 32 位(4 字节),可以存储 32 位的整数或指针。
-
用途:
- D 寄存器主要用于存储运算的操作数、结果以及临时数据。它们在算术运算、逻辑运算、数据传输等操作中被广泛使用。
-
寻址模式:
- D 寄存器可以与多种寻址模式结合使用,支持直接寻址、间接寻址等方式,方便数据的访问和操作。
-
操作:
- D 寄存器可以参与各种指令的操作,如加法、减法、位运算等。指令可以直接对 D 寄存器进行操作,也可以将 D 寄存器的值存储到内存中或从内存中加载数据。
D 寄存器的使用场景
- 算术运算: D 寄存器用于存储参与运算的数值。
- 数据传输: 在数据传输指令中,D 寄存器可以作为源或目标。
- 函数参数: 在调用子程序时,D 寄存器常用于传递参数。
示例
以下是一个简单的汇编代码示例,展示如何使用 D 寄存器进行基本的算术运算:
MOVE.L #10, D0 ; 将 10 加载到 D0 寄存器MOVE.L #5, D1 ; 将 5 加载到 D1 寄存器ADD.L D1, D0 ; D0 = D0 + D1,D0 现在为 15
地址寄存器(A)
68000(68k)架构中,A 寄存器(地址寄存器)是用于存储内存地址的寄存器。68k 处理器有 8 个地址寄存器,分别为 A0 到 A7。以下是对 A 寄存器的详细描述:
A 寄存器的特点
-
数量:
- 68k 处理器有 8 个地址寄存器,编号为 A0 到 A7。
-
大小:
- 每个 A 寄存器的大小为 32 位(4 字节),可以存储 32 位的内存地址。
-
用途:
- A 寄存器主要用于存储内存地址,支持数据的加载和存储操作。它们在指令中用于指向数据或指令的内存位置。
-
寻址模式:
- A 寄存器可以与多种寻址模式结合使用,包括直接寻址、间接寻址、基址寻址和相对寻址等。这使得程序能够灵活地访问内存中的数据。
-
堆栈指针:
- A7 寄存器通常用作堆栈指针(SP),指向当前堆栈的顶部。堆栈用于存储函数调用的返回地址、局部变量等。
A 寄存器的使用场景
- 内存访问: A 寄存器用于指向数据在内存中的位置,支持数据的读取和写入。
- 函数调用: 在函数调用中,A 寄存器可以用于传递参数和返回地址。
- 堆栈管理: A7 寄存器作为堆栈指针,管理函数调用的堆栈帧。
示例
以下是一个简单的汇编代码示例,展示如何使用 A 寄存器进行内存操作:
LEA array, A0 ; 将数组的地址加载到 A0 寄存器MOVE.L (A0), D0 ; 从 A0 指向的地址加载数据到 D0 寄存器ADD.L #1, D0 ; D0 = D0 + 1MOVE.L D0, (A0) ; 将 D0 的值存储回 A0 指向的地址
堆栈寄存器(SS)
在68k架构中,堆栈寄存器是用于管理程序运行时的堆栈的关键组件。68k系列处理器使用一个专用的寄存器来指向当前堆栈的顶部,这个寄存器被称为堆栈指针(Stack Pointer)。
在68k架构中,堆栈指针寄存器通常是 A7(地址寄存器7),它指向当前堆栈的顶部。
堆栈是一个后进先出(LIFO)的数据结构,用于存储临时数据,如函数调用的返回地址、局部变量和中断处理程序的上下文。
堆栈操作
我们来看下堆栈指针的移动和数据写入逻辑。
在68k汇编语言中,-(A7) 和 (A7)+ 分别用于表示压栈和出栈操作。
执行代码
move.l #10,-(a7)
未执行前原始堆栈地址A7指向:01000000,没有任何数据
执行:move.l #10,-(a7)
执行:move.l #20,-(a7)
执行出栈:move.l (a7)+,d0
std函数模拟
我们知道c语言的std约定是:调用函数先压入执行代码的后一个位置,然后参数从右往左压入,在函数内部出栈从左(后入先出)往右获取参数,执行完成获取代码执行的位置,跳转。
我们来模拟这个过程:
假设函数:
public int add(int a,int b)
用98k模拟堆栈实现:
ORG $1000
START: ; first instruction of program* Put program code heremove.l #10,-(a7) #第二个参数压栈。move.l #20,-(a7) #第一个参数压栈。LEA *+12, A0 *计算下LEA占用4个字节,一直到move.l d0,d2是12个字节,*+12就是从PC当前位置+12个就是下一个执行代码的位置move.l a0,4(a7) *将下一个执行的地址压栈JMP addmove.l d0,d2SIMHALT ; halt simulatoradd:move.l (a7)+,a0 ;地址出栈move.l (a7)+,d0 ;第一个参数出栈move.l (a7)+,d1 ;第二个参数出栈add.l d1,d0JMP (a0)* Put variables and constants hereEND START ; last line of source
案例(9*9乘法表)
*-----------------------------------------------------------
* Title :
* Written by :
* Date :
* Description:
*-----------------------------------------------------------ORG $1000
START: ; first instruction of program* Put program code heremove.b #start_index,d2 ;行索引move.b #start_index,d3 ;列索引
row:jsr print_str_line ;到row的部分就添加一个换行,jsr调用子程序,子程序需要RTS返回add.b #1,d2 ;每运行一次+1move.b #start_index,d3cmp #end_index+1,d2 ;到达最后一行+1直接退出BEQ exitcol:add.b #1,d3move.b d2,d1jsr print_num ;打印行的数字lea tmp_str,a1move.b #'*',(a1) ;打印一个*jsr print_strmove.b d3,d1jsr print_num ;打印一个列的数字move.b #'=',(a1) jsr print_str ;打印一个=move.b #1,d4muls d2,d4muls d3,d4move.b d4,d1jsr print_num ;打印一个列的数字move.b #' ',(a1) jsr print_str ;打印一个空格cmp d3,d2BEQ rowBNE col
print_num:move.b #3,d0TRAP #15 RTS
print_str:move.b #0,1(a1) ;打印字符的结尾move.b #14,d0TRAP #15 RTS
print_str_line:move.b #0,(a1) ;打印字符的结尾move.b #13,d0TRAP #15 RTS
exit:SIMHALT ; halt simulator* Put variables and constants here
tmp_str ds.b 2
end_index equ 9
start_index equ 0END START ; last line of source
效果