Linux Stack-main()为什么不将ebp推送到堆栈上

Linux Stack-main()为什么不将ebp推送到堆栈上,linux,assembly,fedora,Linux,Assembly,Fedora,我正在查看main开头的堆栈,但是缺少main的ebp 我声明了一个变量来检查它在堆栈上的位置,结果发现这个变量和n\uu libc\u start\u main的返回地址之间有零 我正在使用的系统 I'm using fedora Linux 3.1.2-1.fc16.i686 ASLR is disabled. Debugging with GDB. 代码如下: void main(){ char ret ='a'; } 注册信息: (gdb) eax

我正在查看main开头的堆栈,但是缺少main的ebp

我声明了一个变量来检查它在堆栈上的位置,结果发现这个变量和
n\uu libc\u start\u main的返回地址之间有零

我正在使用的系统

I'm using fedora Linux 3.1.2-1.fc16.i686 
ASLR is disabled.
Debugging with GDB.
代码如下:

void main(){

        char ret ='a';

}
注册信息:

(gdb)
eax            0x1      1
ecx            0xbffff5f4       -1073744396
edx            0xbffff584       -1073744508
ebx            0x2dbff4 2998260
esp            0xbffff554       0xbffff554
**ebp            0xbffff558       0xbffff558**
esi            0x0      0
edi            0x0      0
eip            0x804839a        0x804839a <main+6>
我能想到的唯一一件事是,
libc\u start\u main
的函数序言出于某种原因没有推动main的ebp


编辑1:

-编译时不使用Opatmization(gcc-ggdb file.c)

主管道组件(gcc版本4.6.2 20111027)


用于查看堆栈的局部变量的断点显示相同的内容:变量后跟零,然后返回到
libc\u start

,如果编译时未进行优化,您几乎肯定会发现
ebp
/
rbp
实际上被推到堆栈上,然后基于
esp
/
rsp
进行设置。但是,它是由
main
本身完成的,而不是像您所建议的那样由
libc
完成的

以下是由gcc 4.4.5产生的汇编代码:

main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        movb    $97, -1(%rbp)
        leave
        ret
       .cfi_endproc
如果使用优化选项编译,您可能会发现
main
的整个主体都被优化了(
gcc-O3
):

与其猜测,不如看看反汇编(例如在
gdb
中),看看在您的特定情况下会发生什么

此外,即使在未优化的情况下,您也必须实际执行函数序言,以便按照预期的方式设置寄存器

最后,当您看到堆栈上的数据之间存在明显的间隙时,您不应该感到惊讶,因为堆栈要对齐:

   -mpreferred-stack-boundary=num
       Attempt to keep the stack boundary aligned to a 2 raised to num
       byte boundary.  If -mpreferred-stack-boundary is not specified, the
       default is 4 (16 bytes or 128 bits).

如果编译时没有进行优化,您几乎肯定会发现
ebp
/
rbp
实际上被推到堆栈上,然后基于
esp
/
rsp
进行设置。但是,它是由
main
本身完成的,而不是像您所建议的那样由
libc
完成的

以下是由gcc 4.4.5产生的汇编代码:

main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        movb    $97, -1(%rbp)
        leave
        ret
       .cfi_endproc
如果使用优化选项编译,您可能会发现
main
的整个主体都被优化了(
gcc-O3
):

与其猜测,不如看看反汇编(例如在
gdb
中),看看在您的特定情况下会发生什么

此外,即使在未优化的情况下,您也必须实际执行函数序言,以便按照预期的方式设置寄存器

最后,当您看到堆栈上的数据之间存在明显的间隙时,您不应该感到惊讶,因为堆栈要对齐:

   -mpreferred-stack-boundary=num
       Attempt to keep the stack boundary aligned to a 2 raised to num
       byte boundary.  If -mpreferred-stack-boundary is not specified, the
       default is 4 (16 bytes or 128 bits).
编译器生成的汇编代码中不需要使用特定的调用约定。这就是为什么它被称为约定而不是要求:-)

在任何情况下,您都需要记住,C的“普通”x86调用约定要求函数本身处理堆栈帧的设置和分解。换句话说,这是
main
的责任,而不是启动代码(通常在
main
之前运行的代码,用于设置C运行时环境,如堆栈设置、创建
argc/argv
、任何库预初始化等)

此外,推送到堆栈上的
ebp
是构建当前堆栈帧之前
ebp
的上一个值

当前堆栈帧的构建过程的一部分是保存当前ebp,然后将新值加载到
ebp
寄存器中,以便轻松访问传递的参数和局部变量

通过使用
gcc-S
编译代码片段,您可以看到:

main:
    pushl    %ebp              ; Push PREVIOUS ebp.
    movl     %esp, %ebp        ; Load ebp for variable access.
    subl     $16, %esp         ; Allocate space on stack.

    movb     $97, -1(%ebp)     ; Store 'a' into variable.

    leave                      ; Tear down frame and return.
    ret
前三行和后两行是彼此的镜像,即设置和拆卸代码。在这种情况下,很有可能启动代码将
ebp
设置为零,可能是因为它不在乎-它不必担心调用约定,只需确保
argc
argv
存在。

编译器生成的汇编代码中不需要使用特定的调用约定。这就是为什么它被称为约定而不是要求:-)

在任何情况下,您都需要记住,C的“普通”x86调用约定要求函数本身处理堆栈帧的设置和分解。换句话说,这是
main
的责任,而不是启动代码(通常在
main
之前运行的代码,用于设置C运行时环境,如堆栈设置、创建
argc/argv
、任何库预初始化等)

此外,推送到堆栈上的
ebp
是构建当前堆栈帧之前
ebp
的上一个值

当前堆栈帧的构建过程的一部分是保存当前ebp,然后将新值加载到
ebp
寄存器中,以便轻松访问传递的参数和局部变量

通过使用
gcc-S
编译代码片段,您可以看到:

main:
    pushl    %ebp              ; Push PREVIOUS ebp.
    movl     %esp, %ebp        ; Load ebp for variable access.
    subl     $16, %esp         ; Allocate space on stack.

    movb     $97, -1(%ebp)     ; Store 'a' into variable.

    leave                      ; Tear down frame and return.
    ret

前三行和后两行是彼此的镜像,即设置和拆卸代码。在这种情况下,启动代码很有可能将
ebp
设置为零,可能是因为它不在乎-它不必担心调用约定,只需确保
argc
argv
存在。

如果您是为x86_64编译,则
ebp
/
rbp
被调用方保存。这意味着如果需要使用它,
main()
应该保存它。如果不是,则不要求被调用方或调用方保存旧的寄存器值

如果您感兴趣,请参阅的第3.2节了解更多信息。