关于汇编:MIPS:堆栈指针($ sp)和堆栈的相关用法

MIPS: relevant use for a stack pointer ($sp) and the stack

目前,我正在为我的计算机组织中期学习,并且试图完全了解堆栈指针和堆栈。 我知道围绕这个概念的以下事实:

  • 遵循先进先出的原则
  • 向堆栈中添加内容需要两个步骤:

    1
    2
    addi $sp, $sp, -4
    sw $s0, 0($sp)

我认为让我无法完全理解的是,我无法提出一种相关的,不言而喻的情况,在这种情况下,我将需要和/或想要使用堆栈指针来跟踪数据。

有人可以详细介绍整个概念,并给我一些有用的代码示例吗?


堆栈的一个重要用途是嵌套子程序调用。

每个子例程可以具有该子例程本地的一组变量。这些变量可以方便地存储在堆栈框架中的堆栈中。一些调用约定也会在堆栈上传递参数。

使用子例程还意味着您必须跟踪调用者,即返回地址。
一些体系结构为此目的有专用的堆栈,而另一些体系结构隐式地使用"普通"堆栈。默认情况下,MIPS仅使用寄存器,但是在非叶函数(即调用其他函数的函数)中,返回地址将被覆盖。因此,您必须保存原始值,通常将其保存在局部变量中的堆栈上。调用约定还可以声明某些寄存器值必须在函数调用之间保留,您可以类似地使用堆栈来保存和恢复它们。

假设您有以下C片段:

1
2
3
4
5
6
7
8
extern void foo();
extern int bar();
int baz()
{
    int x = bar();
    foo();
    return x;
}

MIPS程序集可能如下所示:

1
2
3
4
5
6
7
8
9
addiu $sp, $sp, -8  # allocate 2 words on the stack
sw $ra, 4($sp)      # save $ra in the upper one
jal bar             # this overwrites $ra
sw $v0, ($sp)       # save returned value (x)
jal foo             # this overwrites $ra and possibly $v0
lw $v0, ($sp)       # reload x so we can return it
lw $ra, 4($sp)      # reload $ra so we can return to caller
addiu $sp, $sp, 8   # restore $sp, freeing the allocated space
jr $ra              # return


MIPS调用约定要求前四个功能参数位于寄存器a0a3中,其余的(如果有更多的话)在堆栈上。更重要的是,尽管这些参数已在寄存器中传递,但它还要求函数调用程序在堆栈中为前四个参数分配四个插槽。

因此,如果要访问参数5(以及其他参数),则需要使用sp。如果该函数依次调用其他函数并在调用之后使用其参数,则它将需要在堆栈的这四个插槽中存储a0a3,以避免它们丢失/覆盖。同样,使用sp将这些寄存器写入堆栈。

如果您的函数具有局部变量,并且无法将所有变量都保留在寄存器中(例如当调用其他函数时不能保留a0a3时),则必须使用堆栈空间局部变量,这再次需要使用sp

例如,如果您有:

1
2
3
4
int tst5(int x1, int x2, int x3, int x4, int x5)
{
  return x1 + x2 + x3 + x4 + x5;
}

它的拆卸类似于:

1
2
3
4
5
6
7
tst5:
        lw      $2,16($sp) # r2 = x5; 4 slots are skipped
        addu    $4,$4,$5   # x1 += x2
        addu    $4,$4,$6   # x1 += x3
        addu    $4,$4,$7   # x1 += x4
        j       $31        # return
        addu    $2,$4,$2   # r2 += x1

请参见,sp用于访问x5

然后,如果您有类似以下的代码:

1
2
3
4
5
6
7
8
9
int binary(int a, int b)
{
  return a + b;
}

void stk(void)
{
  binary(binary(binary(1, 2), binary(3, 4)), binary(binary(5, 6), binary(7, 8)));
}

这是编译后反汇编的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
binary:
        j       $31                     # return
        addu    $2,$4,$5                # r2 = a + b

stk:
        subu    $sp,$sp,32              # allocate space for local vars & 4 slots
        li      $4,0x00000001           # 1
        li      $5,0x00000002           # 2
        sw      $31,24($sp)             # store return address on stack
        sw      $17,20($sp)             # preserve r17 on stack
        jal     binary                  # call binary(1,2)
        sw      $16,16($sp)             # preserve r16 on stack

        li      $4,0x00000003           # 3
        li      $5,0x00000004           # 4
        jal     binary                  # call binary(3,4)
        move    $16,$2                  # r16 = binary(1,2)

        move    $4,$16                  # r4 = binary(1,2)
        jal     binary                  # call binary(binary(1,2), binary(3,4))
        move    $5,$2                   # r5 = binary(3,4)

        li      $4,0x00000005           # 5
        li      $5,0x00000006           # 6
        jal     binary                  # call binary(5,6)
        move    $17,$2                  # r17 = binary(binary(1,2), binary(3,4))

        li      $4,0x00000007           # 7
        li      $5,0x00000008           # 8
        jal     binary                  # call binary(7,8)
        move    $16,$2                  # r16 = binary(5,6)

        move    $4,$16                  # r4 = binary(5,6)
        jal     binary                  # call binary(binary(5,6), binary(7,8))
        move    $5,$2                   # r5 = binary(7,8)

        move    $4,$17                  # r4 = binary(binary(1,2), binary(3,4))
        jal     binary                  # call binary(binary(binary(1,2), binary(3,4)), binary(binary(5,6), binary(7,8)))
        move    $5,$2                   # r5 = binary(binary(5,6), binary(7,8))

        lw      $31,24($sp)             # restore return address from stack
        lw      $17,20($sp)             # restore r17 from stack
        lw      $16,16($sp)             # restore r16 from stack
        addu    $sp,$sp,32              # remove local vars and 4 slots
        j       $31                     # return
        nop

希望我能对代码进行注释而不会出错。

因此,请注意,编译器选择在函数中使用r16r17,但将它们保留在堆栈中。由于该函数调用了另一个函数,因此它还需要将其返回地址保留在堆栈中,而不是简单地将其保留在r31中。

PS请记住,在将控制权实际转移到新位置之前,MIPS上的所有分支/跳转指令均有效执行紧随其后的指令。这可能会造成混淆。