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

简介: 写个demo来玩一玩linux平台下使用lldb加载sos来调试netcore应用。当然,在真实的产线环境中需要分析的数据和难度远远高于demo所示,所以demo的作用也仅仅只能起到介绍工具的作用。

写个demo来玩一玩linux平台下使用lldb加载sos来调试netcore应用。
当然,在真实的产线环境中需要分析的数据和难度远远高于demo所示,所以demo的作用也仅仅只能起到介绍工具的作用。
通常正常情况下,分析个几天才能得出一个结论的的结果都还是比较令人开心的!,很多时候分析来分析去也搞不出个所以然,也是很正常的(当然,也是自己学艺不精(^_^))
在linux平台下的sos调试远没有在windows下面用windbg来得舒服,该有的命令很多都没有。
微软爸爸还要加油努力啊!如果能做到linux下的dmp能在windows下面用windbg之类的工具那就爽翻了,哈哈,当然不可能,臆想一下下拉。

lldb工具的安装,linux下netcore如何生成dump文件,查看下文
centos7使用lldb调试netcore应用转储dump文件

图片有点多,文章有点长,来一个大纲先

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

模拟分析内存泄漏

命令走一个,进入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 分析先走一波。对堆上面的对象进行统计
img_d6ee7eea9907f2b71c60c53ec9c6994e.png
大于2kb的对象看一看
img_ef65da45412fbb20f231e3a9f4e1d818.png

图上反馈byte[]数组对象占的内存最大,而且是远超其他类型的,因此可以判定应该是byte[]在代码的某个地方没有释放。进去跟进去即可。
真实情况项目情况很可能是占用内存最大,对象最多的string对象。分析起来真的有时候看运气,凭经验!...(^_^)
dumpheap -mt addr(byte[]数组的MT地址) 过滤看看类型是byte[]的都有那些对象。
img_eb860a4d4144d54fdec688f9be1db50e.png
img_099274f40b785dcf13f4750db53fb1c6.png
看上去特征特别明显,全是大小为1048600的bte[]对象。接下来随便找一个看看具体对象的数据是什么
dumpobj addr(对象地址);查看对象的基本结构
img_efa91de71bf44f63b0bda26cbd8c29b5.png
内存数据看上去全是 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;
img_73c68d85adce7887a2128dde0644e360.png
接下来,我们在看看这些个对象的gcroot对象是谁,也就是说这些个对象到底由谁持有
gcroot addr(对象地址)
img_66f7409fcd8c412d57585b3fab0a8d87.png
在挨个看一看,能发现我们的这个list对象lenth有400个,_version=501;这是因为我clear过一次,所以。clear+1,add([100])个数组,所以400+100+1=501;
如果这是时候有一个objsize命令可以使用,我们就能计算出来这个list是一个400M的丑陋大对象。可惜linux下面木有。
img_de0df94f3cb6c69162010c4b19a5f257.png
那就只能用查看数据的方法看看这个数组的具体详情拉。
sos DumpArray -details(可以把每个对象的基本结构都打印出来),能看到他的每一个元素都有1M(size:1048600(0x100018) bytes)大小
img_2c6863940a87295f4a661facf4f1ece7.png
img_a43dd10b1060b1a9df776a0c294d63b3.png

内存泄漏调试分析结论

上图种gcroot有3个结果。
第一个,用DumpArray查看后发现,应该是一个系统的静态对象,里面存储都是context之类的东西。
第二个,就是我们的问题list对象。即List<byte[]>
第三个,是第二个list对象的items。
所以问题就出在我们这个静态的 list对象上了,那从代码上搜索一下就比较容易发现我们的List<byte[]>在哪里了。

疑问一

img_1add5e37130b21b288c2a9b59c44e65e.png
上图种是书籍Pro .Net Performance: Optimize Your C# Applications第98页的一个列子,可惜没有搞懂他的这个地址怎么出来的,能直接拉出来堆栈信息...

疑问二

按理来说1M应该等于1048576,那为什么这里显示是1048600呢,多余的24byte是啥玩意呢?
dumpobj查看byte[]对象信息
dumpmt查看byte[]类型的mt信息
x addr(对象地址,x命令是lldb的命令,用户查看地址处的内存数据。可以使用 -c 24指定需要查看多少位数据)
img_fdacb43b630ec8b3e3bf58323c5e3059.png
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);
}

img_fdacb43b630ec8b3e3bf58323c5e3059.png
img_4a7489d9ef0627df9b1a531438657fb7.png
但是这24位数据内存结构为何这么组织,以及具体的含义就不是特别清楚了,有待考证!!!
学艺不精!,准备回家看看C#本质论有没有说到这部分内容...或者哪位大哥可以说清楚一下,不胜感激!!!
google搜索的时候发现 Pro .Net Performance: Optimize Your C# Applications,这本书很屌啊!!!,绝壁值得一看,就是英文不行,求中文版啊!!!,好想吐槽一下国内的垃圾编辑或作者,好的书一本都不翻译,垃圾玩意全翻译过来。
http://codingsight.com/precise-computation-of-clr-object-size/
img_cf89806cec0f82e581405890c06a2607.png
https://stackoverflow.com/questions/38056513/why-does-windbg-show-system-int32-variables-as-24-bytes
img_c6da4b015ae5ebfff6aea06f387666bb.png

死循环调试分析

clrthreads -live 先看看还在运行的线程有那些。然后通过thread select 线程编号(lldb命令)。来切换到当前线程。线程编号不是列表种的id字段,而是最前面一行的id。lldb 可以通过thread list命令来列举所有线程。

img_42b7999ace737b51c5c902e5eb9a304d.png
剩下的工作就是体力活动拉,一个一个看,一个一个分析。
比如,我们切换到线程3看一看他当前的堆栈信息
clrstack命令可以查看当前线程在托管代码种的堆栈信息。
dumstack则可以看到非托管代码种的堆栈信息
thread backtrace lldb查看堆栈信息的命令。
img_02099ad278df0274a6f9141903a7be0c.png
img_2e6505778c383a0e50dfad8270405589.png
线程3,能看到当前栈在非托管代码中(libcoreclr.so!TwoWayPipe::WaitForConnection),看方法名字也能猜到干嘛的,不太像我们的目标。
另外,linux下面
ps -T -p 32728 命令可以查看到进行下线程的基本情况
top -H -p 32728 更happy。
所以在排查高cpu问题的时候能提供许多便利性,反而比内存问题要来得方便很多。(图中的pid等数据不是一致性的。因为在写blog的时候图片是多次截取的。)
img_e06169858c1c0498dbe7228ddfae7479.png
img_6992949e978572bad66ad853de6227e7.png
所以在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]。
img_9258dd67456255fb1b93d17c7e2242bf.png
看一看当前栈上面都有一些上面参数
CLRStack [-a] [-l] [-p];-p:看参数,-l:看局部变量,-a:=-l+-p;
img_7ec23a8c294038d28d629a9f2f848ece.png
img_e0f286149972f6b89125da3cff200d48.png
当然,我们的代码是异步的,也没有捕获任何action里面的变量,所以这里的这个参数,以及参数里面的属性啥都没有。
从dll反编译代码也能和我们lldb看到的东西一一对以上。
img_fe49140e9837c8e698b4179a7ab76559.png

内存泄漏调试分析结论

到这里,问题就很明显能看出来了,当然主要还是我们的DEMO是最简单的。还是开篇说过的那句话:通常正常情况下,分析个几天才能得出一个结论的的结果都还是比较令人开心的!,很多时候分析来分析去也搞不出个所以然,也是很正常的(当然,也是自己学艺不精(^_^),当自勉!)
还能看一看具体方法的汇编代码等信息。
img_4b331b000109ef5f7b4b559b2c95f11f.png
img_fb0773509aae1a7be92f266ea09bfb14.png

参考资料:
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

目录
相关文章
|
1月前
|
监控 Linux
centos 中查看 内存及磁盘使用率
通过这些命令,您可以全面了解系统资源的使用情况,从而更好地管理和优化系统。
112 22
|
4月前
|
监控 JavaScript 算法
如何使用内存监控工具来定位和解决Node.js应用中的性能问题?
总之,利用内存监控工具结合代码分析和业务理解,能够逐步定位和解决 Node.js 应用中的性能问题,提高应用的运行效率和稳定性。需要耐心和细致地进行排查和优化,不断提升应用的性能表现。
232 77
|
4月前
|
存储 缓存 JavaScript
如何优化Node.js应用的内存使用以提高性能?
通过以上多种方法的综合运用,可以有效地优化 Node.js 应用的内存使用,提高性能,提升用户体验。同时,不断关注内存管理的最新技术和最佳实践,持续改进应用的性能表现。
189 62
|
4月前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
100 31
|
3月前
|
开发框架 .NET PHP
网站应用项目如何选择阿里云服务器实例规格+内存+CPU+带宽+操作系统等配置
对于使用阿里云服务器的搭建网站的用户来说,面对众多可选的实例规格和配置选项,我们应该如何做出最佳选择,以最大化业务效益并控制成本,成为大家比较关注的问题,如果实例、内存、CPU、带宽等配置选择不合适,可能会影响到自己业务在云服务器上的计算性能及后期运营状况,本文将详细解析企业在搭建网站应用项目时选购阿里云服务器应考虑的一些因素,以供参考。
|
4月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
106 1
|
4月前
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
85 3
|
4月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
4月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
5月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
141 1

热门文章

最新文章