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

目录
相关文章
|
7天前
|
开发框架 监控 .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
|
30天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
1月前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
49 2
|
28天前
|
运维 JavaScript Linux
容器内的Nodejs应用如何获取宿主机的基础信息-系统、内存、cpu、启动时间,以及一个df -h的坑
本文介绍了如何在Docker容器内的Node.js应用中获取宿主机的基础信息,包括系统信息、内存使用情况、磁盘空间和启动时间等。核心思路是将宿主机的根目录挂载到容器,但需注意权限和安全问题。文章还提到了使用`df -P`替代`df -h`以获得一致性输出,避免解析错误。
|
3月前
|
数据采集 Rust 安全
Rust在网络爬虫中的应用与实践:探索内存安全与并发处理的奥秘
【8月更文挑战第31天】网络爬虫是自动化程序,用于从互联网抓取数据。随着互联网的发展,构建高效、安全的爬虫成为热点。Rust语言凭借内存安全和高性能特点,在此领域展现出巨大潜力。本文探讨Rust如何通过所有权、借用及生命周期机制保障内存安全;利用`async/await`模型和`tokio`运行时处理并发请求;借助WebAssembly技术处理动态内容;并使用`reqwest`和`js-sys`库解析CSS和JavaScript,确保代码的安全性和可维护性。未来,Rust将在网络爬虫领域扮演更重要角色。
77 1
|
2月前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
70 0
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
366 0
|
21天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
44 1
|
26天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。