訓練營中有朋友問在 Linux 上如何抓 crash dump,在我的系列文章中演示的大多是在 Windows 平臺上,這也沒辦法要跟著市場走,誰讓 .NET 的主戰場在工控 和 醫療 呢,上一張在 合肥 分享時的一個統計圖。
這就導致總有零星的朋友問 Linux 平臺上如何生成 crash dump,這一篇就來整理下來減少後續的溝通成本。
為了方便演示,寫了一段簡單的 C# 程式碼,故意拋異常讓程式崩潰。
static void Main(string[] args)
{
throw new Exception("OutOfMemory");
Console.ReadLine();
}
一般來說作業系統層面都支援當一個程序異常退出時自動捕獲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
微軟的 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 的方式:前者適合非託管程式,後者適合託管程式,相信這篇文章能極大的節省各方的溝通成本,花點時間整理下很值。