記一次 .NET 某醫院預約平臺 非托管泄露分析

来源:https://www.cnblogs.com/huangxincheng/archive/2023/07/05/17528211.html
-Advertisement-
Play Games

## 一:背景 ### 1. 講故事 前幾天有位朋友找到我,說他的程式有記憶體泄露,讓我幫忙排查一下,截圖如下: ![](https://img2023.cnblogs.com/blog/214741/202307/214741-20230705122211449-1056329846.png) 說實 ...


一:背景

1. 講故事

前幾天有位朋友找到我,說他的程式有記憶體泄露,讓我幫忙排查一下,截圖如下:

說實話看到 32bit, 1.5G 這些關鍵詞之後,職業敏感告訴我,他這個可能是虛擬地址緊張所致,不管怎麼說,有了 Dump 就可以上馬分析。

二:WinDbg分析

1. 虛擬地址緊張所致嗎

要看是不是虛擬地址緊張,可以用 !address -summary 觀察下記憶體段統計信息,截圖如下:

我去,用 WinDbg Preview 盡然分析不了,在載入 ntdll 的過程中死掉了,如果你是我們調試訓練營的朋友,應該會深深的有體會,我們分析的第一個dump就存在這個情況,這個載入不了其實就預示著一種非托管泄露,這裡暫不劇透。

WinDbg Preview 分析不了怎麼辦呢?可以用 Windbg 的其他版本哈,比如 Windbg10, WinDbg6 等等,這裡就採用 WinDbg10 X86 版本打開吧。


0:000> !address -summary

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                    179          8cbb1000 (   2.199 GB)           54.97%
Heap                                   6598          376f6000 ( 886.961 MB)  48.09%   21.65%
<unknown>                              3091          31954000 ( 793.328 MB)  43.02%   19.37%
Image                                   376           8c0d000 ( 140.051 MB)   7.59%    3.42%
Stack                                    75           1780000 (  23.500 MB)   1.27%    0.57%
Other                                     7             4e000 ( 312.000 kB)   0.02%    0.01%
TEB                                      25             19000 ( 100.000 kB)   0.01%    0.00%
PEB                                       1              1000 (   4.000 kB)   0.00%    0.00%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                179          8cbb1000 (   2.199 GB)           54.97%
MEM_COMMIT                             9821          6bfad000 (   1.687 GB)  93.68%   42.18%
MEM_RESERVE                             352           7492000 ( 116.570 MB)   6.32%    2.85%

從卦中 MEM_COMMIT%ofTotal= 42.18% 來看,提交記憶體占總的虛擬地址比重還不到一半,這說明我的猜測是錯的,不存在虛擬地址緊張的情況,這裡稍微提醒一下的是,這裡不存在虛擬地址緊張是因為它開的是 Any CPU 模式,預設能吃到 4G 記憶體。

不管怎麼說,現在被當頭一棒,既然這條路走不通,那會是什麼情況導致的呢?一般來說這個記憶體量我是不願意分析的,但既然分析到這裡也只能繼續分析,接下來用 !eeheap -gc 觀察下托管堆記憶體占用情況。


0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x777c0434
generation 1 starts at 0x77781000
generation 2 starts at 0x01861000
ephemeral segment allocation context: none
 segment     begin  allocated      size
01860000  01861000  0285ffdc  0xffefdc(16773084)
...
77780000  77781000  77aa25c0  0x3215c0(3282368)
Large object heap starts at 0x02861000
 segment     begin  allocated      size
02860000  02861000  031e5cc0  0x984cc0(9981120)
Total Size:              Size: 0x1f7e47e4 (528369636) bytes.
------------------------------
GC Heap Size:    Size: 0x1f7e47e4 (528369636) bytes.

從卦中看當前托管堆也才 528M 和 提交記憶體 1.6G 相距甚遠,所以這個 dump 大概率是存在非托管記憶體泄露,其實 !address -summary 中的 Heap 也能佐證,說到底就是 ntheap 泄露。

2. ntheap 怎麼啦

深挖 ntheap 我就不挖了,省的誤入歧途,文章開頭我說過 ntdll 無法載入的現象預示著一種非托管泄露,對 ,就是 GC 的載入堆泄露,載入堆是 CLR 用來映射 C# 程式集,模塊,類型,方法等用途的一塊私有記憶體,那怎麼去洞察它呢?可以使用 !eeheap -loader 命令洞察。


0:000> !eeheap -loader
Loader Heap:
--------------------------------------
...
Module 05829f78: Size: 0x0 (0) bytes.
Module 0582a8f8: Size: 0x0 (0) bytes.
Module 0582b278: Size: 0x0 (0) bytes.
Module 0582bbf8: Size: 0x0 (0) bytes.
Module 0582c578: Size: 0x0 (0) bytes.
Module 0582cef8: Size: 0x0 (0) bytes.
Module 0582d878: Size: 0x0 (0) bytes.
...
Module 362ea420: Size: 0x0 (0) bytes.
Total size:      Size: 0x0 (0) bytes.
--------------------------------------
Total LoaderHeap size:   Size: 0x7e7e000 (132636672) bytes total, 0x28000 (163840) bytes wasted.
=======================================

雖然載入堆只統計到了 132M,但其中的 module 高達 2.3w 個,其實這裡會有一些相關記憶體是載入堆之外無法統計到的,一般正常的程式不可能有這麼多的module,所以這就是我們接下來突破的點,那怎麼突破呢?最好的辦法就是觀察下這個 module 中到底有什麼 type,使用 !dumpmodule 命令即可。


0:000> !dumpmodule -mt 0582d878
Name:       Unknown Module
Attributes: Reflection 
Assembly:   0c229d38
LoaderHeap:              00000000
TypeDefToMethodTableMap: 050676e4
TypeRefToMethodTableMap: 050676f8
MethodDefToDescMap:      0506770c
FieldDefToDescMap:       05067734
MemberRefToDescMap:      00000000
FileReferencesMap:       05067784
AssemblyReferencesMap:   05067798

Types defined in this module

      MT  TypeDef Name
------------------------------------------------------------------------------
0582dcb0 0x02000002 
0582df90 0x02000003 
0582e018 0x02000004 
0582e0b8 0x02000005 
0582e194 0x02000006 

Types referenced in this module

      MT    TypeRef Name
------------------------------------------------------------------------------

從模塊中並沒有看到類型的文字描述,那怎麼辦呢,我們隨便抽一個 mt 看下這個 mt 下有什麼方法,使用 !dumpmt 命令即可。


0:000> !dumpmt -md 0582dcb0
EEClass:         05068980
Module:          0582d878
Name:            
mdToken:         02000002
File:            Unknown Module
BaseSize:        0x44
ComponentSize:   0x0
Slots in VTable: 8
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
   Entry MethodDe    JIT Name
739819c8 735e61fc PreJIT System.Object.ToString()
73987850 735e6204 PreJIT System.Object.Equals(System.Object)
7398bd80 735e6224 PreJIT System.Object.GetHashCode()
738ddbe8 735e6238 PreJIT System.Object.Finalize()
0583b529 0582dc8c   NONE Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCallBack.InitCallbacks()
0583b52d 0582dc94   NONE Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCallBack..ctor()
0583c7d0 0582dc74    JIT Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCallBack.Write3_root(System.Object)
0583c868 0582dc80    JIT Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCallBack.Write2_CallBack(System.String, System.String, xxx.Models.xxxBack, Boolean, Boolean)

看到卦中的這些信息,我相信有很多朋友知道是怎麼回事了,對,就是 Serialization 泄露,那它序列化什麼類型呢 ? 從卦中看就是 xxx.Models.xxxBack 類,即 xmlSerializer.Serialize(xxx.Models.xxxBack) 的相關邏輯,接下來就需要逆向看下到底是哪裡寫的,結果發現是他的底層庫封裝的,有些方法有問題,有些沒問題,真的是無語哈。


    //有問題的方法
    public static string Serialize(object o, Encoding encoding, string rootName)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(o.GetType(), new XmlRootAttribute(rootName));
        ...
        xmlSerializer.Serialize(memoryStream, o, xmlSerializerNamespaces);
        return encoding.GetString(memoryStream.ToArray());
    }

    //正確的方法
    public static string Serialize(object Obj, Encoding encoding)
    {
        ...
        using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, xmlWriterSettings))
        {
            XmlSerializerNamespaces xmlSerializerNamespaces = new XmlSerializerNamespaces();
            xmlSerializerNamespaces.Add("", "");
            new XmlSerializer(Obj.GetType()).Serialize(xmlWriter, Obj, xmlSerializerNamespaces);
        }
        return encoding.GetString(memoryStream.ToArray());
    }

這是一個老生常談的問題,如果你用 new XmlSerializer(o.GetType(), new XmlRootAttribute(rootName)); 模式的話,一定要緩存起來,否則就會泄露,只能說是微軟造的一個大坑吧,多少人都踩上去了。

三:總結

在我分析的真實dump案例中,見過 Castle ProxyGenerator 的泄露,也見過 CodeAnalysis.CSharp.Scripting 的泄露,還真沒見過 XmlSerializer 的泄露,算是完美的補充了我的案例庫!

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

-Advertisement-
Play Games
更多相關文章
  • 一、問題描述 近日在工作中遇見了一個bug,後端程式頻頻報錯 No bean named 'XXXXX' available 。對比同類程式文件,沒有發現有任何特殊之處。在網上搜索方法基本上就是掃描包配置、註解問題、路徑問題等,皆不能解決我的問題。 排查問題是發現出現問題的類命名不符合駝峰規範,按照 ...
  • ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f4928251cca8446891aafb4d014abf39~tplv-k3u1fbpfcp-zoom-1.image) 博主在瀏覽 medium 社區時,發現了一篇點贊量 1.5k 的文 ...
  • 廢話環節:看過上期文章的小伙伴現在可能還是一頭霧水,怎麼就完成了核心內容,界面呢?哎我說別急讓我先急,博主這不夜以繼日地肝出了界面部分嘛。還是老規矩,不會把所有地方都照顧到,只挑一些有代表性的內容介紹,您各位多擔待🙏。另外博主的JavaFX是跟著B站視頻速成的,指路👉:https://www.b ...
  • ## 目錄 * [1. JUC概述及回顧](#1-juc概述及回顧) * [1.1. JUC是什麼?](#11-juc是什麼) * [1.2. 進程和線程](#12-進程和線程) * [1.3. 並行和併發](#13-並行和併發) * [1.4. wait/sleep的區別](#14-waitsle ...
  • # 🎉Avalonia 11.0.0 正式版發佈! [AvaloniaUI](https://github.com/AvaloniaUI/Avalonia/releases/tag/11.0.0) 發佈11.0.0正式版 終於avalonia發佈了正式版。 ## 更新內容 A11y(輔助功能) 這 ...
  • ​BackgroundImageLayout屬性值 背景圖片重覆:BackgroundImageLayout屬性設置為Tile(預設) 背景圖片左邊顯示:BackgroundImageLayout屬性設置為None 背景圖片右邊顯示:BackgroundImageLayout屬性設置為None,同時 ...
  • FCS一個完整的數據集主要有以下幾部分: 頭段(必須) 從數據集的第一個位元組開始,文件的第一個數據集是從文件的第一個位元組開始,最小長度為58個位元組。 以ASCII碼解析。 記錄內容包括文件版本號(0~5共6位元組)、文本段開始位元組位(10~17共8位元組)、文本段結束位元組位(18~25共8位元組)、數據段 ...
  • ## 前言 一開始是一個自用的應用,原本是用razor寫的。最近有了點新想法,加點新功能,但是我接觸的項目基本都是vue+api的前後端分離,用這razor寫的是真不習慣,最後決定還是用習慣的vue重寫。 之前嘗試過在.net core里使用vue2+webpack,畢竟實際上就是把.vue翻譯成了 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...