当前位置: 首页 > article >正文

68000汇编实战01-编程基础

文章目录

  • 简介
    • 产生背景
    • 应用领域
  • 语言学习
    • 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 汇编语言的背景、应用领域以及学习它的好处的详细介绍。

产生背景

  1. 技术进步

    • 68000 微处理器是 16 位架构,具有 32 位的地址总线,能够寻址高达 4GB 的内存。这使得它在当时的微处理器中具有较高的性能和灵活性。
    • 其设计采用了复杂指令集计算(CISC)架构,支持多种寻址模式和丰富的指令集。
  2. 市场需求

    • 1970 年代末和1980年代初,个人计算机和嵌入式系统的需求迅速增长。68000 微处理器因其性能和成本效益被广泛采用。
    • 许多知名的计算机系统(如 Apple Macintosh、Atari ST 和 Sega Genesis)都使用了 68000 处理器。

应用领域

  1. 个人计算机

    • 68000 微处理器被用于早期的个人计算机,如 Apple Macintosh 和 Atari ST。这些系统的操作系统和应用程序通常使用 68000 汇编语言进行开发。
  2. 嵌入式系统

    • 68000 处理器也被广泛应用于嵌入式系统,如工业控制、汽车电子和消费电子产品。
  3. 游戏机

    • Sega Genesis 和其他游戏机使用 68000 处理器,许多经典游戏都是用 68000 汇编语言编写的,学习后可以做一些hackrom的实战。
  4. 实时系统

    • 由于其高效的性能,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字符串地址写给A1
    lea text,A1
    * 将14号task print 给d0,并执行,14号任务自动获取A1地址的数据并打印
    move #14,D0
    trap #15
    * 执行2号任务,从输入流获取输入,自动写入到A1
    move #2,D0
    trap #15
    * 打印A1地址内容
    move #14,D0
    trap #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',0
    SIMHALT             ; halt simulator

* Put variables and constants here

    END    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 program
       code
       END    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 word
       DC.W   1,2                two words are stored as $0001 and $0002
       DC.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,D0
    TRAP #15  ;将3任务执行,自动打印D1的内容
    add.b #1,d1  ;让d1+1
    CMP #end_index,d1 ;比较d1和end_index的值
    BNE t  ;如果不相等继续跳转到t label执行
    SIMHALT             ; halt simulator

* Put variables and constants here
end_index equ 10
    END    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,d0
    TRAP #15
    CMP #0,d1
    BNE input ;如果不等于0跳转到input标签,继续让输入数字
    BEQ exit  ;如果等于0直接退出

label

exit:
    SIMHALT             ; halt simulator

* Put variables and constants here

    END    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,d0
    TRAP #15
    CMP #0,d1
    BNE  input ;如果不等于0跳转到input标签,继续让输入数字
    BEQ exit  ;如果等于0直接退出

label

exit:
    LEA quit,a0 ;=0跳转到这里后,将quit的地址给到a0,JMP直接跳转到地址,相当于:move.l a0,PC(这是伪代码)
    JMP (a0) ;如果想跳转到a0的下一个地址,可以1(a0) 或者n(a0),当然也可以直接JMP quit
    
quit:
    SIMHALT             ; halt simulator

* Put variables and constants here

    END    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,d0
    TRAP #15
    CMP #0,d1
    BNE  input ;如果不等于0跳转到input标签,继续让输入数字
    BEQ exit  ;如果等于0直接退出
input_notion: ;屏幕上输出提示语
    MOVE    #14,D0
    LEA        INPUT_STR,A1
    TRAP        #15
    RTS  ;注意返回了会运行调用这个函数的下一行
confirm_exit      *屏幕上输出确认提示语
    MOVE    #14,D0
    LEA        CONFIRM_STR,A1
    TRAP        #15
    RTS
exit:
    JSR confirm_exit
    move.b #4,d0
    TRAP #15
    CMP #0,d1
    BEQ quit
    BNE 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):',0
    END    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地址到A1
        trap    #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 program
    lea usercount,A0
    move.b 20,(A0) ;修改A0地址的常量这是非法的。
* Put program code here

    SIMHALT             ; halt simulator

* Put variables and constants here
    ORG    $1200
usercount    dc.b    10,20
                    dc.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 为 10
        MOVE.B #10, D0
        ADD.B #MAX_COUNT, D0     ; 将 MAX_COUNT (100) 加到 D0
        SUB.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 here
    move.B #0,D0
    LEA MULT_TABLE, A0
    MOVE.B #12,(A0, D0)
    SIMHALT             ; halt simulator
    ORG    $1200
* Put variables and constants here
    MULT_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 位的寄存器,包含多个标志位。主要的标志位包括:

  1. N(Negative):

    • 表示最近一次运算的结果是否为负数。
    • 如果结果的最高位(符号位)为 1,则 N 标志被设置。
  2. Z(Zero):

    • 表示最近一次运算的结果是否为零。
    • 如果结果为 0,则 Z 标志被设置。
  3. V(Overflow):

    • 表示最近一次运算是否发生了溢出。
    • 溢出通常发生在有符号数运算中,当结果超出可表示的范围时,V 标志被设置。
  4. C(Carry):

    • 表示最近一次运算是否产生了进位或借位。
    • 在加法运算中,如果产生了进位,C 标志被设置;在减法运算中,如果发生了借位,C 标志也会被设置。
  5. I(Interrupt Mask):

    • 这是一个 3 位的中断屏蔽位,控制中断的响应。
    • I0、I1 和 I2 位用于设置中断优先级,值越大,响应的中断优先级越低。
  6. T(Trace):

    • 这是一个单个位,用于启用或禁用跟踪模式。
    • 当 T 位被设置时,处理器将在每个指令执行后产生一个中断,适用于调试。
  7. S(Supervisor):

    • 这是一个单个位,指示当前处理器是否处于特权模式(超级用户模式)。
    • 当 S 位被设置时,处理器处于超级用户模式,允许执行特权指令。
状态寄存器的作用
  • 条件跳转: 状态寄存器中的标志位用于条件跳转指令(如 BEQBNE 等),根据运算结果的状态决定程序的执行路径。
  • 中断处理: 中断标志位控制中断的响应,允许或禁止特定级别的中断。
  • 运算结果的状态: 通过检查 N、Z、V 和 C 标志,程序可以根据运算结果的状态做出相应的处理。
示例

以下是一个简单的示例,展示如何使用状态寄存器的标志位:

    MOVE.L #5, D0          ; 将 5 加载到 D0
    MOVE.L #3, D1          ; 将 3 加载到 D1
    SUB.L D1, D0           ; D0 = D0 - D1,结果为 2

    ; 检查 Z 标志
    BEQ zero_result        ; 如果 Z 标志为 1,跳转到 zero_result

    ; 检查 N 标志
    BPL positive_result     ; 如果 N 标志为 0,跳转到 positive_result

zero_result:
    ; 处理结果为零的情况
    ; ...

positive_result:
    ; 处理结果为正的情况
    ; ...

数据寄存器(D)

在 68000(68k)架构中,D 寄存器(数据寄存器)是用于存储数据和操作数的寄存器。68k 处理器有 8 个数据寄存器,分别为 D0 到 D7。

D 寄存器的特点
  1. 数量:

    • 68k 处理器有 8 个数据寄存器,编号为 D0 到 D7。
  2. 大小:

    • 每个 D 寄存器的大小为 32 位(4 字节),可以存储 32 位的整数或指针。
  3. 用途:

    • D 寄存器主要用于存储运算的操作数、结果以及临时数据。它们在算术运算、逻辑运算、数据传输等操作中被广泛使用。
  4. 寻址模式:

    • D 寄存器可以与多种寻址模式结合使用,支持直接寻址、间接寻址等方式,方便数据的访问和操作。
  5. 操作:

    • 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 寄存器的特点
  1. 数量:

    • 68k 处理器有 8 个地址寄存器,编号为 A0 到 A7。
  2. 大小:

    • 每个 A 寄存器的大小为 32 位(4 字节),可以存储 32 位的内存地址。
  3. 用途:

    • A 寄存器主要用于存储内存地址,支持数据的加载和存储操作。它们在指令中用于指向数据或指令的内存位置。
  4. 寻址模式:

    • A 寄存器可以与多种寻址模式结合使用,包括直接寻址、间接寻址、基址寻址和相对寻址等。这使得程序能够灵活地访问内存中的数据。
  5. 堆栈指针:

    • A7 寄存器通常用作堆栈指针(SP),指向当前堆栈的顶部。堆栈用于存储函数调用的返回地址、局部变量等。
A 寄存器的使用场景
  • 内存访问: A 寄存器用于指向数据在内存中的位置,支持数据的读取和写入。
  • 函数调用: 在函数调用中,A 寄存器可以用于传递参数和返回地址。
  • 堆栈管理: A7 寄存器作为堆栈指针,管理函数调用的堆栈帧。
示例

以下是一个简单的汇编代码示例,展示如何使用 A 寄存器进行内存操作:

   LEA array, A0         ; 将数组的地址加载到 A0 寄存器
    MOVE.L (A0), D0       ; 从 A0 指向的地址加载数据到 D0 寄存器
    ADD.L #1, D0          ; D0 = D0 + 1
    MOVE.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 here
    move.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 add
    move.l d0,d2
    SIMHALT             ; halt simulator
    
add:
   move.l (a7)+,a0  ;地址出栈
   move.l (a7)+,d0 ;第一个参数出栈
   move.l (a7)+,d1 ;第二个参数出栈
   add.l d1,d0
   JMP (a0)
 
   
* Put variables and constants here

    END    START        ; last line of source

案例(9*9乘法表)

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

* Put program code here
    move.b #start_index,d2 ;行索引
    move.b #start_index,d3  ;列索引
row:
    jsr print_str_line ;到row的部分就添加一个换行,jsr调用子程序,子程序需要RTS返回
    add.b #1,d2 ;每运行一次+1
    move.b #start_index,d3
    cmp #end_index+1,d2 ;到达最后一行+1直接退出
    BEQ exit
    col:
        add.b #1,d3
        move.b d2,d1
        jsr print_num ;打印行的数字
        lea tmp_str,a1
        move.b #'*',(a1) ;打印一个*
        jsr print_str
        move.b d3,d1
        jsr print_num ;打印一个列的数字
        move.b #'=',(a1) 
        jsr print_str ;打印一个=
        move.b #1,d4
        muls d2,d4
        muls d3,d4
        move.b d4,d1
        jsr print_num ;打印一个列的数字
        move.b #' ',(a1) 
        jsr print_str ;打印一个空格
        cmp d3,d2
        BEQ row
        BNE col
print_num:
    move.b #3,d0
    TRAP #15 
    RTS    
print_str:
    move.b #0,1(a1) ;打印字符的结尾
    move.b #14,d0
    TRAP #15 
    RTS   
print_str_line:
    move.b #0,(a1) ;打印字符的结尾
    move.b #13,d0
    TRAP #15 
    RTS       
exit:
    SIMHALT             ; halt simulator

* Put variables and constants here
tmp_str ds.b 2
end_index equ 9
start_index equ 0
    END    START        ; last line of source

效果
在这里插入图片描述


http://www.kler.cn/a/414734.html

相关文章:

  • 【Linux】网络连接模式,VM:桥接、NAT、仅主机如何选择?
  • UDP客户端服务器通信
  • Paddle Inference部署推理(一)
  • 快速排序hoare版本和挖坑法(代码注释版)
  • Cypress内存溢出奔溃问题汇总
  • Brain.js 用于浏览器的 GPU 加速神经网络
  • 如何分析Windows防火墙日志
  • Vue前端开发-动态插槽
  • net 站点安全 OwaspHeaders.Core
  • 抓包之查看websocket内容
  • 深入解析音视频流媒体SIP协议交互过程
  • 人工智能如何改变你的生活?
  • sd webui整合包怎么安装comfyui
  • 泷羽sec-shell脚本(1)脚本创建执行与变量使用 学习笔记
  • 【c++篇】:探索c++中的std::string类--掌握字符串处理的精髓
  • Linux 命令 whoami:揭秘当前用户身份
  • 派对灯 Party Lamps [USACO 2.2]
  • DVWA 在 Windows 环境下的部署指南
  • C++设计模式行为模式———状态模式
  • npm库xss依赖的使用方法和vue3 中Web富文本编辑器 wangeditor 使用xss库解决 XSS 攻击的方法
  • ASP.NET Core 入门
  • Linux:文件管理(二)——文件缓冲区
  • DVWA靶场——File Inclusion
  • Linux笔记--基于OCRmyPDF将扫描件PDF转换为可搜索的PDF
  • 大语言模型LLM的微调中 QA 转换的小工具 xlsx2json.py
  • C++语法·叭