不能让 C++ 应用程序“崩溃证明”吗?
Posted
技术标签:
【中文标题】不能让 C++ 应用程序“崩溃证明”吗?【英文标题】:Is it not possible to make a C++ application "Crash Proof"? 【发布时间】:2011-05-29 19:29:55 【问题描述】:假设我们有一个用 C++ 编写的 SDK,它接受一些二进制数据(如图片)并执行一些操作。难道不能让这个SDK“防撞”吗?崩溃主要是指由于用户传递的无效输入(例如异常短的垃圾数据),操作系统在内存访问冲突时强制终止。
我没有使用 C++ 的经验,但是当我用谷歌搜索时,我发现了几种听起来像解决方案的方法(使用向量而不是数组,配置编译器以便执行自动边界检查等)。
当我向开发人员介绍这个时,他说这仍然是不可能的.. 不是我不相信他,但如果是这样,像 Java 这样的语言是如何处理这个的?我认为 JVM 每次执行边界检查。如果是这样,为什么不能手动在 C++ 中做同样的事情?
更新 “崩溃证明”并不是说应用程序不会终止。我的意思是它不应该在没有发生情况的信息的情况下突然终止(我的意思是它会转储核心等,但不可能显示“参数 x 无效”等消息吗?)
【问题讨论】:
C++ 作为一种语言做出了(错误的)假设:a)程序员知道他/她在做什么 b)他们不会做一些愚蠢的事情,比如在使用之前忘记检查一个值。 "crash proof" 太多了 - 大多数人会忽略你后来将其限定为“不应该在没有发生情况的信息的情况下突然终止”。 您确定您的意思是接受图像的 SDK 吗?你的意思是“一个程序”吗? 【参考方案1】:您可以在 C++ 中检查数组的边界,std::vector::at
会自动执行此操作。
这不会使您的应用程序崩溃,您仍然可以故意向自己的脚开枪,但 C++ 中没有任何东西可以强迫您扣动扳机。
【讨论】:
【参考方案2】:没有。即使假设您的代码没有错误。首先,我查看了许多自动提交的崩溃报告,我可以向您保证,硬件的质量远低于大多数开发人员的期望。位翻转在商品机器上太常见了,会导致随机 AV。而且,即使您准备好处理访问冲突,也存在某些例外情况,操作系统别无选择,只能终止进程,例如未能提交 stack guard page。
【讨论】:
【参考方案3】:我所说的崩溃主要是指由于用户传递的无效输入(如异常短的垃圾数据),操作系统在内存访问冲突时强制终止。
这是通常发生的情况。如果您访问一些无效内存,通常操作系统会中止您的程序。
但是,什么是无效内存的问题......您可以随意用垃圾填充堆和堆栈中的所有内存,这从操作系统的角度来看是有效的,从您的角度来看它是无效的,因为您创建了垃圾.
基本上 - 您需要仔细检查输入数据并对此进行中继。没有操作系统会为您执行此操作。
如果你仔细检查你的输入数据,你可能会管理好数据。
【讨论】:
感谢您的回答! “如果你检查你的输入数据”这是否意味着从两个操作系统的角度来看,如果他/她执行了必要的检查,他/她总是可以检测到一个二进制文件是否“有效”? @Enno Shioji 你是什么意思有效?例如,如果你打开一张图片,它只是一个字节序列的文件,它是完全有效的文件,操作系统不关心它的内容是什么,另一方面,如果你假设它是例如jpeg
那么你实际上需要做非常复杂的事情来确保它是“有效的”
我了解到您需要确保应用程序中的数据有效。透视图(可以通过使用元数据等来完成)。但是,除此之外,首先数据必须从操作系统的角度来看是有效的,对吧?是否可以通过检查代码中的输入来确定?【参考方案4】:
我的主要意思是强制终止 由操作系统在内存访问时 违规,由于输入无效 由用户
不确定“用户”是谁。
您可以编写不会因最终用户输入无效而崩溃的程序。在某些系统上,您可能会由于使用过多内存(或因为某些其他程序使用过多内存)而被强制终止。正如 Remus 所说,没有一种语言可以完全保护您免受硬件故障的影响。但这些事情取决于用户提供的数据字节以外的因素。
您在 C++ 中不能轻易做到的是证明您的程序不会因无效输入而崩溃,或者以更糟糕的方式出错,从而造成严重的安全漏洞。所以有时[*] 你认为你的代码对任何输入都是安全的,但事实证明并非如此。您的开发人员可能是这个意思。
如果你的代码是一个函数,例如一个指向图像数据的指针,那么没有什么可以阻止调用者向你传递一些无效的指针值:
char *image_data = malloc(1);
free(image_data);
image_processing_function(image_data);
因此,该功能本身不能“防崩溃”,它要求程序的其余部分不做任何使其崩溃的事情。你的开发者也可能是这个意思,所以也许你应该让他澄清一下。
Java 通过无法创建无效引用来处理这个特定问题 - 您无法在 Java 中手动释放内存,因此特别是在这样做之后您无法保留对它的引用。它以其他方式处理许多其他特定问题,因此在 C++ 中是“未定义行为”并且很可能导致崩溃的情况在 Java 中会做一些不同的事情(可能会引发异常)。
[*] 让我们面对现实吧:在实践中,在大型软件项目中,“经常”。
【讨论】:
【参考方案5】:我认为这是 C++ 代码不是托管代码的情况。
Java、C# 代码是受管理的,也就是说它们由能够执行边界检查和检测崩溃条件的解释器有效地执行。
对于 C++ 的情况,您需要自己执行绑定和其他检查。但是,您可以使用异常处理,这将防止在您无法控制的事件期间发生崩溃。
底线是,C++ 代码本身不是防崩溃的,但良好的设计和开发可以使它们如此。
【讨论】:
【参考方案6】:一般来说,您无法使 C++ API防崩溃,但可以使用一些技术使其更加健壮。对于您的特定示例,我不以为然(而且绝不是详尽无遗):
尽可能检查输入数据的完整性 数据处理代码中的缓冲区限制检查 边缘和角落案例测试 模糊测试 将问题输入放入单元测试以避免回归【讨论】:
【参考方案7】:如果“崩溃证明”仅意味着您希望确保有足够的信息来调查崩溃发生后的解决方案可以很简单。大多数情况下,调试信息在崩溃期间丢失是由于在线程之一中运行的代码的非法内存操作造成的损坏和/或堆栈数据丢失。如果您调用不信任的库或 SDK 的地方很少,您可以简单地保存堆栈跟踪,然后在全局变量指向的某个内存位置调用该库,该全局变量将包含在部分或全部内存转储中当您的应用程序崩溃时由系统生成。在 Windows 上,由 CrtDbg API 提供的此类功能。在 Linux 上,您可以使用回溯 API - 只需在 show_stackframe() 上搜索文档。如果您丢失了堆栈信息,则可以指示调试器在加载转储文件后使用内存中的该位置作为堆栈顶部。好吧,这毕竟不是很简单,但是如果您被内存转储所困扰,却不知道发生了什么,它可能会有所帮助。 嵌入式应用程序中经常使用的另一个技巧是用于详细记录的循环内存缓冲区。记录到缓冲区非常便宜,因为它永远不会被保存,但您可以通过查看崩溃后内存转储中缓冲区的内容来了解崩溃前几毫秒发生的情况。
【讨论】:
【参考方案8】:实际上,使用边界检查会使您的应用程序更有可能崩溃!
这是一个很好的设计,因为这意味着如果您的程序正在运行,那么它更有可能/正确地/运行,而不是不正确地运行。
也就是说,严格来说,在解决停止问题之前,不能对给定的应用程序进行“防崩溃”。祝你好运!
【讨论】:
以上是关于不能让 C++ 应用程序“崩溃证明”吗?的主要内容,如果未能解决你的问题,请参考以下文章