0

我用 gcc 编译我的程序,然后在 a.out 文件上运行 gcc。然后,当我在 gdb 中运行程序时,出现此错误:

Program received signal SIGSEGV, Segmentation fault.
0xbffff118 in ?? ()
(gdb) backtrace
#0  0xbffff118 in ?? ()
#1  0x00000000 in ?? ()

这是什么意思?同样在我之前,何时尝试正常运行程序时出现此错误:

*** stack smashing detected ***: ./benchmark terminated
Aborted (core dumped)

所以我读到这是对缓冲区溢出的保护,我仔细检查了一切,应该一切都很好,所以我使用标志禁用了它:

-fno-stack-protector

编辑:我没有发布代码,因为我想弄清楚如何使用 gdb 而不是让我的代码正常工作。所以我是 gdb 的新手,但我现在成功使用了几次。我仍然无法弄清楚是什么?意味着,在什么情况下,gdb 可以指向我而不是我的代码中的相应调用,这会导致错误,为什么帧 1 的地址是 0x0?

4

2 回答 2

5

当你得到这样的垃圾回溯时,几乎可以肯定的是,你的堆栈以​​某种方式被破坏了,并且实际的返回地址和堆栈帧指针已被覆盖。

该值0xbffff118几乎可以肯定是堆栈中的地址。我相信在许多 x86 Linux 编译器上,编译器从 virtual address 开始堆栈0xc0000000,并从那里向下增长,因此任何以开头的地址0xbfff都很可能是堆栈地址。

通常,指令指针永远不应该在堆栈内。通常发生的方式是指向堆栈的值覆盖存储在堆栈中的返回地址,然后当当前函数返回时,它返回到被覆盖的值。如果堆栈是不可执行的,就像它应该的那样,这将立即引发一个信号;如果堆栈以某种方式是可执行的,那么它可能要等到几条指令后才会崩溃,除非您被恶意利用,在这种情况下,您将度过一段糟糕的时光。

正如您发现的那样,一旦您的堆栈被粉碎,该backtrace命令将不再有用。从那里弄清楚发生了什么的最好方法是手动检查堆栈并搜索可能的返回地址和帧指针。您可以使用该x命令转储内存区域(运行help x以获取详细信息)。我喜欢使用x/<NUMBER>wx4 字节十六进制值转储。所以,这里是如何从堆栈指针开始转储一堆数据$esp

(gdb) x/64wx $esp
0xbffff7c0: 0x00000073  0xbffff9e9  0x0000000b  0x00000012
0xbffff7d0: 0xbffff9e8  0x0be04aa0  0xbffff7f8  0x000018aa
0xbffff7e0: 0x0be04aa0  0xbffff9e8  0x00000000  0x00000002
0xbffff7f0: 0xbffff9e7  0x09a0bb10  0xbffff818  0x000018aa
0xbffff800: 0x09a0bb10  0xbffff9e7  0x00000002  0x0000000e
0xbffff810: 0xbffff9e6  0x015377f0  0xbffff838  0x000018aa
0xbffff820: 0x015377f0  0xbffff9e6  0x00000008  0x00000011
0xbffff830: 0xbffff9e5  0x01537860  0xbffff858  0x000018aa
0xbffff840: 0x01537860  0xbffff9e5  0x00000003  0x0000000f
0xbffff850: 0xbffff9e4  0x001ddbc0  0xbffff878  0x000018aa
0xbffff860: 0x001ddbc0  0xbffff9e4  0x00000018  0x00000017
0xbffff870: 0xbffff9e3  0x00177c50  0xbffff898  0x000018aa
0xbffff880: 0x00177c50  0xbffff9e3  0xbffff8b8  0x00000000
0xbffff890: 0xbffff9e2  0x00176050  0xbffff8b8  0x000018aa
0xbffff8a0: 0x00176050  0xbffff9e2  0xbffff9e1  0x0000000c
0xbffff8b0: 0xbffff9e1  0x00174920  0xbffffb08  0x00001b8a

在这里,$espis0xbffff7c0$eipis 0x00001870(我使用p/x $eip命令得到,但也可以看到它info regs来获取所有寄存器)。因此,每个堆栈帧都将是一个指向堆栈(0xbfff....)更高的指针,后跟一个类似的地址0x00001870。在内存转储中查找这些,我们可以很确定这些是堆栈帧:

0xbffff7f8  0x000018aa
0xbffff818  0x000018aa
0xbffff838  0x000018aa
(etc.)

这是我从一个高度递归的程序中获取的示例,这就是为什么返回地址都相同的原因。一旦找到一些没有被破坏的好堆栈帧,您就可以自己按照帧指针进行操作:

(gdb) x/2wx 0xbffff7f8
0xbffff7f8: 0xbffff818  0x000018aa
(gdb) x/2wx 0xbffff818
0xbffff818: 0xbffff838  0x000018aa
(gdb) x/2wx 0xbffff838
0xbffff838: 0xbffff858  0x000018aa
(gdb) x/2wx 0xbffff858
0xbffff858: 0xbffff878  0x000018aa
...

然后如果你想将指令地址转换为符号名,你可以再次使用该x命令,如果你有调试符号,gdb 会很高兴地打印符号名:

(gdb) x 0x000018aa
0x18aa <add_word+154>:  0x5d18c483

这是关于如何在堆栈被破坏时获得实际有用的堆栈跟踪的快速入门。当然,最好一开始就避免这种情况。我强烈建议您使用-Wall -Wextra -Werror-pedantic如果可以的话)编译您的代码,当然不要使用-fno-stack-protector,除非您有非常非常好的理由这样做。

于 2013-07-05T02:55:49.157 回答
0

一旦堆栈出现乱码,就无法知道发生了什么……在纯 c 中,通常很难粉碎堆栈,您是在进行任何组装还是使用 gotos/标签?这些是我见过的典型路线...

基本上发生的事情是返回地址被覆盖,当您尝试返回该垃圾地址时,堆栈没有意义。

于 2013-07-05T02:19:08.877 回答