Linux 堆栈 - main() 为什么 ebp 没有压入堆栈

发布于 2024-12-21 01:46:52 字数 1304 浏览 1 评论 0原文

我正在查看 main 开头的堆栈,但 main 的 ebp 丢失了。

我声明了一个变量来检查它在堆栈上的位置,结果发现该变量和 n __libc_start_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>

堆栈

(gdb) x/8xw $esp
0xbffff554: 0x00000000(local var) 0x00000000(missing ebp!) 0x0014d6b3(return to libc_start)      0x00000001

0xbffff564:     0xbffff5f4      0xbffff5fc      0x00131fc4      0x0000082d

我唯一能想到的是libc_start_main的函数序言由于某种原因没有推送主程序的ebp!


编辑1:

-在没有Opatmization的情况下编译(gcc -ggdb文件file.c)

主程序集(gcc版本4.6.2 20111027)

 push   %ebp

 mov    %esp,%ebp
 sub    $0x10,%esp
 movb   $0x61,-0x1(%ebp)
 leave
 ret

局部变量处的断点以查看堆栈显示相同的内容,变量后跟零,然后是返回libc_start

I'm viewing the stack at the beginning of main, but the ebp of main is missing.

I declared a variable to check where will it's located on the stack, it turns out that there is zeros between this variable and the return address to n __libc_start_main !

System I'm using

I'm using fedora Linux 3.1.2-1.fc16.i686 
ASLR is disabled.
Debugging with GDB.

Here's the code :

void main(){

        char ret ='a';

}

Register information:

(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>

stack

(gdb) x/8xw $esp
0xbffff554: 0x00000000(local var) 0x00000000(missing ebp!) 0x0014d6b3(return to libc_start)      0x00000001

0xbffff564:     0xbffff5f4      0xbffff5fc      0x00131fc4      0x0000082d

The only thing that I can think of is that the function prologue of the libc_start_main is not pushing the main's ebp for some reason !


Edit 1:

-compiled without Opatmization just (gcc -ggdb file file.c)

Assembly of main ( gcc version 4.6.2 20111027 )

 push   %ebp

 mov    %esp,%ebp
 sub    $0x10,%esp
 movb   $0x61,-0x1(%ebp)
 leave
 ret

A break point at the local variable to view the stack shows the same thing the variable followed by zeros then the return to libc_start

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

迷途知返 2024-12-28 01:46:52

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

在任何情况下,您需要记住,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 就在那里。

There's no requirement for a particular calling convention to be used in the assembly code generated by a compiler. That's why it's called a convention rather than a requirement :-)

In any case, you need to keep in mind that the 'normal' x86 calling convention for C requires the function itself to handle set-up and tear-down of the stack frame. In other words, this is the responsibility of main rather than the startup code (the code that generally runs before your main to set up the C runtime environment such as stack setup, creation of argc/argv, any library pre-initialisation and so on).

Additionally, the ebp pushed on to the stack is the previous value of ebp before the current stack frame is built.

Part of that build process for the current stack frame is the saving of the current ebp and then loading a new value into the ebp register to easily access passed parameters and locals.

You can see that by compiling your code snippet with 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

The first three lines and the last two are mirror images of each other, the set-up and tear-down code. There's a good chance in this case that the startup code had ebp set to zero, possibly because it didn't care - it doesn't have to worry about calling conventions other than to ensure argc and argv are there.

伴我心暖 2024-12-28 01:46:52

如果你在没有优化的情况下进行编译,你几乎肯定会发现 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):

main:
.LFB0:
        .cfi_startproc
        rep
        ret
        .cfi_endproc

与其猜测,为什么不查看反汇编(例如在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).

If you compile without optimization, you'll almost certainly find that ebp/rbp is in fact pushed pushed onto the stack and then set up based on esp/rsp. It is, however, done by main itself and not by libc as you appear to suggest.

Here is the assembly code produced by 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

If you compile with optimization options, you might find that the entire body of main is optimized away (gcc -O3):

main:
.LFB0:
        .cfi_startproc
        rep
        ret
        .cfi_endproc

Instead of guessing, why not look at the disassembly (e.g. in gdb) to see what happens in your particular case?

Also, even in the unoptimized case you have to actually execute the function prologue for the registers to be set up the way you expect.

Finally, you should not be surprised when you see apparent gaps between data on the stack, as the stack is subject to alignment:

   -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).
俯瞰星空 2024-12-28 01:46:52

如果您正在针对 x86_64 进行编译,则 ebp/rbp 将被调用者保存。这意味着 main() 如果需要使用它,就应该保存它。如果不是,则被调用者或调用者都不需要保存旧的寄存器值。

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

If you are compiling for x86_64 then ebp/rbp is callee saved. That means that main() should save it if it needs to use it. If not then there is no requirement for the old register value to be saved by the either callee or the caller.

See section 3.2 of the AMD64 ABI for more information if you are interested.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文