?
快捷搜索:  as  test  1111  test aNd 8=8  test++aNd+8=8  as++aNd+8=8  as aNd 8=8

齊博國際:兼容內核之二十四:Windows的結構化異常處理(第一部分I)

?

布局化非常處置懲罰(Structured Exception Handling),簡稱SEH,是Windows操作系統的一個緊張組成部分。

在ReactOS內核的源代碼中,分外是在實現系統調用的代碼中,讀者已經看到很多類似于這樣的代碼:

if(MaximumSize != NULL && PreviousMode != KernelMode)

{

_SEH_TRY

{

ProbeForRead(MaximumSize, sizeof(LARGE_INTEGER), sizeof(ULONG));

/* make a copy on the stack */

SafeMaximumSize = *MaximumSize;

MaximumSize = &SafeMaximumSize;

}

_SEH_HANDLE

{

Status = _SEH_GetExceptionCode();

}

_SEH_END;

if(!NT_SUCCESS(Status))

{

return Status;

}

}

這段代碼取自NtCreateSection(),其參數之一是指針MaximumSize。系統調用一樣平常都是從用戶空間調用的,是以PreviousMode一樣平常不是KernelMode。以是,只要指針MaximumSize不是NULL,就要從它所指的地方從用戶空間把數值復制到內核空間。那為什么不直接把它的數值作為參數通報,而要這樣繞一下呢?這是由于它的類型為LARGE_INTEGER,而作為參數通報的只能是32位(或以下)的通俗整數。

然而,從用戶空間復制數據到內核空間(或反過來)恰好是輕易誤事出事的。這是由于,用戶法度榜樣的質量相對而言是沒有包管的,這個指針所指向的地址(所在的頁面)大概根本就沒有映射,或者大概不容許讀,那樣就會發生與頁面映射和造訪有關的非常(Exception)。

不過倒也并非只要發生非常就有問題,例如如果頁面已經映射、也容許讀,然則所在頁面已經換出(Swap-Out),那就會發生缺頁非常;而缺頁非常著實不是“非常”而是“正常”,內核從磁盤上換入(Swap-In)目標頁面,就可以從非常處置懲罰法度榜樣返回、并繼承運行了,就像發生了一次中斷一樣。此時CPU將從新履行發生非常的指令,這一次一樣平常就能正常完成了。以是,(物理上的)非常之是否真的(邏輯上)“非常”,還得看詳細的緣故原由。只要沒有分外加以闡明,本文中所講的非常都是指真正意義上的非常。

對付非常的處置懲罰,內核一樣平常會供給默認的要領,例如“殺掉落”當提高程,讓其一逝世百了,這樣至少不會迫害其余進程。然則假如詳細的法度榜樣預期在某一段代碼中有可能發生某幾種特定的非常,并樂意為之供給辦理、解救之道,那當然是更合理、更優雅的要領。舉例言之,要是用戶法度榜樣中有除法運算,CPU在碰著除數為0的時刻就會發生非常,此時默認的處置懲罰要領一樣平常是中止該用戶法度榜樣的運行,由于不知該如何讓它繼承下去了。然而齊博國際這可能發生在已經繼續謀略了幾十個小時今后,離成功大概只有一步之遙了,讓它就這樣退出運行不免難免喪掉太大年夜。假如法度榜樣的設計職員事先預計到有這樣的可能,大概會選擇在這種環境下彈出一個對話框,提示應用者改變幾個參數,然后以新的前提繼承運算;或者至少問一下用戶,是否把發生問題時的“現場”信息經由過程郵件發送給法度榜樣的設計者。顯然,這是更好的辦理規劃。問題在于若何來實現,若作甚法度榜樣的設計者供給這樣做的手段。

簡而言之,便是要為法度榜樣的設計者供給一種手段,使得假使在履行某一段代碼的歷程中發生了特定種類的非常就履行另一些指定的代碼。事實上,這恰是微軟的“布局化非常處置懲罰(Structured Exception Handling)”、即SEH機制要辦理的問題之一。后面讀者將會看到,SEH要辦理兩類問題,這是此中之一。

在上列的代碼片斷中,在_SEH_TRY{}里面是要加以“保護”的代碼,即用戶預計可能會在履行中發生非常的代碼;而_SEH_HANDLE{}里面便是當發生非常時必要履行的代碼;著末的_SEH_END則闡明與SEH有關的代碼到此為止,從此今后的代碼規復常態。這樣,假如在履行_SEH_TRY{}里面受保護代碼的歷程中發生了某些非常,CPU就轉入_SEH_HANDLE{};而若順利履行完_SEH_TRY{}里面的代碼,那就跳過_SEH_HANDLE{}直接到達_SEH_END。

留意在_SEH_TRY{}里面可能會調用其余函數,被調用函數的代碼雖然形式上不在_SEH_TRY{}里面,然則它的本次被調用履行卻同樣是在_SEH_TRY{}所指定齊博國際的保護范圍之內。在本文中,由_SEH_TRY{}所劃定的范圍稱為一個“SEH保護域”,也稱“SEH框架”,由于在履行這些代碼時這體現為客棧上的一個框架。以是在本文中“SEH域”和“SEH框架”是同義詞。說是“保護域”,著實也可以說是“捕捉域”,便是說這是一個必要“捕捉”住非常的域(以是在C++說話頂用“catch”表示捕捉到非常之后要履行的代碼)。留意SEH域和函數是相互自力的兩個觀點。同一個函數,這一次是從_SEH_TRY{}里面調用,它的履行就在SEH域中;下一次不是從_SEH_TRY{}里面調用,就不在這個SEH域中了。以是一個函數(的履行)是否在SEH域里面是個動態的觀點。不過,SEH域老是存在于某個函數的內部,而弗成能游離在函數之外,就像C語句只能存在于函數之內一樣。

在實際利用中,SEH域還可以嵌套,便是在一個SEH域的內部又經由過程_SEH_TRY{}開辟了第二個SEH域。例如,前者大概是針對頁面非常的SEH域,而在這里面又有一部分代碼可能會引起“除數為0”的非常,以是又得將其保護起來,形成一個嵌在外層保護域里面的內層保護域。

顯然,多個SEH框架嵌套就形成了一個SEH框架棧。SEH框架棧既可以只是實質的,也可以既是實質的、又是形式的。比方齊博國際說,一個SEH域的內部調用了一個函數,而在這個函數中又有一個SEH域,那么這兩個SEH域(框架)的嵌套是實質的,但卻不是形式的,由于從代碼上不能一清二楚看出這樣的嵌套關系,這種嵌套關系是運行起來才形成的。然則,假如在第一個SEH域的_SEH_TRY{}內部直接又有一個_SEH_TRY{},那么這兩個SEH域的嵌套關系就既是實質的、又是形式的。在本文中,前者所形成的SEH框架棧稱為“實質”SEH框架棧、或“全局”SEH框架棧,后者所形成的則稱為“形式”SEH框架棧、或“局部”SEH框架棧。之以是如斯,是由于0.3.0版ReactOS的代碼中對付SEH機制的實現有了一些更改。不過,在0.3.0版ReactOS的代碼中并未見到應用形式嵌套的SEH域。

轉頭看前面_SEH_TRY{}里面的代碼。這里受保護的有三個語句,先看對ProbeForRead()的調用。ProbeForRead()是個內核函數,這里也是在內核中調用,以是對這個函數的調用本身并沒有問題。

VOID STDCALL

ProbeForRead (IN CONST VOID *Address, IN ULONG Length, IN ULONG Alignment)

{

ASSERT(Alignment == 1 || Alignment == 2 || Alignment == 4 || Alignment == 8);

if (Length == 0)

return;

if (((ULONG_PTR)Address & (Alignment - 1)) != 0)

{

ExRaiseStatus (STATUS_DATATYPE_MISALIGNMENT);

}

else if ((ULONG_PTR)Address + Length - 1  (ULONG_PTR)MmUserProbeAddress)

{

E齊博國際xRaiseStatus (STATUS_ACCESS_VIOLATION);

}

}

其目的只是反省參數的合理性,而并不真的去造訪用戶空間。假如用戶空間數據所在的地址不與給定命據類型(在這里是ULONG)的界限對齊,或者所在的位置紕謬、長度分歧理,那就要經由過程ExRaiseStatus()以軟件措施模擬非常。這是為什么呢?由于在正常的環境下這是弗成能發生的,既然發生了就必然是出了問題,按理說最好是CPU在碰著這種環境時能引起一次非常,然則386布局的CPU不會(從486開始就會了,這便是17號非常“Alignment Check”),以是就只好經由過程軟件手段來模擬一次“軟非常”。留意這里軟非常的類型為STATUS_DATATYPE_MISALIGNMENT和STATUS_ACCESS_VIOLATION,前者表示與數據類型的界限紕謬齊,后者表示越界造訪,這相稱于硬非常的非常號,然則富厚得多。

就前述的SEH域而言,由此而引起的效果與硬件非常相同,CPU也會轉入_SEH_HANDLE{}里面。

認識C++的讀者可能會遐想到throw語句,實際上也確鑿是同一回事。

假如ProbeForRead()沒有反省出什么問題,前面的第二個語句是“SafeMaximumSize = *MaximumSize”,這是從用戶空間讀取數據寫入系統空間。這里寫入系統空間不會有問題,然則讀用戶空間可能會有問題,假如指針MaximumSize所指的頁面無映射就會發生非常。以是要把它放在_SEH_TRY{}里面。

第三個語句是“MaximumSize = &SafeMaximumSize”,這是對指針MaximumSize進行賦值。作為調用參數,這個變量本來在用戶空間客棧上,CPU因系統調用進入內核今后把它復制到了系統空間客棧上。因而這個賦值操作應該不會引起非常,本可以放在外貌,然則放在_SEH_TRY{}里面也無弗成。以是,并不凡是放在_SEH_TRY{}里面的都必須是可能引起非常的語句。對付不會引起非常的語句,放在_SEH_TRY{}里面或外貌都是一樣。

再看安排在發生非常時加以履行的代碼、即_SEH_HANDLE{}里齊博國際面的代碼。在這里只有一個語句,便是對_SEH_GetExceptionCode()的調用。顧名思義,這便是獲取詳細非常的代碼,例如STATUS_DATATYPE_MISALIGNMENT、STATUS_ACCESS_VIOLATION等等。然后將獲取的代碼賦值給變量Status,這就完事了。再往下便是_SEH_END及其后面的if語句了。當然,這里面也可以有不止一個、以致很多的語句。

留意變量Status藍本已經初始化成STATUS_SUCCESS,而_SEH_TRY{}里面的代碼都不會改變它的值;以是只要“!NT_SUCCESS(Status)”為真就必然已經發生過非常,是以這個系統調用就掉足返回了,而且所返回的便是所發生非常的代碼。而根據所返回的值判斷本次系統調用是否成功,以及采取什么步伐,那便是用戶軟件的事了。

這里還要闡明一下,并不是所有的非常都邑落入這_SEH_HANDLE{}里面。發生非常時,首先是由內核底層的非常處置懲罰法度榜樣“認領”和處置懲罰,例如缺頁非常就會被其認領并處置懲罰,處置懲罰完就返回了。縱然是不歸其認領處置懲罰的非常,也還得看當時是否正在經由過程調試對象(debugger)調試法度榜樣,假如是就交由debugger處置懲罰。只有不受這二者攔截的非常才會落入_SEH_HANDLE{}。后面讀者將看到,每個SEH域都可以經由過程一個“過濾函數”反省本次非常的類型,已抉擇是否認領。假如存在嵌套的SEH域,則首先要由嵌套在最內層(最底層)的SEH域先作過濾,抉擇不予認領才會交給上一層SEH域。以是,只有不被攔截、認領,并經由過程了層層過濾的非常才真正進入本SEH域的_SEH_HANDLE{}。

上面所引的是內核中的代碼,用戶空間的代碼同樣也可以使用SEH所供給的功能和機制,著實C++說話中的try{..}catch{…}終極也是使用SEH實現的。

免責聲明:以上內容源自網絡,版權歸原作者所有,如有侵犯您的原創版權請告知,我們將盡快刪除相關內容。

您可能還會對下面的文章感興趣:

浙江体彩20选5开奖号 2012广西快乐十分直播 融胜配资 老快3开奖结果走图 快乐飞艇骗局 贵州11选5走势图 四川快乐12任五开 河内5分彩官网 上海快3一定件 股票配资怎么配 广东快乐十分专家计 宁夏11选5定位走势图 pk10官方 德易策略 cba赛程2018 11选5江苏遗漏数 广东36选7开奖走势