解释c编译代码

逆向工程 C 手臂 程序分析
2021-06-19 07:28:15

我编译 c 代码来武装这个网站https://godbolt.org/

int F(int a, int b) 
{
    int c=0;
    for(int i=0;i<=10;i++)
    {
        c+=a+b;
    }    
    return c;
}

结果是

F(int, int):
        str     fp, [sp, #-4]!
        add     fp, sp, #0
        sub     sp, sp, #20
        str     r0, [fp, #-16]
        str     r1, [fp, #-20]
        mov     r3, #0
        str     r3, [fp, #-8]
        mov     r3, #0
        str     r3, [fp, #-12]
.L3:
        ldr     r3, [fp, #-12]
        cmp     r3, #10
        bgt     .L2
        ldr     r2, [fp, #-16]
        ldr     r3, [fp, #-20]
        add     r3, r2, r3
        ldr     r2, [fp, #-8]
        add     r3, r2, r3
        str     r3, [fp, #-8]
        ldr     r3, [fp, #-12]
        add     r3, r3, #1
        str     r3, [fp, #-12]
        b       .L3
.L2:
        ldr     r3, [fp, #-8]
        mov     r0, r3
        sub     sp, fp, #0
        ldr     fp, [sp], #4
        bx      lr

我在这里看到参数在堆栈上移动而不是通过寄存器移动。

我有 3 行我不明白

add     fp, sp, #0
sub     sp, sp, #20
str     r0, [fp, #-16]
  1. 为什么 fp 得到值= 0?我读过 fp 但我不确定我是否理解它。
  2. 为什么sp降到20?我知道通常为本地 var 保存 plav,但为什么是 20?
  3. 为什么str r0, [fp, #-16]我不明白

顺便说一句,为什么她的参数在堆栈中而不是在寄存器上传递?谢谢你们

2个回答

参数在寄存器 r0 (a) 和 r1 (b) 中传递;这是ARM 体系结构过程调用标准中定义的约定然而,如果没有启用优化,编译器总是将参数和局部变量存储在局部堆栈帧中(请记住,堆栈向下增长 - 即向更小的地址 - 在 ARM 上)。

 add     fp, sp, #0

该指令不会将 fp 设置为 0,而是设置为 sp 的当前值。fp 作为对栈上局部信息的引用,在 F 的执行过程中保持不变。旧的帧指针、参数和局部变量然后存储在 fp 的负偏移量处。

 sub     sp, sp, #20

在这里,堆栈上保留了 20 字节的空间,以便后续的堆栈操作(例如从 F 内部调用另一个函数)不会修改您的函数 F 的本地堆栈帧。

 str     r0, [fp, #-16]

这会将 r0(参数 a)复制到地址 fp-16 处的本地堆栈帧中。完整的堆栈框架如下所示:

 fp- 4: old fp
 fp- 8: local variable c
 fp-12: local variable i
 fp-16: parameter a
 fp-20: parameter b

注意 sp 指向 fp-20(因为 sp 在函数开始时被复制到 fp,然后 sp 减 20)。这是正确的,因为在 ARM 处理器上,sp 总是指向堆栈上最后使用的元素,即下一个 push 操作首先减少 sp(到 fp-24)并将数据写入新的 sp 地址。

您似乎做出了一些错误的假设,可能是由于不熟悉 ARM 指令集。一起来看看说明书吧。

add     fp, sp, #0

这是一个加法,所以运算是fp = sp+0, 或fp=spx86 上的模拟将是mov ebp, esp

sub     sp, sp, #20

这看起来像sub esp, 20h在 x86 上。基本上,它为局部变量分配 0x20 字节的堆栈空间。

str     r0, [fp, #-16]

与大多数 ARM 指令不同,这里的左操作数 ( r0)不是目标,而是该指令在该位置存储( str) 寄存器 r0 的值fp-0x16x86 模拟将如下所示:

mov [ebp-10h], eax

因此,如您所见,它确实使用的值r0作为输入,并且仅将其作为临时变量存储在堆栈中(稍后它再次检索以执行加法)。中的第二个参数也是如此r1如果您为编译器添加一些优化标志(例如-O1-O2),它很可能会设法执行操作而不必将中间结果存储在堆栈中。这是我-O1使用 GCC 7.2.1得到的结果

F(int, int):
  add r1, r0, r1
  add r0, r1, r1, lsl #2
  add r0, r1, r0, lsl #1
  bx lr

如您所见,不再涉及堆栈,编译器甚至设法使用移位将加法循环转换为优化的乘法加法。