{
char bufBef[32];
char buf[8];
char bufAfter[32];
sprintf(buf,"AAAAAAA\0");
buf[8]='\0';
printf("%s\n",buf);
}
在Windows 7上,我使用Visual Studio 2008编译程序作为调试项目。 3个缓冲区相邻。我用调试器找到了他们的地址,如下所示:
bufBef 0x001afa50
buf 0x001afa40
bufAfter 0x001afa18
语句“buf [8] ='\ 0'”将地址写入buf。当我运行程序时,操作系统报告“调试错误:运行时检查失败#2 - 变量'buf'周围的堆栈已损坏。”
然后我将其编译为发布项目。它安静地运行,没有提出任何错误报告。
我的问题是运行时检测缓冲区如何溢出?
答案 0 :(得分:3)
一般情况下,你没有。你应该编写防御性代码,进行适当的检查,以确保它永远不会超出缓冲区。
调试运行时添加了大量检查以帮助查找此类错误(以及各种其他常见的与内存相关的错误);这些检查通常非常昂贵,因此它们仅包含在调试版本中或运行时附加到调试器。他们也无法发现每一个可能的错误,因此它们并非万无一失;他们只是调试辅助工具。
答案 1 :(得分:3)
如果/RTCs切换效果,您会看到什么。
John Robbins的书Debugging Applications for Microsoft .NET and Microsoft Windows深入探讨了这一点。
相关摘录:
对我们来说幸运的是,微软扩展了 / RTC切换到也进行溢出 和所有多字节的欠载检查 局部变量,如数组。它 这是通过添加四个字节来实现的 这些数组的前端和末端 在结束时检查它们 函数来确保那些额外的字节 仍然设置为0xCC。
请注意,此开关仅适用于未经优化的构建(调试版本)。
答案 2 :(得分:2)
关于Electric Fence的维基百科文章解释了如何捕获缓冲区溢出,以及为什么不应在生产代码中使用此类机制。
答案 3 :(得分:2)
通常,运行时将通过在变量之间分配一些额外空间并使用已知位模式填充该空间来检测溢出。代码运行后,它会查看该空间中的位模式。由于它在任何变量之外,因此应保留相同的位模式。如果内容发生了变化,那么你就写下了一个你不应该有的地方。
答案 4 :(得分:2)
三个缓冲区不相邻。 buf
的开头与bufBef
的开头(堆栈中的以下项)之间的差异是16个字节,但buf
只有8个字节。
其间的8个字节可能填充了8字节的“canary”值。当运行时检测到野性写入已经更改了金丝雀时,它会引发您看到的错误。
(您对buf[8]
的写信会写入地址0x001afa48
,该地址位于buf
和bufBef
之间。
答案 5 :(得分:2)
调试模式下的编译器为操作添加了额外的范围检查。
答案 6 :(得分:1)
您需要了解堆栈结构。通常编译器会在数组周围放置带有随机cookie的额外保护字节,如果函数末尾的值不匹配,则会出现溢出。
答案 7 :(得分:1)
好0x001afa50 - 0x001afa40 = 0x10 = 16
和0x001afa40 - 0x001afa18 = 0x28 = 40
,因此缓冲区之间有一些空间可以留下一些已知的虚拟数据。如果在函数结束时改变了它,它就知道你超出了缓冲区的末尾。我只是猜测 - 他们可能已经采取了另一种方式,但这似乎是一种可能性。
答案 8 :(得分:0)
C明确允许你过度运行(并运行不足)你的缓冲区,这是你自己的危险。
在运行时(在发布版本中)没有简短的方法来检测缓冲区溢出。
答案 9 :(得分:0)
您正在寻找与C不同的语言。某些语言定义了可能的程序的行为,为“做错事”定义了特定的错误行为。另一方面,C留下了“错误”代码未定义的行为,这意味着程序员必须确保他/她从不以导致未定义行为的方式使用该语言。某些实现是面向调试的,或者具有帮助您查找错误的调试模式,在发布/生产使用中部署代码之前,您绝对需要修复这些错误。