需要解决缓冲区溢出问题。

4
我遇到了一个无法解决的缓冲区溢出错误(在C语言中)。首先,它只会发生大约10%的时间。每次从数据库获取的数据似乎并没有太大的不同...至少不足以让我找到任何明显的模式来确定它发生的原因。 Visual Studio 给出的确切信息如下:
"hub.exe 中发生了缓冲区溢出,已破坏程序的内部状态。请按 Break 调试该程序或按 Continue 终止该程序。有关详细信息,请参阅帮助主题 '如何调试缓冲区溢出问题'。"
如果我进行调试,我发现它在 __report_gsfailure() 函数中断,我很确定这是编译器上 /GS 标志的结果,也意味着这是堆栈而不是堆的溢出。我还可以看到它抛出异常的函数,但我找不到任何可能导致此行为的内容。该函数已经存在了很长时间(10多年),尽管进行了一些小的修改,但据我所知,从未发生过这种情况。
我想贴出该函数的代码,但它相当长,并引用了许多专有的函数/变量等。
我基本上只是在寻找一些我可能还没有注意到的东西,或者一些可能有帮助的工具。不幸的是,我找到的几乎每个工具都只能帮助调试堆上的溢出,除非我错了,否则这是在堆栈上发生的。谢谢您的帮助。
5个回答

3

虽然它在Windows中无法使用,但Valgrind是检测错误内存行为最好的工具。

如果您正在调试堆栈,则需要使用低级别工具 - 在任何潜在嫌疑人周围放置一个带有类似0xA5的缓冲区作为标记。在调试器中运行程序并查看哪些标记不再具有正确的大小和内容。 这样做可能会消耗大量的堆栈,但它可以帮助您准确地识别发生了什么。


是的,我以前用过它。虽然我们的服务器代码可以在各种Unix(Solaris/HP/AIX)上运行,但似乎Valgrind在那里并不受支持,所以不幸的是,它对我没有太大帮助。 - Morinar

3

您可以尝试在缓冲区的两端放置一些本地变量,或者甚至将哨兵放入(略微扩展的)缓冲区本身,并在这些值不符合您预期时触发断点。显然,使用数据中不太可能出现的模式是一个好主意。


我添加了一些本地变量缓冲区,希望能捕获一些值,而且在25次尝试中问题没有再现(这比我以前做过的尝试多2-3倍)。就好像我添加的缓冲区恰好足够填充所有内容,使得没有任何崩溃。即使我在调试时进入它们,每次函数返回之前,缓冲区都保存了我期望的确切值。 - Morinar
如果您只是扩展缓冲区,并向其末尾写入一些已知值,会怎样呢? - dash-tom-bang
你说得好像我知道我覆盖了哪个缓冲区一样。如果不清楚的话,我完全不知道。如果我知道我超限的是哪个缓冲区,我只需设置一个硬件断点就能赢得胜利。 - Morinar
哈哈,我明白了。我以为是特定的一个,抱歉。通常情况下,在堆栈溢出的情况下,你的覆盖只会破坏本地变量,因此,如果你有一个带有多个本地缓冲区的函数,它们之间的哨兵可能有助于确定哪一个正在溢出。 - dash-tom-bang
你可以在所有的哨兵值上设置监视点,这样一旦它们中的任何一个发生变化,你就能立即进入调试器。 - caf
1
接受这个帮助我找到了解决方案:我最终通过在各种缓冲区周围放置一些本地变量并找出哪个缓冲区溢出来跟踪它。然后我在缓冲区的两侧放置硬件断点并等待其重现。这是一个难以察觉的小错误,我们告诉函数一个8字节的缓冲区是10字节,并且它会将字符大写(以及其他事情),因此只有当这两个额外的字节恰好包含小写字符时才会重现。感谢大家的帮助! - Morinar

1
过去我为了帮助缩小类似这样的神秘错误,曾经创建了一个具有全局可见性的变量,命名为checkpoint。在罪犯函数内部,我将checkpoint = 0;设置为第一行。然后,在我甚至稍微怀疑可能会导致越界内存引用的函数调用或内存操作之前和之后添加了++checkpoint;语句(并在代码的其余部分中添加检查点,以便每10行左右都有一个检查点)。当程序崩溃时,checkpoint的值将缩小您需要关注的代码行数范围到几行。这可能有点过度,但我在嵌入式系统上做这种事情(无法使用像valgrind这样的工具),但它仍然应该很有用。

好主意!下次会试试。 - Morinar
我几乎每隔一行就有一个……当它崩溃时,它的值恰好是应该的。:-p - Morinar
我不明白你的意思。如果程序在checkpoint为6时崩溃,那么你的问题发生在第六个和第七个++checkpoint语句之间。如果你能在崩溃后读取这个值,它应该可以确定你问题的来源。 - bta
程序崩溃发生在退出函数时。由于函数本身充满了增量,当所有增量都被触发后,它像预期的那样崩溃了。 - Morinar
如果错误信息抱怨缓冲区溢出,并且崩溃发生在从函数返回时,那么最有可能的情况是您的一些代码正在破坏函数调用堆栈。要么是返回地址的存储值,要么是先前堆栈帧的缓存寄存器值已被破坏。这可能不容易追踪。尝试将崩溃的函数分解成较小的子函数。如果其中一个在返回时崩溃,它可能会给我们一些提示。 - bta
此外,您可能希望在调试器中查看原始调用堆栈,并查看上一个堆栈帧的数据是否熟悉(例如可能已从数据库中读取的数据)。 - bta

0
将其包装在异常处理程序中,并在发生异常时转储有用的信息。

0

这个程序是否有递归?如果有,我会检查以确保您没有无限递归的错误。如果您无法手动看到它,有时可以通过频繁暂停并观察堆栈来在调试器中捕获它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接