MIPS32 Stackframe坏了?
MIPS32 Stackframe broken?
所以这里的代码如愿:
计划 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"
每个程序的最后都有具体的输出。
第一个程序运行良好。
在第二个程序中,我做了一个额外的功能来打印一个新行。如果我 运行 这个我得到一个 "bus error".
在第三个程序中,我创建了一个虚拟函数来模拟另一个堆栈级别。
还有一个"bus error"我要打印的号码打印不出来
我认为我们的栈有问题。
我使用了调试器,如果我从堆栈加载我的 return-地址,我的 $ra 中的地址是错误的,我不知道为什么。如果它跳转到这个地址,我会得到 "bus error".
函数如下:
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
我查看了您的程序,大部分看起来都不错。我认为你的堆栈 save/restore 很好。但是,我至少看到了另一个问题。
在每个 jal printf
之后,你正在做一个 立即 jal fflush
,所以 fflush
将得到 printf 的第一个参数 [这是一个字符串指针] 而不是文件描述符指针(例如 stdout
)。
在 mips ABI 下,$a0-$a3
寄存器可能被 callee modified/destroyed。所以,在 printf
returns $a0
之后可以是任何东西。
三个程序好像都有这个问题。 IMO,如果程序 1 正在运行,那只是平局的运气(即)在 $a0
中结束的任何东西都是无害的。也就是说,无论其中有什么,都指向一个不是文件描述符的内存位置,但 fflush
试图将其解释为一个,但幸运的是。
此外,对于 fflush
,$a0
应该指向一个字 [4 字节] 对齐的地址。如果不是,那可能是总线错误的来源。
要解决此问题,请将所有 fflush
调用更改为:
lw $a0,stdout
jal fflush
这应该可行,但是,根据 gcc
汇编程序的作用,您可能必须这样做:
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
我已经检查过您的代码,[再次] 看起来不错。调试它的一种方法是 [using gdb
] 是在您的函数中单步执行 [with stepi
] 并在 jal printf
[或 jal fflush
] 之后放置断点。
在每个 jal
前后,记下 $sp
值。 必须相同。为您的所有功能执行此操作。此外,当从函数返回时,请注意 $sp
值,然后是 lw
中的值 [进入 $ra
]。他们应该都匹配 "expected"
此外,$sp
必须始终为 4 字节对齐。实际上,根据我看过的 mips ABI 文档 [可能已过时],它说堆栈帧必须是 8 字节对齐的。在这一点上这可能有点矫枉过正,但我提到了它。
如果你在未对齐的情况下执行 lw
,这是一个对齐异常,它可能会显示为 SIGBUS
。此外,在 执行 jr $ra
] 之前检查 $ra
值 [。它还必须是 "expected" 值并且是 4 字节对齐的。
换句话说,究竟哪条指令产生了异常?
您可以做的另一件事是注释掉一些函数调用。从 jal fflush
开始。然后,注释掉 jal printf
[在你的子功能中]。我注意到您在一开始就进行了 "naked" printf
调用,这看起来不错。继续这样做,直到程序停止出错。这应该有助于本地化 area/call.
你没有说明你是 运行 在像 spim
或 mars
这样的模拟器中还是真实的 H/W [可能 运行 linux]。我怀疑是真的H/W。但是,您可以在模拟器 [我更喜欢 mars
] 下通过 运行 验证 您的 逻辑,并为 printf
和 [=14= 提供虚拟函数] 就是这样 jr $ra
。请注意,spim
和 mars
都不能 link 到 .o
文件。它们完全从源代码开始工作,因此您只能使用您的 .s
如果您 运行 是真正的 H/W,gdb
应该能够提供有关异常来源的详细信息。如果不是,请创建一个 C 函数,使用 sigaction
为 SIGBUS
设置信号处理程序 [请参阅联机帮助页]。然后,在信号处理程序上放置一个断点。
信号处理程序的参数之一是指向具有附加信息的 siginfo_t
结构的指针。请注意,对于 SIGBUS
,si_code
字段将有一个 BUS_*
值,该值可能有更多 information.The 第三个参数,尽管 void *
是指向可以给你异常时的寄存器值。
在我给出的另一个答案中:mips recursion how to correctly store return address for a function另一个 OP 也有类似的问题。我的回答添加了一些特殊的堆栈对齐和检查代码,可能会给你一些想法。
所以这里的代码如愿:
计划 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"
每个程序的最后都有具体的输出。
第一个程序运行良好。 在第二个程序中,我做了一个额外的功能来打印一个新行。如果我 运行 这个我得到一个 "bus error".
在第三个程序中,我创建了一个虚拟函数来模拟另一个堆栈级别。 还有一个"bus error"我要打印的号码打印不出来
我认为我们的栈有问题。
我使用了调试器,如果我从堆栈加载我的 return-地址,我的 $ra 中的地址是错误的,我不知道为什么。如果它跳转到这个地址,我会得到 "bus error".
函数如下:
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
我查看了您的程序,大部分看起来都不错。我认为你的堆栈 save/restore 很好。但是,我至少看到了另一个问题。
在每个 jal printf
之后,你正在做一个 立即 jal fflush
,所以 fflush
将得到 printf 的第一个参数 [这是一个字符串指针] 而不是文件描述符指针(例如 stdout
)。
在 mips ABI 下,$a0-$a3
寄存器可能被 callee modified/destroyed。所以,在 printf
returns $a0
之后可以是任何东西。
三个程序好像都有这个问题。 IMO,如果程序 1 正在运行,那只是平局的运气(即)在 $a0
中结束的任何东西都是无害的。也就是说,无论其中有什么,都指向一个不是文件描述符的内存位置,但 fflush
试图将其解释为一个,但幸运的是。
此外,对于 fflush
,$a0
应该指向一个字 [4 字节] 对齐的地址。如果不是,那可能是总线错误的来源。
要解决此问题,请将所有 fflush
调用更改为:
lw $a0,stdout
jal fflush
这应该可行,但是,根据 gcc
汇编程序的作用,您可能必须这样做:
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
我已经检查过您的代码,[再次] 看起来不错。调试它的一种方法是 [using gdb
] 是在您的函数中单步执行 [with stepi
] 并在 jal printf
[或 jal fflush
] 之后放置断点。
在每个 jal
前后,记下 $sp
值。 必须相同。为您的所有功能执行此操作。此外,当从函数返回时,请注意 $sp
值,然后是 lw
中的值 [进入 $ra
]。他们应该都匹配 "expected"
此外,$sp
必须始终为 4 字节对齐。实际上,根据我看过的 mips ABI 文档 [可能已过时],它说堆栈帧必须是 8 字节对齐的。在这一点上这可能有点矫枉过正,但我提到了它。
如果你在未对齐的情况下执行 lw
,这是一个对齐异常,它可能会显示为 SIGBUS
。此外,在 执行 jr $ra
] 之前检查 $ra
值 [。它还必须是 "expected" 值并且是 4 字节对齐的。
换句话说,究竟哪条指令产生了异常?
您可以做的另一件事是注释掉一些函数调用。从 jal fflush
开始。然后,注释掉 jal printf
[在你的子功能中]。我注意到您在一开始就进行了 "naked" printf
调用,这看起来不错。继续这样做,直到程序停止出错。这应该有助于本地化 area/call.
你没有说明你是 运行 在像 spim
或 mars
这样的模拟器中还是真实的 H/W [可能 运行 linux]。我怀疑是真的H/W。但是,您可以在模拟器 [我更喜欢 mars
] 下通过 运行 验证 您的 逻辑,并为 printf
和 [=14= 提供虚拟函数] 就是这样 jr $ra
。请注意,spim
和 mars
都不能 link 到 .o
文件。它们完全从源代码开始工作,因此您只能使用您的 .s
如果您 运行 是真正的 H/W,gdb
应该能够提供有关异常来源的详细信息。如果不是,请创建一个 C 函数,使用 sigaction
为 SIGBUS
设置信号处理程序 [请参阅联机帮助页]。然后,在信号处理程序上放置一个断点。
信号处理程序的参数之一是指向具有附加信息的 siginfo_t
结构的指针。请注意,对于 SIGBUS
,si_code
字段将有一个 BUS_*
值,该值可能有更多 information.The 第三个参数,尽管 void *
是指向可以给你异常时的寄存器值。
在我给出的另一个答案中:mips recursion how to correctly store return address for a function另一个 OP 也有类似的问题。我的回答添加了一些特殊的堆栈对齐和检查代码,可能会给你一些想法。