網站首頁 編程語言 正文
一:背景
1.講故事
最近遇到一位朋友的程序崩潰,發現崩潰點在富編輯器 msftedit
上,這個不是重點,重點在于發現他已經開啟了 頁堆
,看樣子是做了最后的掙扎。
0:000> !analyze -v EXCEPTION_RECORD: (.exr -1) ExceptionAddress: 82779a9e (msftedit!CCallMgrCenter::SendAllNotifications+0x00000123) ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000001 Parameter[1]: 8351af28 Attempt to write to address 8351af28 ... STACK_TEXT: 00ffe0dc 827bda2a 8351ae88 00000000 00ffe174 msftedit!CCallMgrCenter::SendAllNotifications+0x123 00ffe110 827bd731 00ffe324 00ffe174 00ffe300 msftedit!CCallMgrCenter::ExitContext+0xda 00ffe120 827bde71 8351ae88 827232dc 28112f80 msftedit!CCallMgr::~CCallMgr+0x17 00ffe300 8290281f 00000102 00000067 00220001 msftedit!CTxtEdit::TxSendMessage+0x201 00ffe374 7576110b 00f20268 00000102 00000067 msftedit!RichEditWndProc+0x9cf 00ffe3a0 757580ca 82901e50 00f20268 00000102 user32!_InternalCallWinProc+0x2b ... SYMBOL_NAME: system_windows_forms+1c45e7 MODULE_NAME: System_Windows_Forms IMAGE_NAME: System.Windows.Forms.dll 0:000> !heap -p Active GlobalFlag bits: vrf - Enable application verifier hpa - Place heap allocations at ends of pages StackTraceDataBase @ 04c20000 of size 01000000 with 00001b18 traces PageHeap enabled with options: ENABLE_PAGE_HEAP COLLECT_STACK_TRACES active heaps: + 5c20000 ENABLE_PAGE_HEAP COLLECT_STACK_TRACES NormalHeap - 5d90000 HEAP_GROWABLE + 5e90000 ENABLE_PAGE_HEAP COLLECT_STACK_TRACES NormalHeap - 4960000 HEAP_GROWABLE HEAP_CLASS_1 ...
由于 頁堆
和 NT堆
的內存布局完全不一樣,這一篇結合我的了解以及 windbg 驗證來系統的介紹下 頁堆
。
二:對 頁堆 的研究
1. 案例演示
為了方便講述,先上一段測試代碼。
int main() { HANDLE h = HeapCreate(NULL, 0, 100); int* ptr = (int*)HeapAlloc(h, 0, 9); printf("ptr= %x", ptr); DebugBreak(); }
接下來用 gflags
開啟下頁堆。
PS C:\Users\Administrator\Desktop> gflags -i ConsoleApplication1.exe +hpa Current Registry Settings for ConsoleApplication1.exe executable are: 02000000 hpa - Enable page heap
然后將程序跑起來,可以看到返回的 handle 句柄。
2. 頁堆布局研究
接下來用 windbg 的 !heap -p
命令觀察頁堆。
0:000> !heap -p Active GlobalFlag bits: hpa - Place heap allocations at ends of pages StackTraceDataBase @ 042e0000 of size 01000000 with 0000000e traces PageHeap enabled with options: ENABLE_PAGE_HEAP COLLECT_STACK_TRACES active heaps: + 5b0000 ENABLE_PAGE_HEAP COLLECT_STACK_TRACES NormalHeap - 710000 HEAP_GROWABLE + 810000 ENABLE_PAGE_HEAP COLLECT_STACK_TRACES NormalHeap - 510000 HEAP_GROWABLE HEAP_CLASS_1 + 56e0000 ENABLE_PAGE_HEAP COLLECT_STACK_TRACES NormalHeap - 5aa0000 HEAP_CLASS_1
稍微解讀下上面的輸出。
+ 56e0000**
表示 頁堆 的堆句柄。
NormalHeap - 5aa0000
表示 頁堆
關聯的 NT堆
,可能有朋友要問了,既然都開啟頁堆
了, 還要弄一個 ntheap 干嘛? 大家不要忘了,windows 的一些系統api會用到這個堆。
接下來有一個問題,如何觀察這兩個 heap 之間的關聯關系呢? 要回答這個問題,需要了解 頁堆
的布局結構,畫個簡圖如下:
從圖中可以看到,離句柄偏移 4k
的位置有一個 DPH_HEAP_ROOT
結構,它相當于 NTHEAP 的_HEAP
,我們拿 56e0000
舉個例子。
0:000> dt nt!_DPH_HEAP_ROOT 56e0000+0x1000 ntdll!_DPH_HEAP_ROOT ... +0x0b4 NormalHeap : 0x05aa0000 Void +0x0b8 CreateStackTrace : 0x042f4d94 _RTL_TRACE_BLOCK +0x0bc FirstThread : (null)
上面輸出的 NormalHeap: 0x05aa0000
就是它關聯的 ntheap 句柄。
3. 堆塊布局研究
對頁堆
有了一個整體認識,接下來繼續研究堆塊句柄,我們發現 ptr=0x56e5ff0
是落在 56e0000
這個頁堆上,接下來我們導出這個頁堆的詳細信息。
0:000> !heap -p -h 56e0000 _DPH_HEAP_ROOT @ 56e1000 Freed and decommitted blocks DPH_HEAP_BLOCK : VirtAddr VirtSize Busy allocations DPH_HEAP_BLOCK : UserAddr UserSize - VirtAddr VirtSize 056e1f70 : 056e5ff0 00000009 - 056e5000 00002000 unknown!fillpattern _HEAP @ 5aa0000 No FrontEnd _HEAP_SEGMENT @ 5aa0000 CommittedRange @ 5aa04a8 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 05aa04a8 0167 0000 [00] 05aa04b0 00b30 - (free) * 05aa0fe0 0004 0167 [00] 05aa0fe8 00018 - (busy) VirtualAllocdBlocks @ 5aa009c
上面的信息如何解讀呢?我們逐一來聊一下吧。
_DPH_HEAP_ROOT @ 56e1000
這個已經和大家聊過了,它和 _HEAP
結構是一致的。
DPH_HEAP_BLOCK :
從字面意思就能看出來和 ntheap
的 heap_entry
是一致的,都是用來描述堆塊信息, 不過有一點要注意,這個堆塊是落在上圖中的 DPH_HEAP_BLOCK Pool
池鏈表結構中的,言外之意就是它不會作為 heap_entry
的頭部附加信息,接下來我們 dt 導出來看看。
0:000> dt ntdll!_DPH_HEAP_BLOCK 056e1f70 +0x000 pNextAlloc : 0x056e1020 _DPH_HEAP_BLOCK +0x000 AvailableEntry : _LIST_ENTRY [ 0x56e1020 - 0x0 ] +0x000 TableLinks : _RTL_BALANCED_LINKS +0x010 pUserAllocation : 0x056e5ff0 "???" +0x014 pVirtualBlock : 0x056e5000 "???" +0x018 nVirtualBlockSize : 0x2000 +0x01c nVirtualAccessSize : 0x20 +0x020 nUserRequestedSize : 9 +0x024 nUserActualSize : 0x56e1f60 +0x028 UserValue : 0x056e1fc8 Void +0x02c UserFlags : 0x3f18 +0x030 StackTrace : 0x042f4dcc _RTL_TRACE_BLOCK +0x034 AdjacencyEntry : _LIST_ENTRY [ 0x56e1010 - 0x56e1010 ] +0x03c pVirtualRegion : (null)
從字段信息看,它記錄了堆塊的分配首地址,棧信息等等,比如用 dds 觀察一下 StackTrace。
0:000> dds 0x042f4dcc 042f4dcc 00000000 042f4dd0 00006001 042f4dd4 000d0000 042f4dd8 78aba8b0 verifier!AVrfDebugPageHeapAllocate+0x240 042f4ddc 77e0ef8e ntdll!RtlDebugAllocateHeap+0x39 042f4de0 77d76150 ntdll!RtlpAllocateHeap+0xf0 042f4de4 77d757fe ntdll!RtlpAllocateHeapInternal+0x3ee 042f4de8 77d753fe ntdll!RtlAllocateHeap+0x3e 042f4dec 00ad1690 ConsoleApplication1!main+0x30 [D:\net6\ConsoleApp1\ConsoleApplication1\DisplayGreeting.cpp @ 14] 042f4df0 00ad1bc3 ConsoleApplication1!invoke_main+0x33 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78] 042f4df4 00ad1a17 ConsoleApplication1!__scrt_common_main_seh+0x157 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 042f4df8 00ad18ad ConsoleApplication1!__scrt_common_main+0xd [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 331] 042f4dfc 00ad1c48 ConsoleApplication1!mainCRTStartup+0x8 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp @ 17] 042f4e00 7646fa29 KERNEL32!BaseThreadInitThunk+0x19 042f4e04 77d975f4 ntdll!__RtlUserThreadStart+0x2f 042f4e08 77d975c4 ntdll!_RtlUserThreadStart+0x1b ...
接下來再回答一個問題,頁堆的堆塊有沒有頭部附加信息呢?當然是有的,叫做 DPH_BLOCK_INFORMATION
,即在 UserPtr-0x20
的位置,我們可以用 dt 顯示一下。
0:000> ?? sizeof(ntdll!_DPH_BLOCK_INFORMATION) unsigned int 0x20 0:000> dt ntdll!_DPH_BLOCK_INFORMATION 056e5ff0-0x20 +0x000 StartStamp : 0xabcdbbbb +0x004 Heap : 0x056e1000 Void +0x008 RequestedSize : 9 +0x00c ActualSize : 0x1000 +0x010 FreeQueue : _LIST_ENTRY [ 0x0 - 0x0 ] +0x010 FreePushList : _SINGLE_LIST_ENTRY +0x010 TraceIndex : 0 +0x018 StackTrace : 0x042f4dcc Void +0x01c EndStamp : 0xdcbabbbb ...
根據上面兩個輸出,在腦海中應該可以繪出如下圖:
這里要稍微解釋下 柵欄頁
的概念。
4. 柵欄頁
每一個 heap_entry 都會占用 8k 的空間,第一個 4k 是用戶區,第二個 4k 是柵欄區,為了就是當代碼越界時訪問了這個 柵欄頁 會立即報錯,因為柵欄頁是禁止訪問的,我們可以提取 UserAddr
附近的內存,看看 056e6000= 056e5000+0x1000
后面是不是都是問號。
0:000> dp 056e5ff0 056e5ff0 c0c0c0c0 c0c0c0c0 d0d0d0c0 d0d0d0d0 056e6000 ???????? ???????? ???????? ???????? 056e6010 ???????? ???????? ???????? ???????? 056e6020 ???????? ???????? ???????? ???????? 056e6030 ???????? ???????? ???????? ???????? 056e6040 ???????? ???????? ???????? ???????? 056e6050 ???????? ???????? ???????? ???????? 056e6060 ???????? ???????? ???????? ???????? 0:000> !address 056e5000+0x1000 Usage: PageHeap Base Address: 056e6000 End Address: 057e0000 Region Size: 000fa000 (1000.000 kB) State: 00002000 MEM_RESERVE Protect: <info not present at the target> Type: 00020000 MEM_PRIVATE Allocation Base: 056e0000 Allocation Protect: 00000001 PAGE_NOACCESS More info: !heap -p 0x56e1000 More info: !heap -p -a 0x56e6000 Content source: 0 (invalid), length: fa000
三:總結
這就是對 頁堆
的一個研究,總的來說 頁堆
是一種專用于調試的堆,優缺點如下:
優點:
因為 柵欄頁 緊鄰 用戶頁,一旦代碼越界進入了 柵欄頁,會立即報 訪問違例 異常,這樣我們就可以獲取第一現場錯誤。
缺點:
對空間造成了巨大浪費,即使 1byte 的內存分配,也需要至少 2 個內存頁 的內存占用 (8k)。
哈哈,對調試程序崩潰類問題,非常值得一試!
原文鏈接:https://www.cnblogs.com/huangxincheng/p/16759052.html
相關推薦
- 2022-05-13 Linux操作系統筆記——GCC編譯器
- 2022-06-08 如何在springboot中使用Thymeleaf
- 2022-04-02 C語言實現簡單的五子棋游戲_C 語言
- 2022-03-16 C++?NFS掛載及掛載命令_C 語言
- 2022-06-15 ASP.NET?MVC使用區域(Area)功能_基礎應用
- 2023-07-02 Python?中的裝飾器實現函數的緩存(場景分析)_python
- 2023-02-06 C++11中longlong超長整型和nullptr初始化空指針_C 語言
- 2022-07-14 C#把dll分別放在指定的文件夾的方法步驟_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支