4

GCC 编译(使用gcc --omit-frame-pointer -s):

    int the_answer() { return 42; }

进入

            .Text
    .globl _the_answer
    _the_answer:
        subl    $12, %esp
        movl    $42, %eax
        addl    $12, %esp
        ret
       .subsections_via_symbols

'$1​​2' 常量在这里做什么,'%esp' 寄存器是什么?

4

4 回答 4

8

简短的回答:堆栈帧。

长答案:当您调用函数时,编译器将操纵堆栈指针以允许诸如函数变量之类的本地数据。由于您的代码正在更改esp,堆栈指针,这就是我假设这里发生的事情。我会认为 GCC 足够聪明,可以在实际上不需要的地方优化它,但你可能没有使用优化。

于 2009-02-01T00:12:21.197 回答
4
_the_answer:
    subl    $12, %esp
    movl    $42, %eax
    addl    $12, %esp
    ret

第一个 subl 递减堆栈指针,为函数中可能使用的变量腾出空间。例如,一个槽可用于帧指针,另一个用于保存返回地址。你说它应该省略帧指针。这通常意味着它省略了加载/存储来保存/恢复帧指针。但通常代码仍会为其保留内存。原因是它使分析堆栈的代码更加容易。很容易给堆栈的偏移量一个最小宽度,所以你知道你总是可以访问 FP+0x12 来获得第一个局部变量槽,即使你省略了保存帧指针。

好吧,eax据我所知,x86 用于处理调用者的返回值。最后一个 addl 只是破坏了之前为您的函数创建的框架。

在函数的开头和结尾生成指令的代码称为函数的“尾声”和“序言”。这是我的端口在必须在 GCC 中创建函数序言时所做的事情(对于打算尽可能快速和多功能的实际端口来说,这要复杂得多):

void eco32_prologue(void) {
    int i, j;
    /* reserve space for all callee saved registers, and 2 additional ones:
     * for the frame pointer and return address */
    int regs_saved = registers_to_be_saved() + 2;
    int stackptr_off = (regs_saved * 4 + get_frame_size());

    /* decrement the stack pointer */
    emit_move_insn(stack_pointer_rtx, 
                   gen_rtx_MINUS(SImode, stack_pointer_rtx, 
                                 GEN_INT(stackptr_off)));

    /* save return adress, if we need to */
    if(eco32_ra_ever_killed()) {
        /* note: reg 31 is return address register */
        emit_move_insn(gen_rtx_MEM(SImode, 
                           plus_constant(stack_pointer_rtx, 
                                         -4 + stackptr_off)),  
                       gen_rtx_REG(SImode, 31));
    }

    /* save the frame pointer, if it is needed */
    if(frame_pointer_needed) {
        emit_move_insn(gen_rtx_MEM(SImode, 
                           plus_constant(stack_pointer_rtx, 
                                         -8 + stackptr_off)), 
                       hard_frame_pointer_rtx);
    }

    /* save callee save registers */
    for(i=0, j=3; i<FIRST_PSEUDO_REGISTER; i++) {
        /* if we ever use the register, and if it's not used in calls
         * (would be saved already) and it's not a special register */
        if(df_regs_ever_live_p(i) && 
           !call_used_regs[i] && !fixed_regs[i]) {
            emit_move_insn(gen_rtx_MEM(SImode, 
                               plus_constant(stack_pointer_rtx, 
                                             -4 * j + stackptr_off)), 
                           gen_rtx_REG(SImode, i));
            j++;
        }
    }

    /* set the new frame pointer, if it is needed now */
    if(frame_pointer_needed) {
        emit_move_insn(hard_frame_pointer_rtx, 
                       plus_constant(stack_pointer_rtx, stackptr_off));
    }
}

我省略了一些处理其他问题的代码,主要是告诉 GCC 哪些指令对异常处理很重要(即帧指针的存储位置等等)。好吧,被调用者保存的寄存器是调用者在调用之前不需要保存的寄存器。被调用的函数关心根据需要保存/恢复它们。正如您在第一行中看到的,我们总是为返回地址和帧指针分配空间。该空间只是几个字节,无关紧要。但是我们只在必要时生成存储/加载。最后注意“硬”帧指针是“真实”帧指针寄存器。由于某些 gcc 内部原因,这是必要的。“frame_pointer_needed”标志由 GCC 设置,只要我不能省略存储帧指针。在某些情况下,它必须被存储,例如当alloca(它动态地改变堆栈指针)被使用时。GCC 关心这一切。请注意,自从我编写该代码以来已经有一段时间了,所以我希望我在上面添加的其他注释不是全错的 :)

于 2009-02-01T00:18:45.910 回答
3

堆栈对齐。在函数入口处,esp是 -4 mod 16,因为返回地址已被call. 减去 12 重新对齐它。除了在使用 mmx/sse/等的多媒体代码中,没有充分的理由在 x86 上将堆栈对齐到 16 字节,但是在 3.x 时代的某个地方,gcc 开发人员决定无论如何都应该保持堆栈对齐,强加序言/尾声开销,增加的堆栈大小,以及为了一些特殊目的利益而导致所有程序上的缓存抖动增加(这恰好是我感兴趣的一些领域,但我仍然认为这是不公平和不好的决定)。

通常,如果您启用任何优化级别,gcc 将删除用于叶函数(不进行函数调用的函数)的堆栈对齐的无用序言/结尾,但一旦您开始调用,它就会返回。

您也可以使用-mpreferred-stack-boundary=2.

于 2011-03-30T14:20:22.877 回答
1

使用 GCC 4.3.2 我得到这个函数:

the_answer:
movl    $42, %eax
ret

...加上周围的垃圾,使用以下命令行:echo 'int the_answer() { return 42; }' | gcc --omit-frame-pointer -S -x c -o - -

您使用的是哪个版本?

于 2009-02-01T00:15:47.757 回答