一种通用跨平台实现SEH的解决方案

一. 前言

众所周知,在软件的代码中,处理软件本身的逻辑只要大约1/3的代码,另外2/3的代码实际上是在处理各种各样的异常情况。

这些异常情况一方面是因为不同用户之间不同的硬件软件环境要处理。另一方面是程序中可能出现的bug。比较典型的情况就是因为读取不到特定的信息,导致计算结果错误最后访问非法地址引起异常。

对于这些情况我们可以增加代码中的条件检查,增加大量的if判断来确定有没有出错,但是最会导致如果出错在一个比较深的子函数中,需要从多层函数返回,才能回到合适的错误处理流程中,而且大量额外的判断也会影响程序的性能。对此c++编译器和操作系统各自提供了自己的解决方案。

c++编译器提出的方案是提供了c++异常,它的本质上还是上面提到的条件判断然后在代码里边手动抛出异常,但是它的好处是异常可以自动跨函数回到用户设定的,合适的异常处理代码;但是如果出现了判断之外的异常比如说除零或者是访问非法地址之类的CPU异常只能引起程序崩溃。

操作系统的异常处理方案(Windows/MACOS/Linux这样的现代操作系统都有)能够处理CPU异常,但是它的问题是异常发生之后会跳转到注册的异常处理函数,这个函数是独立于产生异常代码的,无法精确定位异常到底出现在什么函数的什么位置,也无法处理资源回收清理事宜。

对此,VC编译器提出了结构化异常的解决方案。通过结构化异常,用户代码不需要增加条件判断就能处理包括CPU定义的异常之类的各种异常情况。还可以象C++异常那样,跳转到合适的堆栈环境去处理回收清理事务。可以说是完美的平衡了代码的简洁和安全性。

但是,事情总是有但是,不论是Linux/MACOS还是Mingw环境的编译器都不支持结构化异常。程序员必须自己维护大量的环境来妥善处理异常事务。而对于Visual LVM和Blue Print这种后台需要跨平台运行的软件更是一场灾难。明明在Windows平台上使用VC编译器有很简单的方案可以解决问题,但是,其他平台的部分却需要增加大量的代码去处理,实在是大大增加了开发的成本。

出于降低开发成本,优化代码的目的。本文就来讨论一下如何在使用非VC编译器的情况下模拟实现VC编译器自带的结构化异常处理方案。这样可以将各个平台上的异常处理代码统一起来,实现不同平台上的源代码级兼容。

二. 准备工作

我们首先考虑的是Linux平台。在Linux平台上可以注册异常处理函数,去截获各种异常,那么要解决的就是当异常发生的时候,如何跳转到用户定义的异常处理函数中,甚至最好的情况就是在出现异常的函数体内去做处理。

这种时候就需要用到所有操作系统都提供的两个系统函数setjmp和longjmp。

int setjmp(jmp_buf env);           // 保存当前执行环境void longjmp(jmp_buf env,int val); // 跳转到 setjmp 保存的位置

. jmp_buf:一个特殊的数据结构,用于保存程序当前的执行环境(如寄存器、栈指针等)。

. setjmp:保存调用函数时的代码位置及堆栈环境,首次调用时返回0

               当通过longjmp跳转到env中保存的代码位置时,返回longjmp提供的val

. longjmp:跳转到从setjmp(env)返回时的位置,并且返回值是val

查看操作系统提供的系统函数说明,我们可以看到setjmp函数会保存当前运行环境(指令位置,寄存器信息和堆栈环境信息),而longjmp函数可以跨函数跳转到setjmp保存的代码位置,并且恢复那一时刻的运行环境

下面我们写一个简单的使用setjmp/longjmp函数的例子。

#include <stdio.h>
#include <setjmp.h>jmp_buf jump_buffer;void foo()
{printf("准备触发跳转...\n");longjmp(jump_buffer, 42);  // 跳转到 setjmp,并返回 42
}int main()
{int ret=setjmp(jump_buffer);  // 首次调用返回 0,在调用longjmp跳转后返回 42if(!ret){printf("首次调用 setjmp\n");foo();}else{printf("从 longjmp 返回,返回码: %d\n", ret);}return 0;
}

运行结果如下:

首次调用 setjmp

准备触发跳转...

从 longjmp 返回,返回码: 42

从运行结果我们可以看到,代码的运行顺序是

ret=setjmp(jump_buffer);

foo()

ret=setjmp(jump_buffer); //注意这里其实是从foo()里的longjmp调过来的。

printf("从 longjmp 返回,返回码: %d\n", ret);

现在,基于异常处理函数和上述的两个API来模拟结构化异常处理的逻辑就很清晰了。

在开始模拟之前,先对各个区域作一个简要定义

__try                        ----------------------------+
{                            ------------+               ||               |//User code                          +异常监测块      ||               |
}                            ------------+               ||
__except(ExceptionFilter)    ------------+异常过滤函数    |异常处理块|
{                            ------------+               ||               |//Do clean work                      +清理回收块      ||               |
}                            ------------+               |----------------------------+

三.第一次模拟

在进入异常监测块的时候,执行setjmp保存当前运行环境和位置,再注册异常处理信息,然后运行代码。如果代码运行正常,在异常监测块结束的时候,注销记录的异常处理信息;如果出现异常,在异常处理函数中间使用longjmp跳转到我们记录的运行位置,即回到出现异常的函数(也可能是出现异常的函数的某个适合做清理工作的父函数)在处理完清理回收信息之后,清除之前记录的运行信息,并继续运行。

下面我们可以写出以下的代码:

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>jmp_buf SectionEntry; //用于保存异常监视区入口以及运行环境struct sigaction SignalHandler; //用于保存当前异常处理记录struct sigaction OldHandler; //用于保存旧异常处理记录static void _ExceptionHandler(int iSignal,siginfo_t *pSignalInfo,void *pContext)
//异常处理函数,在本例中是直接跳转到异常监视区开始位置,并利用longjmp函数跳转到异常环境回收例程
{printf("    Got SIGSEGV at address: %lXH, %p\n",(long) pSignalInfo->si_addr,pContext);siglongjmp(SectionEntry,1); //跳转到记录的异常监视块开始处
}int StartSEHService(void)
//异常处理初始化函数,应该程序启动之后运行
{SignalHandler.sa_sigaction=_ExceptionHandler;//指向我们定义的异常处理函数if(-1==sigaction(SIGSEGV,&SignalHandler,&OldHandler)) //注册自己的异常处理函数并保存原来的异常处理函数{perror("Register sigaction fail");return 0;}else{return 1;}
}int main(void)
{char sz[]="Exception critical section & exception clean up section was run in THE SAME FUNCTION, I show the same information as proof.";//用于展示异常清理块跟异常监测块拥有相同环境的字符串StartSEHService();//初始化异常处理服务,在程序开始时调用一次printf("--------------Virtual SEH Test Start--------------\n");{printf("  +Enter critical section @ %s %d\n",__FILE__,__LINE__); //进入异常监视区if(!sigsetjmp(SectionEntry,1)) //保存当前运行位置以及环境信息{ //setjmp返回0表示刚刚完成了环境保存工作,下面运行可能产生异常的代码//------------------------------- Exception monitor block start -------------------------------printf("Hello, we go into exception monitor section\n  This is the local string :'%s'\n",sz);printf("  Let's do some bad thing\n");*((char*)0)=0;//产生异常//------------------------------- Exception monitor block end -------------------------------printf("  -Leave critical section @ %s %d\n",__FILE__,__LINE__); //这一行其实不会运行}else{ //非0表示是从longjmp函数中跳转过来(在注册的系统异常处理程序中我们设置longjmp的参数是1),在本例中表明发生了异常并无法恢复运行,执行环境清理工作//------------------------------- Exception clean block start -------------------------------printf("  Exception occur! do clean work\n  and we can access local data in the same environment of exception critical section:\n'%s'\n",sz);//------------------------------- Exception clean block end -------------------------------}}sigaction(SIGSEGV,&OldHandler,&OldHandler); //程序结束,恢复原有异常处理函数
}

运行结果如下:

--------------Virtual SEH Test Start--------------

  +Enter critical section @ test1.cpp 37

Hello, we go into exception monitor section

  This is the local string :'Exception critical section & exception clean up section was run in THE SAME FUNCTION, I show the same information as proof.'

  Let's do some bad thing

    Got SIGSEGV at address: 0H, 0x7fff3b6627c0

  Exception occur! do clean work

  and we can access local data in the same environment of exception critical section:

'Exception critical section & exception clean up section was run in THE SAME FUNCTION, I show the same information as proof.'

恭喜!你进行了第一次在产生异常的函数内处理异常后环境清理的工作。

我们理一次上述代码的基本逻辑:

  1. 注册系统异常处理函数
  2. 调用sigsetjmp保存代码运行位置和运行环境
  3. 触发异常
  4. 系统调用注册的异常处理函数
  5. 异常处理函数调用siglongjmp回到保存的代码位置
  6. main函数中的异常处理例程显示与触发异常的代码运行在相同环境

四.渐入佳境

既然基本逻辑走通了,那么我们就可以准备模拟SEH的工作了。

众所周知,SEH的基本代码结构是

__try
{//Exception critical section
}
__except(ExceptionFilter)
{//Clean work section
}

异常监测块

注册异常拦过滤函数(独立函数)

环境清理块

再看看我们上面写的代码

大致结构是

异常处理机制初始化(程序启动时运行一次)

保存异常监测块/清理块入口位置和环境

进入异常监测块

产生异常

异常清理块

既然我们是要模拟SEH以实现源代码级跨平台兼容,而SEH是微软编译器内嵌支持的,因此它最大,只能是别的平台迁就它。

所以第一步我们需要增加独立的异常过滤函数

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>//SEH中异常过滤函数有3个返回值,这里我们先把它们的定义放上来#define EXCEPTION_EXECUTE_HANDLER       1 //异常无法修复,运行环境清理回收例程
#define EXCEPTION_CONTINUE_SEARCH       0 //异常无法修复,尝试搜索上一级异常处理块
#define EXCEPTION_CONTINUE_EXECUTION    -1 //异常修复,恢复运行产生异常的代码//既然跟异常相关的数据多起来了,我们定义一个结构用于存储异常处理块相关的信息typedef struct _EXCEPTION_NODE
{jmp_buf SectionEntry;//保存异常监视区入口以及运行环境int     (*FilterRoutine)(int iSignal,siginfo_t *pSignalInfo,void *pContext);//异常过滤函数指针
}EXCEPTION_NODE,*PEXCEPTION_NODE;EXCEPTION_NODE ExceptionNode;//异常监视区信息块struct sigaction SignalHandler; //当前异常处理块struct sigaction OldHandler; //旧异常处理块static void _ExceptionHandler(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{ //异常处理函数,在本例中是直接跳转到异常监视区开始位置,并利用setjmp/longjmp函数的特性跳转到异常环境回收例程printf("    Got SIGSEGV at address: %lXH, %p\n",(long) pSignalInfo->si_addr,pContext);ExceptionNode.FilterRoutine(iSignal,pSignalInfo,pContext);siglongjmp(ExceptionNode.SectionEntry,1); //跳转到记录的异常监视块开始处
}int StartSEHService(void)
{ //异常处理初始化函数,应该程序启动之后运行SignalHandler.sa_sigaction=_ExceptionHandler;if(-1==sigaction(SIGSEGV,&SignalHandler,&OldHandler)){ //注册自己的异常处理函数perror("Register sigaction fail");return 0;}else{return 1;}
}//异常过滤函数
int ExceptionFilter(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{printf("Exception occur! We cannot fix it now.\n");return EXCEPTION_EXECUTE_HANDLER;//这里我们先强制返回这个值
}int main(void)
{int  iResult;char sz[]="Exception critical section & exception clean up section was run in THE SAME FUNCTION, I show the same information as proof.";//用于展示异常清理块跟异常颢块拥有相同环境的客串StartSEHService();//初始化异常处理服务,在程序开始时调用一次printf("--------------Virtual SEH Test Start--------------\n");{printf("  +Enter critical section @ %s %d\n",__FILE__,__LINE__); //进入异常监视区iResult=sigsetjmp(ExceptionNode.SectionEntry,1); //保存当前运行位置以及环境信息if(2==iResult){ //setjmp返回2表示已经完成了异常过滤例程注册工作,下面运行可能产生异常的高危代码//------------------------------- Exception monitor block start -------------------------------printf("Hello, we go into exception monitor section\n  This is the local string :'%s'\n",sz);printf("  Let's do some bad thing\n");*((char*)0)=0;//------------------------------- Exception monitor block end -------------------------------printf("  -Leave critical section @ %s %d\n",__FILE__,__LINE__); //这一行其实不会运行}else if(!iResult){ //setjmp返回0表示刚刚完成了环境保存工作,此时我们需要注册异常过滤例程printf("  *Register exception filter @ %s %d\n",__FILE__,__LINE__);ExceptionNode.FilterRoutine=ExceptionFilter;//注册异常过滤函数siglongjmp(ExceptionNode.SectionEntry,2); //跳转到异常监视块开始处    }else{ //非0/2表示是从系统异常处理程序中的longjmp函数跳转过来,在本例中表明发生了异常并无法恢复运行,处理善后工作//------------------------------- Exception clean block start -------------------------------printf("  Exception occur! do clean work\n  and we can access local data in the same environment of exception critical section:\n'%s'\n",sz);//------------------------------- Exception clean block end -------------------------------}}sigaction(SIGSEGV,&OldHandler,&OldHandler); //恢复原有异常处理函数
}

上述代码的基本结构是

信号(异常)处理函数

异常过滤函数

异常监测块

注册异常拦过滤函数(独立函数)

异常清理块

这里我们可以看到,已经跟SEH一样了。

既然基本结构已经相同了,那么我们把异常维护相关的代码放进宏定义里,现在代码变成这个样子了:

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>//SEH中异常过滤函数有3个返回值,这里我们先把它们的定义放上来#define EXCEPTION_EXECUTE_HANDLER       1#define EXCEPTION_CONTINUE_SEARCH       0#define EXCEPTION_CONTINUE_EXECUTION    -1//既然跟异常相关的数据多起来了,我们定义一个结构用于存储异常块相关的信息
typedef struct _EXCEPTION_NODE
{jmp_buf SectionEntry;//保存异常监视区入口以及运行环境int     (*FilterRoutine)(int iSignal,siginfo_t *pSignalInfo,void *pContext);//异常过滤函数指针
}EXCEPTION_NODE,*PEXCEPTION_NODE;EXCEPTION_NODE ExceptionNode;//异常监视区信息块struct sigaction SignalHandler; //当前异常处理块struct sigaction OldHandler; //旧异常处理块static void _ExceptionHandler(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{ //异常处理函数,在本例中是直接跳转到异常监视区开始位置,并利用setjmp/longjmp函数的特性跳转到异常环境回收例程printf("    Got SIGSEGV at address: %lXH, %p\n",(long) pSignalInfo->si_addr,pContext);ExceptionNode.FilterRoutine(iSignal,pSignalInfo,pContext);siglongjmp(ExceptionNode.SectionEntry,1); //跳转到记录的异常监视块开始处
}int StartSEHService(void)
{ //异常处理初始化函数,应该程序启动之后运行SignalHandler.sa_sigaction=_ExceptionHandler;if(-1==sigaction(SIGSEGV,&SignalHandler,&OldHandler)){ //注册自己的异常处理函数perror("Register sigaction fail");return 0;}else{return 1;}
}//异常过滤函数
int ExceptionFilter(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{printf("Exception occur! We cannot fix it now.\n");return EXCEPTION_EXECUTE_HANDLER;//这里我们先强制返回这个值
}#define TRY_START \int iResult; \iResult=sigsetjmp(ExceptionNode.SectionEntry,1); /*保存当前运行位置以及环境信息*/ \if(2==iResult) \/*setjmp返回2表示已经完成了异常过滤例程注册工作,下面运行可能产生异常的高危代码*/#define TRY_EXCEPT(filter) \else if(!iResult) \{ /*setjmp返回0表示刚刚完成了环境保存工作,此时我们需要注册异常过滤例程*/ \printf("  *Register exception filter @ %s %d\n",__FILE__,__LINE__); \ExceptionNode.FilterRoutine=filter;/*注册异常过滤函数*/ \siglongjmp(ExceptionNode.SectionEntry,2); /*跳转到异常监视块开始处*/ \} \else \/*非0/2表示是从系统异常处理程序中的longjmp函数跳转过来,在本例中表明发生了异常并无法恢复运行,处理善后工作*/int main(void)
{char sz[]="Exception critical section & exception clean up section was run in THE SAME FUNCTION, I show the same information as proof.";//用于展示异常清理块跟异常颢块拥有相同环境的客串StartSEHService();printf("--------------Virtual SEH Test Start--------------\n");TRY_START{printf("  +Enter critical section @ %s %d\n",__FILE__,__LINE__); //进入异常监视区//------------------------------- Exception monitor block start -------------------------------printf("Hello, we go into exception monitor section\n  This is the local string :'%s'\n",sz);printf("  Let's do some bad thing\n");*((char*)0)=0;//------------------------------- Exception monitor block end -------------------------------printf("  -Leave critical section @ %s %d\n",__FILE__,__LINE__); //这一行其实不会运行}TRY_EXCEPT(ExceptionFilter){//------------------------------- Exception clean block start -------------------------------printf("  Exception occur! do clean work\n  and we can access local data in the same environment of exception critical section:\n'%s'\n",sz);//------------------------------- Exception clean block end -------------------------------}sigaction(SIGSEGV,&OldHandler,&OldHandler); //恢复原有异常处理函数
}

现在除开初始化异常服务和相关的函数,程序主体结构看上去跟SEH几乎一模一样了。

但是,事情并没有这么简单。

首先,在前面我们提到SEH支持在异常过滤例程中有3种返回信息。我们需要在系统异常处理程序中增加对异常过滤函数返回不同值之后的相应处理。其次,SEH是支持嵌套的,所以还需要支持向后搜索合适的异常处理块,因此我们需要为注册的SEH信息建立一个链表。同时,在退出异常处理块(修复失败并清理或正常结束)之后,还要注销当前异常处理块的信息。

所以我们还需要增加很多内容:

修改后的代码如下

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>//3种SEH中异常过滤函数返回值#define EXCEPTION_EXECUTE_HANDLER       1#define EXCEPTION_CONTINUE_SEARCH       0#define EXCEPTION_CONTINUE_EXECUTION    -1//既然跟异常相关的数据多起来了,我们定义一个结构用于存储异常块相关的信息
typedef struct _EXCEPTION_NODE
{struct _EXCEPTION_NODE *Prev;//异常处理块链int                    RunStatus;//前述例子里的运行状态(0:完成运行环境记录,1:异常无法修复,转到清理块运行,2:异常信息块注册成功,跳转到异常监测区运行jmp_buf                SectionEntry;//保存异常监视区入口以及运行环境int                    (*FilterRoutine)(int iSignal,siginfo_t *pSignalInfo,void *pContext);//异常过滤函数指针
}EXCEPTION_NODE,*PEXCEPTION_NODE;static struct _SEH_SET
{PEXCEPTION_NODE  Chain;struct sigaction SignalHandler,OldHandler;int              Initialized;
}s_Maintain={0};//SEH管理块static void _ExceptionHandler(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{ //异常处理函数,在本例中是直接跳转到异常监视区开始位置,并利用setjmp/longjmp函数的特性跳转到异常环境回收例程int             iResult;PEXCEPTION_NODE pEntry,pPrev;printf("    Got SIGSEGV at address: %lXH, %p\n",(long) pSignalInfo->si_addr,pContext);pEntry=s_Maintain.Chain;//准备遍历注册的异常处理块do{iResult=pEntry->FilterRoutine(iSignal,pSignalInfo,pContext);//调当前异常块的异常过滤函数switch(iResult){case EXCEPTION_EXECUTE_HANDLER://当前异常无法解决,执行善后清理工作s_Maintain.Chain=pEntry->Prev;//注销当前异常处理块siglongjmp(pEntry->SectionEntry,1);//跳转到注册异常块的善后清理入口break;case EXCEPTION_CONTINUE_SEARCH://注册异常块无法处理当前异常,尝试上一级异常处理pPrev=pEntry->Prev;s_Maintain.Chain=pEntry->Prev;//既然需要到上一级异常块处理,那么当前异常块已经无用了,注销当前异常块pEntry=pPrev;//转到上一级异常处理块break;case EXCEPTION_CONTINUE_EXECUTION://异常修复完成,返回异常发生处继续运行return;break;default://异常过滤程序返回了错误的值printf("Bad exception filter result %d\n",iResult);break;}}while(pEntry);printf("    No more handler\n");sigaction(SIGSEGV,&s_Maintain.OldHandler,NULL);
}int StartSEHService(void)
{ //异常处理初始化函数,应该程序启动之后运行s_Maintain.SignalHandler.sa_sigaction=_ExceptionHandler;if(-1==sigaction(SIGSEGV,&s_Maintain.SignalHandler,&s_Maintain.OldHandler)){ //注册自己的异常处理函数perror("Register sigaction fail");return 0;}else{s_Maintain.Initialized=1;return 1;}
}//异常过滤函数
int ExceptionFilter(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{printf("Exception occur! We cannot fix it now.\n");return EXCEPTION_EXECUTE_HANDLER;//这里我们先强制返回这个值
}int main(void)
{char sz[]="Exception critical section & exception clean up section was run in THE SAME FUNCTION, I show the same information as proof.";//用于展示异常清理块跟异常颢块拥有相同环境的客串printf("--------------Virtual SEH Test Start--------------\n");printf("  +Enter critical section @ %s %d\n",__FILE__,__LINE__); //进入异常监视区/* TRY_START */{EXCEPTION_NODE __weLees_ExceptionNode;__weLees_ExceptionNode.Prev=NULL;if(!s_Maintain.Initialized){StartSEHService();}__weLees_ExceptionNode.Prev=s_Maintain.Chain;s_Maintain.Chain=&__weLees_ExceptionNode;__weLees_ExceptionNode.RunStatus=sigsetjmp(__weLees_ExceptionNode.SectionEntry,1); //保存当前运行位置以及环境信息if(2==__weLees_ExceptionNode.RunStatus)
/* TRY_START end */{ //setjmp返回2表示已经完成了异常过滤例程注册工作,下面运行可能产生异常的高危代码//------------------------------- Exception monitor block start -------------------------------printf("Hello, we go into exception monitor section\n  This is the local string :'%s'\n",sz);printf("  Let's do some bad thing\n");*((char*)0)=0;//------------------------------- Exception monitor block end -------------------------------printf("  -Leave critical section @ %s %d\n",__FILE__,__LINE__); //这一行其实不会运行s_Maintain.Chain=__weLees_ExceptionNode.Prev;//注销异常块}
/* TRY_EXCEPT(filter) */else if(!__weLees_ExceptionNode.RunStatus){ //setjmp返回0表示刚刚完成了环境保存工作,此时我们需要注册异常过滤例程printf("  *Register exception filter @ %s %d\n",__FILE__,__LINE__);__weLees_ExceptionNode.FilterRoutine=ExceptionFilter;//注册异常过滤函数siglongjmp(__weLees_ExceptionNode.SectionEntry,2); //跳转到异常监视块开始处}else//非0/2表示是从系统异常处理程序中的longjmp函数跳转过来,在本例中表明发生了异常并无法恢复运行,处理善后工作
/* TRY_EXCEPT(filter) end */{//------------------------------- Exception clean block start -------------------------------printf("  Exception occur! do clean work\n  and we can access local data in the same environment of exception critical section:\n'%s'\n",sz);//------------------------------- Exception clean block end -------------------------------}
/* TRY_END */}
/* TRY_END end */sigaction(SIGSEGV,&s_Maintain.OldHandler,&s_Maintain.OldHandler); //恢复原有异常处理函数
}

这一次的代码中我们为了更直观地展示代码结构,没有把异常块注册/注销部分提取到宏里,而是用注释标记出了它们的内容和在代码中的位置。

其中异常区要注意以下几点

  1. 首先,每个异常处理块需要定义一个EXCEPTION_NODE结构用于记录相关信息。既然这个结构只在异常处理块范围内使用,那么完全不需要额外为它分配空间,只需要把它作为一个局部变量放到函数栈里。
  2. 首先用花括号把整个异常处理块包起来是有必要的,为了注册异常处理块,需要函数非开头位置定义异常登记块变量,使用花括号包起来可以避免在在某些编译器上编译失败,以及确定异常块的作用域,即在异常嵌套时,内部异常处理块定义的异常登记块只属于内部异常处理块,不会影响外层代码。
  3. 为此增加了一个额外的TRY_END块以保证代码正确,它的内容就是一个右花括号。
  4. 异常注册块定义一个比较长的,用户不容易使用的变量名,避免跟用户定义的变量发生冲突。
  5. 上述代码中用于识别异常监视块初始化状态的iResult变量移动到异常注册块中,同样是为了减少变量名冲突。

除了对异常监视区大改动之外,对系统异常处理函数也做了大改。

  1. 为了支持异常处理链,增加了遍历注册的异常处理链操作
  2. 调用异常过滤例程之后,分别按不同的返回值做出处理。

异常过滤例程返回的合法返回值有3种

EXCEPTION_EXECUTE_HANDLER(1)

    当前异常无法解决,需要跳转到当前异常处理块的异常清理块,执行清理回收工作。

    对这种情况,异常处理程序要做的是:

        注销当前异常块

        跳转到注册异常处理块的异常清理块入口

EXCEPTION_CONTINUE_SEARCH(0)

    注册异常块无法处理当前异常,尝试上一级异常处理

    对这种情况,异常处理程序要做的是:

        因为既然要跳转到上一级异常处理块,那么当前块所在的运行环境都会失效,所以注销当前异常块

        把上一级异常处理块设置为当前异常处理块

        循环,继续调用(上一层异常处理块的)异常过滤函数

EXCEPTION_CONTINUE_EXECUTION(-1)

    异常修复完成,返回异常发生处继续运行,通常用于调试器

    对这种情况,异常处理程序要做的是:

        直接返回,让系统继续运行产生异常的代码

五.完全状态

经过上述改动,SEH的基本要点都已经实现了。

下面就让我们把相关的代码移动到宏里面,来进行实战环境中的异常处理测试。

以下是最终的模拟SEH服务和测试代码

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>//SEH中异常过滤函数有3个返回值,这里我们先把它们的定义放上来#define EXCEPTION_EXECUTE_HANDLER       1#define EXCEPTION_CONTINUE_SEARCH       0#define EXCEPTION_CONTINUE_EXECUTION    -1//既然跟异常相关的数据多起来了,我们定义一个结构用于存储异常块相关的信息
typedef struct _EXCEPTION_NODE
{struct _EXCEPTION_NODE *Prev;int                    RunStatus;jmp_buf                SectionEntry;//保存异常监视区入口以及运行环境int                    (*FilterRoutine)(int iSignal,siginfo_t *pSignalInfo,void *pContext);//异常过滤函数指针
}EXCEPTION_NODE,*PEXCEPTION_NODE;static __thread struct _SEH_SET
{PEXCEPTION_NODE  Chain;struct sigaction SignalHandler,OldHandler;int              Initialized;
}s_Maintain={0};//SEH管理块static void _ExceptionHandler(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{ //异常处理函数,在本例中是直接跳转到异常监视区开始位置,并利用setjmp/longjmp函数的特性跳转到异常环境回收例程int             iResult;PEXCEPTION_NODE pEntry,pPrev;printf("    Got SIGSEGV at address: %lXH, %p\n",(long) pSignalInfo->si_addr,pContext);pEntry=s_Maintain.Chain;//准备遍历注册的异常处理块do{iResult=pEntry->FilterRoutine(iSignal,pSignalInfo,pContext);//调当前异常块的异常过滤函数switch(iResult){case EXCEPTION_EXECUTE_HANDLER://当前异常无法解决,执行善后清理工作s_Maintain.Chain=pEntry->Prev;//注销当前异常块siglongjmp(pEntry->SectionEntry,1);//跳转到注册异常块的善后清理入口break;case EXCEPTION_CONTINUE_SEARCH://注册异常块无法处理当前异常,尝试上一级异常处理pPrev=pEntry->Prev;s_Maintain.Chain=pEntry->Prev;//既然需要到上一级异常块处理,那么当前异常块已经无用了,注销当前异常块pEntry=pPrev;//转到上一级异常处理块break;case EXCEPTION_CONTINUE_EXECUTION://异常修复完成,返回异常发生处继续运行return;break;default://异常过滤程序返回了错误的值printf("Bad exception filter result %d\n",iResult);break;}}while(pEntry);printf("    No more handler\n");sigaction(SIGSEGV,&s_Maintain.OldHandler,NULL);
}int StartSEHService(void)
{ //异常处理初始化函数,应该程序启动之后运行s_Maintain.SignalHandler.sa_sigaction=_ExceptionHandler;if(-1==sigaction(SIGSEGV,&s_Maintain.SignalHandler,&s_Maintain.OldHandler)){ //注册自己的异常处理函数perror("Register sigaction fail");return 0;}else{s_Maintain.Initialized=1;return 1;}
}#define TRY_START \
{ \EXCEPTION_NODE __weLees_ExceptionNode; \__weLees_ExceptionNode.Prev=NULL; \if(!s_Maintain.Initialized) \{ \StartSEHService(); \} \__weLees_ExceptionNode.Prev=s_Maintain.Chain; \s_Maintain.Chain=&__weLees_ExceptionNode; \__weLees_ExceptionNode.RunStatus=sigsetjmp(__weLees_ExceptionNode.SectionEntry,1); /*保存当前运行位置以及环境信息*/ \if(2==__weLees_ExceptionNode.RunStatus) \{#define TRY_EXCEPT(filter) \s_Maintain.Chain=__weLees_ExceptionNode.Prev; \} \else if(!__weLees_ExceptionNode.RunStatus) \{/*setjmp返回0表示刚刚完成了环境保存工作,此时我们需要注册异常过滤例程*/ \printf("  *Register exception filter @ %s %d\n",__FILE__,__LINE__); \__weLees_ExceptionNode.FilterRoutine=filter;/*注册异常过滤函数*/ \siglongjmp(__weLees_ExceptionNode.SectionEntry,2);/*跳转到异常监视块开始处*/ \} \else/*非0/2表示是从系统异常处理程序中的longjmp函数跳转过来,在本例中表明发生了异常并无法恢复运行,处理善后工作*/#define TRY_END }//异常过滤函数
int ExceptionFilter1(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{printf("Test1 : Exception occur! We cannot fix it now.\n");return EXCEPTION_EXECUTE_HANDLER;//这里我们先强制返回这个值
}void Test1(void)
{char sz[]="Exception critical section & exception clean up section was run in THE SAME FUNCTION, I show the same information as proof.";printf("--------------Virtual SEH Test1 : simple exception handling--------------\n");printf("  +Enter critical section @ %s %d\n",__FILE__,__LINE__); //进入异常监视区TRY_START{ //setjmp返回2表示已经完成了异常过滤例程注册工作,下面运行可能产生异常的高危代码//------------------------------- Exception monitor block start -------------------------------printf("Test1 : Hello, we go into exception monitor section\n  This is the local string :'%s'\n",sz);printf("Test1 :   Let's do some bad thing\n");*((char*)0)=0;//------------------------------- Exception monitor block end -------------------------------printf("  -Leave critical section @ %s %d\n",__FILE__,__LINE__); //这一行其实不会运行}TRY_EXCEPT(ExceptionFilter1){//------------------------------- Exception clean block start -------------------------------printf("Test1 :   Exception occur! do clean work\n  and we can access local data in the same environment of exception critical section:\n'%s'\n",sz);//------------------------------- Exception clean block end -------------------------------}TRY_END
}//异常过滤函数
int ExceptionFilter2(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{printf("Test2 : Exception occur! We cannot fix it now.\n");return EXCEPTION_EXECUTE_HANDLER;//这里我们先强制返回这个值
}void Test2(void)
{char sz[]="Exception critical section & exception clean up section was run in THE SAME FUNCTION, I show the same information as proof.";printf("\n--------------Virtual SEH Test2 : nested exception handling--------------\n");TRY_START{ //setjmp返回2表示已经完成了异常过滤例程注册工作,下面运行可能产生异常的高危代码//------------------------------- Exception monitor block start -------------------------------printf("Test2 : +Enter outer critical section @ %s %d\n",__FILE__,__LINE__); //进入异常监视区TRY_START{ //setjmp返回2表示已经完成了异常过滤例程注册工作,下面运行可能产生异常的高危代码//------------------------------- Exception monitor block start -------------------------------printf("Test2 : +Enter inner critical section @ %s %d\n",__FILE__,__LINE__); //进入异常监视区printf("Test2 : Hello, we go into exception monitor section\n  This is the local string :'%s'\n",sz);printf("Test2 :   Let's do some bad thing\n");*((char*)0)=0;//------------------------------- Exception monitor block end -------------------------------printf("  -Leave critical section @ %s %d\n",__FILE__,__LINE__); //这一行其实不会运行}TRY_EXCEPT(ExceptionFilter2){//------------------------------- Exception clean block start -------------------------------printf("Test2 :   Exception occur in INNER level! do clean work\n  and we can access local data in the same environment of exception critical section:\n'%s'\n",sz);//------------------------------- Exception clean block end -------------------------------}TRY_END}TRY_EXCEPT(ExceptionFilter2){//------------------------------- Exception clean block start -------------------------------printf("Test2 :   Exception occur in outer level! do clean work\n  and we can access local data in the same environment of exception critical section:\n'%s'\n",sz);//------------------------------- Exception clean block end -------------------------------}TRY_END
}//异常过滤函数
int ExceptionFilter3(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{printf("Test3 : Exception occur! We cannot fix it now.\n");return EXCEPTION_EXECUTE_HANDLER;//这里我们先强制返回这个值
}//异常过滤函数
int ExceptionFilter3_1(int iSignal,siginfo_t *pSignalInfo,void *pContext)
{printf("Test3 : Exception occur! We cannot fix it now, try upper level fixing.\n");return EXCEPTION_CONTINUE_SEARCH;//这里我们先强制返回这个值
}void Test3(void)
{char sz[]="Exception critical section & exception clean up section was run in THE SAME FUNCTION, I show the same information as proof.";printf("\n--------------Virtual SEH Test3 : nested exception handling-try outer exception handler --------------\n");TRY_START{ //setjmp返回2表示已经完成了异常过滤例程注册工作,下面运行可能产生异常的高危代码//------------------------------- Exception monitor block start -------------------------------printf("Test3 : +Enter outer critical section @ %s %d\n",__FILE__,__LINE__); //进入异常监视区TRY_START{ //setjmp返回2表示已经完成了异常过滤例程注册工作,下面运行可能产生异常的高危代码//------------------------------- Exception monitor block start -------------------------------printf("Test3 : +Enter inner critical section @ %s %d\n",__FILE__,__LINE__); //进入异常监视区printf("Test3 : Hello, we go into exception monitor section\n  This is the local string :'%s'\n",sz);printf("Test3 :   Let's do some bad thing\n");*((char*)0)=0;//------------------------------- Exception monitor block end -------------------------------printf("  -Leave critical section @ %s %d\n",__FILE__,__LINE__); //这一行其实不会运行}TRY_EXCEPT(ExceptionFilter3_1){//------------------------------- Exception clean block start -------------------------------printf("Test3 :   Exception occur in INNER level! do clean work\n  and we can access local data in the same environment of exception critical section:\n'%s'\n",sz);//------------------------------- Exception clean block end -------------------------------}TRY_END}TRY_EXCEPT(ExceptionFilter3){//------------------------------- Exception clean block start -------------------------------printf("Test3 :   Exception occur in outer level! do clean work\n  and we can access local data in the same environment of exception critical section:\n'%s'\n",sz);//------------------------------- Exception clean block end -------------------------------}TRY_END
}int main(void)
{Test1();Test2();Test3();sigaction(SIGSEGV,&s_Maintain.OldHandler,&s_Maintain.OldHandler); //恢复原有异常处理函数
}

代码中一共有三个测试例子。

第一个是简单的异常测试。

第二个是嵌套异常,内层代码触发异常并处理。

第三个是嵌套异常,内层代码触发异常后,异常过滤例程无法处理异常,请求使用外层异常处理。当然这里没有测试异常修复并重新运行的情况,在Linux/OSX取得异常详细信息比较复杂,本文就不就此展开了。

以上代码在Ubuntu和OSX/x86_64上测试运行通过。理论上该代码能兼容FreeBSD。只需要在FreeBSD上重新编译即可。

至此,我们在非Windows平台上模拟SEH实现基本完成了。对于Windows平台和VC编译器,只需要定义以下宏:

#define TRY_START __try#define TRY_EXCEPT(filter) __except(filter(GetExceptionInformation()))#define TRY_END

即可实现在所有平台中以统一格式

TRY_START
{//work code}
TRY_EXCEPT(filter)
{//clean up code}
TRY_END

实现跟SEH相同的异常处理流程。

当然,各个平台上异常过滤函数的声明和参数各不相同,需要分别处理。

六.一点补充

即可实现Windows/Linux/OSX等平台代码的源代码级兼容。接下来要注意的是三点:

1. SEH服务是线程独立的,因此在定义异常处理头结构s_Maintain时需要加上__thread/thread_local(C++11)关键字,以保证这个结构是线程独立的。

2. 上述代码中没有考虑到从异常监视区中退出循环,即break指令和goto指令。

3. 上述代码中没有考虑到从异常监视区中退出函数,即return指令。

因为VC在实现SEH时有编译器支持,它会在任意退出指令前加上注销异常处理块的代码,而我们没有编译器支持,只能从代码上想办法,因此对于goto指令,在反复考虑过各种方法之后,只能放弃支持,毕竟c/c++规则中也建议不使用该指令。

对于break指令,执行前需要注销循环中所有要退出的try/except异常处理块,因此需要提前知晓循环中到底有几级异常监测块。不过考虑到同一函数中的单个循环中,通常不会嵌套过多的异常监测块,因此我们提供了以下几个宏,基本能应对绝大部分情况。

#define TRY_BREAK s_Maintain.Chain=s_Maintain.Chain->Prev;break //循环中只有一层异常监视块#define TRY_BREAK2 \s_Maintain.Chain=s_Maintain.Chain->Prev; \s_Maintain.Chain=s_Maintain.Chain->Prev; \break /*循环中有二层异常监视块*/#define TRY_BREAK3 \s_Maintain.Chain=s_Maintain.Chain->Prev; \s_Maintain.Chain=s_Maintain.Chain->Prev; \s_Maintain.Chain=s_Maintain.Chain->Prev; \break /*循环中有三层异常监视块*/

用户视循环中有几层异常块来决定使用哪一个宏来替代break指令。

要实现从异常块中直接退出函数(return指令),同样需要在真正退出前注销函数中所有注册的异常监视块。因此我们也可以象实现TRY_BREAK那样做,定义几个宏:

#define TRY_RETURN s_Maintain.Chain=s_Maintain.Chain->Prev; \return //代码在一层异常监视块中#define TRY_RETURN2 \s_Maintain.Chain=s_Maintain.Chain->Prev; \s_Maintain.Chain=s_Maintain.Chain->Prev; \return /*代码位于两层异常监视块中*/#define TRY_RETURN3 \s_Maintain.Chain=s_Maintain.Chain->Prev; \s_Maintain.Chain=s_Maintain.Chain->Prev; \s_Maintain.Chain=s_Maintain.Chain->Prev; \return /*代码位于三层异常监视块中*/

看上去十分不优雅,而且一旦用户忘记使用宏替代break/return指令就容易出错。

但是!那是针对C语言,如果是C++语言,那就好办了。在C++中,结构/类在离开作用域的时候,会调用它的析构函数,所以我们可以在析构函数里注销异常块!而break和return指令肯定会退出异常块的作用域,这下编译器就会帮我们解决这个问题了。

首先,我们修改为每一个注销异常处理动作增加一行:

(Node)->Prev=NULL;

然后,为EXCEPTION_NODE增加析构函数:

    ~_EXCEPTION_NODE(void){if(Prev){printf("~~~~ ExceptionNode %p, Status %d\n",this,RunStatus);s_Maintain.Chain=s_Maintain.Chain->Prev;Prev=NULL;}}

行了,现在代码就非常优雅了。我们完成了非Windows平台上的SEH功能的模拟。

当然还有unwind问题,这个在VC编译器上会报错,而非Windows平台上整个都是我们模拟的,这个问题只能由用户自己解决了。

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

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

相关文章

25.6.19学习总结

什么是堆&#xff08;Heap&#xff09;&#xff1f; 堆是一种特殊的树形数据结构&#xff0c;它满足以下两个主要属性&#xff1a; 结构性&#xff08;完全二叉树&#xff09;&#xff1a; 堆总是一个完全二叉树 (Complete Binary Tree)。这意味着&#xff0c;除了最后一层&am…

【前后前】导入Excel文件闭环模型:Vue3前端上传Excel文件,【Java后端接收、解析、返回数据】,Vue3前端接收展示数据

【前后前】导入Excel文件闭环模型&#xff1a;Vue3前端上传Excel文件&#xff0c;【Java后端接收、解析、返回数据】&#xff0c;Vue3前端接收展示数据 一、Vue3前端上传&#xff08;导入&#xff09;Excel文件 ReagentInDialog.vue <script setup lang"ts" na…

网络基础入门:从OSI模型到TCP/IP协议详解

网络基础入门&#xff1a;从OSI模型到TCP/IP协议详解 一、网络基础概念与OSI七层模型 1.1 网络通信的本质 计算机网络的核心是将抽象语言转换为二进制数据进行传输与计算&#xff0c;这一过程涉及多层抽象与转换&#xff1a; 应用层&#xff1a;人机交互—抽象语言------编…

Linux致命漏洞CVE-2025-6018和CVE-2025-6019

Qualys 最近披露了两个影响主流 Linux 发行版的本地权限提升 (LPE) 漏洞&#xff0c;分别是 CVE-2025-6018 和 CVE-2025-6019。这两个漏洞可以被串联利用&#xff0c;使得非特权用户在几秒钟内获得系统的 root 权限&#xff0c;从而实现对系统的完全控制。 一、漏洞详情 这两…

【Docker基础】Docker镜像管理:docker push详解

目录 引言 1 Docker镜像推送基础概念 1.1 什么是Docker镜像推送 1.2 镜像仓库概述 1.3 镜像标签与版本控制 2 docker push命令详解 2.1 基本语法 2.2 常用参数选项 2.3 实际命令示例 2.4 推送流程 2.5 步骤描述 3 镜像推送实践示例 3.1 登录管理 3.2 标签管理 3…

FPGA基础 -- Verilog行为建模之循环语句

行为级建模&#xff08;Behavioral Modeling&#xff09;是 Verilog HDL 中最接近软件编程语言的一种描述方式&#xff0c;适用于功能建模和仿真建模的初期阶段。在行为级中&#xff0c;循环语句&#xff08;loop statements&#xff09;是常见且重要的控制结构&#xff0c;用于…

从C学C++(7)——static成员

从C学C(7)——static成员 若无特殊说明&#xff0c;本博客所执行的C标准均为C11. static成员和成员函数 对于特定类型的全体对象而言&#xff0c;有时候可能需要访问一个全局的变量。比如说统计某种类型对象已创建的数量。 通常在C中使用全局变量来实现&#xff0c;如果我们…

大模型和ollama一起打包到一个docker镜像中

如何将大模型镜像和 Ollama 镜像打包在一个 Docker 镜像中 最近工作中有个需求是将ollama和大模型一起打成一个镜像部署&#xff0c;将自己的操作步骤分享给大家。将大模型与 Ollama 服务打包在同一个 Docker 镜像中&#xff0c;可以简化部署流程并确保环境一致性。下面详细介…

2025年渗透测试面试题总结-攻防研究员(应用安全)(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 攻防研究员(应用安全) 一、基础部分 1. HTTP状态码对比 2. HTTP请求方法核心作用 3. 网络分层协议速查表…

SpringBoot新闻项目学习day3--后台权限的增删改查以及权限管理分配

新增管理员修改管理员删除管理员登录 新增管理员 1.点击新增按钮打开一个对话框 2.确定新增对话框要显示哪些内容 3.提交 4.后端处理、保存 5.响应前端 vue代码 <template><!-- 新增代码内容是比较多的,建议抽取出来,定义到一个独立的vue文件中在列表组件中导入…

算法导论第二十五章 深度学习的伦理与社会影响

第二十五章 深度学习的伦理与社会影响 技术的光芒不应掩盖伦理的阴影 随着深度学习技术在各领域的广泛应用&#xff0c;其引发的伦理和社会问题日益凸显。本章将深入探讨这些挑战&#xff0c;并提供技术解决方案和最佳实践&#xff0c;引导读者构建负责任的人工智能系统。 25.…

Linux中ansible模块补充和playbook讲解

一、模块使用 1.1 Yum模块 功能&#xff1a;管理软件包&#xff0c;只支持RHEL&#xff0c;CentOS&#xff0c;fedora&#xff0c;不支持Ubuntu其它版本 参数说明name要操作的软件包名称&#xff0c;支持通配符&#xff08;如 httpd, nginx*&#xff09;&#xff0c;也可以是…

唐代大模型:智能重构下的盛世文明图谱

引言&#xff1a;当长安城遇见深度学习 一件唐代鎏金舞马衔杯银壶的虚拟复原品正通过全息投影技术演绎盛唐乐舞。这个跨越时空的场景&#xff0c;恰似唐代大模型技术的隐喻——以人工智能为纽带&#xff0c;连接起长安城的盛世气象与数字时代的文明重构。作为人工智能与历史学…

国产ARM/RISCV与OpenHarmony物联网项目(三)网关设备控制

一、设备控制界面与功能设计 程序界面运行与设计效果如下: 设备控制相关程序调用关系图如下&#xff1a; 其中device_control.html程序为网页界面显示程序&#xff0c;led_alarm.cgi程序为光线数据的报警超限数据设置与管理&#xff0c;led_control.cgi程序功能为对Led灯的开…

微信小程序反编译实战教程

在实际渗透测试或安全分析中&#xff0c;经常会遇到微信小程序中的签名加密&#xff08;sign&#xff09;机制&#xff0c;这些机制大多具备防重放、防篡改的特性&#xff0c;导致我们在抓包时难以直接复现请求。 &#x1f50d; 另一方面&#xff0c;一些小程序的代码中往往会…

【NLP入门系列三】NLP文本嵌入(以Embedding和EmbeddingBag为例)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 博主简介&#xff1a;努力学习的22级本科生一枚 &#x1f31f;​&#xff1b;探索AI算法&#xff0c;C&#xff0c;go语言的世界&#xff1b;在迷茫中寻找光芒…

文心一言(ERNIE Bot):百度打造的知识增强大语言模型

1. 产品概述 文心一言&#xff08;ERNIE Bot&#xff09;是百度自主研发的知识增强大语言模型&#xff0c;于2023年3月16日正式发布&#xff0c;对标OpenAI的ChatGPT&#xff0c;具备文本生成、多模态交互、逻辑推理、中文理解等能力。该模型基于百度的飞桨深度学习平台和文心…

Java-49 深入浅出 Tomcat 手写 Tomcat 实现【02】HttpServlet Request RequestProcessor

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月13日更新到&#xff1a; AI炼丹日志-28 - Aud…

在VB.net中,文本插入的几个自定义函数

一、如果你是高手&#xff0c;一定“识货”&#xff0c;分享给你 二、可应用于文本插入的几种方式&#xff1a;6种 三、需要用到以下的几个函数&#xff1a; 上代码&#xff1a; Module TextModule <summary> 在指定位置插入文本 </summary> <p…

QC -io 服务器排查报错方式/报错: Failed to convert string to integer of varId variable!“

进断点控制台有报错之后&#xff0c;复制报错信息到 头部菜单栏 1.编辑 -> 2.Find/Replace ->3.Advanced Find ->4. Project“xxxxx” 能找到问题点 再分析定位 在排查报错时候&#xff0c;进入了这个报错&#xff0c;msgInfo "MyTcpRedis: Failed to conver…