centos7 lldb 調試netcore應用的記憶體泄漏和死迴圈示例(dump文件調試)

来源:https://www.cnblogs.com/calvinK/archive/2018/07/06/9274239.html
-Advertisement-
Play Games

寫個demo來玩一玩linux平臺下使用lldb載入sos來調試netcore應用。 當然,在真實的產線環境中需要分析的數據和難度遠遠高於demo所示,所以demo的作用也僅僅只能起到介紹工具的作用。 通常正常情況下,分析個幾天才能得出一個結論的的結果都還是比較令人開心的!,很多時候分析來分析去也搞 ...


寫個demo來玩一玩linux平臺下使用lldb載入sos來調試netcore應用。
當然,在真實的產線環境中需要分析的數據和難度遠遠高於demo所示,所以demo的作用也僅僅只能起到介紹工具的作用。
通常正常情況下,分析個幾天才能得出一個結論的的結果都還是比較令人開心的!,很多時候分析來分析去也搞不出個所以然,也是很正常的(當然,也是自己學藝不精(^_^))
在linux平臺下的sos調試遠沒有在windows下麵用windbg來得舒服,該有的命令很多都沒有。
微軟爸爸還要加油努力啊!如果能做到linux下的dmp能在windows下麵用windbg之類的工具那就爽翻了,哈哈,當然不可能,臆想一下下拉。

圖片有點多,文章有點長,來一個大綱先

  • 準備DEMO程式的代碼
  • 生成待調試分析的dump文件
  • 目前linux下sos支持的命令
  • 模擬分析記憶體泄漏
  • 記憶體泄漏調試分析結論
  • 記憶體泄漏分析疑問一
  • 記憶體泄漏分析疑問二
  • 死迴圈調試分析
  • 記憶體泄漏調試分析結論

準備DEMO程式的代碼

廢話不多說,先上demo程式代碼。代碼超級簡單,模擬記憶體泄漏就簡單的往一個靜態list裡面每次插入1M的byte[];死迴圈則就是一個while(true);
PS:話說markdown插入代碼能不能有收起,展開功能呢。那就爽歪歪拉 @dudu

namespace linxu_dump_lldb.Controllers
{
    class env
    {
        public static bool cpu_flag;
        public static bool setcpu_flag(bool flag) => cpu_flag = flag;
        public static bool getcpu_flag() => cpu_flag;
        public static List<byte[]> memory = new List<byte[]>();
    }
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        public string index() =>(GC.GetTotalMemory(false) / 1024.0 / 1024).ToString("0.00M");
        [HttpGet]
        public void begin_cpu()
        {
            env.setcpu_flag(true);
            Task.Run(() => {while (env.getcpu_flag()){}});
        }
        [HttpGet]
        public void begin_memory()
        {
            var size_1m = 1 * 1024 * 1024;
            for (int i = 0; i < 100; i++)  env.memory.Add(new byte[size_1m]);
        }
        [HttpGet]
        public void end_cpu() => env.setcpu_flag(false);
        [HttpGet]
        public void end_memory()
        {
            env.memory.Clear();
            GC.Collect();
        }}}

生成待調試分析的dump文件

生成模擬記憶體泄漏的dump

請求介面begin_memory來個幾次後,然後通過createdump工具生成dump包,執行了4-5次begin_memory,也就是加了大約400-500M的byte[]放到靜態變數中

生成死迴圈的dump包

請求介面begin_cpu開始非同步任務進入死迴圈,然後通過createdump工具生成dump包

目前linux下sos支持的命令

當前dotnet版本2.1.1。如下圖所示支持,sos支持的命令,缺少幾個比較有用的命令:ProcInfo ,ObjSize ,SyncBlk,其他缺少的趕腳也用不太上。最最重要的是gdb,lldb的調試命令不熟悉,或者說找不到windbg所對應命令還是蠻難受的,需要進一步認真學習才行...

模擬分析記憶體泄漏

命令走一個,進入lldb。

/usr/local/llvm-3.9.0/bin/lldb dotnet -c /opt/dump_file/memory_dump -o "plugin load /usr/share/dotnet/shared/Microsoft.NETCore.App/2.1.1/libsosplugin.so"

dumpheap -stat 分析先走一波。對堆上面的對象進行統計

大於2kb的對象看一看

圖上反饋byte[]數組對象占的記憶體最大,而且是遠超其他類型的,因此可以判定應該是byte[]在代碼的某個地方沒有釋放。進去跟進去即可。
真實情況項目情況很可能是占用記憶體最大,對象最多的string對象。分析起來真的有時候看運氣,憑經驗!...(^_^)
dumpheap -mt addr(byte[]數組的MT地址) 過濾看看類型是byte[]的都有那些對象。


看上去特征特別明顯,全是大小為1048600的bte[]對象。接下來隨便找一個看看具體對象的數據是什麼
dumpobj addr(對象地址);查看對象的基本結構

記憶體數據看上去全是 00 00 00。可以說是一個預設的byte[]對象。可以在進入查看一下
sos DumpArray -start 0 -length 10 00007fd5febff9d8(對象地址)
查看數據對象,上一張圖上我們能看到數組的lenght有1048576個,所以加上-start,-length參數,只查看最前面10個對象。不然刷屏得刷死咯。
在接著使用
sos DumpVC(查看值類型命令) 00007fd611151460(數組元素類型的mt地址) 00007fd5febff9e9(數組元素對象的地址)
a 如下圖所示,每個數組元素的類型都是byte,他們的value都是0;

接下來,我們在看看這些個對象的gcroot對象是誰,也就是說這些個對象到底由誰持有
gcroot addr(對象地址)

在挨個看一看,能發現我們的這個list對象lenth有400個,_version=501;這是因為我clear過一次,所以。clear+1,add([100])個數組,所以400+100+1=501;
如果這是時候有一個objsize命令可以使用,我們就能計算出來這個list是一個400M的醜陋大對象。可惜linux下麵木有。

那就只能用查看數據的方法看看這個數組的具體詳情拉。
sos DumpArray -details(可以把每個對象的基本結構都列印出來),能看到他的每一個元素都有1M(size:1048600(0x100018) bytes)大小

記憶體泄漏調試分析結論

上圖種gcroot有3個結果。
第一個,用DumpArray查看後發現,應該是一個系統的靜態對象,裡面存儲都是context之類的東西。
第二個,就是我們的問題list對象。即List<byte[]>
第三個,是第二個list對象的items。
所以問題就出在我們這個靜態的 list對象上了,那從代碼上搜索一下就比較容易發現我們的List<byte[]>在哪裡了。

疑問一


上圖種是書籍Pro .Net Performance: Optimize Your C# Applications第98頁的一個列子,可惜沒有搞懂他的這個地址怎麼出來的,能直接拉出來堆棧信息...

疑問二

按理來說1M應該等於1048576,那為什麼這裡顯示是1048600呢,多餘的24byte是啥玩意呢?
dumpobj查看byte[]對象信息
dumpmt查看byte[]類型的mt信息
x addr(對象地址,x命令是lldb的命令,用戶查看地址處的記憶體數據。可以使用 -c 24指定需要查看多少位數據)

x addr 前16位數據小紅框標記,最後8位小紅框標記。中間的則是1M的01。01:byte數據,代碼直接賦值。

for (int i = 0; i < 100; i++)
{
    var x = new byte[size_1m];
    for (int j = 0; j < x.Length; j++) x[j] = 1;
    env.memory.Add(x);
}



但是這24位數據記憶體結構為何這麼組織,以及具體的含義就不是特別清楚了,有待考證!!!
學藝不精!,準備回家看看C#本質論有沒有說到這部分內容...或者哪位大哥可以說清楚一下,不勝感激!!!
google搜索的時候發現 Pro .Net Performance: Optimize Your C# Applications,這本書很屌啊!!!,絕壁值得一看,就是英文不行,求中文版啊!!!,好想吐槽一下國內的垃圾編輯或作者,好的書一本都不翻譯,垃圾玩意全翻譯過來。
http://codingsight.com/precise-computation-of-clr-object-size/

https://stackoverflow.com/questions/38056513/why-does-windbg-show-system-int32-variables-as-24-bytes

死迴圈調試分析

clrthreads -live 先看看還在運行的線程有那些。然後通過thread select 線程編號(lldb命令)。來切換到當前線程。線程編號不是列表種的id欄位,而是最前面一行的id。lldb 可以通過thread list命令來列舉所有線程。


剩下的工作就是體力活動拉,一個一個看,一個一個分析。
比如,我們切換到線程3看一看他當前的堆棧信息
clrstack命令可以查看當前線程在托管代碼種的堆棧信息。
dumstack則可以看到非托管代碼種的堆棧信息
thread backtrace lldb查看堆棧信息的命令。


線程3,能看到當前棧在非托管代碼中(libcoreclr.so!TwoWayPipe::WaitForConnection),看方法名字也能猜到幹嘛的,不太像我們的目標。
另外,linux下麵
ps -T -p 32728 命令可以查看到進行下線程的基本情況
top -H -p 32728 更happy。
所以在排查高cpu問題的時候能提供許多便利性,反而比記憶體問題要來得方便很多。(圖中的pid等數據不是一致性的。因為在寫blog的時候圖片是多次截取的。)


所以在dump包的時候可以記錄下來高cpu的線程id,然後通過thread select 找到對應的線程編號。在然後直接切換過去看一看就完事拉。
所以 thread select 30
clrstack看一看,嗯!當前線程在 linxu_dump_lldb.Controllers.ValuesController+<>c.b__1_0() [C:\Users\czd89\source\repos\ConsoleApp4\linxu_dump_lldb\Controllers\ValuesController.cs @ 31]。

看一看當前棧上面都有一些上面參數
CLRStack [-a] [-l] [-p];-p:看參數,-l:看局部變數,-a:=-l+-p;


當然,我們的代碼是非同步的,也沒有捕獲任何action裡面的變數,所以這裡的這個參數,以及參數裡面的屬性啥都沒有。
從dll反編譯代碼也能和我們lldb看到的東西一一對以上。

記憶體泄漏調試分析結論

到這裡,問題就很明顯能看出來了,當然主要還是我們的DEMO是最簡單的。還是開篇說過的那句話:通常正常情況下,分析個幾天才能得出一個結論的的結果都還是比較令人開心的!,很多時候分析來分析去也搞不出個所以然,也是很正常的(當然,也是自己學藝不精(^_^),當自勉!)
還能看一看具體方法的彙編代碼等信息。

參考資料:
https://docs.microsoft.com/en-us/dotnet/framework/tools/sos-dll-sos-debugging-extension
https://github.com/dotnet/coreclr/blob/master/Documentation/building/debugging-instructions.md
https://lldb.llvm.org/tutorial.html
https://stackoverflow.com/questions/38056513/why-does-windbg-show-system-int32-variables-as-24-bytes
http://codingsight.com/precise-computation-of-clr-object-size/
https://zhuanlan.zhihu.com/p/20838172
https://blog.csdn.net/inuyashaw/article/details/55095545


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • RabbitMQ系列目錄 1. "RabbitMQ在Ubuntu上的環境搭建" 2. "深入解讀RabbitMQ工作原理及簡單使用" 3. Rabbit的幾種工作模式介紹與實踐 4. Rabbit事務與消息確認 5. Rabbit集群搭建 6. 使用HAProxy為RabbitMQ搭建負載均衡 7. ...
  • 一. 字典的簡單介紹字典(dict)是python中唯一的一個映射類型.他是以{ }括起來的鍵值對組成. 在dict中key是 唯一的. 在保存的時候, 根據key來計算出一個記憶體地址. 然後將key-value保存在這個地址中. 這種演算法被稱為hash演算法,所以, 切記, 在dict中存儲的key ...
  • 上一篇:LeetCode鏈表相加-Python<二> 以前沒怎麼做過演算法題,來來去去都是那些迴圈,所以先從數組簡單題開始做。 這兩天最大心得: 總在邊界里考慮不周到,導致錯誤 做暈的時候,連變數名都跟參數重名了 有時候,開始考慮的情況太粗糙 幸好,都找出了問題,並通過。下麵貼貼代碼: 題目:無重覆字 ...
  • 一. 字典的簡單介紹 1,字典(dict)是python中唯一的一個映射類型.他是以{ }括起來的鍵值對組成. 在dict中key是 唯一的. 在保存的時候, 根據key來計算出一個記憶體地址. 然後將key-value保存在這個地址中. 這種演算法被稱為hash演算法, 所以, 切記, 在dict中存儲 ...
  • 使用要求: Sender代碼必須要在一臺有攝像頭的電腦上運行起來。然後把數據編碼,壓縮之後,再傳給另外一個電腦 Reciever作為接受端,沒什麼特別的要求。 兩個電腦都必須要按轉好numpy + opencv (安裝方法的話,在我的opencv文集中有一篇就是寫這個的) 對了,我的接受端關閉操作是 ...
  • 熟悉ASP.NET架構的開發者一定對於HTTP Modules與HTTP Handlers不陌生。兩者的作用主要是對網路請求執行特定的處理工作。而在.NET Core中,它們都被Middleware(中件間)取代了。 之前的Http Modules和HTTP Handlers是如下圖般處理請求的: ...
  • 我們能否像神一樣地創建一個世界? 對於創建世界而言,程式員的創作能力最接近於神——相對於導演,作家,漫畫家而言,他們創建的世界(作品)一旦完成,就再也不會變化,創建的角色再也不會成長。而程式員創建的世界,每次運行都可以有不一樣的內容,而且最為重要的,我們還能給與角色不能學習的能力(AI[人工智慧]... ...
  • Revit內置了一些命令,直接調用Revit操作方式。 可以去API文檔查詢PostableCommand枚舉,還是很多的。 話不多說,直接上代碼 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...