第1章 QtSpim汇编程序开发环境
1.1 QtSpim简介
QtSpim是支持完整MIPS32指令集的MIPS微处理器模拟器,支持MIPS宏汇编指令。它可以直接打开并运行MIPS汇编指令源程序(扩展名为.s、.asm、.txt),支持MIPS汇编指令程序调试,但不支持执行二进制程序。界面如图1-1所示,包含菜单栏、快捷键栏、寄存器显示窗口、内存显示窗口、消息窗口等。
图1-1 QtSpim界面布局
1.2 QtSpim菜单栏简介
1.2.1 File菜单
File菜单下含有的子菜单如图1-2所示。
图1-2 QtSpim File子菜单
(1)Load File:装载MIPS汇编语言程序。单击之后弹出如图1-3所示的文件选择窗口,支持装载扩展名为.s、.asm、.txt的汇编语言源文件。
图1-3 QtSpim文件选择窗口
(2)Recent Files:重新装载最近使用过的汇编语言程序。
(3)Reinitialize and Load File:重新初始化模拟器并装载汇编语言程序,该功能不同于Load File之处为它重新初始化模拟器。
(4)Save Log File:保存数据到日志文件。单击之后弹出如图1-4所示的数据保存窗口,供用户选择数据来源、文件保存路径及名称。
图1-4 QtSpim数据保存窗口
(5)Print:输出窗口数据到打印机。单击之后弹出如图1-5所示的数据打印窗口,供用户选择需要打印的窗口数据。
图1-5 数据打印窗口
(6)Exit:退出模拟器。
QtSpim只能装载汇编语言源程序。若源程序有错误,则在装载过程中报错,每次装载时指出源程序中根据汇编顺序出现的第一个错误。
QtSpim不支持源程序编辑。因此必须利用其他文字编辑工具编辑汇编语言源程序,并保存为*.s、*.asm或*.txt文件之后再装载到QtSpim。若装载时发现源程序存在错误,则必须重新在编辑器中订正错误并保存之后再重新装载,直到没有错误为止。
1.2.2 Simulator菜单
Simulator菜单包含的子菜单如图1-6所示。
图1-6 QtSpim Simulator子菜单
(1)Clear Registers:将所有的通用寄存器清零。
(2)Reinitialize Simulator:重新初始化模拟器。
(3)Run Parameters:设置程序运行时需输入的参数。可以设置的参数如图1-7所示。
图1-7 运行程序参数输入窗口
(4)Run/Continue:运行/继续运行程序。
(5)Pause:暂停运行。
(6)Single Step:单步运行。
(7)Display Symbols:在消息窗口显示代码中含有的标号及对应的地址。
(8)Settings:设置模拟器界面以及微处理器相关参数。
1.2.3 其余菜单
Registers菜单用于设置寄存器内容显示数制,支持十六进制、十进制、二进制三种不同数制。图1-8表示采用十六进制形式显示寄存器数据。
图1-8 寄存器数据显示设置
Text Segment菜单用于设置代码显示窗口显示的内容,可选择显示的内容如图1-9所示。
图1-9 Text Segment子菜单
代码显示窗口布局如图1-10所示。
图1-10 代码显示窗口布局
QtSpim模拟器在用户代码段增加了部分代码实现程序运行控制,且所有用户代码都存储在0x00400000~0x00440000地址范围内。为方便核对用户代码在模拟器代码段中的位置,注释中显示了用户编写的汇编源代码。
Data Segment菜单用于设置数据显示窗口的显示内容以及显示制式,可选择显示的内容如图1-11所示。
图1-11 Data Segment子菜单
数据显示窗口布局如图1-12所示。指示用户数据区地址范围为0x1000 0000~0x1004 0000,用户栈地址范围为0x7fff f844~0x8000 0000,内核数据区地址范围为0x9000 0000~0x9001 0000。模拟器以字为单位显示存储空间中的十六进制数据。
图1-12 数据显示窗口布局
需要注意的是:MIPS微处理器采用大字节序管理数据,但是由于模拟器运行在PC上,因此模拟器采用小字节序,这不影响数据处理结果。
Window菜单用于选择需显示的窗口以及窗口排列方式,可选择的窗口如图1-13所示,包括整型寄存器窗口、浮点型寄存器窗口、代码窗口、数据窗口、控制台窗口。
图1-13 Window子菜单
1.3 QtSpim汇编、调试程序示例
1.3.1 QtSpim用户程序入口
QtSpim为所有用户程序提供了如图1-14所示用户程序入口代码。该段代码的功能为获取程序运行时命令行入口参数,并调用用户程序。
图1-14 程序入口代码段
1.3.2 QtSpim汇编查错
本节通过一个具体例子介绍QtSpim汇编查错过程。
若已知一汇编源程序需实现如下功能:根据输入数据1、2、3,分别将32乘以2、4、8,并显示不同情况下的乘积。伪代码如图1-15所示。
图1-15 example伪代码
用户撰写的汇编语言代码如图1-16所示。
图1-16 汇编语言代码
当把图1-16所示程序装载到QtSpim时,弹出如图1-17所示的错误提示对话框。由于QtSpim不支持显示汉字,所以提示框中有乱码,即汇编源程序中所有汉字都显示为乱码。错误之处由“∧”标注,即代码中的双引号显示为乱码。由此可推测双引号错误地采用了中文全角标点符号。提示消息中的line 4表示错误之处在第4行,错误类型为不能识别的字符。
图1-17 引号错误提示对话框
图1-18中指出错误行号为第5行,错误类型为语法错误。
图1-18 错误行提示
将全角双引号修改为半角双引号之后再装载,错误消失。按F5键或单击图1-19中的Run/Continue或单击图1-20中的三角形可运行程序。
图1-19 Run菜单
图1-20 Run快捷键
QtSpim再次报如图1-21所示的错误,并指出错误原因为指令索引了没有定义的符号。指令为jal main,即符号main没有定义。由此可知用户程序没有定义标号main。
图1-21 程序入口main错误
用户程序的第一条指令原来采用top标注,因此需要将程序中所有出现的top都修改为main。再次装载程序并运行,不再报同类错误。出现如图1-22所示的Console窗口,说明程序可以运行,但是功能是否正确,还需要进一步测试。
图1-22 显示输入提示
此时程序代码如图1-23所示。
图1-23 汇编纠错后的代码
1.3.3 QtSpim查看程序存储映像
程序存储映像主要分为三部分:数据段存储映像、代码段存储映像、栈存储映像。图1-23所示的汇编程序没有用到栈,因此只需观察数据段存储映像和代码段存储映像。
数据段存储映像通过数据窗口观察,已知数据窗口显示的用户数据段内容如图1-24所示。由于用户代码没有定义数据段起始地址,因此数据段起始地址由装载程序分配为0x10010000,对照用户数据段定义(如图1-25所示),可知jumptable的起始地址为0x10010000。jumptable包含的标号地址分别为0x00400024、0x00400060、0x00400068、0x00400070。由此可知,main指示的地址为0x00400024,case1指示的地址为0x00400060,case2指示的地址为0x00400068,case3指示的地址为0x00400070。紧接着为字符串"\n\n Input a value from 1 to 3:"的ASCII码,且4个字符为一组显示,如0x49200a0a表示字符串"\n\n I"逆序组合的ASCII码,即0x49→I、0x20→space、0x0a→\n、0x0a→\n。字符串末地址为0x1001002d,末尾字符空格的ASCII码为0x20。
图1-24 用户数据段数据
图1-25 用户数据段定义
数据段存储映像如表1-1所示。
表1-1 数据段存储映像
代码段存储映像通过代码显示窗口查看。代码显示窗口看到的用户代码段数据如图1-26所示。
图1-26 代码显示窗口数据
各部分含义如图1-27所示。每条MIPS32汇编指令都是4字节,因此两条相邻MIPS汇编指令地址之差为4。获取用户代码在内存中的映像:一种方法为对照代码显示窗口中注释部分的用户汇编程序源代码获取用户代码区域;另一种方法为查找标号main的地址获得用户代码起始地址,即在反汇编指令中找到指令jal 0x00400024[main],该指令指出0x00400024为标号main的地址。代码显示窗口反汇编指令中采用包含标号和地址的算术运算式表示相对偏移地址,如指令“blez$2,—24[main-0x0040 003c]”,指出—24通过main-0x0040 003c计算而来。由于main表示0x0040 0024,因此main-0x0040 003c为—24。可以利用该方法核算其他绝对地址和相对地址是否正确,还可以通过代码显示窗口核对宏汇编指令与汇编指令的对应关系。需要注意的是,QtSpim采用跳转目标地址减去当前指令地址计算条件跳转指令的跳转距离(相对偏移地址),而不是采用跳转目标地址减去紧跟当前指令的下一条指令地址。也就是说,QtSpim模拟器计算条件跳转指令跳转距离采用的方法与实际MIPS微处理器不一致。第2章介绍的MARS MIPS模拟器不存在这个问题。
图1-27 代码显示窗口数据的含义
图1-23所示的程序代码段存储映像如表1-2所示。
表1-2 程序的代码段存储映像
1.3.4 QtSpim调试查错
汇编代码编写完成,装载运行之后若发现不是预期结果,需调试程序。下面仍然针对图1-23所示的程序阐述QtSpim调试程序方法。
首先直接运行程序,并在Console输入1~3之间的数据,此时Console显示如图1-28所示结果,并报如图1-29所示的错误。
图1-28 显示结果
图1-29 运行结束出错
图1-23所示程序要求当用户输入2时,输出128(32×4)。但是此时输出0,表明程序初始的被乘数$s0可能为0,因此在输出之前设置一个断点。断点设置方法为在代码显示窗口相应行右击,在如图1-30所示的窗口中单击Set Breakpoint。
图1-30 断点设置取消窗口
本程序输入2时,显示0,因此找到如图1-31所示case2对应语句并设置断点。
图1-31 设置case2断点
重新设置PC,使PC指向main处并运行程序。具体步骤为:在寄存器窗口选中PC寄存器,右击,在如图1-32所示窗口选择Change Register Contents,如图1-33所示,在弹出窗口输入程序入口地址0x00400024。
图1-32 修改寄存器窗口
图1-33 修改PC值
再次单击运行,程序从头开始运行,并且碰到断点停下来,弹出如图1-34所示窗口,用户可以选择继续运行(Continue)、单步执行(Single Step)、放弃(Abort)等。
图1-34 断点弹出窗口
若单击放弃,观察寄存器窗口发现如图1-35所示$s0=0。这说明前面指令没有正确初始化寄存器$s0的值为32。因此需要在前面加入初始化$s0的汇编指令。
图1-35 寄存器值
程序起始地址处加入宏汇编指令li$s0,32,变为图1-36所示代码。再次装载并运行程序,将得到正确的显示结果。
图1-36 起始代码修改
最后还有一处图1-29所示运行结束错误,错误原因为试图执行非法指令。这是由于程序没有利用系统功能调用退出,导致继续往下执行。因此,需要加入系统功能调用退出程序。结尾处加入系统功能调用之后的代码如图1-37所示。用户再重新初始化并装载该程序代码,并测试不同输入1、2、3,发现都可以正常地显示结果。如果输入数据不在1~3的范围内,则重新要求用户输入。至此程序调试结束。
图1-37 结束代码修改
在程序差错调试示例中,介绍了如何设置断点、如何修改寄存器的值,下面再简要介绍经常用到的程序调试技术,包括单步执行和修改内存单元的值等。
QtSpim单步执行菜单如图1-38所示,该命令一次执行一条指令。当需要查看逐条指令执行是否正确时,可以采用该命令。
图1-38 单步执行菜单
修改内存单元值在数据显示窗口,用户根据变量的定义计算出变量存储地址,然后再选中该存储地址单元,右击,在如图1-39所示的窗口中选择Change Memory Contents,就可以在如图1-40所示弹出窗口输入新的值,从而修改内存单元的值。需要注意的是:内存单元数据修改以字为边界对齐,如果仅修改某个字节或某个半字的值,则需保持其他存储单元的内容不变。图1-40修改地址为0x10010014的字型数据。修改之前数据段内的值如图1-42(a)所示,修改之后如图1-42(b)所示。图1-41前三个字节不变,仅改变最低地址字节,修改之后的数据段如图1-42(c)所示。
图1-39 内存单元数据子窗口
图1-40 内存单元字数据修改窗口
图1-41 内存单元字节数据修改窗口
图1-42 数据段内存数据修改前后对比