centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)

写个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所对应命令还是蛮难受的,需要进一步认真学习才行...

centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)

模拟分析内存泄漏

命令走一个,进入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 分析先走一波。对堆上面的对象进行统计

centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)


大于2kb的对象看一看

centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)

图上反馈byte[]数组对象占的内存最大,而且是远超其他类型的,因此可以判定应该是byte[]在代码的某个地方没有释放。进去跟进去即可。
真实情况项目情况很可能是占用内存最大,对象最多的string对象。分析起来真的有时候看运气,凭经验!...(^_^)
dumpheap -mt addr(byte[]数组的MT地址) 过滤看看类型是byte[]的都有那些对象。

centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)


centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)


看上去特征特别明显,全是大小为1048600的bte[]对象。接下来随便找一个看看具体对象的数据是什么
dumpobj addr(对象地址);查看对象的基本结构

centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)


内存数据看上去全是 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;

centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)


接下来,我们在看看这些个对象的gcroot对象是谁,也就是说这些个对象到底由谁持有
gcroot addr(对象地址)

centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)


在挨个看一看,能发现我们的这个list对象lenth有400个,_version=501;这是因为我clear过一次,所以。clear+1,add([100])个数组,所以400+100+1=501;
如果这是时候有一个objsize命令可以使用,我们就能计算出来这个list是一个400M的丑陋大对象。可惜linux下面木有。

centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)


那就只能用查看数据的方法看看这个数组的具体详情拉。
sos DumpArray -details(可以把每个对象的基本结构都打印出来),能看到他的每一个元素都有1M(size:1048600(0x100018) bytes)大小

centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)


centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试)

内存泄漏调试分析结论

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfxyd.html