如果我覆盖堆栈上的返回地址会发生什么?

时间:2015-05-25 14:38:33

标签: c++ c stack-overflow

我知道这是一种危险的行为,我只是想知道发生了什么。

代码如下:

#include<stdio.h>
#include<stdlib.h>

static int count = 0;

void hello(void){
    count ++;  
    fprintf(stderr,"hello! %d\n",count);
}

void foo(void){
    void *buf[10];
    static int i;

    for(i = 0 ; i < 100 ; i++){ // put enough data to overwrite the return address on stack
        buf[i] = hello;
    }
}

int main(void){
    int buf[1000];
    foo();
    return 0;
}

结果:

……
hello! 83
hello! 84
hello! 85
hello! 86
hello! 87
hello! 88
hello! 89
Segmentation fault (core dumped)

为什么hello func被称为89次? 当foo返回时,pc寄存器获取hello func的地址,不是吗? 所以你好叫,然后在里面做什么。那又怎样?该计划是否回归主要功能? 89来自哪里?我必须误解一些事情,请指出。

3 个答案:

答案 0 :(得分:9)

您将函数hello的地址写入foo的堆栈,超过您使用buf保留的空间量的90倍。当foo试图返回时,它会将hello的第一个额外地址(其中一个)加载到程序计数器中,弹出它,然后返回&#34;它。我之所以说&#34;其中一个&#34;是因为在您保留的数组的末尾和返回地址的开头之间,堆栈上可能有一些空间。

hello假设它由foo调用。但是,它没有被调用,换句话说,foo的剩余部分的地址没有被推到堆栈上,hello返回。因此,当hello返回时,它弹出的东西是堆栈中的下一个东西,它又是hello的地址。

这再次看起来就像hello的观点,但它实际上并不是一个电话,因为它是一个回复,所以hello不断回归自己并弹出每次从堆栈中获取hello的一个地址,直到它用完这些地址。

在那些用完之后,堆栈上的下一个字节,当你被视为指针时,你没有被覆盖,它们被存储在内存中的其他地方,可能是未分配给你的进程内存映射的东西。 hello试图回到那个,当程序发生错误时就是这样。

答案 1 :(得分:5)

这是未定义行为。当您进入未分配的内存任何都可能发生,具体取决于&#34;垃圾&#34;现在有。

分段错误基本上意味着你走出了程序的受保护内存(在你的情况下花了#34; 89次&#34;)。您的操作系统会对此进行保护,并告诉您您尝试在为该程序分配的空间之外编写。

(旧操作系统不是这种情况,如果程序溢出到系统空间,这样的错误可能会导致操作系统崩溃。)

答案 2 :(得分:1)

正如Nahum所说,这是未定义的行为,但这并不意味着它无法解释:

100 - 89 = 11

这正是本地堆栈的大小(void * [10] + int)。

尝试此操作(未定义的行为,可能会擦除您的硬盘或修复全球经济):

#include<stdio.h>
#include<stdlib.h>

static int count = 0;

static void hello(void){
    count ++;  
    fprintf(stderr,"hello! %d\n",count);
}

static void foo(void){
    void *buf[1];
    static int i;

    for(i = 0 ; i <= 100 + 2; i++){ // put enough data to overwrite the return address on stack
        buf[i] = hello;
    }
}

static void bye(void) {
    printf("Bye, bye...\n");
    exit(EXIT_SUCCESS);
}

int main(void){
    void *buf[1000];
    int i;

    for (i = 0; i < 1000; ++i) {
        buf[i] = &bye;
    }
    foo();
    return 0;
}