Linux 上的 .NET 崩溃了怎么抓 Dump

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 上的 .NET 崩溃了怎么抓 Dump相关的知识,希望对你有一定的参考价值。

一:背景

1. 讲故事

训练营中有朋友问在 Linux 上如何抓 crash dump,在我的系列文章中演示的大多是在 Windows 平台上,这也没办法要跟着市场走,谁让 .NET 的主战场在工控医疗 呢,上一张在 合肥 分享时的一个统计图。

这就导致总有零星的朋友问 Linux 平台上如何生成 crash dump,这一篇就来整理下来减少后续的沟通成本。

二:如何生成

1. 案例代码

为了方便演示,写了一段简单的 C# 代码,故意抛异常让程序崩溃。


        static void Main(string[] args)
        
            throw new Exception("OutOfMemory");
            Console.ReadLine();
        

2. 操作系统层面捕获

一般来说操作系统层面都支持当一个进程异常退出时自动捕获Crash Dump,Linux 如此,Windows 也如此,当然默认是不支持的,需要用 ulimit 开启,这个命令可以用来配置当前系统资源的使用额度,用 limit -a 观察。


[root@localhost data]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 14950
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 14950
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

卦中的 core file size 就是用来指定生成 dump 文件的大小,默认为 0,即表示不生成,我们可以将其改成 unlimited ,即不限文件大小。


[root@localhost data]# ulimit -c unlimited

如果你想永久保存,可以修改环境变量文件 /etc/profile, 在末尾新增 ulimit -c unlimited 即可。


[root@localhost data]# vim /etc/profile
[root@localhost data]# source /etc/profile

接下来将程序在 CentOS7 上跑起来,从输出看马上就产生了崩溃文件,默认在应用程序目录下。


[root@localhost data]# dotnet Example_1_1.dll
hello world!
Unhandled exception. System.Exception: OutOfMemory
   at Example_1_1.Program.Main(String[] args) in D:\\skyfly\\1.20230528\\src\\Example\\Example_1_1\\Program.cs:line 13
Aborted (core dumped)

[root@localhost data]# ls
core.39653   Example_1_1.deps.json  Example_1_1.pdb
Example_1_1  Example_1_1.dll        Example_1_1.runtimeconfig.json

core.39653 生成好了之后,可以 copy 到 Windows 平台上使用 windbg 分析,这里有一点要注意,linux 上的 dump,windbg 默认不自动加载 sos 的,需要你手工 .load 一下。


0:000> .load  C:\\Users\\Administrator\\.dotnet\\sos64\\sos.dll
0:000> !t
ThreadCount:      3
UnstartedThread:  0
BackgroundThread: 2
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                            Lock  
 DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1     9ae5 000055DF29280340    20020 Preemptive  00007F352C01EE20:00007F352C01FFD0 000055df29267810 -00001 Ukn <Invalid Object> (00007f352c0138c8)
   7    2     9aea 000055DF2928D990    21220 Preemptive  0000000000000000:0000000000000000 000055df29267810 -00001 Ukn (Finalizer) 
   1    3     9aeb 000055DF292B99E0    21220 Preemptive  0000000000000000:0000000000000000 000055df29267810 -00001 Ukn 

0:000> !pe
Exception object: 00007f352c0138c8
Exception type:   <Unknown>
Message:          OutOfMemory
InnerException:   <none>
StackTrace (generated):
    SP               IP               Function
    00007FFE584563C0 00007F3561BE2E86 Example_1_1.dll!Unknown+0x96

StackTraceString: <none>
HResult: 80131500

从卦中看,虽然异常信息有,但看不到默认的托管函数名 Main,而是用 Unknown 替代的,这就比较尴尬了,毕竟 Linux 不是微软弄的,很多地方水土不服。

可这是无数 Linux 人及官方首推生成 crash dump 的方式,在 .netcore 上总会有这样和那样的问题,那怎么办呢?问题总得要解决,针对这种场景,微软巧用开机启动的 dotnet 进程为载体,在程序崩溃的时候通过读取 环境变量 的方式来生成 crash dump。


[root@localhost ~]# ps -ef | grep dotnet
root       6566   6520  0 12:06 pts/2    00:00:00 grep --color=auto dotnet

3. 使用 dotnet 环境变量捕获

微软的 MSDN:https://learn.microsoft.com/en-us/dotnet/core/diagnostics/collect-dumps-crash 上详细的记录了如何通过读取环境变量来生成 crash dump。

大体分如下三个参数:

  • COMPlus_DbgEnableMiniDump

  • COMPlus_DbgMiniDumpType

  • COMPlus_DbgMiniDumpName

看到这三个变量,我敢断定它是借助了 Windows WER 生成 crash dump 的思想,不过载体不一样,前者是 dontet父进程,后者是 wer系统服务

接下来将这三个变量配置到环境变量文件中,然后把程序跑起来了,参考如下:


[root@localhost data]# vim /etc/profile
[root@localhost data]# source /etc/profile
[root@localhost data]# dotnet Example_1_1.dll
hello world!
Unhandled exception. System.Exception: OutOfMemory
   at Example_1_1.Program.Main(String[] args) in D:\\skyfly\\1.20230528\\src\\Example\\Example_1_1\\Program.cs:line 13
[createdump] Gathering state for process 40422 dotnet
[createdump] Crashing thread 9de6 signal 6 (0006)
[createdump] Writing full dump to file /data2/coredump.dmp
[createdump] Written 119734272 bytes (29232 pages) to core file
[createdump] Target process is alive
[createdump] Dump successfully written
Aborted (core dumped)

[root@localhost data]# cd /data2 ; ls
coredump.dmp

有了这个 coredump.dmp 之后,再把它拖到 windbg 中观察。


0:000>  .load  C:\\Users\\Administrator\\.dotnet\\sos64\\sos.dll
0:000> !t
ThreadCount:      3
UnstartedThread:  0
BackgroundThread: 2
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                            Lock  
 DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1     9de6 00005603DBF7C520    20020 Preemptive  00007FF13801EE20:00007FF13801FFD0 00005603dbf639f0 -00001 Ukn System.Exception 00007ff1380138c8
   5    2     9deb 00005603DBF89B70    21220 Preemptive  0000000000000000:0000000000000000 00005603dbf639f0 -00001 Ukn (Finalizer) 
   6    3     9dec 00005603DBFB5C50    21220 Preemptive  0000000000000000:0000000000000000 00005603dbf639f0 -00001 Ukn 
0:000> !pe
Exception object: 00007ff1380138c8
Exception type:   System.Exception
Message:          OutOfMemory
InnerException:   <none>
StackTrace (generated):
    SP               IP               Function
    00007FFC7A324A10 00007FF16F852E86 Example_1_1!Example_1_1.Program.Main(System.String[])+0x96

StackTraceString: <none>
HResult: 80131500

从卦中看,这次终于有了,不容易,所以在 Linux 平台上,首推环境变量的模式,如果你对 coredump 的名字有自定义要求,也可以修改根据下图中的模板参数修改。


export COMPlus_DbgEnableMiniDump=1
export COMPlus_DbgMiniDumpType=4
export COMPlus_DbgMiniDumpName=/data2/%p-%e-%h-%t.dmp

[root@localhost data2]# ls
41758-dotnet-localhost.localdomain-1685332206.dmp

三:总结

这篇大概介绍了两个抓 dump 的方式:前者适合非托管程序,后者适合托管程序,相信这篇文章能极大的节省各方的沟通成本,花点时间整理下很值。

Linux下的core dump

linux下core dump【总结】 

在linux下,程序莫名突然崩溃了怎么办,有的程序有日志,有的程序没有则么办,或者自己忘记定义了怎么办,而且,有没有其他途径能配合程序日志更快的解决问题?
 
基本概念: 当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做Core Dump(中文有的翻译成“核心转储”)。我们可以认为 core dump 是“内存快照”,但实际上,除了内存信息之外,还有些关键的程序运行状态也会同时 dump 下来,例如寄存器信息(包括程序指针、栈指针等)、内存管理信息、其他处理器和操作系统状态和信息。core dump 对于编程人员诊断和调试程序是非常有帮助的,因为对于有些程序错误是很难重现的,例如指针异常,而 core dump 文件可以再现程序出错时的情景。
 
查看系统的core功能是否打开:
[root@VM_13_117_centos security]# ulimit -a
core file size          (blocks, -c) unlimited      ##就是这里啦!
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 128337
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 100001
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 128337
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
 

参数描述及使用

ulimited 不限制用户可以使用的资源,但本设置对可打开的最大文件数(max open files)
和可同时运行的最大进程数(max user processes)无效
-a 列出所有当前资源极限
-c 设置core文件的最大值.单位:blocks
-d 设置一个进程的数据段的最大值.单位:kbytes
-f Shell 创建文件的文件大小的最大值,单位:blocks
-h 指定设置某个给定资源的硬极限。如果用户拥有 root 用户权限,可以增大硬极限。任何用户均可减少硬极限
-l 可以锁住的物理内存的最大值
-m 可以使用的常驻内存的最大值,单位:kbytes
-n 每个进程可以同时打开的最大文件数
-p 设置管道的最大值,单位为block,1block=512bytes
-s 指定堆栈的最大值:单位:kbytes
-S 指定为给定的资源设置软极限。软极限可增大到硬极限的值。如果 -H 和 -S 标志均未指定,极限适用于以上二者
-t 指定每个进程所使用的秒数,单位:seconds
-u 可以运行的最大并发进程数
-v Shell可使用的最大的虚拟内存,单位:kbytes
 
 
在linux中,我们可以用ulimit -a指令来查看,哪些系统设置被限制了,要么开关,要么限制大小,要么无限制,总共三种方案。

开启core dump功能:

执行指令: ulimit -c unlimited
这个命令只对当前终端有效,要想永久有效,修改配置文件/etc/security/limits.conf,格式如下:
* soft core unlimited
* soft nofile 100001
* hard nofile 100002
root soft nofile 100001
root hard nofile 100002

配置core记录程序PID:

默认生成的core文件保存在与可执行文件的同级目录下,名字就叫core;
通过修改/proc/sys/kernel/core_uses_pid : echo 1 > /proc/sys/kernel/core_uses_pid 就可以让生成的core文件名加上pid,其中pid为该挂掉进程的pid,那么这里,当有很多个进程同时存在的时候,哪个进程挂了,对应的core文件就会以该进程的PID作为文件后缀,形似:“core.PID”

配置core文件存储路径与名称格式:

通过修改/proc/sys/kernel/core_pattern开控制生成的core文件的位置以及文件名格式
echo  "/tmp/corefile-%e-%p-%t" > /proc/sys/kernel/core_pattern 
上面的配置的作用为将core文件保存在/tmp/corefile目录中,且格式为:"core-命令名-pid-时间戳";
 
笔者的线上服务器中/proc/sys/kernel/core_pattern 内容为core,则说明,保存在默认位置,无特殊格式,但/proc/sys/kernel/core_uses_pid中为1,所以pid记录功能功能是打开的,同时也在线上环境的bin目录下发现core文件,说明解释正确。

core文件的调试:

在core文件所在目录下键入:
gdb -c core   (-c指定core文件)
它会启动GNU的调试器,来调试core文件,并且会显示生成此core文件的程序名,中止此程序的信号等等
如果你已经知道是由什么程序生成此core文件的,比如MySQL崩溃了生成core.12345,那么用此指令调试:
gdb ./mysql core.12345
以犀牛的线上例子为准:
[root@VM_200_97_centos bin]# gdb ./sdo_security -d core.22624
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-83.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
 
warning: /app/sdo/server/bin/core.22624 is not a directory.
Reading symbols from /app/sdo/server/bin/sdo_security...done.
(gdb)
 
Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
Core was generated by `./sdo_security -d\'.
Program terminated with signal 6, Aborted.
#0  0x00007fe3c8ceb625 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.166.el6_7.7.x86_64 libgcc-4.4.7-16.el6.x86_64 libstdc++-4.4.7-16.el6.x86_64 zlib-1.2.3-29.el6.x86_64
(gdb) quit
 
这里截取了最后几行,其中最后面的Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.166.el6_7.7.x86_64 libgcc-4.4.7-16.el6.x86_64 libstdc++-4.4.7-16.el6.x86_64 zlib-1.2.3-29.el6.x86_64
这里有个调试依赖的报错,提示你直接执行这个语句,也就是安装后面的那几个包,这里不做阐述。
 

注意:在线上环境部署中,一般我们设置有监控监本,进程core了之后,会有脚本自动拉起来,再分析coredump文件,有个问题:

如果此时发现core文件大小的上限设置的不够,导致coredump文件被截断了,下面具体的做法是?

1:更改文件大小上限(一般都是unlimited,由于core文件大多会很大,因此需要密切关注磁盘容量变化)

2:重启业务进程!这步需要注意,如果存在单点的话,需要规划时间执行重启操作,否则在下次crash的时候,得到的coredump文件依旧是不完整的!

 
core在C环境下的程序中常常用到!共勉。
 

以上是关于Linux 上的 .NET 崩溃了怎么抓 Dump的主要内容,如果未能解决你的问题,请参考以下文章

.NET程序崩溃了怎么抓 Dump ? 我总结了三种方案

记一次 .NET 某工控MES程序 崩溃分析

记一次 .NET 某企业 ERP网站系统 崩溃分析

Linux下的core dump

如何在.NET程序崩溃时自动创建Dump?

linux下core dump