【问题标题】:gdb with stange error: ??. what does it mean?带有 stange 错误的 gdb:??。这是什么意思?
【发布时间】:2013-07-05 10:55:49
【问题描述】:

我用 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?

【问题讨论】:

  • 这意味着应用程序段错误。从您的 cmets 判断,您可能正在覆盖堆栈或写入超出堆栈。如果您使用警告 (-Wall -Wextra) 和调试符号 (-g) 编译应用程序,也会有所帮助。另外,为什么要禁用堆栈保护机制?它可以确保您的程序安全。
  • 我从一开始就使用 -g 标志,我现在使用 -Wall 和 -Wextra 并消除了所有警告,但仍然相同......
  • 如果你使用-gGDB应该可以给你失败的函数的名称和行。
  • ...关于stackoverflow 上的stackoverflow 的问题! :)
  • 我用过它,它可以给我起名字,以前用它作为不同的程序,它在那里工作

标签: c gdb


【解决方案1】:

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

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

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

您发现,一旦您的堆栈被破坏,backtrace 命令将不再有用。从那里弄清楚发生了什么的最好方法是手动检查堆栈并搜索可能的返回地址和帧指针。您可以使用x 命令转储内存区域(运行help x 了解详细信息)。我喜欢使用x/<NUMBER>wx 转储为 4 字节十六进制值。所以,这里是如何从堆栈指针$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

这里,$esp0xbffff7c0$eip0x00001870(我使用 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,除非您有一个非常、非常的充分理由这样做。

【讨论】:

  • 很好的答案,我将有一些东西可以试用和学习!
【解决方案2】:

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

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

【讨论】:

  • 我既没有做汇编也没有做任何 goto,我用 printf 得出的结论是问题在 for 循环内崩溃,但并不总是在同一点
  • 哦,是的 printf 可以做到,如果您使用任何可变参数函数,您最终可能会出现不匹配的堆栈推送/弹出...但编译器通常会读取它并给您一个警告。
猜你喜欢
  • 2011-03-06
  • 2011-12-19
  • 2015-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多