在gcc编译的main()中无用的序言的动机,禁用它?

时间:2018-04-30 18:50:54

标签: c gcc

鉴于以下最小测试用例:

void exit(int);

int main() { 
    exit(0);
}

GCC 4.9及更高版本的32位x86目标产生类似:

main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
        subl    $4, %esp
        subl    $12, %esp
        pushl   $0
        call    exit

请注意复杂的堆栈重新排列代码。然而,将函数重命名为除main之外的任何函数,它给出了(更合理):

xmain:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        subl    $12, %esp
        pushl   $0
        call    exit

-O的差异更加明显。由于main没有任何变化;重命名,它产生:

xmain:
        subl    $24, %esp
        pushl   $0
        call    exit

在回答这个问题时注意到以上内容:

How do i get rid of call __x86.get_pc_thunk.ax

这种行为(及其动机)是否记录在案,是否有任何方法可以抑制它? GCC具有x86特定于目标的选项,用于设置首选/假定的传入和传出堆栈对齐以及启用/禁用任意函数的重新对齐,但它们似乎不适合main

2 个答案:

答案 0 :(得分:6)

这个答案是基于源头潜水。我不知道开发者的意图或动机是什么。所有涉及的代码似乎都可以追溯到2008年,这是在我自己的GCC工作之后,但很久以前人们的记忆可能已经变得模糊了。 (GCC 4.9于2014年发布;你是否回过头了?如果我对这段代码的引入是正确的,main的笨拙堆栈对齐应该在4.4版本中开始。)

GCC的x86后端似乎已被编码,以便在进入main时对堆栈对齐做出超保守的假设,无论命令行选项如何。函数ix86_minimum_incoming_stack_boundary被调用来计算每个函数的条目上的预期堆栈对齐,以及它做的最后一件事......

12523   /* Stack at entrance of main is aligned by runtime.  We use the
12524      smallest incoming stack boundary. */
12525   if (incoming_stack_boundary > MAIN_STACK_BOUNDARY
12526       && DECL_NAME (current_function_decl)
12527       && MAIN_NAME_P (DECL_NAME (current_function_decl))
12528       && DECL_FILE_SCOPE_P (current_function_decl))
12529     incoming_stack_boundary = MAIN_STACK_BOUNDARY;
12530 
12531   return incoming_stack_boundary;
如果正在编译的函数是MAIN_STACK_BOUNDARY

...会覆盖预期的堆栈对齐到保守常量main。编译64位代码时MAIN_STACK_BOUNDARY为128(位),编译32位代码时为main。据我所知,没有命令行旋钮可以使堆栈比进入main的堆栈更加对齐。我可以说服它跳过-m32 -mpreferred-stack-boundary=2的堆栈对齐,告诉它不需要额外的对齐,用main: pushl $0 call exit 编译你的测试程序给我

%ecx

与GCC 7.3。

13695 /* Grab the argument pointer. */ 13696 t = plus_constant (Pmode, stack_pointer_rtx, m->fs.sp_offset); 13697 insn = emit_insn (gen_rtx_SET (crtl->drap_reg, t)); 13698 RTX_FRAME_RELATED_P (insn) = 1; 13699 m->fs.cfa_reg = crtl->drap_reg; 13700 m->fs.cfa_offset = 0; 13701 13702 /* Align the stack. */ 13703 insn = emit_insn (ix86_gen_andsp (stack_pointer_rtx, 13704 stack_pointer_rtx, 13705 GEN_INT (-align_bytes))); 13706 RTX_FRAME_RELATED_P (insn) = 1; 13707 的只写操作似乎是错过优化错误。它们来自ix86_expand_prologue的这一部分:

main

目的是在重新排列堆栈之前保存指向传入参数区域的指针,以便可以直接访问参数。要么是因为这在管道中发生得相当晚(在寄存器分配之后),要么因为指令被标记为FRAME_RELATED,所以当它们变得不必要时,没有任何东西能够再次删除这些指令。

我想GCC开发人员至少会一个关于此的错误报告,但是他们可能会合理地认为它是低优先级的,因为这些是在整个生命周期中只执行一次的指令程序,当TicketsController不使用它的参数时,它们实际上只是死了,它们只发生在传统的32位ABI中,我现在认为它被认为是二等目标。

答案 1 :(得分:0)

main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
        subl    $4, %esp

上面的部分复制了调用堆栈帧,因为你没有定义main()的任何参数只包含返回地址-4(%ecx)和帧指针,进入$ 16字节对齐的堆栈;因此,我的WAG是为了适应不能正确对齐堆栈的运行时(crt0.s)。

push%ebp有点赠品 - 尽管有这个蹦床,但它通过crt0.s建立了一致的回溯。

这只是退出的“正常”调用,堆栈正确对齐......

subl    $12, %esp
pushl   $0
call    exit