window c++异常处理与设计原理

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了window c++异常处理与设计原理相关的知识,希望对你有一定的参考价值。

前言

本文需要基础SEH知识,如果不熟悉可以参阅作者其他文章:SEH 学习笔记链接

我们知道C++有异常语法如下所示:

#include<stdio.h>
#include<typeinfo>
class MyClass ;

int main(int argc, char* arg)


	try 
		printf(" try %d\\r\\n", argc);
		switch (argc)
		
		case 1: 
			throw 1;
			break;
		
		case 2: 
			throw MyClass();
			break;
		
		default:
			break;
		
	
	catch (int i) 
		printf("catch (int i) %d\\r\\n", i);
	
	catch (MyClass i) 
		printf("catch (MyClass i)\\r\\n");
	
	printf("xxxxxx\\r\\n");


	try 
		printf(" try22 %d\\r\\n", argc);
		switch (argc)
		
		case 1: 
			throw 1;
			break;
		
		case 2: 
			throw MyClass();
			break;
		
		default:
			break;
		
	
	catch (int i) 
		printf("catch222 (int i) %d\\r\\n", i);
	
	catch (MyClass i) 
		printf("catch2222 (MyClass i)\\r\\n");
	

	printf("program end \\r\\n");
	return 0;

windows中其实现本质采用SEH来完成的。
如果一上来全盘脱出汇编实现很难理解其本质思想,我们先自己设计实现在看VC编译器作者汇编实现更容易理解。

我们自定义实现c++异常的步骤:

  1. 在进入main函数的插入SEH处理器这样我们可以捕获这个函数的全部异常。
  2. 在编译扫描代码时我们可以记录一个表,给每个try记录一个条目并赋值编号,再触发异常的时候根据id找到条目进行下一步catch分发

    那么我们在代码中如何知道是try表中的id项处理呢?

我们可以编译插入局部变量 我们先取名为curtryID,当执行try函数体时赋值为对应try表ID,当发生异常时我们查看当前curtryID取出对应try表的某个条目,如果ID没有对应的证明当前代码行没有try包裹。

现在我们知道了哪些代码被try包裹但是我们无法感知异常发生时应该被try的哪个catch处理,因此我们需要在try表的每个条目下额外增加一个catch

(3) 构建catch表,当异常发生时判断类型,在catch表中获取对应类型进行寻找处理函数(利用RTTI)。

上面的逻辑我们绘制一个流程图来表示如下:

源码层查看try表

首先打开相关调试符号关联

相关Demo

class MyClass 

;

int main(int argc, char* arg)


	try 

		printf("try catch\\r\\n");
		//...略
		throw 3;
	
	catch (int i) 
		printf("catch (int i)\\r\\n");
	
	catch (MyClass i) 
		printf("catch (MyClass i)\\r\\n");
	

	return 0;

按照上节讲述代码应该会覆盖异常处理链条(fs:[0])


上面的代码可以简化用如下图所示

我们在跳转到0AC67E0h地址处:

我们首先在最后一行下一个断点00AC67FB jmp ___CxxFrameHandler3 (0AC137Fh)


单步步入最终到微软一个导入函数中。注意这里eax传入了一个地址,这个地址是一个对象地址,存储就是我们上文讨论的各种try表。

我们在VS安装目录下可以打开这个文件继续进行源码调试
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30133\\crt\\src\\i386\\trnsctrl.cpp"

查看上面的代码你就会明白FuncInfo就是我们传入的数据结构,我们先不讨论这个结构的具体细节,我们假设里面有表就行。
我们继续跟进函数

继续跟进相关函数

继续查看FindHandler





查看了相关流程后我们直接详细的查看FuncInfo对应的数据结构,我们只需要关心TryBlockMapEntry* pTryBlockMap;即可

/*
 * The magicNumber here is incremented with every compiler change that does not
 * break backwards compatibility. If for some reason backward compatibility
 * should be broken, then we will add new handler. What this means is that
 * current handler functions can assume that the structure layout that they
 * know about will remain the same and so even if magicNumber > my_magicNumber,
 * the handler can assume that what all it needs is there. The magicNumber will
 * be revised every time new data is added at the end of this structure.
 */
typedef const struct _s_FuncInfo

	unsigned int		magicNumber:29;		// Identifies version of compiler
	unsigned int		bbtFlags:3;			// flags that may be set by BBT processing
	__ehstate_t			maxState;			// Highest state number plus one (thus
											// number of entries in unwind map)
#if _EH_RELATIVE_FUNCINFO
	int					dispUnwindMap;		// Image relative offset of the unwind map
	unsigned int		nTryBlocks;			// Number of 'try' blocks in this function
	int					dispTryBlockMap;	// Image relative offset of the handler map
	unsigned int		nIPMapEntries;		// # entries in the IP-to-state map. NYI (reserved)
	int					dispIPtoStateMap;	// Image relative offset of the IP to state map
	int					dispUwindHelp;		// Displacement of unwind helpers from base
	int					dispESTypeList;		// Image relative list of types for exception specifications
#else
	UnwindMapEntry*		pUnwindMap;			// Where the unwind map is
	unsigned int		nTryBlocks;			// Number of 'try' blocks in this function
	TryBlockMapEntry*	pTryBlockMap;		// Where the handler map is
	unsigned int		nIPMapEntries;		// # entries in the IP-to-state map. NYI (reserved)
	void*				pIPtoStateMap;		// An IP to state map.  NYI (reserved).
	ESTypeList*			pESTypeList;		// List of types for exception specifications
#endif
	int					EHFlags;			// Flags for some features.
 FuncInfo;

TryBlockMapEntry* pTryBlockMap;是我们所有try表,可参阅如下代码,我们知道try表往往会嵌套一个catch表其内部表示就是HandlerType* pHandlerArray

//
// HandlerMapEntry - associates a handler list (sequence of catches) with a
//	range of eh-states.
//
typedef const struct _s_TryBlockMapEntry 
	__ehstate_t		tryLow;				// Lowest state index of try
	__ehstate_t		tryHigh;			// Highest state index of try
	__ehstate_t		catchHigh;			// Highest state index of any associated catch
	int				nCatches;			// Number of entries in array
#if _EH_RELATIVE_FUNCINFO
	int				dispHandlerArray;	// Image relative offset of list of handlers for this try
#else
	HandlerType* pHandlerArray;	// List of handlers for this try
#endif
 TryBlockMapEntry;

我们最后看catch项存储的信息至少有两个一个是异常处理类型TypeDescriptor* pTyp还有一个异常处理地址void * addressOfHandler;

//
// HandlerType - description of a single 'catch'
//
typedef const struct _s_HandlerType 
	unsigned int	adjectives;			// Handler Type adjectives (bitfield)
#if _EH_RELATIVE_FUNCINFO
	int				dispType;			// Image relative offset of the corresponding type descriptor
	int				dispCatchObj;		// Displacement of catch object from base
	int				dispOfHandler;		// Image relative offset of 'catch' code
#if defined(_WIN64) || defined(_CHPE_X86_ARM64_EH_)
	int				dispFrame;			// displacement of address of function frame wrt establisher frame
#endif
#else   // _EH_RELATIVE_FUNCINFO
	TypeDescriptor*	pType;				// Pointer to the corresponding type descriptor
	ptrdiff_t		dispCatchObj;		// Displacement of catch object from base
	void *			addressOfHandler;	// Address of 'catch' code
#endif // _EH_RELATIVE_FUNCINFO
 HandlerType;

JVAV异常表的对比

java异常可能就简单很多了 我们查看如下代码

package org.example;

public class Main 
    static void studyExceptionTable()
        try 
            System.out.printf("try");
             throw new Exception();
        catch (IllegalAccessException exception)
            System.out.printf("IllegalAccessException");
        catch (IllegalArgumentException e)
            System.out.printf("IllegalArgumentException");
        catch (Exception e)
            System.out.printf("Exception");
        
    
    public static void main(String[] args) 


    


由于java是依赖JVM进行跨平台因此相关数据结构需要较为通用的结构,所以异常表非常简单易懂,异常处理后直接拼接goto回到正常程序流程。

参考链接

https://docs.microsoft.com/en-us/cpp/cppcx/wrl/raiseexception-function?view=msvc-170
https://docs.microsoft.com/en-us/windows/win32/debug/getexceptioninformation
https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-exception_record

https://stackoverflow.com/questions/39113168/c-rtti-in-a-windows-64-bit-vectoredexceptionhandler-ms-visual-studio-2015

以上是关于window c++异常处理与设计原理的主要内容,如果未能解决你的问题,请参考以下文章

window c++异常处理与设计原理

C++软件异常分析与排查的学习历程

异常处理 C++

Atitit 异常机制与异常处理的原理与概论

Java中的异常处理机制的简单原理和应用。

使用源代码行信息处理 C++ 异常