%lo(source)($6) 和 .frame 在汇编代码中是什么意思?

QAw*_*wAQ 0 c assembly mips

我将一个简单的 c 程序组装到 mips 并尝试理解汇编代码。通过与c代码的比较,我几乎理解它,但仍然遇到一些问题。

我使用mips-gcc生成汇编代码: $ mips-gcc -S -O2 -fno-delayed-branch -I/usr/include lab3_ex3.c -o lab3_ex3.s

这是我对汇编代码如何工作的猜测:

main 是程序的入口。

$6 是源数组的地址。

$7 是 dest 数组的地址。

$3 是源数组的大小。

$2是变量k并初始化为 0。

$L3 是循环

$5$4是的地址source[k]dest[k]

sw $3,0($5)相当于存储source[k]$3.

lw $3,4($4)相当于赋值source[k]dest[k]

addiu $2,$2,4相当于k++

bne $3, $0, $L3意味着 ifsource[k]为零则退出循环,否则跳转到 label $L3

$L2 只是做一些清理工作。

设置$2为零。

跳转到$31(返回地址)。

我的问题是:

  1. 有什么.frame $sp,0,$31作用?
  2. 为什么lw $3,4($4)而不是lw $3,0($4)
  3. 记号%lo(source)($6)是什么意思?($hi 和 $lo$ 寄存器用于乘法,为什么在这里使用它们?)

谢谢。

C

int source[] = {3, 1, 4, 1, 5, 9, 0};
int dest[10];

int main ( ) {
    int k;
    for (k=0; source[k]!=0; k++) {
    dest[k] = source[k];
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

集会

.file   1 "lab3_ex3.c"
    .section .mdebug.eabi32
    .previous
    .section .gcc_compiled_long32
    .previous
    .gnu_attribute 4, 1
    .text
    .align  2
    .globl  main
    .set    nomips16
    .ent    main
    .type   main, @function
main:
    .frame  $sp,0,$31       # vars= 0, regs= 0/0, args= 0, gp= 0
    .mask   0x00000000,0
    .fmask  0x00000000,0
    lui $6,%hi(source)
    lw  $3,%lo(source)($6)
    beq $3,$0,$L2
    lui $7,%hi(dest)
    addiu   $7,$7,%lo(dest)
    addiu   $6,$6,%lo(source)
    move    $2,$0
$L3:
    addu    $5,$7,$2
    addu    $4,$6,$2
    sw  $3,0($5)
    lw  $3,4($4)
    addiu   $2,$2,4
    bne $3,$0,$L3
$L2:
    move    $2,$0
    j   $31
    .end    main
    .size   main, .-main
    .globl  source
    .data
    .align  2
    .type   source, @object
    .size   source, 28
source:
    .word   3
    .word   1
    .word   4
    .word   1
    .word   5
    .word   9
    .word   0

    .comm   dest,40,4
    .ident  "GCC: (GNU) 4.4.1"
Run Code Online (Sandbox Code Playgroud)

TSG*_*TSG 5

首先,main$L3$L2对于3个基本块的标签。您对它们的功能大致正确。

问题 1:.frame 在做什么

这不是 MIPS 指令。它是描述此函数的(堆栈)帧的元数据:

  • 堆栈由 指向$sp,它是 的别名$29
  • 以及堆栈帧的大小(0,因为函数在堆栈中既没有局部变量,也没有参数)。此外,该函数非常简单,它可以与临时寄存器一起工作,并且不需要保存被调用者保存的寄存器$16-$23
  • 旧的返回地址($31用于 MIPS 调用约定)

有关 MIPS 调用约定的更多信息,请参阅此文档

问题 2:为什么 lw $3,4($4) 而不是 lw $3,0($4)

这是由于循环的优化。通常,加载和存储的顺序是:

  • 加载源[0]
  • 存储目的地[0]
  • 负载源[1]
  • 存储目标[1] ....

您假设循环完全在$L3, 并且包含load source[k]store dest[k]。不是。有两个线索可以看出这一点:

  • 块中有一个负载main不对应循环外的任何负载
  • 在基本块中$L3,存储在加载之前。

实际上,load source[0]是在名为 的基本块中执行的main。然后,基本块中的循环$L3store dest[k];load source[k+1];。因此,加载使用的偏移量比存储的偏移量多 4,因为它正在为下一次迭代加载整数。

问题 3:lo/hi 语法是什么?

这与指令编码和指针有关。让我们假设一个 32 位架构,即一个指针是 32 位。与大多数固定大小的指令 ISA 一样,让我们​​假设指令大小也是 32 位。

加载和从存放前source/dest阵列,你需要他们的三分球装入寄存器$6$7分别。因此,您需要一条指令将 32 位常量地址加载到寄存器中。然而,一条 32 位指令必须包含一些位来编码操作码(指令是哪个操作)、目标寄存器等。因此,一条指令剩下不到 32 位来编码常量(称为立即数)。因此,您需要两条指令将 32 位常量加载到寄存器中,每条指令加载 16 位。的lo/hi参照常数的一半被加载到其中。

示例:假设dest地址为 0xabcd1234。有两条指令可以将此值加载到$7.

    lui $7,%hi(dest)
    addiu   $7,$7,%lo(dest)
Run Code Online (Sandbox Code Playgroud)

lui是立即加载上限。它将dest( 0xabcd)的地址的前 16 位加载到 的前 16 位$7。现在, 的值为$70xabcd0000。

addiu是添加立即未签名。它将dest( 0x1234)的地址的低 16 位与现有值相加$7得到 的新值$7。因此,$7现在持有 0xabcd0000 + 0x1234 = 0xabcd1234,即dest.

类似地,lw $3,%lo(source)($6)从 指向的地址$6(它已经保存了 的地址的前 16 位source)在偏移量%lo(source)(该地址的后 16 位)处加载。实际上,它会加载source.