如何在 NET 程式萬種死法中有效的生成 Dump (下)

neuyu發表於2021-09-09

一:背景

上一篇我們聊到了如何通過 procdump 抓取 cpu爆高記憶體暴漲 兩種情況,這一篇再聊聊如何去抓程式 掛死意外退出

二:程式掛死

1. 定義

程式掛死 簡單的說就是程式沒有響應,既然沒響應了,可能 死鎖, 可能 負載過大執行緒池耗盡 等等情況,萬千世界,啥情況都有???。

既然是用 procdump 去抓,我得先了解下它對 掛死 (hung on) 的定義?


-h	Write dump if process has a hung window (does not respond to window messages for at least 5 seconds).

從上面的定義看,人家貌似是判斷視窗是否在指定時間內響應 windows訊息 來判別的,我知道你在想什麼?,你尋找的web請求響應時間過長,這種場景通過 -h 是抓不到的,我感覺它特別適合那些帶有 GUI 程式的抓取,比如說:(WPF,Winform) 。

2. 案例演示

現在我準備建立一個簡單的 winform 程式,在 button 事件中故意讓主執行緒sleep造成程式假死,參考程式碼如下:


    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread.Sleep(1000 * 10);

            MessageBox.Show("clicked me!");
        }
    }

接下來啟動 cmd 視窗,輸入:


C:\Windows\system32>procdump -ma -h -w WindowsFormsApp1.exe E:\net5\hungwindow.dmp

ProcDump v10.0 - Sysinternals process dump utility
Copyright (C) 2009-2020 Mark Russinovich and Andrew Richards
Sysinternals - www.sysinternals.com

Waiting for process named WindowsFormsApp1.exe...

啟動程式後點選 button 讓 winform 假死,可以看到 procdump 在 5s 之後自動輸出了dump。


C:\Windows\system32>procdump -ma -h -w WindowsFormsApp1.exe E:\net5\hungwindow.dmp


Press Ctrl-C to end monitoring without terminating the process.

[14:49:53] Hung Window:
[14:49:53] Dump 1 initiated: E:\net5\hungwindow.dmp
[14:49:53] Dump 1 writing: Estimated dump file size is 303 MB.
[14:49:53] Dump 1 complete: 303 MB written in 0.7 seconds
[14:49:54] Dump count reached.

然後用 windbg 看看每一個執行緒都在做什麼?


0:000> ~*e !clrstack
OS Thread Id: 0x6698 (0)
Child SP       IP Call Site
00cfeb60 7722327c [HelperMethodFrame: 00cfeb60] System.Threading.Thread.SleepInternal(Int32)
00cfebe4 5da9be7b System.Threading.Thread.Sleep(Int32)
00cfebec 02d1238d WindowsFormsApp1.Form1.button1_Click(System.Object, System.EventArgs) [E:\net5\ConsoleApp1\WindowsFormsApp1\Form1.cs @ 23]
00cfec04 5a3b95bb System.Windows.Forms.Control.OnClick(System.EventArgs)
00cfec18 5a3bbe57 System.Windows.Forms.Button.OnClick(System.EventArgs)
...

三:意外退出

1. 概念

意外退出 我想很多朋友都遇到過,本來 Console 程式跑的好好地,半夜收到報警簡訊.... 還有使用者反饋,你那終端可行呀,點了幾下就掛掉了。。。???

有些朋友可能在想,sd,這問題還不簡單,加一個全域性 未處理異常 不就好啦??? 真搞不懂怎麼想的 ???。

哈哈,總以為 全域性異常處理 能夠包治百病,還是太年輕了,記得上一家公司用了阿里的sdk,底層用了 C++ 封裝,程式莫名退出了,全域性異常處理也沒任何日誌,說到這裡我想你也知道了,非託管層丟擲的異常,託管層這時候就是弟弟,就這麼簡單???

2. 演示

我準備在程式中丟擲一個簡單的 DivideByZeroException ,方便讓程式退出。


    public class Program
    {
        public static void Main(string[] args)
        {
            var result = CalcDAL();

            Console.WriteLine($"result={result}");

            Console.ReadLine();
        }

        public static int CalcDAL()
        {
            try
            {
                var query = "0";
                Thread.Sleep(2000);  //do sth...

                return 0 / Convert.ToInt32(query);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw;
            }
        }
    }

程式跑起來後,在 procdump 上用 -e 命令抓取。


C:\Windows\system32>procdump -ma -e  -w ConsoleApp1.exe E:\net5\test.dmp

ProcDump v10.0 - Sysinternals process dump utility
Copyright (C) 2009-2020 Mark Russinovich and Andrew Richards
Sysinternals - www.sysinternals.com

Waiting for process named ConsoleApp1.exe...

Press Ctrl-C to end monitoring without terminating the process.

[15:29:56] Exception: 04242420
[15:29:58] Exception: C0000094.INT_DIVIDE_BY_ZERO
[15:29:58] Exception: C0000094.INT_DIVIDE_BY_ZERO
[15:29:58] Exception: C0000094.INT_DIVIDE_BY_ZERO
[15:29:58] Unhandled: C0000094.INT_DIVIDE_BY_ZERO
[15:29:58] Dump 1 initiated: E:\net5\test-2.dmp
[15:29:58] Dump 1 writing: Estimated dump file size is 50 MB.
[15:29:59] Dump 1 complete: 50 MB written in 0.2 seconds
[15:29:59] Dump count reached.

從輸出看,萬事ok。

3. 擴充

不知道有沒有朋友還記得 VS 有一個 異常斷點 嗎? 表示當某種異常丟擲時,程式自動進入斷點處除錯狀態,這是一個幫助找到bug的利器,但還是有一定限制的,畢竟程式都跑在生產上,你也不能把 vs 搬過去,也不可能搞個遠端除錯啥的,所以當程式丟擲了某一種異常後,怎麼自動生成一個 dump 呢???

在強大的 procdump 面前這些都是弟弟,??,主要通過下面兩種方式進行異常碰撞檢索。

  • 通過 異常型別 抓取

何為 異常型別,比如本節的 DivideByZeroException 異常,通過在 procdump 中設定 -e 1 -f DivideByZeroException 即可。

CalcDAL() 方法中的 throw 去掉,保證程式不異常退出。


        public static int CalcDAL()
        {
            try
            {
                var query = "0";
                Thread.Sleep(2000);  //do sth...

                return 0 / Convert.ToInt32(query);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return 0;
            }
        }

然後用 proddump 輸入如下命令。


C:\Windows\system32>procdump -ma  -w -e 1 -f   "divide by zero"  -w ConsoleApp1.exe E:\net5\test.dmp

ProcDump v10.0 - Sysinternals process dump utility
Copyright (C) 2009-2020 Mark Russinovich and Andrew Richards
Sysinternals - www.sysinternals.com

Waiting for process named ConsoleApp1.exe...

Press Ctrl-C to end monitoring without terminating the process.

CoreCLR Version: v5.0.3

[15:44:15] Exception: E0434F4D.System.DivideByZeroException ("Attempted to divide by zero.")
[15:44:15] Dump 1 initiated: E:\net5\test-3.dmp
[15:44:16] Dump 1 writing: Estimated dump file size is 50 MB.
[15:44:16] Dump 1 complete: 50 MB written in 0.2 seconds
[15:44:16] Dump count reached.

看到上面的 Exception: E0434F4D.System.DivideByZeroException ("Attempted to divide by zero.") 了嘛? 哈哈,已經成功捕獲啦,是不是挺有意思?。

  • 通過 異常資訊 抓取

異常資訊 的話,我覺得更加靈活,比如我搜尋一下:divide by zero 關鍵詞就能成功捕獲。


C:\Windows\system32>procdump -ma  -w -e 1 -f   "divide by zero"  -w ConsoleApp1.exe E:\net5\test.dmp

[15:46:34] Exception: E0434F4D.System.DivideByZeroException ("Attempted to divide by zero.")
[15:46:34] Dump 1 initiated: E:\net5\test-4.dmp
[15:46:34] Dump 1 writing: Estimated dump file size is 49 MB.
[15:46:34] Dump 1 complete: 49 MB written in 0.2 seconds
[15:46:35] Dump count reached.

四:總結

混混沌沌寫了這麼多,上下兩篇四種抓取方法我想你都學會了吧,萬事開頭難,有了dump,接下來就是好好研究咯!

更多高質量乾貨:參見我的 GitHub: dotnetfly

圖片名稱

相關文章