C++异常处理机制学习(持续更新)

news/2025/1/1 9:19:45/文章来源:https://www.cnblogs.com/T0fV404/p/18638919

具体的异常要回去学中断这些,我打算到时候再细致研究,故而这里只是粗浅地讨论C++的异常处理机制.(其实没太看懂原理和应用的关系,以后还要深入研究)


首先我们要探究一下seh异常处理机制,从与其相关的数据结构讲起.

  1. TIB结构
    TIB (Thread Infoimation Block, 线程信息块)是保存线程基本信息的数据结构。在用户模式下, 它位于TEB(Thread Environment Block,线程环境块)的头部,而TEB是操作系统为了保存每个线程的私有数据创建的,每个线程都有自己的TEB.在 Windows 2000 DDK 中,TIB的定义如下:

    typedef struct _NT_TIB { struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;//指向异常处理链装 PVOID StackBase;//当而线程所使用的栈的栈底 PVOID StackLimit;//当前线程所征用的栈的栈PVOID SubSystemTib; union { PVOID FiberData; ULONG Version;};PVOID ArbitraryUserPointer; struct _NT_TIB *Self; //指向 TIB结构自身 
    } NT_TIB;
    

    从Windows2000到10都是如此.其中要关注_EXCEPTION_REGISTRATION_RECORD的指针ExceptionList,它位于TIB的偏移0,也是TEB的0.在x86的用户模式下,FS段处理器指向当前线程的TEB数据,也就是TEB总是由fs:[0]指向的,而x64则是gs:[0].而当线程运行在内核模式下时,Windows将 FS段选择器指向内核中的KPCRB结构(Processor Control Region Block,处理器控制块), 该结构的头部同样是上述的NT_TIB结构。

  2. _EXCEPTION_REGISTRATION_RECORD 结构
    该结构是个单链表,用于描述线程异常处理过程的地址.

    typedef struct _EXCEPTION_REGISTRATION_RECORD { 	struct _EXCEPTION_REGISTRATION_RECORD *Next; //指向下一个结构的指针 PEXCEPTION_ROUTINE Handler; //当前异常处理回调函数的地址 
    } EXCEPTION REGISTRATION RECORD;
    

这样就能达到回滚的效果了.

由于我们知道SEH本质就是一个链表,所以我们只需要把我们写好的一个_EXCEPTION_REGISTRATION_RECORD结构插入到链表头就行。首先push指向我们handler的地址,然后push fs:[0],此时就成功的创造了一个_EXCEPTION_REGISTRATION_RECORD。最后mov fs:[0],esp,就成功的修改了我们的TEB,相当于插入了一个新的节点。

卸载就是把esp赋值为刚刚存入fs:[0]的(像上图的下面的那个_EXCEPTION_REGISTRATION_RECORD的next的地址)。然后pop一下保证栈帧平衡,就可以了。

接下来是具体处理try-except块的相关流程,直接贴文[原创]C++异常处理 学习笔记-软件逆向-看雪-安全社区|安全招聘|kanxue.com

image-20220616204142053

test_seh函数的最开始,先push handler,再push fs:[0]。

看到这个源代码里的try....except块里的代码

image-20220616204701598

下方的except对应这个,ida显示的是__except filter

image-20220616204856744

except里的处理部分的代码呢?在这里

image-20220616204953960

__except($LN8)中的LN8是刚刚的filter对应的label,这里个人感觉就是判断是哪个filter里的处理代码的标志,看label。

验证一下,又加了一点代码

可以看到

img

image-20220616210755394

except里面的操作都对应的是except filter

image-20220616210902828

image-20220616210955176

而从这个except filter上方的label可以找到对应的处理代码。

所以以后我再遇到这种seh的题,首先定位filter,然后根据filter的label找到对应的处理代码,然后下断进行分析。

以下是我的补充,这里的expect语句是Windows下的扩展,详见try-except 语句 | Microsoft Learn


本文主要介绍C++编程语言中的异常处理(try-catch-throw)的相关内知识,同时通过示例代码介绍异常处理的使用方法。

1 概述
C++编程语言中的异常是指在程序运行时发生的特殊情况,例如除数为“0”的情况。异常机制提供了一种转移程序控制权的方式。

C++编程语言中的异常处理涉及到三个关键字:try、catch、throw。这三个关键字的介绍如下:

throw:当问题出现时,通过使用throw关键字让程序抛出一个异常;
try:关键字try块中的代码被称为保护代码,该段代码可能会抛出异常。try关键字后通常跟着一个或多个catch块;
catch:在想要处理异常的地方,使用catch关键字捕获异常,并进行相关的异常处理。
2 用法
2.1 使用throw抛出异常
可以使用throw关键字在代码中抛出异常。throw关键字操作的对象类型即为抛出异常的类型。

示例代码内容如下:

// 定义除法函数,当分母为0时,此函数会抛出异常信息
double Division(double x, double y)
{
if (0 == int(y))
{
throw "divisor y is zero.";
}

return (x / y);
}
上述代码的函数Division中throw操作的对象是“const char *”类型,所以其抛出的异常类型即为“const char *”类型。如果想要捕获到该异常,则需要将catch语句对应的异常类型设置为“const char *”。

2.2 使用try-catch语句处理异常
示例代码内容如下:

try
{
// 保护代码,可能抛出异常
// throw 抛出异常
}
catch (ExceptionName e1)
{
// 能捕获到 ExceptionName 类型的异常
// 具体的异常处理操作
}
catch (ExceptionName e2)
{
// 能捕获到 ExceptionName 类型的异常
// 具体的异常处理操作
}
catch (ExceptionName eN)
{
// 能捕获到 ExceptionName 类型的异常
// 具体的异常处理操作
}
如果try块中的代码在不同情况下会抛出不同类型的异常,则可使用多个catch语句,捕获并处理这些不同类型的异常。catch只能捕获到其规定类型的异常。

上面的示例代码会捕获到类型为ExceptionName的异常。如果想让catch能够捕获try块抛出的任何类型的异常,则需要在异常类型声明括号内使用省略号“...”,示例代码内容如下:

try
{
// 保护代码,可能抛出任何类型的异常
// throw 抛出异常
}
catch (...)
{
// 能捕获到任何类型的异常
// 具体的异常处理操作
}
3 示例代码
下面展示一个完整的异常处理示例代码。示例代码(exception_demo1.cpp)的内容如下:

include

include

using namespace std;

// 定义除法函数,当分母为0时,此函数会抛出异常信息
double Division(double x, double y)
{
if (0 == int(y))
{
throw "divisor y is zero.";
}

return (x / y);
}

int main()
{
double a = 5.0;
double b = 0.0;
double c = 0;

// try块内为被保护的代码,如果块内代码抛出异常,则会被相关的catch捕获到
try
{
c = Division(a, b);
cout << "c is: " << c << endl;
}
catch (const char * exception)
{
cout << "exception info is: " << exception << endl;
}

return 0;
}

上述代码的Division函数抛出了一个类型为“const char”的异常,因此,为了捕获到该异常,catch的异常类型必须也是“const char”。

编译并执行上述代码,结果如下:

4 异常规格说明
抛出异常的函数后接的throw(),称为异常规格说明,表示该函数可以抛出哪种类型的异常,具体的异常类型在throw()的括号中声明。

具体包括以下几种情况:

如果函数不抛出任何类型的异常,则throw()的括号中无内容。示例如下:
double Division(double x, double y) throw ()
如果函数可以抛出类型为“A”、“B”的异常,则写法为“throw(A, B)”。示例如下:
double Division(double x, double y) throw (const char, int)
上述示例表示Division函数可以抛出“const char
”和“int”类型的异常;
如果函数可以抛出任何类型的异常,则不需要后接异常规格说明。示例如下:
double Division(double x, double y)
上述示例表示Division函数可以抛出任何类型的异常。
注意:如果函数抛出异常的类型,与异常规格说明不一致,则会导致程序执行出错。例如,在上述示例代码中,将Division函数的异常规格说明设置为“int”类型,则程序执行时会报如下错误:

[root@node1 /opt/liitdar/mydemos/simples]# ./exception_demo1
terminate called after throwing an instance of 'char const*'
Aborted
[root@node1 /opt/liitdar/mydemos/simples]#
————————————————

                    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/liitdar/article/details/86129430


C++ 异常处理 | 菜鸟教程

C++ 异常处理

异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。

异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
  • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。

如果有一个块抛出一个异常,捕获异常的方法会使用 trycatch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:

try { // 保护代码 }catch( ExceptionName e1 ) { // catch 块 }catch( ExceptionName e2 ) { // catch 块 }catch( ExceptionName eN ) { // catch 块 }

如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。

抛出异常

您可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。

以下是尝试除以零时抛出异常的实例:

double division(int a, int b) { if( b == 0 ) { throw "Division by zero condition!"; } return (a/b); }

捕获异常

catch 块跟在 try 块后面,用于捕获异常。您可以指定想要捕捉的异常类型,这是由 catch 关键字后的括号内的异常声明决定的。

try { // 保护代码 }catch( ExceptionName e ) { // 处理 ExceptionName 异常的代码 }

上面的代码会捕获一个类型为 ExceptionName 的异常。如果您想让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 ...,如下所示:

try { // 保护代码 }catch(...) { // 能处理任何异常的代码 }

下面是一个实例,抛出一个除以零的异常,并在 catch 块中捕获该异常。

实例

#include using namespace std; double division(int a, int b) { if( b == 0 ) { throw "Division by zero condition!"; } return (a/b); } int main () { int x = 50; int y = 0; double z = 0; try { z = division(x, y); cout << z << endl; }catch (const char* msg) { cerr << msg << endl; } return 0; }

由于我们抛出了一个类型为 const char* 的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char*。当上面的代码被编译和执行时,它会产生下列结果:

Division by zero condition!

C++ 标准的异常

C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:

C++ 异常的层次结构

下表是对上面层次结构中出现的每个异常的说明:

异常 描述
std::exception 该异常是所有标准 C++ 异常的父类。
std::bad_alloc 该异常可以通过new 抛出。
std::bad_cast 该异常可以通过dynamic_cast 抛出。
std::bad_typeid 该异常可以通过typeid 抛出。
std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。
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 当发生数学下溢时,会抛出该异常。

定义新的异常

您可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:

实例

#include #include using namespace std; struct MyException : public exception { const char * what () const throw () { return "C++ Exception"; } }; int main() { try { throw MyException(); } catch(MyException& e) { std::cout << "MyException caught" << std::endl; std::cout << e.what() << std::endl; } catch(std::exception& e) { //其他的错误 } }

这将产生以下结果:

MyException caught
C++ Exception

在这里,what() 是异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因。


注意:执行 throw 语句类似与执行 return 返回语句,它会终止函数的执行,但是 throw 并不是把控制权交给调用函数,而是沿函数调用序列进行回退,直到找到包含 try 块的函数并能成功匹配异常。
啥意思呢?考虑如下代码:

#include <iostream>using std::cin;
using std::cout;
using std::endl;int add(int a, int b)
{if (!(a + b))throw "Note: a is allowed to equal to -b";cout << "add end\n";return a + b;
}int func(int a, int b)
{int c;try {c = add(a, b);} catch (char ch) {cout << "func catch\n";}cout << "func end\n";return a * c;
}int main()
{int a, b, c;cin >> a >> b;try {c = func(a, b);cout << c << endl;} catch (const char* s) {cout << s << endl;}cout << "END\n";return 0;
}

这里调用关系为:main -> func -> add,输入 a = 1, b = -1,最后会在 add 函数中触发异常,这时 throw 会抛出异常,这时会沿调用路径回溯寻找 try - catch 捕获异常,所以这里触发异常后,控制权就直接回到了 main 函数中,所以 cout << "func end\n"; 不会执行:输出结果如下

xiaozaya@pwn:~/rubbish/cpp$ ./a.out
1 -1
Note: a is allowed to equal to -b
END
————————————————

                    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_61670993/article/details/135251333


由于我只做过hgame-mini的C嘎嘎,故只能拿这题来说说经验.其实主要就是在伪代码找expection,然后去汇编代码,看try和catch,看不懂的可以看ida的注释.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/860870.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PYTHON语言学习笔记(基础语法篇)

Python学习笔记 序言 主要是以小甲鱼的视频为主,https://space.bilibili.com/314076440 一些特性 多次调用方法是从左到右.而参数是函数则先执行参数. 一行如果要多个赋值,用;隔开 input().split() IO 看我放在另一个地方的文档.<D:\Document\md\PYTHON\IO.md> 数据类型 …

2024-11-28《关于mybatis创建的mapper映射路径不对导致的系列报错》

关于mybatis创建的mapper映射路径不对导致的系列报错今天在写mybatis项目的时候,使用注解发现无法使用别名,添加ResultMap的时候直接报错显示无法解析。 经过百度了好久也是成功的发现了问题的所在,就是这个:这个路径创建的时候我以为创建的是分级目录,实际上创建成为了co…

2024-11-29《axios获取不到response返回的响应的解决方法》

axios获取不到response返回的响应的解决方法今天在用mybatis+vue+axios写登录界面的时候,发现用户名还有密码的数据都能够传输到servlet里,但是当servlet对html界面进行相应的时候,axios却收不到消息,经过长时间的排查后也没有发现问题,终于在今晚的百度下发现了结果,上原…

如何解决宝塔面板无法登录的问题?

您好,当您遇到宝塔面板无法登录的问题时,可以按照以下步骤进行排查和解决:确认初始密码:如果您从未修改过宝塔面板的登录密码,默认情况下,宝塔面板的初始密码与服务器的初始密码相同。您可以尝试使用服务器的初始密码进行登录。如果您不确定服务器的初始密码,可以在云服…

如何解决网站默认80端口无法访问的问题?

您好,当您遇到网站默认80端口无法访问的问题时,可以按照以下步骤进行排查和解决:确认域名解析:首先,检查域名是否正确解析到服务器的IP地址。您可以通过在线DNS查询工具(如(网址))来验证域名解析是否正确。如果解析有问题,请联系域名注册商进行修正。检查服务器状态:登…

请问如何解决宝塔面板无法登录的问题?

您好,当您遇到宝塔面板无法登录的问题时,可以按照以下步骤进行排查和解决,确保能够顺利访问并管理您的服务器:确认初始密码:如果您从未修改过宝塔面板的登录密码,默认情况下,宝塔面板的初始密码与服务器的初始密码相同。您可以尝试使用服务器的初始密码进行登录。如果您…

网站频繁出现500错误 - 云服务器性能问题

关于您提到的网站经常报500错误并且服务器卡死的问题,这是一个比较复杂的情况,涉及到多个方面。为了帮助您彻底解决这个问题,我们需要从以下几个角度进行分析和排查: 一、服务器资源监控 首先,当网站出现500错误时,服务器资源(CPU、内存、磁盘I/O等)是否处于高位占用状…

网站后台打开出错 - 虚拟主机/数据库问题

一、检查PHP版本兼容性 首先,您提到有时登录时会遇到错误提示,而有时又能正常访问。这种情况可能与PHP版本有关。不同的PHP版本对某些函数或库的支持程度不同,如果您的网站程序依赖于特定版本的PHP特性,当切换到不兼容的版本时,可能会导致功能异常。 建议您尝试切换PHP版本…

2024-11-11《VsCode运行时输出日志》

VsCode运行C#时终端输出日志最近在使用vscode运行C#项目的时候,终端总会先输出一些无关的日志信息才会输出运行结果,搜索解决后发现是终端输出了日志信息,首先咱们看一下他输出的信息。这是我的代码: using System; using System.Collections.Generic; using System.Lin…

2024-10-31《c#学习》

今天进行了C#的初步学习,主要了解了C#的相关知识:目录基础语法 数据类型值类型 引用类型 动态(Dynamic)类型 字符串类型 指针类型类型转换 变量 常量 运算符 判断 循环 封装 基础语法首先是在C#里的基础语法,大致与C++还有Java类似,可以说是二者的结合体,基本的Hello Worl…

搬家后无法进入后台怎么办

问题描述: 网站搬家后,无法进入后台管理界面,可能是由于密码问题或配置错误引起的。请帮助恢复。 解决方案: 您好,网站搬家后无法进入后台管理界面确实是一个常见的问题。为了帮助您顺利恢复后台访问,建议您按照以下步骤逐一排查:确认密码正确:首先,确保您使用的后台登…

数据库密码无法更改怎么办

问题描述: 虚拟主机环境下,尝试更改数据库密码时遇到问题,无法成功更改。请帮助解决。 解决方案: 您好,数据库密码无法更改可能是由于权限不足、配置文件错误或数据库连接问题引起的。为了帮助您顺利更改密码,建议您按照以下步骤逐一排查:确认权限:确保您有足够的权限执…