【问题标题】:Is "jr $ra" required to end a MIPS assembly language program? (MARS & QtSpim behave differently!)结束 MIPS 汇编语言程序是否需要“jr $ra”? (MARS 和 QtSpim 的行为不同!)
【发布时间】:2021-05-10 02:08:44
【问题描述】:

如果您在 MARS 中的 MIPS 汇编语言程序的末尾添加 jr $ra,您将收到一条错误消息:

无效的程序计数器值:0x00000000

下面的示例 1 失败:

.data

theMsg: .asciiz "Hello World\n"

.text
.globl main

main:   li $v0, 4       
        la $a0, theMsg  
        syscall         
        
        jr $ra

      

下面的示例 2 有效:

.data

theMsg: .asciiz "Hello World\n"

.text
.globl main

main:   li $v0, 4       
        la $a0, theMsg  
        syscall

     
    

MARS 说 “程序已完成运行(掉到底部)”,但没有错误消息。

现在,如果您在 QtSpim 中运行示例 2,您将收到一条错误消息:

尝试在 0x00400030 处执行非指令

如果您在 QtSpim 中运行示例 1,它就可以正常工作。

任何人都可以对此有所了解吗?
哪个 MIPS 模拟器是正确的?

【问题讨论】:

  • 两个模拟器都是对的。你的两个例子都是错误的。您需要执行退出系统调用来终止程序。
  • @fuz:听起来QtSpim 将$ra 中的有效返回地址传递给您的main,使其能够像C 编译器的main 在真实系统中一样返回。 (SPIM 和 MARS 都是玩具系统,并且有自己的系统调用,与 GNU/Linux MIPS 或其他东西非常不同)。但是,是的,我认为对于这些模拟器来说,退出系统调用更为标准,因为没有任何可见的 _start 入口点可供您返回。
  • 感谢大家。该更改对 MARS 和 QtSpim 都有效。我使用了代码 10 并进行了系统调用。

标签: assembly mips mars-simulator qtspim


【解决方案1】:

标准的工作方式是进行 exit(0) 系统调用:

   li $v0, 10         # call number
   syscall            # exit()

如果你想在程序中使用jal,这也避免了将传入的$ra保存在main中的任何位置,所以很方便。

这对于在 Linux 等主流操作系统中运行的真实手写 asm 程序来说也更“现实”,而不仅仅是 MARS/SPIM 模拟的“玩具”系统。

(与 Linux exit(int) 不同,MARS/SPIM 玩具系统调用不会在 $a0 或其他任何地方采取退出状态。它只是退出。)


在 MARS 中,对于它所模拟的“玩具”系统,显然从底部掉线是一个有效的选择。但是,这在任何真正的硬件 CPU 中都不起作用;内存中总会有下一个东西,CPU 会尝试获取并执行它1

MARS 和 SPIM 都没有尝试模拟像 Linux 这样的真实操作系统,只是提供自己的特定环境2MARS 与 SPIM 模拟的系统彼此之间存在一些细微差别,包括您发现的系统。

没有对错,只是不同:没有他们试图匹配/模仿的真实环境。

SPIM 甚至可以选择在模拟系统的内存 IIRC 中包含一些内核代码或类似的东西。我可能记错了,但如果不是,那么某些系统调用处理实际上可能由更多 MIPS 代码完成,更接近运行操作系统的真实 MIPS CPU。 (与 MARS 不同,在 MARS 中,系统调用实现纯粹是在您通过 syscall 调用的模拟器内的 Java 中,而不是根据 MIPS 指令和模拟硬件的设备驱动程序。)

在真正的操作系统下(例如带有 gcc 和 glibc 的 GNU/Linux),main 将是一个适当的函数,通常从 _start 进程入口点调用(在实际调用 main 之前间接通过 __libc_start_main 执行更多初始化操作)。 _start 是真正的进程入口点,是在用户空间中运行的第一条指令(模动态链接),并且不是函数(任何地方都没有返回地址);您唯一的选择是进行退出系统调用(或崩溃或永远运行)。当main 返回时,_start 将其返回值(int,因此在 $v0 中)作为 arg 传递给 exit library function which does cleanup stuff,就像刷新 stdio 缓冲区一样,然后进行 _exit 系统调用。

显然 SPIM 打算将它们的 main 标签类似于 C 中的 main 函数,或者至少它会获得一个有效的返回地址。如果它在$a0$a1 中获得int argcchar *argv[],则IDK。

为了使jr $ra 工作,SPIM 必须将初始的$ra 设置为某个地址,就像您的 main 是从某个地方调用的一样。您可能会找到将 $v0 复制到 $a0,然后进行退出系统调用的代码。

有些人会混淆地使用main 作为无法返回的入口点的名称,不幸的是,我认为即使在现实世界的嵌入式开发中也是如此。在 GNU/Linux 系统的标准工具链(gcc / clang)中,进程入口点默认称为_start

main 令人困惑,因为它是允许返回的 C 函数(由 asm 启动的东西调用)的标准名称。不能返回的东西不是函数,但在C 中,main 绝对 一个函数。 C 是 Unix/Linux 系统编程的低级语言,许多其他语言都建立在 libc 和 CRT 启动代码的标准工具链之上。


脚注 1: 大多数 ISA 都有关于 PC 如何从 0xffffffc 包装到 0 或其他任何内容的规则,因此即使将您的代码放在地址空间的最末端也无法做到到达终点时它会自行停止。或者如果确实如此,那将是某种故障,没有退出到操作系统。 (在这种情况下,MARS 或 SPIM 充当操作系统,处理您运行的 syscall 指令等)。请注意,裸机上的实际操作系统无法“退出”,只能重置或关闭机器。它不会在任何可以退出的“下方”运行。

脚注 2:系统调用非常有限,例如没有光标移动,并且一些系统调用会做库函数(不是系统调用)在真实系统中会做的事情,例如int字符串转换。但 MARS/SPIM 仅将其作为 I/O 的一部分提供,没有 atoisprintf(buf, "%d", num)。这就是为什么“玩具”标签适用的原因,尤其是它们提供的一组系统调用,这与 Linux 的一组系统调用非常

但也适用于 MARS 具有的简单位图图形,以及 MARS 和 SPIM 默认的 no-branch-delay 默认选项。真正的 MIPS CPU 有一个分支延迟槽,直到 MIPS32r6 重新排列操作码并提供新的无延迟槽分支指令。

MARS 至少(也许是 SPIM)在其内置汇编器中对汇编时常量的支持也非常有限,例如你不能像在 MIPS 的 GNU 汇编器中那样在汇编时使用 .equmsglen = . - msg 来计算 msg: .ascii "hello" 的长度。

【讨论】:

  • @Chris:哦,我错过了 MARS 在jr $ra 上失败了,所以你的选择都不能同时使用。更新了我的答案,将通过 syscall 部分的出口放在顶部,未来的读者可以更容易地找到它,因为没有其他东西可以跨两个模拟器移植。
  • +1,我在 long 中添加了关于 SPIM 及其内核代码/异常处理程序包含机制的评论。
【解决方案2】:

添加到@Peter 的非常好的答案:

SPIM 可以选择包含内核代码,通过 Simulator->Settings->Load Exception Handler(您可以选择一个文件或使用默认值),该处理程序是汇编源代码。默认设置是使用默认处理程序(与不使用处理程序相反)。

编写这样的处理程序时,可以在.ktext & .kdata 中包含代码,也可以在用户.text.data 中包含代码。任何异常处理程序都会在用户代码之前组装和加载。

标准异常处理程序文件包括——用于放置在用户.text中——加载argc/argv,然后执行jal main,然后执行系统调用#10(所以它有点像crt0中的_start),这意味着我们可以返回 (jr $ra) 到该启动代码。这就是为什么在 SPIM 中用户代码出现在 [00400020] 而在 MARS 中你的用户代码从 00400000 开始。

SPIM 直到运行时才报告丢失的符号!!!因此,如果未找到 main,则在执行 jal main 时会报告缺少的符号 main

但是,当您确实有一个有效的main 符号时,它不必在文件中的第一个位置——它可以在任何地方。

虽然 MARS 也从 .text 的开头开始执行,但相比之下,MARS 中的默认异常处理程序没有提供 _start 等效项,因此我们必须使用主代码启动汇编程序(但我们并不真的需要main 符号)或在此处放置 j somewhere。如果您放弃使用默认处理程序,SPIM 的行为将更像 MARS。

【讨论】:

  • TL:DR:在用户 PoV 的默认设置中,SPIM 将 main 标签作为函数调用。 MARS 并不关心标签,而是从用户代码的顶部开始执行。 (如果您想有效地编写自己的 _start,则该指令可以是 jal main 和退出系统调用。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-16
  • 1970-01-01
  • 2019-02-19
  • 2015-04-26
相关资源
最近更新 更多