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

What %lo(source)($6) and .frame mean in assembly code?

我assemble一个简单的c程序mips并尝试理解汇编代码。通过与c代码的对比,我差不多明白了,但还是遇到了一些问题。

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

以下是我对汇编代码工作原理的猜测:

main为程序入口

</code>为源数组地址</p> <p><code>是dest数组的地址

</code>是源数组的大小。</p> <p><code>是变量k,初始化为0。

$L3是循环

</code>和<code>source[k]dest[k]的地址。

sw ,0() 等同于 source[k] 存储在 </code>.</p> <p><code>lw ,4()等同于将source[k]赋值给dest[k].

addiu ,,4 等同于 k++.

bne , [=31=], $L3 表示如果 source[k] 为零则退出循环,否则跳转到标签 $L3

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

</code> 设置为零。</p> <p>跳转到<code>(return地址)。

我的问题是:

  1. .frame $sp,0, 有什么作用?
  2. 为什么 lw ,4() 而不是 lw ,0()
  3. 写法%lo(source)()是什么意思? ($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;
}

大会

.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,       # vars= 0, regs= 0/0, args= 0, gp= 0
    .mask   0x00000000,0
    .fmask  0x00000000,0
    lui ,%hi(source)
    lw  ,%lo(source)()
    beq ,[=11=],$L2
    lui ,%hi(dest)
    addiu   ,,%lo(dest)
    addiu   ,,%lo(source)
    move    ,[=11=]
$L3:
    addu    ,,
    addu    ,,
    sw  ,0()
    lw  ,4()
    addiu   ,,4
    bne ,[=11=],$L3
$L2:
    move    ,[=11=]
    j   
    .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"

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

问题一:.frame 是做什么的

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

  • 堆栈由 $sp 指向,</code> 的别名。</li> <li> 和堆栈帧的大小(0,因为函数既没有局部变量,也没有堆栈上的参数)。此外,该函数足够简单,可以使用临时寄存器,不需要保存被调用者保存的寄存器 <code>-.
  • 旧的 return 地址(</code> MIPS 调用约定)</li> </ul> <p>有关 MIPS 调用约定的详细信息,请参阅此 <a href="https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf" rel="nofollow noreferrer">doc</a>。</p> <p><strong>问题 2:为什么 lw $3,4($4) 而不是 lw $3,0($4)</strong></p> <p>这是由于循环的优化。通常,加载和存储的顺序为:</p> <ul> <li>加载源[0]</li> <li>存储目标[0]</li> <li>加载源[1]</li> <li>存储目标[1] ....</li> </ul> <p>您假设循环完全在 <code>$L3 中,并且包含 load source[k]store dest[k]。它不是。有两条线索可以看出这一点:

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

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

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

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

    source/dest数组加载和存储之前,需要将它们的指针分别加载到寄存器</code>和<code>中。因此,您需要一条指令将 32 位常量地址加载到寄存器中。然而,一条 32 位指令必须包含一些位来编码操作码(指令是什么操作)、目标寄存器等。因此,一条指令剩下不到 32 位来编码常量(称为立即数)。因此,您需要两条指令将一个 32 位常量加载到一个寄存器中,每条指令加载 16 位。 lo/hi 指的是加载常量的一半。

    例子:假设dest在地址0xabcd1234。有两条指令将此值加载到 </code>.</p> <pre><code> lui ,%hi(dest) addiu ,,%lo(dest)

    lui 是 Load Upper immediate。它将dest0xabcd)地址的高16位加载到</code>的高16位。现在,<code> 的值为 0xabcd0000。

    addiu 是无符号加法。它将dest0x1234)地址的低16位与</code>中的现有值相加得到<code>的新值。因此,</code> 现在拥有 0xabcd0000 + 0x1234 = 0xabcd1234,即 <code>dest 的地址。

    类似地,lw ,%lo(source)()</code>指向的地址加载(它已经保存了<code>source地址的前16位)偏移量为%lo(source)(该地址的低 16 位)。实际上,它加载了 source.

    的第一个单词