如果我覆盖堆栈上的 return 地址会怎样?

What happens if i overwrite the return address on the stack?

我知道这是危险的行为,但我想弄清楚到底发生了什么。

代码如下:

#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 the 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 函数被调用了 89 次? 当函数fooreturns时,pc寄存器应该得到hello函数的地址,不是吗? 于是调用了hello,执行了里面的代码,然后呢?程序 return 不应该到 main 函数吗? “89”从何而来?我好像漏了点什么,谁能指点一下。

嗯,这是 未定义 行为。当您进入未分配的内存时 任何事情 都可能发生,具体取决于 "junk" 目前有什么。

段错误基本上意味着您走出了程序的受保护内存(在您的情况下,它花费了“89 次”)。您的操作系统会保护您免受这种情况的影响,并告诉您您试图在为程序分配的 space 之外写入。

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

您将函数 hello 的地址写入 foo 的堆栈,超出您使用 buf 保留的 space 的数量 90 次。当 foo 尝试 return 时,它将 hello 的第一个额外地址(之一)加载到程序计数器中,弹出它,然后 "returned" 到它。我说 "one of" 的原因是因为在您保留的数组末尾和 return 地址开始之间的堆栈上可能有一些 space

hello 假定它是由 foo 调用的。但是,它并没有被调用,也就是说foo的剩余地址没有被压入栈中hello到return。所以,当 hello returned 时,它弹出的东西是堆栈中的下一个东西,它又是 hello.

的地址

hello 的角度来看,这又像是一次调用,但实际上并不是一次调用,因为它是 return,因此 hello 保持 return 每次从堆栈中弹出 hello 个地址,直到 运行 从这些地址中取出。

在那些 运行 输出之后,堆栈中未被覆盖的下一个字节,当被视为指针时,指向内存中的其他位置,可能指向未分配给您的进程内存的内容地图。 hello 试图 return 到那个,那是程序出现段错误的时候。

正如 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;
}