------ 解析因內核棧溢出致使的 「double fault」 藍屏 ------

——————————————————————————————————————————————————————————————————————————數據結構

前一篇指出 tail_recursivef_factorial() 會遞歸調用自身來計算某個正整數的階乘。當要計算的目標數值過大,經歷屢次調用後,網站

就會耗盡可用的內核棧,引起一次頁錯誤異常,而轉移控制到錯誤處理程序前再次向無效的內存地址壓入「陷阱幀」則會讓本來可this

處理的異常升級爲「double fault」,導致系統崩潰。本篇經過試圖計算 685! 來觸發「double fault」並進行分析。spa

 

將編譯好的驅動拷貝到被調試機器上,利用 sc.exe 把它加載至內核空間,源碼中(參見上一篇)設置的初始斷點被激活從而斷入線程

調試機上的 WinDbg.exe,觀察驅動入口點「DriverEntry()」內的局部變量,其中「Number」的值 0x2ad 正是要計算階乘的數設計

685:指針

 

 

按下「g」鍵恢復執行,沒多久就讓系統崩潰了,這在咱們的意料之中,若是沒有鏈接宿主機上的調試器,目標系統就會直接調試

藍屏,而且顯示「bug check」代碼——0000007F:blog

 

在 MSDN 網站上搜索該錯誤碼,它對應於「UNEXPECTED_KERNEL_MODE_TRAP」,官方給出的解釋以下:遞歸

 

The UNEXPECTED_KERNEL_MODE_TRAP bug check has a value of 0x0000007F.
This bug check indicates that the Intel CPU generated a trap and the kernel failed to catch this trap.

This trap could be a bound trap (a trap the kernel is not permitted to catch) or a double fault
(a fault that occurred while processing an earlier fault, which always results in a system failure).

這種錯誤是因爲 Intel CPU 生成了一個陷阱(trap),而內核未能捕獲這個陷阱。
此陷阱多是一個受困陷阱(內核不容許捕獲的陷阱),或一個「double fault」(當處理一個早先的錯誤時又出現一個錯誤,
這樣就老是會致使系統故障)。

 

原文描述中的後一種狀況(處理錯誤時又發生另外一個錯誤)就是咱們此刻的處境。

UNEXPECTED_KERNEL_MODE_TRAP 有四個參數,你能夠從上一張圖看到,首個參數值爲「0x00000008(陷阱編號)」,

官方對該值的解釋爲:

0x00000008, or Double Fault, indicates that an exception occurs during a call to the handler for a prior exception.
Typically, the two exceptions are handled serially.
However, there are several exceptions that cannot be handled serially,
and in this situation the processor signals a double fault. There are two common causes of a double fault:

A kernel stack overflow. This overflow occurs when a guard page is hit, and the kernel tries to push a trap frame.
Because there is no stack left, a stack overflow results, causing the double fault.
If you think this overview has occurred, use !thread to determine the stack limits, and then use kb
(Display Stack Backtrace) with a large parameter (for example, kb 100) to display the full stack.

A hardware problem.

「Double Fault」,指明在調用前一個異常處理程序期間,又出現了一個異常。通常而言,兩個異常是順序處理的。
然而,有一些異常沒法順序處理,在這種狀況下處理器就會發出一個「double fault」信號。有兩種常見狀況會致使
「double fault」:

1。一次內核棧溢出。當接觸到一個保護頁時就會發生此類溢出,而後內核試圖向其中壓入一個陷阱幀。
由於已經沒有剩餘棧可用了,致使又一次棧溢出,形成「double fault」。若是你認爲發生了這種溢出,利用「!thread」調試器
命令肯定棧界限,而後使用「kb」(顯示棧回溯)命令,並帶着較大的參數(好比 kb 100)來顯示完整的棧。

2。硬件問題

 

遵循原文的指示,咱們先檢查當前線程的棧界限,而後執行棧回溯看是否真的越界了,以下圖所示,內核棧邊界在 8bf47000 處,

而發生異常前的最後一次遞歸調用的幀指針(ChildEBP)爲 8bf47008 ,已經快要出界了:

 

nt!KiTrap08」是實際的陷阱處理程序,有趣的是前面的陷阱編號(0x00000008)就在這個例程的名字中,這毫不是巧合,

實際上「nt!KiTrap08」就是「double fault」專用的異常處理程序!

傳遞給它的第三個參數「801d8940」同時也就是 UNEXPECTED_KERNEL_MODE_TRAP 的第二個參數,它是一

個「nt!_NT_TIB」結構的SubSystemTib」字段值:

其實這個字段中包含的信息對於咱們此刻的故障排查而言並不那麼重要,只是怕有人好奇它的前因後果,才略做說明罷了。

上圖中的 nt!KiTrap08 棧幀名稱後面給出了一個 TSS(任務狀態段)的段選擇符爲 28。這纔是關鍵的信息,經過它可會回到

事故現場,分析異常發生時的上下文。這個段選擇符存儲在「nt!_KTSS」結構的首個字段(Backlink)內:

 

看到這裏應該可以稍稍體會出內核中相關數據結構設計的多麼用心良苦!

放下咱們的多愁善感,利用調試器的「.tss」命令,後接段選擇符,便可回到事故現場,以下所示,異常發生時,EIP 指向即將執行

的指令地址爲 977bd06f,換言之是該地址處的「前一條」指令(push ecx)致使的異常,爲啥這條壓棧指令會致使異常呢?

你看「那時」的 esp 已經指向了內核棧的邊界點(8bf47000),而壓棧指令須要先把 esp 值減去 4 字節,而後再把 ecx 的內容

寫入 8BF46FFC 地址處,該地址已經位於邊界以外。

還記得前一篇咱們計算出每次遞歸調用都會消耗掉 16 字節的內核棧空間嗎?這出錯前的最後一次調用中,試圖消耗的最後 4 字節

就在邊界以外!

 

 

低於 8bf47000 的虛擬內存沒有分配實際的物理頁,並且咱們模擬對 8BF46FFC 執行物理地址轉譯也失敗,證明是因爲訪問到

無效地址引起異常的(CR2 寄存器存儲致使頁錯誤的訪問地址):

 

 

如前所述,頁錯誤發生後,在控制權轉移到 nt!KiTrap08 以前,再次向這個無效的地址壓入一張「陷阱幀」,致使再度出現錯誤,

而 nt!KiTrap08 經過傳遞給它的首個參數(0x0000007f)明白了這是一個「double fault」,因此調用 nt!KiBugCheck,後者

探測調試器是否存在,決定是要繪製藍屏仍是斷入調試器。這就是前面那張棧回溯輸出的由來!

 

執行「kv 1000」回溯大範圍的棧幀,你能夠看到 683 次(棧幀編號 0x2b0 - 5)對 tail_recursivef_factorial() 的調用,在

咱們的預測點(685 號棧幀)以前就發生了溢出:

 

最後介紹一個強大的命令「!analyze -v」,它會自動分析內核崩潰的緣由,並給出全部對故障排除有幫助的信息,對於本例而言,

有價值的信息截圖以下:

 

 

—————————————————————————————————————————————————————
小結:本篇以源碼+調試+在線文檔等綜合手段排除了「double fault」藍屏故障;編寫驅動並上機調試是理解內核設計思想

的最佳途經!
—————————————————————————————————————————————————————

相關文章
相關標籤/搜索