这个 MIPS 过程调用的错误是什么?
What is mistake of this MIPS procedure call?
此 MIPS 代码包含调用另一个函数 foo(a,b) 的函数 int func(int a, int b)。
func: addi $sp,$sp,-4
sw $ra, 0($sp)
addi $a0,$a0,1
addi $a1,$a1,2
jal foo
addi $a0,$v0,4
add $v0,$a0,$a1
lw $ra,0($sp)
addi $sp,$sp,4
jr $ra
这段代码有一些错误,但我不知道哪里错了。
最严重的违规行为是 func
期望 $a1
在对 foo
的函数调用后继续存在——这是不正确的。根据调用约定,$a1
是一个参数寄存器,不会被函数调用保留。 $a1
也不是 return 值,因此在函数调用后应将其视为未初始化,即它包含无用的垃圾。
func: addi $sp,$sp,-4
sw $ra, 0($sp)
addi $a0,$a0,1
addi $a1,$a1,2
jal foo # this call effectively wipes out argument registers (ok)
addi $a0,$v0,4 # here the function re-initializes $a0 from return value $v0 (ok)
add $v0,$a0,$a1 # but here it uses uninitliazed $a1 (not ok)
lw $ra,0($sp)
addi $sp,$sp,4
jr $ra
保存func
s $a1
参数的正确方法是将其存储在内存中,稍后在函数调用后将其加载回来;这将需要在堆栈帧中添加一个词。
func: addiu $sp,$sp,-8
sw $ra, 4($sp)
addi $a0,$a0,1
addi $a1,$a1,2
sw $a1, 0($sp) # save a1 in stack before call
jal foo # this call effectively wipes out argument registers
lw $a1, 0($sp) # restore a1 from stack after call
addi $a0,$v0,4 # here the function re-initializes $a0 from return value $v0
add $v0,$a0,$a1 # now using re-initialized $a1 (ok)
lw $ra,4($sp)
addiu $sp,$sp,8
jr $ra
在上面,我保存了更新后的$a1
寄存器,但是可以肯定的是,仅仅阅读代码并不清楚原始的,未增加的$a1
或更新后的 $a1
是实际需要的。看起来 $a1
正在作为参数传递给 foo,所以可能是 foo
想要递增的值,但 func
后来却没有。
或者,可以将 $a1
存储在 $s
寄存器中,因为调用约定确保在函数调用中保留这些寄存器——但是,根据这个定义,$s
注册自己,如果被调用者使用,必须保存和恢复,所以堆栈帧中的一个额外的字仍然是必要的。
其他违反 MIPS 调用约定的是:
MIPS CC 要求堆栈帧按 8 字节对齐,因此即使您只需要一个字,我们也应该将堆栈帧大小四舍五入为 8 字节的倍数。然而,许多汇编程序员忽略了这一点,并没有造成严重后果。
MIPS CC 还规定 space 将 4 个参数寄存器保存在堆栈中,并且此 space 由调用者提供,以便被调用者可以使用它(不需要被调用者分配任何堆栈)。尽管技术上有要求,但汇编程序员几乎从不在简单的任务中这样做。如果被调用者利用了这 4 个本来应该存在的词,但它们不存在,就会发生不好的事情。 (我没有在上面的解决方案中解决这个问题。)在编写调用 varargs 函数的函数时,我会最低限度地遵循这个要求,比如 printf、sprintf、scanf 等。这个保存区域用于 4 个参数寄存器与第 5 个、第 6 个等参数的内存连续,使整个参数块在内存中连续,这在可变参数函数的实现中很有用。
https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf
此外,用于分配和释放堆栈的 addi
space 比 addiu
更好——因为这是指针算法(地址是无符号的),有符号整数溢出在最好的,最坏的是有害的(通过导致不相关的溢出异常)。
此 MIPS 代码包含调用另一个函数 foo(a,b) 的函数 int func(int a, int b)。
func: addi $sp,$sp,-4
sw $ra, 0($sp)
addi $a0,$a0,1
addi $a1,$a1,2
jal foo
addi $a0,$v0,4
add $v0,$a0,$a1
lw $ra,0($sp)
addi $sp,$sp,4
jr $ra
这段代码有一些错误,但我不知道哪里错了。
最严重的违规行为是 func
期望 $a1
在对 foo
的函数调用后继续存在——这是不正确的。根据调用约定,$a1
是一个参数寄存器,不会被函数调用保留。 $a1
也不是 return 值,因此在函数调用后应将其视为未初始化,即它包含无用的垃圾。
func: addi $sp,$sp,-4
sw $ra, 0($sp)
addi $a0,$a0,1
addi $a1,$a1,2
jal foo # this call effectively wipes out argument registers (ok)
addi $a0,$v0,4 # here the function re-initializes $a0 from return value $v0 (ok)
add $v0,$a0,$a1 # but here it uses uninitliazed $a1 (not ok)
lw $ra,0($sp)
addi $sp,$sp,4
jr $ra
保存func
s $a1
参数的正确方法是将其存储在内存中,稍后在函数调用后将其加载回来;这将需要在堆栈帧中添加一个词。
func: addiu $sp,$sp,-8
sw $ra, 4($sp)
addi $a0,$a0,1
addi $a1,$a1,2
sw $a1, 0($sp) # save a1 in stack before call
jal foo # this call effectively wipes out argument registers
lw $a1, 0($sp) # restore a1 from stack after call
addi $a0,$v0,4 # here the function re-initializes $a0 from return value $v0
add $v0,$a0,$a1 # now using re-initialized $a1 (ok)
lw $ra,4($sp)
addiu $sp,$sp,8
jr $ra
在上面,我保存了更新后的$a1
寄存器,但是可以肯定的是,仅仅阅读代码并不清楚原始的,未增加的$a1
或更新后的 $a1
是实际需要的。看起来 $a1
正在作为参数传递给 foo,所以可能是 foo
想要递增的值,但 func
后来却没有。
或者,可以将 $a1
存储在 $s
寄存器中,因为调用约定确保在函数调用中保留这些寄存器——但是,根据这个定义,$s
注册自己,如果被调用者使用,必须保存和恢复,所以堆栈帧中的一个额外的字仍然是必要的。
其他违反 MIPS 调用约定的是:
MIPS CC 要求堆栈帧按 8 字节对齐,因此即使您只需要一个字,我们也应该将堆栈帧大小四舍五入为 8 字节的倍数。然而,许多汇编程序员忽略了这一点,并没有造成严重后果。
MIPS CC 还规定 space 将 4 个参数寄存器保存在堆栈中,并且此 space 由调用者提供,以便被调用者可以使用它(不需要被调用者分配任何堆栈)。尽管技术上有要求,但汇编程序员几乎从不在简单的任务中这样做。如果被调用者利用了这 4 个本来应该存在的词,但它们不存在,就会发生不好的事情。 (我没有在上面的解决方案中解决这个问题。)在编写调用 varargs 函数的函数时,我会最低限度地遵循这个要求,比如 printf、sprintf、scanf 等。这个保存区域用于 4 个参数寄存器与第 5 个、第 6 个等参数的内存连续,使整个参数块在内存中连续,这在可变参数函数的实现中很有用。
https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf
此外,用于分配和释放堆栈的 addi
space 比 addiu
更好——因为这是指针算法(地址是无符号的),有符号整数溢出在最好的,最坏的是有害的(通过导致不相关的溢出异常)。