MIPS32 Stackframe坏了吗?

时间:2016-05-17 08:33:23

标签: assembly stack mips mips32

所以这里是希望的代码:

计划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"

每个节目的结尾都是具体的输出。

第一个程序运行正常。 在第二个程序中,我为打印新行创建了一个额外的功能。如果我运行这个,我会得到一个"总线错误"。

在第三个程序中,我创建了一个虚拟函数来模拟另一个stacklevel。 还有一个"总线错误"我要打印的号码不打印。

我认为我们的筹码存在一些问题。

我使用了调试器,如果我从堆栈加载我的返回地址,我的$ 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

1 个答案:

答案 0 :(得分:2)

我查看了你的程序,大部分都看起来不错。我认为你的堆栈保存/恢复很好。但是,我至少看到了另外一个问题。

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

在mip ABI下,{em>被调用者可能会修改/销毁$a0-$a3个寄存器。因此,printf返回$a0后可以是任何内容。

这三个程序似乎都有这个问题。国际海事组织,如果计划1正在运作,它只是平局的运气(即)$a0中的任何结果都是无害的。也就是说,无论它是什么,都指向一个不是文件描述符的内存位置,但是fflush试图将其解释为一个并且运气不好。

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

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

lw     $a0,stdout
jal    fflush

这应该可行,但是,根据gcc汇编程序的作用,你可能需要这样做:

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

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

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

在每个jal之前和之后,请注意$sp值。 必须是一样的。为您的所有功能执行此操作。此外,从函数返回时,请记下$sp值,然后记下lw [进入$ra]的值。它们都应该匹配"期望"

此外,$sp必须始终以4字节对齐。实际上,根据我见过的[可能已过时]的mip ABI文件,它表示堆栈帧必须是8字节对齐的。在这一点上这可能有点过头了,但我提到了它。

如果你从未对齐的lw进行,那就是对齐例外,它可能会显示为SIGBUS。此外,检查$ra值[之前执行jr $ra]。它也必须是"预期"值和4字节对齐。

换句话说,完全哪条指令会产生异常?

你可以做的另一件事是注释掉一些函数调用。从jal fflush开始。然后,在您的子功能中注释jal printf [。我注意到你做了一个"裸体" printf一开始就打电话,这似乎很好。继续这样做,直到程序停止故障。这应该有助于本地化/呼叫。

您没有说明是否在spimmars或真实H / W [可能正在运行Linux]等模拟器中运行此功能。我怀疑是真正的H / W.但是,您可以通过在模拟器[我更喜欢mars]下运行验证您的逻辑,并为printffflush提供虚拟函数{{1} }}。请注意,jr $raspim都无法链接到mars个文件。它们纯粹来自源代码,因此您只能使用.o

如果您在真实的H / W上运行,.s应该能够提供有关异常来源的详细信息。如果没有,请创建一个C函数,使用gdbSIGBUS设置信号处理程序[参见联机帮助页]。然后,在信号处理程序上放置一个断点。

信号处理程序的一个参数是指向具有附加信息的sigaction结构的指针。请注意,对于siginfo_tSIGBUS字段将具有可能包含更多信息的si_code值。第三个参数,尽管BUS_*是指向可以为您提供的内容的指针异常时的寄存器值。

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