【问题标题】:MIPS32 Stackframe broken?MIPS32 Stackframe 坏了?
【发布时间】:2016-09-13 06:02:08
【问题描述】:

所以这里的代码如愿:

程序 1

.text
  .globl main
  main: 
    li     $t0, 10
    mtc1    $t0, $f12
    cvt.s.w $f12, $f12               # 10.0 as single in $f12
    jal     printFloat      

    li      $v0, 4001               #sys_exit
    syscall

printFloat:
        addi    $sp, $sp, -4            #opens the stack frame            
        sw      $ra, 0($sp)             #saves the return adress

        cvt.d.s $f12, $f12              #converts the single to double
        la      $a0, strDouble          #arguments needed for printf ("%f" in $a0, upper 32 bit of the float in $a2, lower ones in $a1)
        mfc1    $a1, $f12               
        mfc1    $a2, $f13               
        jal     printf                  
        jal     fflush                  

        la      $a1, strBreakLine       #arguments needed for printf (Adress of String in $a1, "%s" in $a0)    
        la      $a0, strStringOut       
        jal     printf                  
        jal     fflush                  

        lw      $ra, 0($sp)             #restores the return adress
        addi    $sp, $sp, 4             #pops the stack frame
        jr      $ra

.data
strDouble:      .asciiz "%f"
strStringOut:   .asciiz "%s"
strBreakLine:   .asciiz "\n"


phm15fix@ci20:~$ gcc -o test -g test1.s
phm15fix@ci20:~$ ./test
10.000000

程序 2

.text
.globl main
main: 
        li     $t0, 10
        mtc1    $t0, $f12
        cvt.s.w $f12, $f12               # 10.0 as single in $f12
        jal     printFloat      

        li      $v0, 4001               #sys_exit
        syscall

printFloat:
        addi    $sp, $sp, -4            #opens the stack frame            
        sw      $ra, 0($sp)             #saves the return adress

        cvt.d.s $f12, $f12              #converts the single to double
        la      $a0, strDouble          #arguments needed for printf ("%f" in $a0, upper 32 bit of the float in $a2, lower ones in $a1)
        mfc1    $a1, $f12               
        mfc1    $a2, $f13               
        jal     printf                  
        jal     fflush
        jal     printNewLine                  

        lw      $ra, 0($sp)             #restores the return adress
        addi    $sp, $sp, 4             #pops the stack frame
        jr      $ra

printNewLine:
        addi    $sp, $sp, -4            #opens the stack frame            
        sw      $ra, 0($sp)             #saves the return adress

        la      $a1, strBreakLine       #arguments needed for printf (Adress of String in $a1, "%s" in $a0)    
        la      $a0, strStringOut       
        jal     printf                  
        jal     fflush 

        lw      $ra, 0($sp)             #restores the return adress
        addi    $sp, $sp, 4             #pops the stack frame
        jr      $ra

.data
strDouble:      .asciiz "%f"
strStringOut:   .asciiz "%s"
strBreakLine:   .asciiz "\n"


phm15fix@ci20:~$ gcc -o test -g test1.s
phm15fix@ci20:~$ ./test
10.000000
Bus error

程序 3

.text
.globl main
main:
        li     $t0, 10
        mtc1    $t0, $f12
        cvt.s.w $f12, $f12               # 10.0 as single in $f12
        jal     function

        li      $v0, 4001               #sys_exit
        syscall

function:
        addi    $sp, $sp, -4            #opens the stack frame
        sw      $ra, 0($sp)             #saves the return adress

        jal     printFloat

        lw      $ra, 0($sp)             #restores the return adress
        addi    $sp, $sp, 4             #pops the stack frame
        jr      $ra

printFloat:
        addi    $sp, $sp, -4            #opens the stack frame
        sw      $ra, 0($sp)             #saves the return adress

        cvt.d.s $f12, $f12              #converts the single to double
        la      $a0, strDouble          #arguments needed for printf ("%f" in $a0, upper 32 bit of the float in $a2, lower ones in $a1)
        mfc1    $a1, $f12
        mfc1    $a2, $f13
        jal     printf
        jal     fflush
        jal     printNewLine

        lw      $ra, 0($sp)             #restores the return adress
        addi    $sp, $sp, 4             #pops the stack frame
        jr      $ra

printNewLine:
        addi    $sp, $sp, -4            #opens the stack frame
        sw      $ra, 0($sp)             #saves the return adress

        la      $a1, strBreakLine       #arguments needed for printf (Adress of String in $a1, "%s" in $a0)
        la      $a0, strStringOut
        jal     printf
        jal     fflush

        lw      $ra, 0($sp)             #restores the return adress
        addi    $sp, $sp, 4             #pops the stack frame
        jr      $ra

.data
strDouble:      .asciiz "%f"
strStringOut:   .asciiz "%s"
strBreakLine:   .asciiz "\n"

每个程序的结尾都是特定的输出。

第一个程序运行良好。 在第二个程序中,我制作了一个额外的功能来打印新行。如果我运行它,我会收到“总线错误”。

在第三个程序中,我创建了一个虚拟函数来模拟另一个堆栈级别。 还有一个“总线错误”,我要打印的号码没有打印。

我认为我们的堆栈有问题。

我使用了调试器,如果我从堆栈加载返回地址,我的 $ra 中的地址错误,我不知道为什么。如果它跳转到这个地址,我会得到“总线错误”。

以下是函数:

PRINT_DOUBLE:
        addi    $sp, $sp, -4            
        sw      $ra, 0($sp)             

        la      $a0, strDouble          
        mfc1    $a1, $f12               
        mfc1    $a2, $f13               
        jal     printf                  
        nop
#       lw      $a0, stdout
#        jal     fflush                  
        jal     BREAK_LINE

        lw      $ra, 0($sp)             
        addi    $sp, $sp, 4             
        jr      $ra


BREAK_LINE:
        addi    $sp, $sp, -4            
        sw      $ra, 0($sp)             

        la      $a0, strBreakLine
        jal     PRINT_STRING

        lw      $ra, 0($sp)             
        addi    $sp, $sp, 4             
        nop

        jr      $ra

PRINT_STRING:
        addi    $sp, $sp, -4            
        sw      $ra, 0($sp)             

        add     $a1, $a0, $zero         
        la      $a0, strStringOut       
        jal     printf                  
        la      $a0, stdout
        lw      $a0, 0($a0)
        jal     fflush                  

        lw      $ra, 0($sp)             
        addi    $sp, $sp, 4             
        nop
        jr      $ra

【问题讨论】:

  • 这可能与您的问题无关,但您为什么使用printf("%s", "\n"); 而不仅仅是printf("\n");(或者更简单:puts("");)?
  • 通常,除非您使用最小的代码示例将代码放入实际问题中,否则 SO 人将非常无助。 Pastebin 和其他链接没有帮助。
  • 另外,使用调试器至少查明错误的确切位置。

标签: assembly stack mips mips32


【解决方案1】:

我查看了您的程序,其中大部分看起来都很好。我认为您的堆栈保存/恢复很好。但是,我至少发现了另一个问题。

在每个jal printf 之后,您正在执行一个立即 jal fflush,因此fflush 将获得 printf 的第一个参数 [这是一个字符串指针] 而不是文件描述符指针(例如stdout)。

在 mips ABI 下,$a0-$a3 寄存器可能会被 callee 修改/销毁。所以,printf 返回后,$a0 可以是任何东西。

这三个程序似乎都有这个问题。国际海事组织,如果程序 1 正常工作,那只是抽签的运气(即)任何以$a0 结尾的东西都足够无害。也就是说,无论它是什么,都指向一个不是文件描述符但fflush 试图将其解释为一个并且幸运的内存位置。

另外,对于fflush$a0 应该指向一个字 [4 字节] 对齐的地址。如果不是,那可能是总线错误的根源。

要解决此问题,请将所有 fflush 调用更改为:

lw     $a0,stdout
jal    fflush

这应该可行,但是,根据 gcc 汇编器的作用,您可能必须这样做:

la     $a0,stdout
lw     $a0,0($a0)
jal    fflush

我已经看到如果我尝试从函数跳转回来,则会出现总线错误 例如,我跳转到 PRINT_DOUBLE,然后跳转到 BREAK_LINE,然后跳转到 PRINT_STRING 如果我跳转回 BREAK_LINE,一切都很好,但是从 BREAK_LINE 回到 PRINT_DOUBLE 我得到了总线错误

我已经检查了您的代码,并且 [再次] 似乎很好。调试此问题的一种方法是[使用gdb] 是在您的函数中单步[使用stepi] 并在jal printf [或jal fflush] 之后放置断点。

在每个jal 之前和之后,请注意$sp 值。它必须相同。为您的所有功能执行此操作。此外,从函数返回时,请注意 $sp 的值,然后是来自 lw 的值 [进入 $ra]。它们都应该符合“预期”

此外,$sp 必须始终保持 4 字节对齐。实际上,根据我看过的 [可能过时的] mips ABI 文档,它说堆栈帧必须是 8 字节对齐的。在这一点上这可能有点矫枉过正,但我​​会提到它。

如果您从 unaligned 执行 lw,这是一个对齐异常,可能会显示为 SIGBUS。此外,检查$ra 值[ 执行jr $ra]。它还必须是“预期”值并且是 4 字节对齐的。

换句话说,究竟哪条指令产生了异常?

您可以做的另一件事是注释掉一些函数调用。从jal fflush 开始。然后,注释掉jal printf [在您的子功能中]。我注意到你一开始就打了一个“赤裸”的printf 电话,这似乎很好。继续这样做,直到程序停止出错。这应该有助于定位区域/呼叫。

您没有说明您是在 spimmars 之类的模拟器中运行它,还是在真正的 H/W [可能运行 linux] 中运行它。我怀疑真正的硬件。但是,您可以通过在模拟器下运行来验证 您的 逻辑 [我更喜欢 mars] 并为 printffflush 提供仅执行 jr $ra 的虚拟函数。请注意,spimmars 都不能链接到 .o 文件。它们完全来自源代码,因此您只能使用您的 .s

如果您在真正的硬件上运行,gdb 应该能够提供有关异常来源的详细信息。如果没有,请创建一个 C 函数,使用 sigaction [参见手册页] 为 SIGBUS 设置信号处理程序。然后,在信号处理程序上放置一个断点。

信号处理程序的参数之一是指向具有附加信息的siginfo_t 结构的指针。请注意,对于SIGBUSsi_code 字段将具有可能包含更多信息的BUS_* 值。第三个参数,虽然void * 是一个指针,它可以为您提供当时的寄存器值例外。

在我给出的另一个答案中:mips recursion how to correctly store return address for a function 另一个 OP 也有类似的问题。我的回答添加了一些特殊的堆栈对齐和检查代码,可能会给您一些想法。

【讨论】:

  • 感谢您的建议,我现在使用此构造...但是我已经看到如果我尝试从函数跳转回来,则会出现总线错误 例如我跳转到 PRINT_DOUBLE,我跳转到BREAK_LINE 然后我跳到 PRINT_STRING 如果我跳回 BREAK_LINE 一切都很好,但从 BREAK_LINE 回到 PRINT_DOUBLE 我得到了总线错误我在问题中创建了一个新的代码-sn-p
猜你喜欢
  • 2018-03-12
  • 2010-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多