1. 程式人生 > >獲取C++異常的名稱 (Windows和Linux下)

獲取C++異常的名稱 (Windows和Linux下)

前一陣子在群裡探討到C++異常上能否應用模板,目前來看答案是不能。但是難道異常就不能更靈活一些麼?難道只能try...catch已知的基於類繼承體系的異常,對未知的異常re-throw麼?(雖然那是經典且正確的方法)經過一番探索,找到了某些黑魔法。

先來看一段程式碼:

int ttt;
void func(){    throw std::pair<int, int&>(1, ttt);}
int main(){    try {        func();    }    catch (...)    {// 能在這裡獲取到異常的名稱麼?    }}

我們的需求是僅捕獲 template<typename T> std::pair<T,T&> 這樣的異常,由於try-catch無法應用模板,因此能否在catch(...)裡獲取到這個異常並且在執行時得到其型別名稱?

先考慮C++自己能否完成這一任務。<exception>標頭檔案中提供了std::current_exception()函式,能夠返回當前的異常. 同時C++還提供了一定的raii功能——typeid,看起來很美好。但是首先,current_exception()返回型別是exception_ptr,是一個指向被丟擲異常的指標或引用,除了被用來rethrow_exception之外毫無用處,被丟擲的異常型別資訊完全丟失。其次typeid並不是一個可靠的工具。在VS下,typeid(T).name()會返回T的字面型別。而在GCC上,typeid(T).name()得到的是一個被編碼的(mangled)

字串。(當然GCC也可以用abi::__cxa_demangle來解析並獲取真正的型別名)

那麼,這個問題在C++自身內就無法得到解決。因此來看不同編譯器下如何實現。

GCC的<cxxabi.h>中提供了abi::__cxa_current_exception_type()這樣一個函式,能夠返回當前正在處理的異常的相關資訊,使用abi::__cxa_current_exception_type()->name()可以得到一個被編碼的字串,然後再呼叫__cxa_demangle進行解碼即可獲得當前異常的型別字串。返回的結果非常規整,可以直接操作字串來實現我們的需求。程式碼如下:

int
main(){    try {        func();    }    catch (...)    {printf("ExceptionName: %s\n", abi::__cxa_demangle(abi::__cxa_current_exception_type()->name(), 0, 0, NULL));    }}

VS下這個問題顯得有一些複雜,首先VS並沒有提供cxxabi.h這麼好用的內部函式,所以如果只用C++語言設施無法解決問題。但是由於我們在VS下,因而又多了一個新的異常處理系統:__try,__except和__finally。

其中__except接受一個指定格式的函式,用於識別並處理異常。這個函式可以返回三個值:-1 表示忽略當前異常繼續執行 0 表示無法匹配異常,系統將繼續搜尋可能的__except塊 1 表示異常已經成功處理完成 (這就跟平時解引用一個空指標彈出來的那個Debug Error對話方塊差不多)

於是程式碼變成了這樣:

int main(){    __try {
        func();
    }    __except (filter(GetExceptionCode(), GetExceptionInformation())) {
puts("in except");
    }}

其中filter是一個這樣的函式:

int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {    if (code == 異常程式碼) {        return EXCEPTION_EXECUTE_HANDLER;    }    else {        return EXCEPTION_CONTINUE_SEARCH;    };}

其中code是異常程式碼,__EXCEPTION_POINTERS是一個指向正在處理的異常的結構體(參見MSDN)其中異常程式碼有很多種,比如EXCEPTION_ACCESS_VIOLATION就是平時訪問非法記憶體(解空指標)的異常程式碼。然而這裡我們要處理的是C++異常,通過探索發現,對於所有C++ throw丟擲導致的異常,code都是0xE06D7363,通過搜尋資料,確定這是Windows SEH下表示C++異常的程式碼。

到這裡,儘管已經獲取到了包含異常資訊的結構體,但是仍然無法得到我們想要的型別名。通過蒐集資料,找到了一個非常神奇的方法,如下:

_EXCEPTION_POINTERS->ExceptionRecord->ExceptionInformation是一個儲存異常引數的陣列,其中引數的數量儲存在_EXCEPTION_POINRTERS->ExceptionRecord->NumberParameters中。對於C++異常,引數數量總是3或4.

引數0是一些系統使用的內部值,引數1是一個指向被丟擲物件的地址的指標,引數2是一個指向被丟擲異常的描述資訊的指標。如果作業系統是64位的,那麼有引數3是一個HINSTANCE值,用於指示是哪一個DLL丟擲了異常。

引數2是唯一與異常資訊有關的直接資訊,然而需要經過一系列的處理才能夠得到我們想要的型別名。

首先將引數2當做一個DWORD指標,然後偏移3個DWORD大小,取值。

然後把上一步得到的值當做一個DWORD指標,然後偏移1個DWORD大小,取值。

接下來把上一步得到的值當做一個DWORD指標,然後偏移1個DWORD大小,取值

最後把上一步得到的值當做一個char指標,然後偏移2個void指標大小。這個字串就是我們要獲取的型別名。

(注:當以64位編譯時需要把每個步驟取到的值加上引數3的那個HINSTANCE才行)

語言描述看起來可能比較複雜,來看程式碼:

DWORD step1 = ep->ExceptionRecord->ExceptionInformation[2];DWORD step2 = *((DWORD*)(step1+sizeof(DWORD) * 3));DWORD step3 = *((DWORD*)(step2+sizeof(DWORD)));DWORD step4 = *((DWORD*)(step3 + sizeof(DWORD)));char* ptr = (char*)(step4 + sizeof(void*) * 2);printf("Now we get: %s\n", ptr);

在我的電腦上,執行得到的型別名是:[email protected]@[email protected]@

令人開心的是,我們得到了一些結果。然而,我們並不知道這個結果是什麼。不過直覺判斷這應該就是編碼過後的型別名。

這裡,需要了解一下typeid(T).raw_name()。這個函式返回T型別編碼後的字串。執行typeid(std::pair<int,int&>).raw_name() 獲取到的結果是:[email protected]@[email protected]@ 完全一致。

那麼如何從這個奇怪的字串回到std::pair<int,int&>這樣的格式呢?VS只為我們提供了一個功能很有限的函式:UnDecorateSymbolName(具體用法參見MSDN) 說他功能有限,是因為這個函式本質上是通過查詢一個內建的表格來獲取對應的結果,而不是想象中的動態逆向解析符號串。因此解析正確率完全取決與庫裡是否有待查詢的符號。從而我們的程式碼最終變成了:

#include <algorithm>#include <typeinfo>#include <iostream>#include <Windows.h>#include <excpt.h>#include <Dbghelp.h>#pragma comment(lib,"dbghelp.lib")using namespace std;
int ttt = 1;char buff[256];
void func(){    throw pair<int, int&>(1, ttt);}
int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep){printf("%X\n",code);printf("pair<int,int&> is : %s\n", typeid(pair<int, int&>).raw_name());
    DWORD step1 = ep->ExceptionRecord->ExceptionInformation[2];    DWORD step2 = *((DWORD*)(step1+sizeof(DWORD) * 3));    DWORD step3 = *((DWORD*)(step2+sizeof(DWORD)));    DWORD step4 = *((DWORD*)(step3 + sizeof(DWORD)));    char* ptr = (char*)(step4 + sizeof(void*) * 2);printf("Now we get: %s\n", ptr);
    UnDecorateSymbolName(ptr, buff, 256, 0);printf("%s\n", buff);
return EXCEPTION_EXECUTE_HANDLER;}
int main(){    __try {        func();    }    __except (filter(GetExceptionCode(), GetExceptionInformation())) {printf("In __except block\n");    }
return 0;}

不知道是不是因為模板的複雜性,這個函式基本上對模板無效。對我們的得到的編碼串呼叫UnDecorateSymbolName並不能得到正確的結果(只會返回原來的字串,進一步表示無法解析符號串)從而我們還是無法達到最開始的需求(無奈)。

不過,值得一提的是,同樣執行在Windows下的MinGW-GCC通過其abi函式庫是能夠達到與Linux下GCC一樣的執行效果的。如果不是提前儲存了型別名,那麼肯定還是有某種方法能夠從這堆編碼串反推出實際型別名稱。

相關推薦

獲取C++異常名稱 (WindowsLinux)

前一陣子在群裡探討到C++異常上能否應用模板,目前來看答案是不能。但是難道異常就不能更靈活一些麼?難道只能try...catch已知的基於類繼承體系的異常,對未知的異常re-throw麼?(雖然那是經典且正確的方法)經過一番探索,找到了某些黑魔法。先來看一段程式碼:int t

C++ Boost在WindowsLinux的編譯安裝

C++作為歷史上最成功的語言之一,除了它具有面向物件的性質之外,還有一個非常重要的創新,那就是泛型程式設計。泛型的思想其實是為所有問題提供一個解決方案的模版,程式設計師只需把每次的具體問題放到模版裡面,那麼就可以獲得該問題的解決方案,利用模版做到具體問題具體分析。C++ STL毫無疑問是泛型思想的一

如何在WindowsLinux獲取當前執行緒的ID號

Linux下獲取當前執行緒ID號函式: pthread_t pthread_self(); 返回:當前執行緒的ID號 pthread_t 資料型別的定義如下: typedef unsigned long int pthread_t; sizeof(pthread_t) =

C/C++ 輸入輸出緩衝區在WindowsLinux對比

C++中cin、cout,cerr和C的stdin、stdout、stderr都是同步的,即iostream 物件和 and cstdio流是同步的,同步關係如下: 同步即表明我們可以在程式中混合用cout和printf或其他對應的流對。可以用std::ios

windowsLinux定時啟動或關閉服務

ref sta article start 處理程序 window pin blog win http://blog.csdn.net/clare504/article/details/17410643 1、Windows下的定時啟動程序可以采用系統的計劃和任務,定時

windowslinux的抓包工具

gpo tcpdump linu window clas dst post blog windows Linux 抓包工具 tcpdump 示例 tcpdump -i bond0 host 10.70.11.182 -w ./sms.cap windows抓包

windowsLinux安裝nodejs

兩個文件 tro window 顯示 name 測試 分享 如圖所示 運行 在windows下安裝nodejs 1.首先下載nodejs安裝包, https://nodejs.org/en/download/ 點擊下載相應的版本 然後將文件夾解壓到安裝目錄(任

python在windowslinux的安裝配置

str net .net cat ria 技術 連接 top 成功 一、windows下安裝python3.6 安裝編輯器:Ecplise+pydev插件 Eclipse是寫JAVA的IDE, 這樣就可以通用了,學習代價小。 學會了Eclipse, 以後寫Pytho

ffmpeg windowslinux轉換格式

import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList;

webpack build 在windowsLinuxbuild需注意路徑的大小寫

如:route/index.js 實際路徑:/page/WanHG/index.vue 在index.js中寫法 const ImageList = () => import('/page/Admin/imgaeList.vue') const WanHgIndex = (

Mysql 在 windows linux 的安裝配置

這篇文章講解 Mysql 在 Windows 下的手動安裝和在 Linux(CentOs) 下的下載、配置、連線。還包括預設字符集等的設定。 首先,都要執行移除資料庫的操作 mysqld --remov

Windows linux CUnit編譯安裝教程

本文大部分均參看以下連結,安裝當中有些地方不詳細,我添加了 首先要安裝mingw和msys,下載地址:點選開啟連結 1. 安裝  1)準備環境:Win7安裝MinGW 1.1)Win7已就緒 1.2)下載 MinGW:www.mingw.org =>

go語言學習-beego框架學習bee在windowslinux的安裝

windows下的安裝 windows下的安裝和linux下其實是相同的命令 go get github.com/beego/bee //或者使用 go install github.com/beego/bee 在windows下安裝完成之後需要在 環

windowslinux打包python程式

一、windows下用py2exe打包python程式生成exe檔案 1、py2exe是一種python釋出的打包工具,可以把python指令碼轉換成windows下的可執行程式,不需要安裝python便可執行。 2、首先需要安裝py2exe,我的python是2.7版本,

windowslinux讀取檔案換行符的一個坑——\r\n\n

      拿同事的一個windows下的C程式,在Linux下跑,結果不正確。定位為讀取.ini配置檔案錯誤。該配置檔案是在windows下編輯的,網上查到資料,說是windows和linux下對換行符處理不同導致的。      

windowslinux換行符區別

把windows下換行符轉換為linux下換行符 python環境下有一個很簡單的解決方法: #!usr/bin/env python #coding: utf-8 import sys, pprint import os def main(): print '

新版本Django在WindowsLinux的部署

伴隨著人工智慧和深度學習的火熱發展,Python也成為了一門非常熱門的語言。我們可以看到熱門的深度學習框架都提供Python的介面,有些甚至只有Python的介面,這一定程度上推動了Python的普及。當然,在我們完成模型的訓練之後,我們總是要搭建一個演示的

淺談windowslinux記憶體分配規律

首先先說明下,本文中程式碼來自牛刀教程。寫的很不錯。給我不少的啟發。謝謝了 我們都知道,在使用C語言時,比如定義一個數組,一個變數。那麼系統都會隨機的分配記憶體。那麼你知道記憶體分配的規律嗎? 讓我們用兩個實驗來說明windows和linux下,記憶體分配方式的不同。 同一

磁碟分割槽在WindowsLinux的表現形式對比

寫在前面的話:磁碟分割槽在Windows下面比較好理解,在Linux下會有掛載的概念,理解起來比較難,但是可以通過與Windows對比,以一種通俗的方式將他們梳理清楚。 ====正文開始==== 我們的電腦磁碟出廠經過初始化後,要想使用,就要對其

maven打包pom檔案節點在windowslinux的格式差異

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>mave