一種通用跨平臺實現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
英文地址,請注明出處:http://en.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…