PerfView专题 (第十三篇):洞察 .NET程序 的非托管句柄泄露

perfview,专题,第十,三篇,洞察,net,程序,托管,句柄,泄露 · 浏览次数 : 328

小编点评

**背景** * 如何洞察 .NET程序非托管句柄泄露的文章使用 WinDbg 的 `!htrace` 命令实现句柄泄露。 * WinDbg 是以侵入式的方式解决这个问题,在生产环境中大多数情况下是不能走附加进程的模式,所以这也是它最大的局限性。 **PerfView 分析** * 测试案例中,使用 C++ 代码在 C# 中调用 `CSharpCreateEvent` 方法,导致句柄泄露。 * C# 中调用 C++ 代码的方法时,可以使用 `Task` 的形式进行异步执行,以避免阻塞主线程。 * perfview 可以开启内核级别的 OS Handle ETW 事件进行分配和释放监控,以捕获并分析句柄泄露。 * 在 Focus process 中指定 Example_20_1_4.exe ,并选中 Handle 复选框,截图显示 Event 事件的泄露哈。 **总结** * PerfView 的最大好处是无侵入性,因为它可以与 WinDbg 同步地收集和分析句柄泄露。 * WinDbg 和 Perfview 真是一对好搭档,优势互补。

正文

一:背景

1. 讲故事

前几天写了一篇 如何洞察 .NET程序 非托管句柄泄露 的文章,文中使用 WinDbg 的 !htrace 命令实现了句柄泄露的洞察,在文末我也说了,WinDbg 是以侵入式的方式解决了这个问题,在生产环境中大多数情况下是不能走附加进程的模式,所以这也是它最大的局限性。

那如何以非侵入的方式解决这个问题呢?这就是本篇讨论的重点,对,就是用 CLR 团队 鼎力推荐的 Perfview 来解决这个问题,哈哈,是我昨天看文档无意发现的 😁😁😁。

二:PerfView 分析

1. 测试案例

还是用那一篇文章的例子,让 C# 和 C++ 交互的时候产生句柄泄露, C++ 代码如下:


// Example_20_1_5.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

extern "C"
{
	_declspec(dllexport) void CSharpCreateEvent();
}

#include "iostream"
#include <Windows.h>

using namespace std;

void CSharpCreateEvent()
{
	HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	printf("\nEvent句柄值: %#08x	", hEvent);

}

在 C# 中我用 Task 的形式调用 CSharpCreateEvent 函数来产生 Event 句柄泄露,参考代码如下:


	internal class Program
    {

        [DllImport("Example_20_1_5", CallingConvention = CallingConvention.Cdecl)]
        extern static void CSharpCreateEvent();

        static void Main(string[] args)
        {
            try
            {
                while (true)
                {
                    Task.Run(() =>
                    {
                        CSharpCreateEvent();
                    });

                    Thread.Sleep(10);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadLine();
        }
    }

2. Handle 分配监控

在 perfview 中可以开启内核级别的 OS Handle ETW事件 来进行分配和释放监控,用 +1-1 表示,为了让事件产生的更少,在 Focus process 中指定 Example_20_1_4.exe ,并且选中 Handle 复选框,截图如下:

如果嫌面板太麻烦,可以使用 /KernelEvents:Handle,Default 命令即可,完整的 Command 命令如下:


PerfView.exe  "/DataFile:PerfViewData.etl" /BufferSizeMB:256 /StackCompression /CircularMB:500 /KernelEvents:Handle,Default /NoGui /FocusProcess:"Example_20_1_4.exe" /NoNGenRundown collect

接下来点击 Start Collection 开启收集,收集一会之后点击 Stop Collection,生成好 Zip 之后选择 Advanced -> Windows Handle Ref Count Stacks 选项,在弹出面板中选择我们的 Example_20_1_4 程序,删除 GroupPats 分组信息,处理后的截图如下:

从上面的面板信息的 Handle Type Event 来看,当前有 5445 个 Event 事件没有被 Close,原来是 Event事件 的泄露哈,接下来在右键面板中选择 Goto -> Item in Callers 选项可以看到每一个 Event 的详细列表。

最后就是抽选其中几个,观察到底是什么代码创建的 Event ? 截图如下:

仔细观察面板信息,可以清楚的看到,原来是 Main 函数中有一个匿名方法,它在内部调用了 Example_20_1_5!CSharpCreateEvent 方法来创建句柄。

到这里就真相大白了。

三:总结

通过这个案例可以看到 PerfView 的最大好处就是无侵入性,WinDbg 和 Perfview 真的是一对好搭档,优势互补。

图片名称

与PerfView专题 (第十三篇):洞察 .NET程序 的非托管句柄泄露相似的内容:

PerfView专题 (第十三篇):洞察 .NET程序 的非托管句柄泄露

## 一:背景 ### 1. 讲故事 前几天写了一篇 `如何洞察 .NET程序 非托管句柄泄露` 的文章,文中使用 WinDbg 的 `!htrace` 命令实现了句柄泄露的洞察,在文末我也说了,WinDbg 是以侵入式的方式解决了这个问题,在生产环境中大多数情况下是不能走附加进程的模式,所以这也是

PerfView专题 (第十四篇): 洞察那些 C# 代码中的短命线程

## 一:背景 ### 1. 讲故事 这篇文章源自于分析一些疑难dump的思考而产生的灵感,在dump分析中经常要寻找的一个答案就是如何找到死亡线程的生前都做了一些什么?参考如下输出: ``` C# 0:001> !t ThreadCount: 22 UnstartedThread: 0 Backg

PerfView专题 (第十五篇): 如何洞察 C# 中的慢速方法

## 一:背景 ### 1. 讲故事 在 dump 分析旅程中,经常会遇到很多朋友反馈一类问题,比如: * 方法平时都执行的特别快,但有时候会特别慢,怎么排查? * 我的方法第一次执行特别慢,能看到慢在哪里吗? 相信有朋友肯定说,加些日志不就好了,大方向肯定是没问题的,但加日志的颗粒度会比较粗而且侵

PerfView专题 (第十六篇): 如何洞察C#托管堆内存的 "黑洞现象"

## 一:背景 ### 1. 讲故事 首先声明的是这个 `黑洞` 是我定义的术语,它是用来表示 `内存吞噬` 的一种现象,何为 `内存吞噬`,我们来看一张图。 ![](https://img2023.cnblogs.com/blog/214741/202307/214741-202307241003