Stack overflow in Window kernel drivers

 

Drivers taking too much space on the kernel stack that results in a kernel stack overflow, which will then crash the system with one of the following bugchecks:

1. STOP 0x7F: UNEXPECTED_KERNEL_MODE_TRAP with Parameter 1 set to EXCEPTION_DOUBLE_FAULT, which is caused by running off the end of a kernel stack.

2. STOP 0x1E: KMODE_EXCEPTION_NOT_HANDLED, 0x7E: SYSTEM_THREAD_EXCEPTION_NOT_HANDLED, or 0x8E: KERNEL_MODE_EXCEPTION_NOT_HANDLED, with an exception code of STATUS_ACCESS_VIOLATION, which indicates a memory access violation.

3. STOP 0x2B: PANIC_STACK_SWITCH, which usually occurs when a kernel-mode driver uses too much stack space.

Kernel Stack Overview

內核模式堆棧是一個有限的存儲區域,經常用於存儲從一個函數傳遞到另一個函數的信息以及用於局部變量存儲。雖然堆棧被映射到系統空間,但是它被視爲原始調用例程的線程上下文的一部分,而不是驅動程序本身的一部分。這意味着只要調用線程正在運行,就可以保證存在堆棧,但是它可以隨線程一起清除。在任何內核模式線程上運行的代碼(不管它是系統線程還是驅動程序創建的線程)都使用該線程的內核模式堆棧(除非代碼是 DPC,這種情況下它在某些平臺上使用處理器的 DPC 堆棧)。

內核模式堆棧的大小在不同的硬件平臺上有所不同。例如:

在基於 x86 的平臺上,內核模式堆棧是 12K

在基於 x64 的平臺上,內核模式堆棧是 24K。(基於 x64 的平臺包括使用 AMD64 體系結構處理器的系統和使用 Intel EM64T 體系結構處理器的系統)。

在基於 Itanium 的平臺上,內核模式堆棧是 32K(帶有 32K 的備份存儲區)。(如果處理器用光來自其寄存器文件的寄存器,那麼它在分配函數返回之前使用備份存儲區來存放寄存器的內容。這不會直接影響到堆棧分配,但是操作系統在基於 Itanium 的平臺上使用的寄存器比在其它平臺上使用的更多,這使得更多的堆棧可用於驅動程序。)

每個線程都分配有一個內核模式堆棧,所以即使少量增加堆棧的大小都會極大地增加系統的內存佔用。因此,給定平臺上內核模式堆棧的大小由操作系統設置,不能修改。

 

使用內核模式堆棧的指南。

驅動程序應該適當地使用內核模式堆棧並避免深度嵌套或遞歸的調用。傳遞許多字節數據的嚴重遞歸的函數可能很快用光堆棧空間。驅動程序不應該在堆棧上傳遞大量數據,而應該分配系統空間的內存(根據數據將被用於何處,從分頁內存池和未分頁內存池進行分配)並傳遞數據的指針。對於遞歸的函數,驅動程序應該限制發生遞歸調用的次數。

儘可能將函數設計爲採用單個結構的指針而不是單個變量作爲參數。如果您需要將大量基於堆棧的參數從一個函數傳遞到另一個函數,那麼將局部變量歸類到結構中並將結構的本地副本的指針傳遞給目標函數。這將在隨後的調用中節約內核堆棧空間。例如,下面的結構在堆棧上佔據單個 PVOID 的大小,而不需要單獨傳遞變量的 6 ULONG

typedef struct _COMPUTE_AXIS_COUNT_PARAMS {

ULONG x;

ULONG y;

ULONG z;

ULONG xcount;

ULONG ycount;

ULONG zcount;

} COMPUTE_AXIS_COUNT_PARAMS;

COMPUTE_AXIS_COUNT_PARAMS params;

ComputeAxisCount(&params);

 

不管函數是否進行嵌套或遞歸調用,驅動程序都應該通過只將指針或簡單計數器聲明爲局部變量來使內核模式堆棧的佔用最小。避免將局部變量聲明爲字節或字符串數組來用作函數的本地緩衝區。而應該聲明已在分頁內存池或未分頁內存池中進行分配的緩衝區指針。(記住,未分頁內存池也是一種有限的資源,請節約使用。)如果您必須在堆棧上聲明結構的本地副本,那麼請確保結構相對較小。避免在堆棧上聲明大型結構或聚合結構(例如 C++ 類)的本地副本。

在驅動程序的中斷服務例程 (ISR) 中最小化內核模式堆棧佔用非常重要。從 ISR 調用的函數與在任何線程中調用的函數具有相同的堆棧限制。但是,ISR 在任意線程上下文中運行,因此 ISR 會使用它正好在其上運行的線程的內核模式堆棧。這可能是當前線程的堆棧或處理器的 DPC 堆棧(如果 DPC 正在運行的話)。在任何情況下,ISR 可用的堆棧都很少(取決於該線程上堆棧的其它用戶)。

在延遲過程調用 (DPC) 中,驅動程序在使用內核模式堆棧方面可以自由一些。DPC 爲所有平臺(除了基於 Itanium 的平臺)上的每個處理器使用一個內核模式堆棧。(在基於 Itanium 的平臺上,DPC 使用當前線程的堆棧。)對於每個處理器,操作系統分配單個內核模式堆棧,供運行在該處理器上的任何 DPC 使用。在給定的處理器上,同一時間只有一個 DPC 在運行,所以 DPC 實際上擁有自己的堆棧。

要確定是否存在足夠的堆棧空間來調用函數或執行任務,驅動程序可以調用 IoGetStackLimits IoGetRemainingStackSize 例程。如果沒有足夠的堆棧空間可用,那麼驅動程序可以將任務排隊到一個工作項(在單獨的線程中運行,因此擁有其自身的內核模式堆棧)。但是,請記住,工作項將以 PASSIVE_LEVEL 級別在系統工作線程中運行。請記住,在原始版本的 Windows Server 2003 RTM 和早期版本的 Windows 中,必須以 IRQL PASSIVE_LEVEL IRQL APC_LEVEL 調用 IoGetStackLimits IoGetRemainingStackSize,因此不能以 DISPATCH_LEVEL 或更高級別(例如 DPC 例程)從任何例程調用它們。從 Windows Server 2003 SP1 開始,可以以任何 IRQL 調用 IoGetStackLimits IoGetRemainingStackSize

重要:驅動程序不應該另外分配內存並將其作爲內核模式堆棧使用。這絕不是任何平臺的建議實踐,因爲它會影響操作系統的穩定性和可靠性。在基於 x64 的系統上,如果操作系統檢測到未經授權的內核模式堆棧,那麼它將生成一個錯誤檢查並關閉系統。

在您的驅動程序中調試內核模式堆棧使用情況。 PREfast Windows DDK 提供的帶有驅動程序特定規則的靜態源代碼分析工具,可用來查找正在使用過量內核模式堆棧的函數。使用 PREfast 編譯命令開關 /STACKHOGTHRESHOLD 來更改 PREfast 的默認堆棧使用閾值。

堆棧空間耗盡將導致操作系統崩潰和一個或幾個可能的錯誤檢查。可能包括下列錯誤檢查:

0x7FUNEXPECTED_KERNEL_MODE_TRAPParm1 設置爲 EXCEPTION_DOUBLE_FAULT),由超出內核堆棧結束地址引起。

0x1EKMODE_EXCEPTION_NOT_HANDLED0x7ESYSTEM_THREAD_EXCEPTION_NOT_HANDLED 0x8EKERNEL_MODE_EXCEPTION_NOT_HANDLED 和一個異常碼 STATUS_ACCESS_VIOLATION,後者指示內存訪問違法。

0x2BPANIC_STACK_SWITCH,通常在內核模式驅動程序使用過多堆棧空間時發生。

要調試這些問題,請使用 kf(顯示堆棧跟蹤)調試器命令來顯示每個函數消耗的堆棧數量。如果堆棧跟蹤顯示不完整,那麼您可以通過檢查指示返回地址的符號的原始堆棧內存來手動確定堆棧跟蹤。要爲您的搜索建立邊界,請使用 !thread 調試器擴展來找出堆棧的限制,然後使用 dps(顯示字和符號)調試器命令來檢查內存並嘗試解析每個指針大小數目的符號。(請注意,在堆棧上找到的符號不一定指示有效的返回地址。)

檢查基於 x86 的平臺上的堆棧使用情況的另一項技術是檢查函數的反彙編並查看函數入口處堆棧指針的移動量。(這種技術還可以在基於 x64 的平臺上使用,但是反彙編稍有不同。)例如,下列典型的函數序言指示函數本地堆棧使用 140 字節 (0x8c),不計算函數可能調用的子例程消耗的任何附加堆棧:

1e3bf1de 8bff             mov     edi,edi 1e3bf1e0 55               push    ebp 1e3bf1e1 8bec             mov     ebp,esp 1e3bf1e3 81ec8c000000     sub     esp,0x8c

您可以使用 uf(反彙編函數)調試器命令或 WinDBG 反彙編窗口用彙編語言顯示函數代碼。

您應該做什麼?

在您的驅動程序中最小化堆棧使用率:

只聲明一個局部變量(指針或簡單計數器)。

決不要在棧上分配大型結構或其他聚合結構(例如 C++ 類)。

不要將局部變量聲明爲字節或字符串數組來用作函數的本地緩衝區。而應該在分頁內存池或未分頁內存池中分配緩衝區並聲明一個該緩衝區的指針。

避免深度嵌套或遞歸的調用。

不要將堆棧上的大量數據傳遞給另一個函數,而應該分配系統空間內存並傳遞數據的指針。

在遞歸函數中,限制發生遞歸調用的次數。

儘可能將函數設計爲採用單個結構的指針而不是單個變量作爲參數。

使用 IoGetStackLimits IoGetRemainingStackSize 例程來確定是否存在足夠的堆棧空間來調用函數去執行任務,如果沒有,將任務排隊到一個工作項中。

分析和調試驅動程序中內核模式堆棧的使用情況:

使用 PREfast 來查找過度使用內核模式堆棧的函數。

使用 kf 調試器命令顯示每個函數消耗的堆棧數量。如果堆棧顯示不完整,那麼使用 !thread dps 來識別可能的返回地址。

在基於 x86 和基於 x64 的平臺上,檢查函數的反彙編來查看函數入口處堆棧指針的移動量。

 

Pasted from <http://www.microsoft.com/china/whdc/driver/tips/KMstack.mspx>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章