Search code examples
assemblystackmipsmips32

MIPS32 Stackframe broken?


So here the code as wished:

PROGRAM 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

PROGRAM 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

PROGRAM 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"

At the end of every program is the specific output.

The first program works fine. In the second program I made an extra function for printing a new line. If I run this I get a "bus error".

And in the third program I made a dummy function to simulate another stacklevel. There is also a "bus error" and the number i want to print is not printing.

I think there are some problem with our stack.

I used the debugger and if I load my return-address from stack I have the wrong address in my $ra and i dont know why. If it jumps to this address i get the "bus error".

Here are the functions:

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

Solution

  • I've looked over your program and most of it looks fine. I think your stack save/restore is fine. But, I see at least one other problem.

    After every jal printf, you're doing an immediate jal fflush, so fflush will get printf's first argument [which is a string pointer] instead of a file descriptor pointer (e.g. stdout).

    Under the mips ABI, the $a0-$a3 registers may be modified/destroyed by the callee. So, after printf returns $a0 can be anything.

    All three programs seem to have this problem. IMO, if program 1 is working, it's just the luck of the draw (i.e.) whatever ends up in $a0 is harmless enough. That is, whatever is in it, points to a memory location that isn't a file descriptor but fflush tries to interpret it as one and lucks out.

    Also, for fflush, $a0 should point to an address that is word [4 byte] aligned. If it's not, that could be the source of the bus error.

    To fix this, change all fflush calls to:

    lw     $a0,stdout
    jal    fflush
    

    That should probably work, but, depending on what the gcc assembler does, you may have to do:

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

    I've seen that the buserror appears if I try to jump back from a function For example I jump to PRINT_DOUBLE, there I jump to BREAK_LINE and there I jump to PRINT_STRING If I jump then back to BREAK_LINE all is fine, but back from BREAK_LINE to PRINT_DOUBLE I get the buserror

    I've desk checked your code and [once again] seems fine. One way to debug this is to [using gdb] is to single step [with stepi] within your functions and put breakpoints after the the jal printf [or jal fflush].

    Before and after each jal, note the $sp value. It must be the same. Do this for all your functions. Also, when returning from a function, note the $sp value, then the value from the lw [which goes into $ra]. They should all match the "expected"

    Further, $sp must be 4 byte aligned at all times. Actually, according to a mips ABI document I've seen [possibly dated], it says that stack frames must be 8 byte aligned. That may be overkill at this point, but I mention it.

    If you do lw from unaligned, that's an alignment exception, which will probably show up as SIGBUS. Further, check the $ra value [before doing the jr $ra]. It also must be the "expected" value and be 4 byte aligned.

    In other words, exactly which instruction produces the exception?

    Another thing you can do is to comment out some function calls. Start with the jal fflush. Then, comment out jal printf [in your sub-functions]. I note that you do a "naked" printf call at the outset, which seems fine. Keep doing this until the program stops faulting. This ought to help localize the area/call.

    You didn't state whether you were running this in a simulator like spim or mars or real H/W [possibly running linux]. I suspect real H/W. But, you could verify your logic by running under a simulator [I prefer mars] and provide dummy functions for printf and fflush that just do jr $ra. Note that neither spim nor mars can link to .o files. They work purely from source, so you can only use your .s

    If you're running on real H/W, gdb ought to be able to provide detailed information on the source of the exception. If not, create a C function that sets up a signal handler for SIGBUS using sigaction [see the manpage]. Then, put a breakpoint on the signal handler.

    One of the arguments to the signal handler is a pointer to a siginfo_t struct that has additional information. Note that for SIGBUS, the si_code field will have a BUS_* value that may have more information.The third argument, although void * is a pointer to something that can give you the register values at the time of the exception.

    In another answer I gave: mips recursion how to correctly store return address for a function another OP had a similar problem. My answer added some special stack alignment and check code that may give you some ideas.