Linux 上的 .NET 崩潰了怎么抓 Dump
一:背景
1. 講故事
訓(xùn)練營(yíng)中有朋友問(wèn)在 Linux 上如何抓 crash dump,在我的系列文章中演示的大多是在 Windows 平臺(tái)上,這也沒(méi)辦法要跟著市場(chǎng)走,誰(shuí)讓 .NET 的主戰(zhàn)場(chǎng)在工控 和 醫(yī)療 呢,上一張?jiān)?合肥 分享時(shí)的一個(gè)統(tǒng)計(jì)圖。
這就導(dǎo)致總有零星的朋友問(wèn) Linux 平臺(tái)上如何生成 crash dump,這一篇就來(lái)整理下來(lái)減少后續(xù)的溝通成本。
二:如何生成
1. 案例代碼
為了方便演示,寫了一段簡(jiǎn)單的 C# 代碼,故意拋異常讓程序崩潰。
static void Main(string[] args)
{
throw new Exception("OutOfMemory");
Console.ReadLine();
}
2. 操作系統(tǒng)層面捕獲
一般來(lái)說(shuō)操作系統(tǒng)層面都支持當(dāng)一個(gè)進(jìn)程異常退出時(shí)自動(dòng)捕獲Crash Dump,Linux 如此,Windows 也如此,當(dāng)然默認(rèn)是不支持的,需要用 ulimit 開(kāi)啟,這個(gè)命令可以用來(lái)配置當(dāng)前系統(tǒng)資源的使用額度,用 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 就是用來(lái)指定生成 dump 文件的大小,默認(rèn)為 0,即表示不生成,我們可以將其改成 unlimited ,即不限文件大小。
[root@localhost data]# ulimit -c unlimited
如果你想永久保存,可以修改環(huán)境變量文件 /etc/profile, 在末尾新增 ulimit -c unlimited 即可。
[root@localhost data]# vim /etc/profile
[root@localhost data]# source /etc/profile
接下來(lái)將程序在 CentOS7 上跑起來(lái),從輸出看馬上就產(chǎn)生了崩潰文件,默認(rèn)在應(yīng)用程序目錄下。
[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 平臺(tái)上使用 windbg 分析,這里有一點(diǎn)要注意,linux 上的 dump,windbg 默認(rèn)不自動(dòng)加載 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
從卦中看,雖然異常信息有,但看不到默認(rèn)的托管函數(shù)名 Main,而是用 Unknown 替代的,這就比較尷尬了,畢竟 Linux 不是微軟弄的,很多地方水土不服。
可這是無(wú)數(shù) Linux 人及官方首推生成 crash dump 的方式,在 .netcore 上總會(huì)有這樣和那樣的問(wèn)題,那怎么辦呢?問(wèn)題總得要解決,針對(duì)這種場(chǎng)景,微軟巧用開(kāi)機(jī)啟動(dòng)的 dotnet 進(jìn)程為載體,在程序崩潰的時(shí)候通過(guò)讀取 環(huán)境變量 的方式來(lái)生成 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 環(huán)境變量捕獲
微軟的 MSDN:https://learn.microsoft.com/en-us/dotnet/core/diagnostics/collect-dumps-crash 上詳細(xì)的記錄了如何通過(guò)讀取環(huán)境變量來(lái)生成 crash dump。
大體分如下三個(gè)參數(shù):
-
COMPlus_DbgEnableMiniDump
-
COMPlus_DbgMiniDumpType
-
COMPlus_DbgMiniDumpName
看到這三個(gè)變量,我敢斷定它是借助了 Windows WER 生成 crash dump 的思想,不過(guò)載體不一樣,前者是 dontet父進(jìn)程,后者是 wer系統(tǒng)服務(wù)。
接下來(lái)將這三個(gè)變量配置到環(huán)境變量文件中,然后把程序跑起來(lái)了,參考如下:
[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
有了這個(gè) 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 平臺(tái)上,首推環(huán)境變量的模式,如果你對(duì) coredump 的名字有自定義要求,也可以修改根據(jù)下圖中的模板參數(shù)修改。
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
三:總結(jié)
這篇大概介紹了兩個(gè)抓 dump 的方式:前者適合非托管程序,后者適合托管程序,相信這篇文章能極大的節(jié)省各方的溝通成本,花點(diǎn)時(shí)間整理下很值。
