是“jr $ra"吗?需要结束 MIPS 汇编语言程序吗?(MARS 和 QtSpim 的行为不同!) [英] Is "jr $ra" required to end a MIPS assembly language program? (MARS & QtSpim behave differently!)

查看:99
本文介绍了是“jr $ra"吗?需要结束 MIPS 汇编语言程序吗?(MARS 和 QtSpim 的行为不同!)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

<块引用>

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

以下示例 1 失败:

.datatheMsg: .asciiz "Hello World\n";.文本.globl 主主要: li $v0, 4la $a0, theMsg系统调用jr $ra

以下示例 2 有效:

.datatheMsg: .asciiz "Hello World\n";.文本.globl 主主要: li $v0, 4la $a0, theMsg系统调用

MARS 说程序已完成运行(从底部掉下来)",但没有错误消息.

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

<块引用>

尝试在 0x00400030 处执行非指令

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

有人能解释一下吗?
哪个 MIPS 模拟器是正确的?

解决方案

标准的工作方式——无处不在的方式是进行 exit(0) 系统调用:

 li $v0, 10 # 电话号码系统调用#exit()

如果您想在程序中使用 jal,这也避免了必须将传入的 $ra 保存在 main 中的任何位置,因此很方便.

这也更现实";用于在 Linux 等主流操作系统中运行的真实手写 asm 程序,而不仅仅是玩具"MARS/SPIM 模拟的系统.

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


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

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

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

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

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

显然,SPIM 希望它们的 main 标签像一个 C main 函数,或者至少它得到一个有效的返回地址.IDK,如果它在 $a0$a1 中得到 int argcchar *argv[].

要使 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)在其内置汇编器中对汇编时间常量的支持也非常有限,例如你不能做 .equmsglen = .- msg 在汇编时计算 msg: .ascii "hello" 的长度,就像在 MIPS 的 GNU 汇编器中一样.

If you put a jr $ra at the end of a MIPS assembly language program in MARS, you will get an error message saying:

invalid program counter value: 0x00000000

Example 1 below fails:

.data

theMsg: .asciiz "Hello World\n"

.text
.globl main

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

      

Example 2 below works:

.data

theMsg: .asciiz "Hello World\n"

.text
.globl main

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

     
    

MARS says "program is finished running (dropped off bottom)" but there are no error messages.

Now, if you run Example 2 in QtSpim, you will get an error saying:

Attempt to execute non-instruction at 0x00400030

If you run Example 1 in QtSpim, it works just fine.

Can anyone shed some light on this?
Which MIPS simulator is right?

解决方案

The standard works-everywhere way is making an exit(0) system call:

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

That also avoids having to save the incoming $ra anywhere in main if you want to use jal inside your program, so it's convenient.

That's also more "realistic" for real-world hand-written asm programs running in a mainstream OS like Linux, not just the "toy" system that MARS/SPIM simulate.

(Unlike Linux exit(int), the MARS/SPIM toy system call doesn't take an exit status in $a0 or anywhere else. It just exits.)


In MARS, apparently dropping off the bottom is a valid option for the "toy" system that it simulates. That never works in any real-hardware CPU, though; there's always something next in memory and the CPU will try to fetch and execute it1.

Neither MARS nor SPIM are trying to emulate a real OS like Linux, just provide their own specific environment2. The systems that MARS vs. SPIM simulate have some minor differences from each other, including the one you found.

Neither one is right or wrong, just different: there is no real-world environment that they're trying to match / emulate.

SPIM might even have an option to include some kernel code or something like that in the simulated system's memory, IIRC. I may be misremembering, but if not then some of the syscall handling might actually be done by more MIPS code, coming closer to a real MIPS CPU running an OS. (As opposed to MARS where the system-call implementation is purely in Java inside the simulator that you're calling into via syscall, not in terms of MIPS instructions and device drivers for simulated hardware.)

Under a real OS (e.g. GNU/Linux with gcc and glibc), main would be a proper function called normally from the _start process entry point (indirectly via __libc_start_main to do some more init stuff before actually calling main). _start is the real process entry point, first instruction that runs in user-space (modulo dynamic linking), and is not a function (no return address anywhere); your only option is to make an exit system call (or crash or keep running forever). When main returns, _start passes its return value (an int thus in $v0) as an arg to the exit library function which does cleanup stuff like flushing stdio buffers, then makes an _exit system call.

Apparently SPIM intends their main label to be like a C main function, or at least it gets a valid return address. IDK if it gets int argc and char *argv[] in $a0 and $a1.

For jr $ra to work, SPIM must be setting the initial $ra to some address, like your main was called from somewhere. You'll probably find code that which copies $v0 to $a0, then makes an exit system call.

Some people do confusingly use main as a name for entry-points that can't return, unfortunately, I think even in real-world embedded development. In standard toolchains for GNU/Linux systems (gcc / clang), the process entry point is by default called _start.

main is confusing because it's the standard name for a C function (called by asm startup stuff), which is allowed to return. Something you can't return from isn't a function, but in C, main definitely is a function. C is the low-level language of Unix/Linux systems programming, and many other languages build on top of that standard toolchain of libc and CRT startup code.


Footnote 1: Most ISAs have rules for how PC can wrap from 0xffffffc to 0 or whatever, so even putting your code at the very end of the address space can't make it stop by itself when reaching the end. Or if it does, it will be some kind of fault, not exiting to the OS. (In this case the MARS or SPIM are acting as the OS, handling the syscall instructions you run among other things). Note that an actual OS on bare metal has no way to "exit", only reset or power-off the machine. It's not running "under" anything it can exit back to.

Footnote 2: With very limited system calls, e.g. no cursor movement, and some syscalls which do things that library functions (not syscalls) would do in a real system, e.g. int<->string conversion. But MARS/SPIM only provide that as part of I/O, no atoi or sprintf(buf, "%d", num). This is why the "toy" label applies, especially to the set of system calls they provide, which is very much not like Linux's set of system calls.

But also to stuff like the simple bitmap graphics MARS has, and to the no-branch-delay default option both MARS and SPIM default to. Real MIPS CPUs have a branch-delay slot, until MIPS32r6 re-arranged the opcodes and provided new no-delay-slot branch instructions.

MARS at least (and maybe SPIM) have pretty limited support for assemble-time constants in their built-in assembler as well, e.g. you can't do .equ or msglen = . - msg to compute a length of a msg: .ascii "hello" at assemble time like you could in GNU assembler for MIPS.

这篇关于是“jr $ra"吗?需要结束 MIPS 汇编语言程序吗?(MARS 和 QtSpim 的行为不同!)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆