Linux Stack-main()为什么不将ebp推送到堆栈上
我正在查看main开头的堆栈,但是缺少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
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节了解更多信息。