2023-07-06 12:26:15來源:一線碼農聊技術
前幾天有位朋友找到我,說他的程序有內存泄露,讓我?guī)兔ε挪橐幌?,截圖如下:
(資料圖片僅供參考)
圖片
說實話看到 32bit, 1.5G 這些關鍵詞之后,職業(yè)敏感告訴我,他這個可能是虛擬地址緊張所致,不管怎么說,有了 Dump 就可以上馬分析。
二:WinDbg分析1. 虛擬地址緊張所致嗎要看是不是虛擬地址緊張,可以用!address -summary觀察下內存段統(tǒng)計信息,截圖如下:
圖片
我去,用 WinDbg Preview 盡然分析不了,在加載ntdll的過程中死掉了,如果你是我們調試訓練營的朋友,應該會深深的有體會,我們分析的第一個dump就存在這個情況,這個加載不了其實就預示著一種非托管泄露,這里暫不劇透。
用WinDbg Preview分析不了怎么辦呢?可以用 Windbg 的其他版本哈,比如Windbg10, WinDbg6等等,這里就采用WinDbg10 X86版本打開吧。
0:000> !address -summary--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotalFree 179 8cbb1000 ( 2.199 GB) 54.97%Heap 6598 376f6000 ( 886.961 MB) 48.09% 21.65% 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 %ofTotalMEM_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 內存。
不管怎么說,現(xiàn)在被當頭一棒,既然這條路走不通,那會是什么情況導致的呢?一般來說這個內存量我是不愿意分析的,但既然分析到這里也只能繼續(xù)分析,接下來用!eeheap -gc觀察下托管堆內存占用情況。
0:000> !eeheap -gcNumber of GC Heaps: 1generation 0 starts at 0x777c0434generation 1 starts at 0x77781000generation 2 starts at 0x01861000ephemeral segment allocation context: none segment begin allocated size01860000 01861000 0285ffdc 0xffefdc(16773084)...77780000 77781000 77aa25c0 0x3215c0(3282368)Large object heap starts at 0x02861000 segment begin allocated size02860000 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 無法加載的現(xiàn)象預示著一種非托管泄露,對 ,就是 GC 的加載堆泄露,加載堆是 CLR 用來映射 C# 程序集,模塊,類型,方法等用途的一塊私有內存,那怎么去洞察它呢?可以使用!eeheap -loader命令洞察。
0:000> !eeheap -loaderLoader 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.=======================================
雖然加載堆只統(tǒng)計到了132M,但其中的 module 高達2.3w個,其實這里會有一些相關內存是加載堆之外無法統(tǒng)計到的,一般正常的程序不可能有這么多的module,所以這就是我們接下來突破的點,那怎么突破呢?最好的辦法就是觀察下這個 module 中到底有什么 type,使用!dumpmodule命令即可。
0:000> !dumpmodule -mt 0582d878Name: Unknown ModuleAttributes: Reflection Assembly: 0c229d38LoaderHeap: 00000000TypeDefToMethodTableMap: 050676e4TypeRefToMethodTableMap: 050676f8MethodDefToDescMap: 0506770cFieldDefToDescMap: 05067734MemberRefToDescMap: 00000000FileReferencesMap: 05067784AssemblyReferencesMap: 05067798Types 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 0582dcb0EEClass: 05068980Module: 0582d878Name: mdToken: 02000002File: Unknown ModuleBaseSize: 0x44ComponentSize: 0x0Slots in VTable: 8Number of IFaces in IFaceMap: 0--------------------------------------MethodDesc Table Entry MethodDe JIT Name739819c8 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)的相關邏輯,接下來就需要逆向看下到底是哪里寫的,結果發(fā)現(xiàn)是他的底層庫封裝的,有些方法有問題,有些沒問題,真的是無語哈。
//有問題的方法 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的泄露,算是完美的補充了我的案例庫!
關鍵詞:
一:背景1 講故事前幾天有位朋友找到我,說他的程序有內存泄露,讓我?guī)?/p>
7月6日消息,在傳言出現(xiàn)幾個月后,F(xiàn)acebook母公司Meta開發(fā)的社交媒體應
物理安全系統(tǒng)對于數(shù)據(jù)中心始終至關重要。但現(xiàn)在這些系統(tǒng)——包括監(jiān)控攝
對于準確的決策和績效而言,數(shù)據(jù)是連接技術、人員和流程的粘合劑。當一
生病后,行動不便的吳浩冉將更多精力放在了學習上,并從初中起就萌發(fā)了
日前,網通社從官方獲取了一組寶駿云朵(圖片)內飾官圖。新車定位緊湊型
1、提高生產力隨著許多組織轉向遠程工作,保持效率至關重要。在家工作有
雖然GPT和PaLM等專有軟件占據(jù)了市場主導地位,但是仍舊有很多開發(fā)人員
CPO概念6日盤中強勢拉升,截至發(fā)稿,光庫科技漲超11%,華工科技漲逾9%
在岸、離岸人民幣對美元匯率則圍繞7 25波動。離岸人民幣對美元匯率日內
中國能建西南院在新型電力系統(tǒng)領域新增3項發(fā)明專利近日,中國能建西南
在陽光下奔跑,在運動場上揮灑汗水,體育鍛煉為孩子們的暑期生活增添樂
log4net是一個廣泛應用的、開源的日志框架,它提供了一種靈活的機制來
Python面向對象編程實戰(zhàn)在Python中,面向對象編程是一種非常重要的編程
Redis是一款高性能的開源內存數(shù)據(jù)庫,它支持多種數(shù)據(jù)結構和豐富的數(shù)據(jù)