寫個demo來玩一玩linux平臺下使用lldb加載sos來調試netcore應用。
固然,在真實的產線環境中須要分析的數據和難度遠遠高於demo所示,因此demo的做用也僅僅只能起到介紹工具的做用。
一般正常狀況下,分析個幾天才能得出一個結論的的結果都仍是比較使人開心的!,不少時候分析來分析去也搞不出個因此然,也是很正常的(固然,也是本身學藝不精(^_^))
在linux平臺下的sos調試遠沒有在windows下面用windbg來得舒服,該有的命令不少都沒有。
微軟爸爸還要加油努力啊!若是能作到linux下的dmp能在windows下面用windbg之類的工具那就爽翻了,哈哈,固然不可能,臆想一下下拉。html
lldb工具的安裝,linux下netcore如何生成dump文件,查看下文
centos7使用lldb調試netcore應用轉儲dump文件linux
廢話很少說,先上demo程序代碼。代碼超級簡單,模擬內存泄漏就簡單的往一個靜態list裏面每次插入1M的byte[];死循環則就是一個while(true);
PS:話說markdown插入代碼能不能有收起,展開功能呢。那就爽歪歪拉 @dudugit
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(); }}}
生成模擬內存泄漏的dumpgithub
請求接口begin_memory來個幾回後,而後經過createdump工具生成dump包,執行了4-5次begin_memory,也就是加了大約400-500M的byte[]放到靜態變量中shell
生成死循環的dump包c#
請求接口begin_cpu開始異步任務進入死循環,而後經過createdump工具生成dump包windows
當前dotnet版本2.1.1。以下圖所示支持,sos支持的命令,缺乏幾個比較有用的命令:ProcInfo ,ObjSize ,SyncBlk,其餘缺乏的趕腳也用不太上。最最重要的是gdb,lldb的調試命令不熟悉,或者說找不到windbg所對應命令仍是蠻難受的,須要進一步認真學習才行...
centos
命令走一個,進入lldb。api
/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.
看一看當前棧上面都有一些上面參數
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