從 C# 崩潰異常 中研究頁堆佈局

来源:https://www.cnblogs.com/huangxincheng/archive/2022/10/07/16759052.html
-Advertisement-
Play Games

一:背景 1.講故事 最近遇到一位朋友的程式崩潰,發現崩潰點在富編輯器 msftedit 上,這個不是重點,重點在於發現他已經開啟了 頁堆 ,看樣子是做了最後的掙扎。 0:000> !analyze -v EXCEPTION_RECORD: (.exr -1) ExceptionAddress: 8 ...


一:背景

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 

稍微解讀下上面的輸出。

  1. + 56e0000**

表示 頁堆 的堆句柄。

  1. 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

上面的信息如何解讀呢?我們逐一來聊一下吧。

  1. _DPH_HEAP_ROOT @ 56e1000

這個已經和大家聊過了,它和 _HEAP 結構是一致的。

  1. DPH_HEAP_BLOCK :

從字面意思就能看出來和 ntheapheap_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)。

哈哈,對調試程式崩潰類問題,非常值得一試!

圖片名稱
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • @Transactional(readOnly=true)就可以把事務方法設置成只讀事務。設置了只讀事務,事務從開始到結束,將看不見其他事務所提交的數據。這在某種程度上解決了事務併發的問題。一個方法內,如果沒有對資料庫insert、update、delete的操作,那麼,這個事務方法是可以設置成re ...
  • Spring @Transactional註解isolation屬性 @Transactional註解通過isolation屬性設置事務隔離級別。如下: @Transactional(isolation=Isolation.DEFAULT) public void method(){} isolat ...
  • 有時候,我們明明在類或者方法上添加了@Transactional註解,卻發現方法並沒有按事務處理。其實,以下場景會導致事務失效。 1、事務方法所在的類沒有載入到Spring IOC容器中。 @Transactional是Spring的註解,未被Spring管理的類中的方法不受@Transaction ...
  • 在Spring里,一個事務方法被另外一個事務方法調用時,兩個方法的事務應該如何進行,說白話一點,就是說當出現異常需要回滾時,各個方法的數據操作是否要全部回滾,事務傳播行為就是決定了這樣的一個處理結果。A事務方法(外部方法)調用了B事務方法(內部方法,又叫被調用方法),B是繼續在A的事務中運行呢?還是 ...
  • 上一節說了Spring的事務配置,其中,聲明式事務配置里有5種配置方式, @Transactional註解應該是最為常用的一種方式了。這一節就說說@Transactional註解。 @Transactional註解可以放到類名或者方法名上面, 寫在類名上面,如下: @Transactional( p ...
  • 一、概念 委托的本質也是一種類型,類似於Class這樣。作用是將一個方法作為參數傳遞給另一個方法,關鍵字是delegate 二、委托的定義使用步驟 第一步聲明委托: public delegate int myDelegate(int a, int b); 1、聲明一個委托類型,可以用訪問修飾符修飾 ...
  • 大家好,先祝大家國慶快樂。不過大家看到這篇文章的時候估計已經過完國慶了 :)。 上一篇我們寫瞭如何通過 SelfContained 模式發佈程式(不安裝運行時運行.NET程式)達到不需要在目標機器上安裝 runtime 就可以運行 .NET 程式的目標。其實除了標準的 self-contained ...
  • .NET下資料庫的負載均衡(有趣實驗)這篇文章發表後,受到了眾多讀者的關註與好評,其中不乏元老級程式員。 讀者來信中詢問最多的一個問題是:它是否能支持“異種資料庫”的負載均衡?? 今天就在此統一回覆:能(暫時只能在.Net6版本下實現。.Net Framwork版本後續會再實現。) 下麵就通過一個例 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...