捕获访问冲突异常?
Posted
技术标签:
【中文标题】捕获访问冲突异常?【英文标题】:Catching access violation exceptions? 【发布时间】:2009-01-19 13:27:03 【问题描述】:例子
int *ptr;
*ptr = 1000;
我可以在不使用任何微软特定的情况下使用标准 C++ 捕获内存访问冲突异常吗?
【问题讨论】:
【参考方案1】:读完就哭!
我想通了。如果你不从处理程序中抛出,处理程序将继续,异常也将继续。
当你抛出自己的异常并处理它时,魔法就会发生。
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <tchar.h>
void SignalHandler(int signal)
printf("Signal %d",signal);
throw "!Access Violation!";
int main()
typedef void (*SignalHandlerPointer)(int);
SignalHandlerPointer previousHandler;
previousHandler = signal(SIGSEGV , SignalHandler);
try
*(int *) 0 = 0;// Baaaaaaad thing that should never be caught. You should write good code in the first place.
catch(char *e)
printf("Exception Caught: %s\n",e);
printf("Now we continue, unhindered, like the abomination never happened. (I am an EVIL genius)\n");
printf("But please kids, DONT TRY THIS AT HOME ;)\n");
【讨论】:
不错的提示,尤其是因为 __try/__except 也不会捕获 AV。 这在 gcc 中不起作用,但在 VC++ 中起作用,但仅在“调试”版本中起作用。仍然支持一个有趣的解决方案。将调用信号处理程序,但不会抛出异常。 这在便携方面不起作用。当调用信号处理程序时,堆栈帧和寄存器处理与普通函数堆栈帧不同(在某些系统上它甚至可能不使用相同的堆栈)。您可以做的最好的事情是设置一个标志来指示信号处理程序已被激活。然后在你的代码中测试那个标志并抛出。 这很有可能引入未定义的行为。为了使它在 POSIX 上工作,不得安装任何替代信号堆栈 (sigaltstack
)(除非 C++ 异常展开实现允许它),并且处理展开机制本身的每个运行时函数都应该是信号安全的。
如果您想返回默认处理程序以发出信号(在这种情况下为 SIGSEGV),请使用以下命令:signal(SIGSEGV, SIG_DFL);
【参考方案2】:
在 Visual Studio 中使用 try -> catch (...) 块有一种非常简单的方法可以捕获任何类型的异常(除以零、访问冲突等)。一个小的项目设置调整就足够了。只需在项目设置中启用 /EHa 选项。请参阅项目属性 -> C/C++ -> 代码生成 -> 将 Enable C++ Exceptions 修改为“Yes With SEH Exceptions”。就是这样!
在此处查看详细信息: https://docs.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-160
【讨论】:
Visual Studio .NET 2003 中没有这样的设置值,只有“No”和“Yes (/EHsc)”。您能否说明启用此设置所需的最小 Visual Studio 版本? 链接似乎指定“Visual Studio 2005” 如果使用 gcc 或 MinGW 会怎样?【参考方案3】:不。当你做坏事时,C++ 不会抛出异常,这会导致性能下降。访问冲突或除以零错误之类的事情更像是“机器”异常,而不是您可以捕获的语言级别的事情。
【讨论】:
我知道是硬件异常,但是有微软特定的关键字处理这个(__try __except)? @Ahmed:是的,但如果你使用它们,可能会发生“不可能”的事情。例如,在 AV 代码行 之后 的某些语句可能已经执行,或者 AV 之前的语句尚未执行。 在下面查看我的答案,如何在 VC++ 中使用常规 try...catch 块启用此类异常处理。 @Aaron 你能详细说明“不可能发生的事情”部分吗?是因为编译器和/或 CPU 重新排序指令吗? 底层操作系统通常会提供捕获此类问题的机制,并且不会产生任何成本,因为异常是由 CPU 架构产生的。调试器能够捕获异常以允许您进行调试的方式证明了这一点,而不会减慢代码执行速度。【参考方案4】:至少对我来说,另一个答案中提到的signal(SIGSEGV ...)
方法不适用于带有 Visual C++ 2015 的 Win32。 对我有用的是使用eh.h
中的_set_se_translator()
。它的工作原理是这样的:
第 1 步)确保在Project Properties / C++ / Code Generation / Enable C++ Exceptions 中启用Yes with SEH Exceptions (/EHa),正如 Volodymyr Frytskyy 的回答中提到的那样。
第 2 步)调用 _set_se_translator()
,为新异常 translator 传入一个函数指针(或 lambda)。它之所以称为翻译器,是因为它基本上只是将低级异常作为更容易捕获的东西重新抛出,例如std::exception
:
#include <string>
#include <eh.h>
// Be sure to enable "Yes with SEH Exceptions (/EHa)" in C++ / Code Generation;
_set_se_translator([](unsigned int u, EXCEPTION_POINTERS *pExp)
std::string error = "SE Exception: ";
switch (u)
case 0xC0000005:
error += "Access Violation";
break;
default:
char result[11];
sprintf_s(result, 11, "0x%08X", u);
error += result;
;
throw std::exception(error.c_str());
);
第 3 步)像往常一样捕捉异常:
try
MakeAnException();
catch(std::exception ex)
HandleIt();
;
【讨论】:
本网站包含几个简单的 _set_se_translator() 方法示例,对我有用,msdn.microsoft.com/en-us/library/5z4bw5h5.aspx【参考方案5】:这种情况取决于实现,因此需要供应商特定的机制才能进行陷阱。对于 Microsoft,这将涉及 SEH,而 *nix 将涉及信号
虽然捕获访问冲突异常通常是一个非常的坏主意。几乎没有办法从 AV 异常中恢复,尝试这样做只会导致更难在程序中找到错误。
【讨论】:
所以您的建议是了解 AV 异常的原因是什么,不是吗? 当然。 AV 代表代码中的错误,捕获异常只会隐藏问题。 为了澄清,C++ 标准对未定义、未指定和实现定义进行了区分。定义的实现意味着实现必须指定发生的事情。问题中的代码是未定义的,这意味着任何事情都可能发生,并且每次都不同。 捕获访问冲突是一个不错的主意——它有利于用户体验。但是,在这种情况下,我做的唯一有意义的事情是 - 使用 Bug Reporting GUI 生成另一个进程并尝试创建当前进程转储。产生一个进程总是成功的操作。然后,我执行 TerminateProcess() 来自我杀死。 捕获异常并默默忽略它是个坏主意。在可能的情况下捕获异常并记录有关应用程序状态的信息以用于诊断是一个非常好的主意。我曾经为需要调试的后端图形库编写了一个 UI。每次它崩溃时,人们都会来找我,因为他们知道我编写了 UI。我在后端放置了一个信号陷阱,它会弹出一个警报,告诉用户图书馆崩溃了。人们开始去找图书馆的作者。【参考方案6】:如前所述,在 Windows 平台上没有非 Microsoft/编译器供应商的方式来执行此操作。然而,在正常的 try catch (exception ex) 方式中捕获这些类型的异常显然是有用的,用于错误报告和更优雅的应用程序退出(正如 JaredPar 所说,应用程序现在可能有麻烦了) .我们在一个简单的类包装器中使用 _se_translator_function,它允许我们在 try 处理程序中捕获以下异常:
DECLARE_EXCEPTION_CLASS(datatype_misalignment)
DECLARE_EXCEPTION_CLASS(breakpoint)
DECLARE_EXCEPTION_CLASS(single_step)
DECLARE_EXCEPTION_CLASS(array_bounds_exceeded)
DECLARE_EXCEPTION_CLASS(flt_denormal_operand)
DECLARE_EXCEPTION_CLASS(flt_divide_by_zero)
DECLARE_EXCEPTION_CLASS(flt_inexact_result)
DECLARE_EXCEPTION_CLASS(flt_invalid_operation)
DECLARE_EXCEPTION_CLASS(flt_overflow)
DECLARE_EXCEPTION_CLASS(flt_stack_check)
DECLARE_EXCEPTION_CLASS(flt_underflow)
DECLARE_EXCEPTION_CLASS(int_divide_by_zero)
DECLARE_EXCEPTION_CLASS(int_overflow)
DECLARE_EXCEPTION_CLASS(priv_instruction)
DECLARE_EXCEPTION_CLASS(in_page_error)
DECLARE_EXCEPTION_CLASS(illegal_instruction)
DECLARE_EXCEPTION_CLASS(noncontinuable_exception)
DECLARE_EXCEPTION_CLASS(stack_overflow)
DECLARE_EXCEPTION_CLASS(invalid_disposition)
DECLARE_EXCEPTION_CLASS(guard_page)
DECLARE_EXCEPTION_CLASS(invalid_handle)
DECLARE_EXCEPTION_CLASS(microsoft_cpp)
原始类来自这篇非常有用的文章:
http://www.codeproject.com/KB/cpp/exception.aspx
【讨论】:
我看到使用 Microsoft 编译器被视为非法指令或访问冲突。很有趣。【参考方案7】:不是异常处理机制, 但是你可以使用 C 提供的 signal() 机制。
> man signal
11 SIGSEGV create core image segmentation violation
写入 NULL 指针可能会导致 SIGSEGV 信号
【讨论】:
@maidamaisignal()
是 posix 标准的一部分。 Windows 实现了 posix 标准(Linux 和 unix 也是如此)【参考方案8】:
这样的违规意味着代码存在严重错误,并且不可靠。我可以看到一个程序可能想要尝试以一种希望不会覆盖以前的数据的方式保存用户的数据,希望用户的数据还没有损坏,但是根据定义没有标准方法处理未定义的行为。
【讨论】:
可以从访问冲突中恢复。除非您很狡猾并保持汇编级指令指针,否则永远不可能从 EIP 跳转破坏中恢复。但是,捕获访问冲突对于生成另一个用于错误报告 GUI 功能的进程很有好处。以上是关于捕获访问冲突异常?的主要内容,如果未能解决你的问题,请参考以下文章
致命错误:未捕获的异常“PDOException”,带有消息“SQLSTATE [42000]:语法错误或访问冲突 PHP 和 PDO
未捕获的 PDOException:SQLSTATE[42000]:语法错误或访问冲突
致命错误:未捕获的 PDOException:SQLSTATE[42000] 语法错误或访问冲突
PHP 致命错误:未捕获的 PDOException:SQLSTATE [42000]:语法错误或访问冲突:1064 您的 SQL 语法有错误