Linux 上的 .NET 崩潰了怎麼抓 Dump

2023-05-29 15:01:34

一:背景

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 的方式:前者適合非託管程式,後者適合託管程式,相信這篇文章能極大的節省各方的溝通成本,花點時間整理下很值。

圖片名稱