承接上篇整體掃描,本文聚焦「堆疊」——每個執行緒擁有的獨立記憶體區域。我們將實測主執行緒與工作者執行緒的堆疊佈局,驗證 NT_TIB(Thread Information Block)中 StackBase/StackLimit 的真實含義,並揭示 PAGE_GUARD 在堆疊動態增長中的關鍵角色。
VS2008 除錯中斷,主執行緒 ESP = 0x00124914,落在區塊 0x00120000 ~ 0x00130000(大小 64KB,MEM_COMMIT,PAGE_READWRITE)。
// FS:[0] 指向 NT_TIB
NT_TIB *pTib = (NT_TIB*)__readfsdword(0);
pTib->StackBase // 0x00130000
pTib->StackLimit // 0x0000A000 (注意:這不是實際可用下限!)
StackBase 與區塊上緣 0x00130000 吻合,但 StackLimit 只有 0xA000,顯然不代表實際保護邊界。
| BaseAddress | AllocationBase | RegionSize | State | Protect | Type |
|---|---|---|---|---|---|
| 0x00030000 | 0x00030000 | 0x00100000 | MEM_RESERVE | PAGE_NOACCESS | MEM_PRIVATE |
| 0x00120000 | 0x00030000 | 0x00010000 | MEM_COMMIT | PAGE_READWRITE | PAGE_GUARD | MEM_PRIVATE |
| 0x00130000 | 0x00030000 | 0x00010000 | MEM_COMMIT | PAGE_READWRITE | MEM_PRIVATE |
關鍵發現:
AllocationBase 統一為 0x00030000。PAGE_GUARD(0x00120000 ~ 0x00121000)。StackLimit (0xA000) 僅反映初始提交下限,非硬性邊界。MSDN 定義:PAGE_GUARD 頁被存取時,系統引發 STATUS_GUARD_PAGE_VIOLATION 例外,隨後移除 PAGE_GUARD 屬性,將該頁轉為正常 PAGE_READWRITE,並將下一頁標記為新的 PAGE_GUARD。
流程:
AllocationBase 以內)。這機制實現了「按需提交」,節省實體記憶體,同時保留 1MB 虛擬空間上限。
掃描所有 MEM_PRIVATE + PAGE_GUARD 區塊,即可定位所有執行緒堆疊:
| BaseAddress | RegionSize | AllocationBase | 推測執行緒 |
|---|---|---|---|
| 0x00120000 | 0x1000 | 0x00030000 | 主執行緒 |
| 0x02A40000 | 0x1000 | 0x02A30000 | 工作者執行緒 1 |
| 0x03B50000 | 0x1000 | 0x03B40000 | 工作者執行緒 2 |
| 0x04C60000 | 0x1000 | 0x04C50000 | 工作者執行緒 3 |
每個執行緒堆疊保留 1MB(預設 CreateThread dwStackSize=0),Guard Page 位於已提交區域的下緣。
CreateThread 第 2 參數 dwStackSize:0 = 預設 1MB 保留;>0 指定保留大小(系統向上對齊至頁邊界)。/STACK:reserve[,commit]:設定主執行緒堆疊保留/初始提交大小。new/malloc)或靜態儲存。堆疊不是固定大小的塊,而是「保留大、提交小、Guard Page 推進」的動態結構。NT_TIB.StackBase 指向保留區頂端,StackLimit 僅反映當前提交下限。理解此機制,有助於診斷堆疊溢位、優化執行緒記憶體佔用、編寫高可靠的多執行緒程式。