4

Linux 上的 .NET 如何自主生成 Dump - 一线码农

 1 year ago
source link: https://www.cnblogs.com/huangxincheng/p/17516112.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

1. 讲故事

前几天微信上有位朋友找到我,说他程序的 线程数 会偶发性瞬时飙高,让我看下大概是什么原因,截图如下:

214741-20230630110439615-636302323.jpg

如果这种问题每天都会出现,比较好的做法就是用 dotnet-trace 捕获 ThreadCreated 事件,但可气的是朋友说大概一个月发生次把,这种情况下用 dotnet-trace 持续监视就没必要了,弄不好把硬盘给爆掉了。

那怎么办呢?还能怎么办,借助第三方工具 来生成dump,比如 procdump,dotnet-dump 等等,但这里又有限制了,很多人不愿意在docker中再开启一个进程,希望让程序自己生成dump,这个其实能理解,很多商业化工具都具有 crash dump 的功能,比如:腾讯会议,有道字典 等等,本篇就来聊一聊如何自主生成 dump。

二:如何自主生成 dump

1. Windows 平台上如何自主

熟悉 Windows 平台的朋友都知道,在 Win32 API 中有一个 MiniDumpWriteDump 的方法声明,方法实现是在 dbghelp.dll中,而且 dbghelp 是操作系统自带的,有了这些知识,我们可以将 dbghelp.lib 静态链接过来生成dump,参考代码如下:


#include <iostream>
#include <Windows.h>
#include <minidumpapiset.h>
#include "Dbghelp.h"
#pragma comment(lib, "dbghelp.lib")

int main()
{
	//1. 创建文件
	HANDLE hFile = CreateFile(L"D:\\testdump\\MiniDump.dmp", GENERIC_READ | GENERIC_WRITE, 0, NULL,
		CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpWithFullMemory, NULL,
		NULL, NULL);

	CloseHandle(hFile);
}

将程序跑起来后,就会生成一个 dump 文件。

214741-20230630110439591-653491296.png

这里简单提醒一下,默认用的 dbghelp.dll 是 Windows 系统目录下的,版本比较老,新功能可能不支持,如果我想用新版本的 dbghelp.dll 去哪里找呢?

其实有一个快捷途径,就是windbg 的安装目录下都会有最新的 dbghelp.dll,可以用 .chain 去寻找。


0:000> .chain
Extension DLL chain:
    dbghelp: image 10.0.25877.1004, API 10.0.6, 
        [path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\dbghelp.dll]
    exts: image 10.0.25877.1004, API 1.0.0, 
        [path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\WINXP\exts.dll]
    ...

哈哈,上面的 dbghelp 就是,接下来用 LoadLibrary 加载进来即可,失败逻辑就不写了哈,参考代码如下:


#include <iostream>
#include <windows.h>
#include <dbghelp.h>

typedef BOOL(WINAPI* MiniDumpWriteDumpT)(
	HANDLE,
	DWORD,
	HANDLE,
	MINIDUMP_TYPE,
	PMINIDUMP_EXCEPTION_INFORMATION,
	PMINIDUMP_USER_STREAM_INFORMATION,
	PMINIDUMP_CALLBACK_INFORMATION);

int main()
{
	//1. 创建文件
	HANDLE hFile = CreateFile(L"D:\\testdump\\MiniDump2.dmp", GENERIC_READ | GENERIC_WRITE, 0, NULL,
		CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	HMODULE hDbgHelp = LoadLibrary(L"dbghelp.dll");

	MiniDumpWriteDumpT pfnMinidumpWriteDump = (MiniDumpWriteDumpT)GetProcAddress(hDbgHelp, "MiniDumpWriteDump");

	pfnMinidumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpWithFullMemory, NULL,
		NULL, NULL);

	CloseHandle(hFile);
}

跑起来后就能看到 Dump 啦。

214741-20230630110439612-1110785701.png

2. Linux 平台上如何自主

在 Linux 平台上确实没有找到类似 MiniDumpWriteDump 的办法,那怎么生成呢? 后来我就在想 dotnet-dump 它为什么能生成dump,沿着这个思路,找到了原来微软还有一个叫客户端诊断库的API Microsoft.Diagnostics.NETCore.Client,它可以帮助我们生成 dump 文件。

214741-20230630110439608-596654863.png

原来它是微软提供的 EventPipe 收集机制,可以收集 .NET 的 ETW 和 EventSource 发生的事件,挺好用的,更多详情可以观察微软的官方文档。

有了这些基础,接下来就可以写个测试案例,从 nuget 拉一下 Microsoft.Diagnostics.NETCore.Client 包。

214741-20230630110439615-2002153555.png

测试代码如下:


    internal class Program
    {
        static void Main(string[] args)
        {
            Task.Run(() =>
            {
                Console.WriteLine("指标异常,要抓 dump 啦!");
                Dumper.TriggerCoreDump(Environment.ProcessId);
            });

            Console.ReadLine();
        }

        public class Dumper
        {
            public static void TriggerCoreDump(int processId)
            {
                var client = new DiagnosticsClient(processId);
                client.WriteDump(DumpType.Full, "/data/minidump.dmp");
            }
        }
    }

上传到 Linux ,执行 dotnet Example_5_1_7.dll 后,minidump.dmp 就出来了,输出如下:


[root@localhost data2]# dotnet Example_5_1_7.dll
指标异常,要抓 dump 啦!
[createdump] Gathering state for process 4061 dotnet
[createdump] Writing full dump to file /data/minidump.dmp
[createdump] Written 147349504 bytes (35974 pages) to core file
[createdump] Target process is alive
[createdump] Dump successfully written

[root@localhost data2]# ls -lh /data
total 141M
-rw-------. 1 root root 141M Jun 30 10:09 minidump.dmp

可以看到这个 dump 有 141M,接下来用 windbg 验证下是否完好,一切正常,参考如下:


0:000> !t
ThreadCount:      9
UnstartedThread:  0
BackgroundThread: 8
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                            Lock  
 DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1      fb0 000055DF915CD9C0    20020 Preemptive  00007F06AC01B0F0:00007F06AC01BFD0 000055df915b4e90 -00001 Ukn 
   5    2      fb5 000055DF915DB010    21220 Preemptive  0000000000000000:0000000000000000 000055df915b4e90 -00001 Ukn (Finalizer) 
   6    3      fb6 000055DF91606F80    21220 Preemptive  0000000000000000:0000000000000000 000055df915b4e90 -00001 Ukn 
   7    4      fb7 000055DF9163A4C0  3021220 Preemptive  00007F06AC032DB0:00007F06AC033FD0 000055df915b4e90 -00001 Ukn (Threadpool Worker) 
   8    5      fb8 000055DF9163BE50  3021220 Preemptive  00007F06AC00D8C0:00007F06AC00F040 000055df915b4e90 -00001 Ukn (Threadpool Worker) 
   9    6      fb9 00007F069C002B70  1021220 Preemptive  00007F06AC0371D0:00007F06AC037FD0 000055df915b4e90 -00001 Ukn (Threadpool Worker) 
  11    7      fbb 00007F069C04F060    21220 Preemptive  00007F06AC02D768:00007F06AC02F3F0 000055df915b4e90 -00001 Ukn 
  12    8      fbd 00007F0694001680  1021220 Preemptive  00007F06AC034388:00007F06AC035FD0 000055df915b4e90 -00001 Ukn (Threadpool Worker) 
  13    9      fbe 00007F068C0013E0  1021220 Preemptive  00007F06AC0383A0:00007F06AC039FD0 000055df915b4e90 -00001 Ukn (Threadpool Worker) 
0:000> ~0s
libpthread_2_17+0xe75d:
00007f07`5c1e575d 488b3c24        mov     rdi,qword ptr [rsp] ss:00007ffd`2c48ead0=0000000000000000
0:000> !clrstack
OS Thread Id: 0xfb0 (0)
        Child SP               IP Call Site
00007FFD2C48EB10 00007f075c1e575d [InlinedCallFrame: 00007ffd2c48eb10] Interop+Sys.ReadStdin(Byte*, Int32)
00007FFD2C48EB10 00007f06e1c42c08 [InlinedCallFrame: 00007ffd2c48eb10] Interop+Sys.ReadStdin(Byte*, Int32)
00007FFD2C48EB00 00007f06e1c42c08 ILStubClass.IL_STUB_PInvoke(Byte*, Int32)
00007FFD2C48EB90 00007f06e1d327d9 System.IO.StdInReader.ReadKey() [/_/src/libraries/System.Console/src/System/IO/StdInReader.cs @ 458]
00007FFD2C48F0C0 00007f06e1d31ded System.IO.StdInReader.ReadLineCore(Boolean) [/_/src/libraries/System.Console/src/System/IO/StdInReader.cs @ 154]
00007FFD2C48F1E0 00007f06e1d31a5a System.IO.StdInReader.ReadLine() [/_/src/libraries/System.Console/src/System/IO/StdInReader.cs @ 86]
00007FFD2C48F200 00007f06e1d311a0 System.IO.SyncTextReader.ReadLine() [/_/src/libraries/System.Console/src/System/IO/SyncTextReader.cs @ 76]
00007FFD2C48F230 00007f06e1d2748c System.Console.ReadLine() [/_/src/libraries/System.Console/src/System/Console.cs @ 721]
00007FFD2C48F240 00007f06e1c336d6 Example_5_1_7.Program.Main(System.String[]) [D:\skyfly\5.20230426\src\Example\Example_5_1_7\Program.cs @ 20]

个人感觉自主生成 Dump 的方式在 Docker 场景下特别适用,通过一点硬编码来避免开启 多进程 的折中方式太棒了,相信这位朋友肯定有了好的思路。😄😄😄

图片名称

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK