首页 > 编程语言 >C++异常处理机制学习(持续更新)

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

时间:2024-12-29 15:08:06浏览次数:1  
标签:抛出 代码 更新 try C++ catch 机制 异常 throw

具体的异常要回去学中断这些,我打算到时候再细致研究,故而这里只是粗浅地讨论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的注释.

标签:抛出,代码,更新,try,C++,catch,机制,异常,throw
From: https://www.cnblogs.com/T0fV404/p/18638919

相关文章

  • 现代CPU处理机制和线程
    对于计算机进程和CPU运行机制的大致理解序言主要是了解后好写代码,也好看得懂那些代码,出于逆向的需求,至于具体的代码编程再说.以下主要是CoreDumped(看的翻译)和EfficLab(B站有号)的视频内容,不过用我自己的理解写了.进程用一种不正规的定义可以把线程当成一个正在执行(其......
  • 消息传递机制(转载)
    进程间的通信方式——pipe(管道)-CSDN博客1.进程间通信每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提......
  • 本地更新正常但上传空间后无法更新缓存
    当您在本地环境中能够顺利地更新网站后台并保存更改,但在上传至虚拟主机后却遇到了无法更新缓存的问题时,这通常意味着存在某些配置差异或环境兼容性问题。以下是一些常见的原因及解决方案,帮助您更好地理解和处理这种情况:服务器环境差异:PHP版本不匹配:不同服务器可能运行着不同......
  • c++11新特性
    智能指针1.管理内存释放问题2.共享所有权和转移//用的最多,内涵一个指向计数器,计数器归0的时候,释放对应的内存//指针本身在栈里面存储,指向的内容是放在堆里面的,栈可以自动释放,堆不可以shared_ptr//检测内存有没有被释放,被释放了就不用了,没被释放才做一些操作weak_ptr//纯......
  • 控制流平坦化初了解(持续更新)
    控制流平坦化初了解原理借助LLVM(一个底层虚拟机项目,我目前不理解虚拟机这个概念)这个项目,目前理解为类似于一个编译器的东西,把源码通过各式各样的复杂的语义分析翻译成另一个玩意.但原本是拿来优化的,然后做安全的人拿来改为混淆,就是OLLVM项目,做安全不得好死......
  • Gemini 智能体agent功能又更新,现在支持写中文版文献综述了!
    我是娜姐@迪娜学姐,一个SCI医学期刊编辑,探索用AI工具提效论文写作和发表。上次介绍了最新的Geminideepresearch智能体写文献综述的功能,给定一个主题,可以生成一篇2-3000字带完整参考文献来源的文献综述,文献来源都是pubmed、PMC、OA期刊等真实文献。很多读者感兴趣,学员也......
  • RocketMQ 消息顺序与事务机制详解
    目录一、简介二、RocketMQ架构概述三、RocketMQ消息流转过程四、RocketMQ消息顺序与事务五、RocketMQ高可用性与扩展性六、RocketMQ应用场景七、总结一、简介RocketMQ是一款高吞吐量、高可扩展性的分布式消息中间件,由阿里巴巴开源,并已成为Apache的顶级项目......
  • [题解](更新中)AtCoder Beginner Contest 386(ABC386) A~E
    A-FullHouse2容易发现,答案为Yes\(\iff\)输入中恰好出现了\(2\)种不同的数,可以用set等数据结构来计算不同元素的个数。点击查看代码#include<bits/stdc++.h>usingnamespacestd;set<int>se;signedmain(){ for(inti=1,a;i<=4;i++){ cin>>a; se.insert(a); } c......
  • ♂hook初探♂(持续更新)
    主要是从攻防世界中的easyhook中学习,感觉好神奇.参考了以下博客:攻防世界逆向高手题之EASYHOOK-CSDN博客RE套路/从EASYHOOK学inlinehook-c10udlnk-博客园先贴源码:sub_401370(aPleaseInputFla);scanf("%31s",input_flag);if(strlen(input_flag)==19)......
  • C++(getchar())
    目录1.函数原型2.功能3.常见用法4.与getchar()的区别5.处理输入错误6.注意事项7.总结getchar()是C和C++中的一个标准输入函数,定义在头文件<cstdio>或<stdio.h>中。它用于从标准输入流(通常是键盘)读取一个字符。1.函数原型intgetchar(void);返回值:成......