C# 托管堆 遭破壞 問題溯源分析

来源:https://www.cnblogs.com/huangxincheng/archive/2023/01/29/17072024.html
-Advertisement-
Play Games

一:背景 1. 講故事 年前遇到了好幾例托管堆被損壞的案例,有些運氣好一些,從被破壞的托管堆記憶體現場能觀測出大概是什麼問題,但更多的情況下是無法做出準確判斷的,原因就在於生成的dump是第二現場,借用之前文章的一張圖,大家可以理解一下。 為了幫助更多受此問題困擾的朋友,這篇來整理一下如何 快狠準 的 ...


一:背景

1. 講故事

年前遇到了好幾例托管堆被損壞的案例,有些運氣好一些,從被破壞的托管堆記憶體現場能觀測出大概是什麼問題,但更多的情況下是無法做出準確判斷的,原因就在於生成的dump是第二現場,借用之前文章的一張圖,大家可以理解一下。

為了幫助更多受此問題困擾的朋友,這篇來整理一下如何 快狠準 的抓取第一現場。

二:抓取第一現場

1. 思路分析

要想抓到第一現場,只需要讓破壞托管堆的那個線程在修改完之後,回到 CLR Pinvoke 層的時候主動觸發GC,因為這時候托管堆已經是損壞狀態了,程式也就會立即崩潰,破壞線程也就被捉jian在床,畫個圖如下:

那如何讓 CLR:PInvoke 主動觸發GC呢? 這就需要藉助微軟的 MDA 托管調試助手,它有一個 gcUnmanagedToManaged 配置項就是專門做這件事情的,參考網址:https://learn.microsoft.com/zh-cn/dotnet/framework/debug-trace-profile/gcunmanagedtomanaged-mda

2. 如何配置 MDA

MDA 的配置非常簡單,大體上分兩步:

  1. 提交註冊表開啟MDA

這裡使用註冊表的方式,需要註意的是,程式和操作系統位數一致的話採用如下方式。


Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework]
"MDA"="1"

如果不一致,採用如下配置,比如 32bit 程式跑在 64bit 系統上。


Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework]
"MDA"="1"

這裡我用的是第二段內容,按照官方文檔描述,將內容保存到 MDAEnable.reg 中,然後在 註冊表編輯器 上導入即可。

  1. 開啟應用程式級捕獲

為了能夠讓 gcUnmanagedToManaged 生效,需要新建應用程式打頭的配置文件,比如: Example_16_1_2.exe.mda.config,內容如下:


<mdaConfig>
	<assistants>
		<gcUnmanagedToManaged/>
	</assistants>
</mdaConfig>

完整截圖:

這樣就算配置好了,當程式在 PInvoke 時,CLR 會讀取註冊表的 MDA 值,如果開啟的話就會讀取 configgcUnmanagedToManaged 子節做相應的邏輯。

tips:如果配置不生效,保守一點的話,建議重啟下機器。

3. 一個托管堆破壞的測試案例

為了演示托管堆損壞,我準備將一個 string 傳給 C++,然後讓 C++ 溢出它來實現托管堆破壞。

C# 代碼如下:


namespace Example_16_1_2
{
    internal class Program
    {
        [DllImport("Example_16_1_3.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
        public extern static void Alloc(string str);

        static void Main(string[] args)
        {
            Test();

            Task.Factory.StartNew(() =>
            {
                Thread.Sleep(3000);
                GC.Collect();
            });

            Console.ReadLine();
        }

        static void Test()
        {
            var str = "hello";
            var str2 = "world!";

            Alloc(str);

        }
    }
}

C++ 代碼如下:


extern "C"
{
	_declspec(dllexport) void Alloc(wchar_t* c);
}

#include "iostream"
#include <Windows.h>
using namespace std;

void Alloc(wchar_t* c)
{
	for (size_t i = 0; i < 10; i++)
	{
		*c++ = 'a';
	}

	wprintf(L"%s \n", c);
}

從代碼邏輯看,只要 Alloc(str) 的線程棧上觸發了 GC 就是第一現場,Task 下的 GC.Collect(); 是第二現場,如果是前者目的就達到了。

激動人心的時刻到了,把程式跑起來後,由於程式崩潰,procdump 立即給我抓了一個 crash dump,截圖如下:

接下來打開 windbg,從序幕信息看果然是 GC 清掃的時候出的問題,托管堆也是損壞狀態,信息如下:


Debug session time: Sun Jan 29 10:14:21.000 2023 (UTC + 8:00)
System Uptime: 0 days 1:14:11.423
Process Uptime: not available
.................................
Loading unloaded module list
..
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(4460.52ac): Access violation - code c0000005 (first/second chance not available)
For analysis of this file, run !analyze -v
eax=00610060 ebx=00000000 ecx=02da23a4 edx=00000001 esi=02da2370 edi=02da2388
eip=79a6f2d1 esp=00d3ef64 ebp=00d3f104 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
clr!WKS::gc_heap::plan_phase+0x79b:
79a6f2d1 f70000000080    test    dword ptr [eax],80000000h ds:002b:00610060=????????

0:000> !VerifyHeap
Could not request method table data for object 02DA1228 (MethodTable: 0000000C).
Last good object: 02DA121C.
object 03da1020: bad member 02DA1228 at 03DA1098
Last good object: 03DA1010.
object 03da2338: bad member 02DA1228 at 03DA2340
Last good object: 03DA2328.
object 03da3568: bad member 02DA2364 at 03DA357C
Last good object: 03DA3558.
Failed to request SyncBlk at index 1.

那是不是主線程引發的GC呢?切過去便知。


0:000> ~0s
eax=00610060 ebx=00000000 ecx=02da23a4 edx=00000001 esi=02da2370 edi=02da2388
eip=79a6f2d1 esp=00d3ef64 ebp=00d3f104 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
clr!WKS::gc_heap::plan_phase+0x79b:
79a6f2d1 f70000000080    test    dword ptr [eax],80000000h ds:002b:00610060=????????
0:000> !clrstack
OS Thread Id: 0x52ac (0)
Child SP       IP Call Site
00d3f220 79a6f2d1 [HelperMethodFrame: 00d3f220] System.StubHelpers.StubHelpers.TriggerGCForMDA()
00d3f294 02bc0aa7 DomainBoundILStubClass.IL_STUB_PInvoke(System.String)
00d3f298 02bc09c9 [InlinedCallFrame: 00d3f298] Example_16_1_2.Program.Alloc(System.String)
00d3f2e0 02bc09c9 Example_16_1_2.Program.Test() [D:\testdump\Example\Example_16_1_2\Program.cs @ 35]
00d3f2f0 02bc0900 Example_16_1_2.Program.Main(System.String[]) [D:\testdump\Example\Example_16_1_2\Program.cs @ 19]
00d3f490 7996f036 [GCFrame: 00d3f490] 
0:000> k 10
 # ChildEBP RetAddr      
00 00d3f104 79a68153     clr!WKS::gc_heap::plan_phase+0x79b
01 00d3f124 79a6847b     clr!WKS::gc_heap::gc1+0xbc
02 00d3f13c 79a68585     clr!WKS::gc_heap::garbage_collect+0x367
03 00d3f15c 79b1ddbd     clr!WKS::GCHeap::GarbageCollectGeneration+0x1bd
04 00d3f16c 79b1de34     clr!WKS::GCHeap::GarbageCollectTry+0x71
05 00d3f198 79d20aed     clr!WKS::GCHeap::GarbageCollect+0xac
06 00d3f204 79d066c0     clr!TriggerGCForMDAInternal+0x7d
07 00d3f28c 02bc0aa7     clr!StubHelpers::TriggerGCForMDA+0x61
WARNING: Frame IP not in any known module. Following frames may be wrong.
08 00d3f2d8 02bc09c9     0x2bc0aa7
09 00d3f2e8 02bc0900     Example_16_1_2!Example_16_1_2.Program.Test+0x39 [D:\testdump\Example\Example_16_1_2\Program.cs @ 35] 
0a 00d3f318 7996f036     Example_16_1_2!Example_16_1_2.Program.Main+0x30 [D:\testdump\Example\Example_16_1_2\Program.cs @ 19] 
0b 00d3f324 799722da     clr!CallDescrWorkerInternal+0x34
0c 00d3f378 7997859b     clr!CallDescrWorkerWithHandler+0x6b
0d 00d3f3ec 79b1b11b     clr!MethodDescCallSite::CallTargetWorker+0x16a
0e 00d3f510 79b1b7fa     clr!RunMain+0x1b3
0f 00d3f77c 79b1b727     clr!Assembly::ExecuteMainMethod+0xf7

從線程棧上的 clr!StubHelpers::TriggerGCForMDA 來看,在 Pinvoke 層果然主動觸發了 GC,成功將 Program.Alloc 這個非托管方法捉jian在床。

三:總結

在此之前很多朋友都會困惑於托管堆破壞導致的程式崩潰,希望這篇文章能夠讓後來者少走彎路。

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

-Advertisement-
Play Games
更多相關文章
  • 在golang中可以使用a := b這種方式將b賦值給a,只有當b能進行深拷貝時a與b才不會互相影響,否則就需要進行更為複雜的深拷貝。 下麵就是Go賦值操作的一個說明: Go語言中所有賦值操作都是值傳遞,如果結構中不含指針,則直接賦值就是深度拷貝;如果結構中含有指針(包括自定義指針,以及切片,map ...
  • 第一種方式:使用{} firstDict = {"name": "wang yuan wai ", "age" : 25} 說明:{}為創建一個空的字典對象 第二種方式:使用fromkeys()方法 second_dict = dict.fromkeys(("name", "age")) #valu ...
  • 如果您想查找高於或低於平均值的數字,可以不必計算該平均值,就能查看更高或更低的值。通過Java應用程式,可以自動突出顯示這些數字。除了快速突出顯示高於或低於平均值的值外,您還可以查看高於或低於的值的個數。現在讓我們看看如何在 Java應用程式中實現此操作。 引入jar包 導入方法1: 手動引入。將  ...
  • 幾乎所有的高級編程語言都有自己的垃圾回收機制,開發者不需要關註記憶體的申請與釋放,Python 也不例外。Python 官方團隊的文章 https://devguide.python.org/internals/garbage-collector 詳細介紹了 Python 中的垃圾回收演算法,本文是這篇 ...
  • 在新版本的pandas中,上述代碼會引起警告,建議改成SQLAlchemy connectable(engine/connection),後續代碼將引入這種升級的連接方式。 ...
  • *以下內容為本人的學習筆記,如需要轉載,請聲明原文鏈接 微信公眾號「englyf」https://mp.weixin.qq.com/s/2GFLTstDC7w6u3fTJxflNA 本文大概 1685 個字,閱讀需花 6 分鐘內容不多, 但也花了一些精力如要交流, 歡迎關註我然後評論區留言 謝謝你的 ...
  • 目錄 背景 get 與 post 的區別 所有介面都用 post 請求? 背景 最近在逛知乎的時候發現一個有趣的問題:公司規定所有介面都用 post 請求,這是為什麼? 看到這個問題的時候其實我也挺有感觸的,因為我也曾經這樣問過我自己。在上上一家公司的時候接到一個項目是從零開始搭建一個微服務,當時就 ...
  • 前言 .NET6 開始,.NET Croe API 項目取消了 Startup.cs 文件,在 Program.cs 文件的 Main 函數中完成服務的註冊和中間件管道的管理。但當我們項目引入更多包的時候,Program.cs 文件也會看起來很臃腫。 而且,我們不只會有一個後端項目,為了方便快速創建 ...
一周排行
    -Advertisement-
    Play Games
  • .NET Core 選項系統的主要實現在 Microsoft.Extensions.Options 和 Microsoft.Extensions.Options.ConfigurationExtensions 兩個 Nuget 包。對於一個框架的源碼進行解讀,我們可以從我們常用的框架中的類或方法入手 ...
  • 最近在工作中遇到一個問題,就是我有多個線程會調用bitmap對象,運行的時候報錯,對象當前正在其他地方使用。第一反應肯定是加鎖啊,於是我就在每個用到bitmap的地方都加了鎖,但是運行之後依然報這個錯 測試代碼如下 using System; using System.Drawing; using ...
  • 一:背景 1. 講故事 前段時間有位朋友微信找到我,說他的程式使用 hsl 庫之後,採集 plc 時記憶體溢出,讓我幫忙看一下怎麼回事,哈哈,貌似是分析之旅中的第二次和 hsl 打交道,既然找到我,那就上 windbg 說話吧。 二:WinDbg 分析 1. 為什麼會記憶體溢出 簡單觀察程式的提交記憶體之 ...
  • 在 IIS 上啟用 Websocket 在 Windows Server 2012 或更高版本上啟用對 WebSocket 協議的支持: 備註 使用 IIS Express 時無需執行這些步驟 通過“管理”菜單或“伺服器管理器”中的鏈接使用“添加角色和功能”嚮導。 選擇“基於角色或基於功能的安裝”。 ...
  • C#-垃圾回收機制(GC) 什麼是GC 官網中有這麼一句話: The garbage collector is a common language runtime component that controls the allocation and release of managed memory ...
  • 呆了2個大屏行業的公司,對大屏幕有一些瞭解,所以整理下所瞭解的觸摸屏相關概念。方便自己以及進入這個行業的小伙伴們,能有個系統、快速的認知。 觸摸屏詳細的知識點,網上其實都有。整理資料過程中,我也瞭解了更多的觸摸屏知識,像聲波屏、光學屏之類的之前就沒接觸。下麵分不同的模塊,給大家介紹 交互觸摸屏類型 ...
  • 近段時間忙於各種項目和對【易排平臺】的優化,沒顧得上分享APS相關的小技巧,回頭看看小公眾號的關註人數早已達1500+,在此爭取時間寫一下這段時間在項目上及平臺優化過程中遇到的一些小技巧,以感謝諸位的關註。過去數月的解決的問題中,涉及最多的是規劃模型中,實現各種時間維度的功能,目前在平臺上也稍有成果 ...
  • 針對大量log日誌快速定位錯誤地方 動態查看日誌 tail -f catalina.ou 從頭打開日誌文件 cat catalina.ou 可以使用 >nanjiangtest.txt 輸出某個新日誌去查看 [[email protected] logs]# cat -n catalina.out |grep 7 ...
  • 前言 RocketMQ是阿裡巴巴旗下一款開源的MQ框架,經歷過雙十一考驗、Java編程語言實現,有非常好完整生態系統。RocketMQ作為一款純java、分散式、隊列模型的開源消息中間件,支持事務消息、順序消息、批量消息、定時消息、消息回溯等 本篇文章第一部分屬於一些核心概念和工作流程的講解;第二部 ...
  • 在java,c#類的成員修飾符包括,公有、私有、程式集可用的、受保護的。 對於python來說,只有兩個成員修飾符:公有成員,私有成員 成員修飾符是來修飾誰呢?當然是修飾成員了。那麼python類的成員包括什麼呢? python成員: 欄位,方法,屬性 每個類成員的修飾符有兩種: 公有成員:內部外部 ...