有关 C/C++ 的异常处理方式笔记
C 的异常处理
在 C 语言中最常使用 setjmp.h
库处理我们的异常。
最简单的异常处理,我们会使用其中的两个函数:setjmp
和
longjmp
jmp_buf
数组
jmp_buf
在 MSVC 中被定义为 DWORD
数组。
jmp_buf
被用来保存跳转现场,即用来保存当前调用的
ESP、EIP、EBP、EBX、EDI、ESI 等寄存器和标志位的信息。
setjmp
函数
setjmp
需要接收一个 jmp_buf
数组指针,将这个时候调用 setjmp
时的寄存器状态保存在
jmp_buf
里,然后返回 0
代表跳转点设置成功。
当 long_jmp
被调用后,会让 setjmp
函数返回一个值代表此次运行为跳转运行。
代码示例:
1 | jmp_buf jb; |
long_jmp
函数
long_jmp
需要接收一个 jmp_buf
数组指针和一个自定义 int
类型信息 (会作为跳转后
set_jmp
的返回值)。
它会根据传入的 jmp_buf
数组还原寄存器信息,即跳转到上一次调用 set_jmp(jmp_buf)
的时刻,然后将传入的 int
类型值作为 set_jmp
的返回值返回。
注意非必要不要传
0
,否则无法分辨set_jmp
是跳转回来的还是正常运行的。
代码示例:
1 | longjmp(jb, -1) |
完整示例
1 |
|
C++ 的异常处理
在 C++ 中最常使用 C++ 标准所提供的 try - catch - throw
异常处理模块来处理我们的异常。
try - catch
块
try
块中的代码被称为保护代码,后面通常跟着一个或多个
catch
块。
catch
块跟在 try
块后面,用于捕获异常。可以指定想要捕捉的异常类型,这是由 catch
关键字后的括号内的异常声明决定的。
代码示例:
1 | try |
throw
语句
当问题出现时,程序会抛出一个异常。这是通过使用 throw
关键字来完成的。
其中 throw
后跟着的信息与 catch
的接收有关。(即 throw
什么就 catch
什么)
代码示例:
1 | double division(int a, int b) |
完整示例
1 |
|
标准异常
C++ 提供了一系列标准的异常,定义在 <exception>
中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:
下表是对上面层次结构中出现的每个异常的说明:
异常 | 描述 |
---|---|
std::exception |
该异常是所有标准 C++ 异常的父类。 |
std::bad_alloc |
该异常可以通过 new
抛出。 |
std::bad_cast |
该异常可以通过 dynamic_cast
抛出。 |
std::bad_exception |
这在处理 C++ 程序中无法预期的异常时非常有用。 |
std::bad_typeid |
该异常可以通过 typeid
抛出。 |
std::logic_error |
理论上可以通过读取代码来检测到的异常。 |
std::domain_error |
当使用了一个无效的数学域时,会抛出该异常。 |
std::invalid_argument |
当使用了无效的参数时,会抛出该异常。 |
std::length_error |
当创建了太长的 std::string 时,会抛出该异常。 |
std::out_of_range |
该异常可以通过方法抛出,例如
std::vector 和
std::bitset<>::operator[]() 。 |
std::runtime_error |
理论上不可以通过读取代码来检测到的异常。 |
std::overflow_error |
当发生数学上溢时,会抛出该异常。 |
std::range_error |
当尝试存储超出范围的值时,会抛出该异常。 |
std::underflow_error |
当发生数学下溢时,会抛出该异常。 |
同时我们也可以自定义异常,代码示例:
1 |
|
Windows SEH 结构化异常处理机制
Windows SEH 结构化异常处理机制核心是由系统维护的一个单向链表,链表中保存每个异常处理函数的指针。
调用机制类似 C++ 的异常处理:
1 | __try |
其中 __except
需要一个宏定义常量,通常是
EXCEPTION_CONTINUE_EXECUTION
、EXCEPTION_EXECUTE_HANDLER
和 EXCEPTION_CONTINUE_SEARCH
。
宏定义常量 | 含义 |
---|---|
EXCEPTION_CONTINUE_EXECUTION |
异常处理完毕,程序继续正常运行异常出现处的下一条指令 |
EXCEPTION_EXECUTE_HANDLER |
异常处理完毕,但是直接执行 __except 块的内容 |
EXCEPTION_CONTINUE_SEARCH |
异常无法处理,告诉 Windows 寻找下一个异常处理函数来处理异常 |
这里我们通常通过函数实现:
1 |
|
其中我们可以通过 GetExceptionCode()
来获取系统所抛出的异常。
在 ida 中我们可以观察到,__excpet
被分析为:
Windows VEH 向量化异常处理机制
Windows VEH 向量化异常处理机制