为什么调用堆栈设置如此?

时间:2009-08-03 19:25:29

标签: c x86 stack

我正在玩调用堆栈,尝试更改函数的返回地址等,并在C中编写此程序:

#include<stdio.h>

void trace(int);
void func3(int);
void func2(int);
void func1(int);

int main(){

    int a = 0xAAAA1111;

    func1(0xFCFCFC01);

    return 0;

}

void func1(int a){

    int loc = 0xBBBB1111;

    func2(0xFCFCFC02);

}

void func2(int a){

    int loc1 = 0xCCCC1111;
    int loc2 = 0xCCCC2222;

    func3(0xFCFCFC03);

}

void func3(int a){

    int loc1 = 0xDDDD1111;
    int loc2 = 0xDDDD2222;
    int loc3 = 0xDDDD3333;

    trace(0xFCFCFC04);

}

void trace(int a){

    int loc = 0xEEEE1111;

    int *ptr = &loc;

    do {
    printf("0x%08X : %08X\n", ptr, *ptr, *ptr);
    } while(*(ptr++) != 0xAAAA1111);

}

(抱歉长度)

这产生了以下输出(我添加了评论):

0xBF8144D4 : EEEE1111 //local int in trace
0xBF8144D8 : BF8144F8 //beginning of trace stack frame
0xBF8144DC : 0804845A //return address for trace to func3
0xBF8144E0 : FCFCFC04 //int passed to trace
0xBF8144E4 : 08048230 //(possibly) uninitialized padding
0xBF8144E8 : 00000000 //padding
0xBF8144EC : DDDD3333 //local int in func3
0xBF8144F0 : DDDD2222 //local int in func3
0xBF8144F4 : DDDD1111 //local int in func3
0xBF8144F8 : BF814518 //beginning of func3 stack frame
0xBF8144FC : 08048431 //return address for func3 to func2
0xBF814500 : FCFCFC03 //parameter passed to func3
0xBF814504 : 00000000 //padding
0xBF814508 : 00000000 //padding
0xBF81450C : 00000000 //padding
0xBF814510 : CCCC2222 //local int in func2
0xBF814514 : CCCC1111 //local int in func2
0xBF814518 : BF814538 //beginning of func2 stack frame
0xBF81451C : 0804840F //return address for func2 to func1
0xBF814520 : FCFCFC02 //parameter passed to func2
0xBF814524 : 00000000 //padding
0xBF814528 : BF816728 //uninitialized padding
0xBF81452C : B7DF3F4E //uninitialized padding
0xBF814530 : B7EA61D9 //uninitialized padding
0xBF814534 : BBBB1111 //local int in func1
0xBF814538 : BF814558 //beginning of func1 stack frame
0xBF81453C : 080483E8 //return address for func1 to main
0xBF814540 : FCFCFC01 //parameter passed to func1
0xBF814544 : 08049FF4 //(maybe) padding
0xBF814548 : BF814568 //(maybe) padding
0xBF81454C : 080484D9 //(maybe) padding
0xBF814550 : AAAA1111 //local int in main

我想知道是否有人可以填补我这里的空白点...我正在运行Ubuntu linux编译与gcc 4.3.3(在x86上 - AMD Turion 64)

0804 ...数字是多少?从底部开始的第三个地址是什么?那是main的返回地址吗?如果是这样,为什么它与堆栈的其余部分相比无序? 0x0804数字是返回地址,或指向代码/数据的指针,而0xBF814数字是堆栈指针

这是什么:

0xBF814524 : 00000000 //padding?
0xBF814528 : BF816728 //I have no idea
0xBF81452C : B7DF3F4E //????
0xBF814530 : B7EA61D9 //????

在func1中的本地int之后看到了什么?

好的,我的堆栈转储几乎完全填满了。

看起来编译器希望将参数压入堆栈,从0x ....... 0地址开始,并且函数之前的局部变量和函数的第一个参数之间的所有内容都是调用似乎是填充(无论是0x00000000还是一些未初始化的值)。其中一些我不确定,因为它们看起来像代码/数据段指针,但我看不到它们在代码中被使用:当堆栈指针减少时它们就在那里。

并且知道在任何类型的项目中触摸调用堆栈都是一个巨大的nono,但这没关系。这很有趣,对吧?

格雷格希望看到集会, 这是

    .file   "stack.c"
    .text
.globl main
    .type   main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    subl    $20, %esp
    movl    $-1431695087, -8(%ebp)
    movl    $-50529279, (%esp)
    call    func1
    movl    $0, %eax
    addl    $20, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size   main, .-main
.globl func1
    .type   func1, @function
func1:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $-1145368303, -4(%ebp)
    movl    $-50529278, (%esp)
    call    func2
    leave
    ret
    .size   func1, .-func1
.globl func2
    .type   func2, @function
func2:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $-859041519, -4(%ebp)
    movl    $-859037150, -8(%ebp)
    movl    $-50529277, (%esp)
    call    func3
    leave
    ret
    .size   func2, .-func2
.globl func3
    .type   func3, @function
func3:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $-572714735, -4(%ebp)
    movl    $-572710366, -8(%ebp)
    movl    $-572705997, -12(%ebp)
    movl    $-50529276, (%esp)
    call    trace
    leave
    ret
    .size   func3, .-func3
    .section    .rodata
.LC0:
    .string "0x%08X : %08X\n"
    .text
.globl trace
    .type   trace, @function
trace:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $40, %esp
    movl    $-286387951, -4(%ebp)
    leal    -4(%ebp), %eax
    movl    %eax, -8(%ebp)
.L10:
    movl    -8(%ebp), %eax
    movl    (%eax), %edx
    movl    -8(%ebp), %eax
    movl    (%eax), %eax
    movl    %edx, 12(%esp)
    movl    %eax, 8(%esp)
    movl    -8(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    $.LC0, (%esp)
    call    printf
    movl    -8(%ebp), %eax
    movl    (%eax), %eax
    cmpl    $-1431695087, %eax
    setne   %al
    addl    $4, -8(%ebp)
    testb   %al, %al
    jne .L10
    leave
    ret
    .size   trace, .-trace
    .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
    .section    .note.GNU-stack,"",@progbits

7 个答案:

答案 0 :(得分:5)

像这样检查堆栈似乎是一步太远了。我建议您在调试器中加载程序,切换到汇编语言视图,然后单步执行每个机器指令。理解CPU堆栈必然需要了解在其上运行的机器指令,这将是一种更直接的方式来查看正在发生的事情。

正如其他人所提到的,堆栈的结构也高度依赖于您正在使用的处理器架构。

答案 1 :(得分:5)

最有可能是stack canaries。您的编译器添加了代码以将其他数据推送到堆栈,然后再将其读回以检测堆栈溢出。

答案 2 :(得分:5)

我猜这些以0x0804开头的值是程序代码段中的地址(如函数调用的返回地址)。以0xBF814开头的标记为返回地址的是堆栈上的地址 - 数据,而不是代码。我猜它们可能是帧指针。

答案 3 :(得分:3)

如前所述,0xBF ...是帧指针,0x08 ...返回地址。

填充归因于alignment issues。其他未识别的值也是填充,因为堆栈未初始化为零或任何其他值。未初始化的变量和未使用的填充空间将包含这些内存位置中的任何字节。

答案 4 :(得分:2)

0xBF ...地址将链接到前一个堆栈帧:

0xBF8144D8 : BF8144F8 //return address for trace
0xBF8144DC : 0804845A //

0xBF8144F8 : BF814518 //return address for func3
0xBF8144FC : 08048431 //????

0xBF814518 : BF814538 //return address for func2?
0xBF81451C : 0804840F //????

0xBF814538 : BF814558 //return address for func1
0xBF81453C : 080483E8 //????

0x08 ...地址将是每种情况下要返回的代码的地址。

我无法代表堆叠中的其他内容;你必须逐步完成汇编语言,看看它到底在做什么。我想它正在将每个帧的开头与特定的对齐方式对齐,以便__attribute__((align))(或者这些天所称的任何东西......)都有效。

答案 5 :(得分:2)

编译器使用EBP存储帧的基址。已经有一段时间了,所以我看了这个,所以我可能会把细节弄错,但这个想法是这样的。

调用函数时有三个步骤:

  1. 调用者将函数的参数压入堆栈。
    • 调用者使用call指令将返回地址压入堆栈,然后跳转到新函数。
    • 被调用的函数将EBP推入堆栈,并将ESP复制到EBP:
    • (注意:表现良好的函数也会将所有GPR推送到堆栈PUSHAD
  2. push EBP
    mov EBP, ESP
    

    当函数返回时:

    1. pops EBP
    2. 执行ret指令,弹出退货地址并跳转到那里。
    3. pop EBP
      ret
      

      问题是,为什么推动EBP,为什么ESP被复制到其中?

      当您输入功能时,ESP指向功能的堆栈最低点。您在堆栈上声明的任何变量都可以作为[ESP + offset_to_variable]访问。这很简单!但请注意,ESP必须始终指向堆栈的顶部,因此当您在堆栈上声明新变量时,ESP会发生变化。现在[ESP + offset_to_variable]并不是那么好,因为你必须记住在分配变量时ESP是什么。

      而不是这样做,函数需要做的第一件事就是将ESP复制到EBP中。 EBP在函数生命周期内不会改变,因此您可以使用`[EBP + offset_to_variable]访问所有变量。但是现在你有另一个问题,因为如果被调用的函数调用另一个函数,EBP将被覆盖。这就是为什么在复制EBP之前需要将其保存到堆栈中,以便在返回到调用函数之前可以恢复它。

答案 6 :(得分:1)

这是一个调试版本还是发布版本?我期望使用调试版本进行一些填充以检测Stack Overflows。