如何洞察 .NET程序 非托管句柄泄露

news2024/12/25 12:32:29

一:背景

1. 讲故事

很多朋友可能会有疑问,C# 是一门托管语言,怎么可能会有非托管句柄泄露呢? 其实一旦 C# 程序与 C++ 语言交互之后,往往就会被后者拖入非托管泥潭,让我们这些调试者被迫探究 非托管领域问题

二:非托管句柄泄露

1. 测试案例

为了方便讲述,我们上一个 Event 泄露的案例,使用 C# 调用 C++ ,然后让 C++ 产生 bug 导致句柄泄露。

先看一下 C++ 代码


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);

}

然后导出一个 CSharpCreateEvent 方法给 C# 使用。


    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. 到底是谁在泄露

如果你的生产环境可以用 WinDbg 附加进程,那用它就可以轻松解决,可以借助 !handle 命令看一下泄露的句柄类型。


0:004> !handle 
...
Handle 16fc
  Type         	Event
1411 Handles
Type           	Count
None           	6
Event          	1337
File           	16
Directory      	4
Mutant         	3
WindowStation  	2
Semaphore      	5
Key            	10
Thread         	8
Desktop        	1
IoCompletion   	5
TpWorkerFactory	3
ALPC Port      	1
WaitCompletionPacket	10

从统计信息看,当前 Event 高达 1337 个,看样子程序存在 Event 泄露,接下来我们就要洞察到底是谁分配的 Event,如果能找到分配 Event 的线程栈,那这个问题就会迎刃而解,对吧,有 WinDbg 在,方圆3公里的bug都要移民,追踪调用栈可以使用 WinDbg 提供的 !htrace 命令。

它的原理很简单,一句话表示就是:挖出现在时间点和快照之间那些没有被 free 处理的 handle 调用栈,结果一清二楚,参考代码如下:


0:011> !htrace -enable
Handle tracing enabled.
Handle tracing information snapshot successfully taken.

0:011> g
(e14.90c0): Break instruction exception - code 80000003 (first chance)
eax=006f2000 ebx=00000000 ecx=7777dfe0 edx=10088020 esi=7777dfe0 edi=7777dfe0
eip=77744e50 esp=0811f97c ebp=0811f9a8 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!DbgBreakPoint:
77744e50 cc              int     3

0:007> !htrace -diff
Handle tracing information snapshot successfully taken.
0xad new stack traces since the previous snapshot.
Ignoring handles that were already closed...
Outstanding handles opened since the previous snapshot:
--------------------------------------
Handle = 0x0000199c - OPEN
Thread ID = 0x000017c8, Process ID = 0x00000e14

0x4ac3d761: +0x4ac3d761
0x4aa0d9f5: +0x4aa0d9f5
0x6674d9c4: +0x6674d9c4
0x66547f33: +0x66547f33
0x6654901a: +0x6654901a
0x776c17c3: +0x776c17c3
0x776c11b9: +0x776c11b9
0x665438c9: +0x665438c9
0x665432bd: +0x665432bd
0x66725089: +0x66725089
0x66724c73: +0x66724c73
0x66724c1e: +0x66724c1e
0x77742f7c: ntdll!NtCreateEvent+0x0000000c
0x770f5746: KERNELBASE!CreateEventExW+0x00000056
0x770e2b04: KERNELBASE!CreateEventW+0x00000024
*** WARNING: Unable to verify checksum for D:\skyfly\20.20230628\src\Example\Example_20_1_4\bin\x86\Debug\net6.0\Example_20_1_5.DLL
0x6ac91755: Example_20_1_5!CSharpCreateEvent+0x00000035
--------------------------------------
...

Displayed 0xaa stack traces for outstanding handles opened since the previous snapshot.

从卦中短暂的时间内快照之间有 170 个句柄没有被释放,而且从调用栈看是 Example_20_1_5!CSharpCreateEvent 方法所致,但这里有一个问题,虽然有非托管栈,但没有看到任何托管部分,那怎么办呢?

3. 如何洞察到托管栈

其实这个问题很简单,既然都 WinDbg 附加了,干脆用 bp 下断点,后续泄露之时必然会被命中,然后通过 !clrstack 或者 k 观察线程栈即可,有了思路就开干。


:007> bp Example_20_1_5!CSharpCreateEvent "k; gc"
breakpoint 0 redefined
0:007> g
 # ChildEBP RetAddr      
00 0848f9e4 080674f3     Example_20_1_5!CSharpCreateEvent [D:\skyfly\20.20230628\src\Example\Example_20_1_5\Example_20_1_5.cpp @ 15] 
WARNING: Frame IP not in any known module. Following frames may be wrong.
01 0848f9e4 0806748b     0x80674f3
02 0848f9f0 0806e3dd     Example_20_1_4!Example_20_1_4.Program.<>c.<Main>b__1_0+0x1b
03 0848f9fc 0806e38d     System_Private_CoreLib!System.Threading.Tasks.Task.InnerInvoke+0x3d
04 0848fa04 0806e307     System_Private_CoreLib!System.Threading.Tasks.Task.<>c.<.cctor>b__272_0+0xd
05 0848fa2c 0806e072     System_Private_CoreLib!System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop+0x37
06 0848fa94 0806c49f     System_Private_CoreLib!System.Threading.Tasks.Task.ExecuteWithThreadLocal+0x82
07 0848faec 6b22f2bc     System_Private_CoreLib!System.Threading.ThreadPoolWorkQueue.Dispatch+0x1bf
08 0848fb88 6b216595     System_Private_CoreLib!System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart+0xdc [/_/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @ 63] 
09 0848fb98 6c00c30f     System_Private_CoreLib!System.Threading.Thread.StartCallback+0x35 [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 106] 
0a 0848fba4 6bf5c07b     coreclr!CallDescrWorkerInternal+0x34
0b 0848fbd8 6bf6799a     coreclr!CallDescrWorkerWithHandler+0x66 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 69] 
0c 0848fc20 6bff619b     coreclr!DispatchCallSimple+0x7f [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 220] 
0d 0848fc44 6bf7c7df     coreclr!ThreadNative::KickOffThread_Worker+0x4b [D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp @ 158] 
0e (Inline) --------     coreclr!ManagedThreadBase_DispatchInner+0x3d [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7321] 
0f 0848fcc8 6bf7c70f     coreclr!ManagedThreadBase_DispatchMiddle+0x8c [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7365] 
10 0848fd20 6bf1116f     coreclr!ManagedThreadBase_DispatchOuter+0x62 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7543] 
11 (Inline) --------     coreclr!ManagedThreadBase_FullTransition+0x21 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7569] 
12 (Inline) --------     coreclr!ManagedThreadBase::KickOff+0x21 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7604] 
13 0848fd54 755b00f9     coreclr!ThreadNative::KickOffThread+0x7f [D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp @ 230] 
14 0848fd64 77737bbe     KERNEL32!BaseThreadInitThunk+0x19
15 0848fdc0 77737b8e     ntdll!__RtlUserThreadStart+0x2f
16 0848fdd0 00000000     ntdll!_RtlUserThreadStart+0x1b
...

从卦中看,一切都非常明白,这里再补充一点,如果想中途再产生 快照,可以用 -snapshot 命令创建一个初始点,参考如下:


0:007> !htrace -snapshot
Handle tracing information snapshot successfully taken.

三:总结

handle 泄露也是一个比较难搞的问题,难点在于生产环境可能不让你用 WinDbg 这种侵入方式,但问题还得要解决,必须创造条件上,当前除了 WinDbg 还没有找到其他方式,有机会再研究下吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/727321.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第二章:安装VM+CentOS7安装+JDK及tomcat安装+安装mysql

目录 1. 安装VMWare 2. centos7安装 2.1 常见问题 3. 查看、设置IP地址 4. 关机与重启 5. 切换为国内源 6. Linux下的目录结构 7. JDK及tomcat安装 7.1 将压缩包上传到linux 7.2 安装JDK 7.3 安装Tomcat 8. 安装mysql 8.1 离线安装 8.2 在线安装 1. 安装VMWare 注意…

CentOS8.5 环境下部署 vsftpd

目录 前言安装vsftpd验证是否安装vsftpd安装vsftpd&#xff1a;操作vsftpd&#xff1a;vsftpd.conf配置创建用户添加端口安全组验证ftp搭建是否成功 前言 在物联网项目中&#xff0c;我们会经常使用到ftp服务器&#xff0c;今天我们就来实现一下centos8.5环境下部署vsftpd的搭建…

Buffer源码

介绍 首先 Buffer 是一个能存储基本数据类型的容器&#xff08;除了 Boolean 类型&#xff09;&#xff0c;从 java.nio 包的继承结构就能看出来。 Java中的Buffer类是一个抽象类。Buffer类提供了一种将数据存储在内存中的方式&#xff0c;并提供了一些操作数据的方法。Buffer…

原型模式:如何最快速地clone一个HashMap散列表?

我们还像学习建造者模式一样 思考 什么是原型模式&#xff1f;主要解决哪些问题&#xff1f; 如果对象的创建成本比较大&#xff0c;而同一个类的不同对象之间差别不大&#xff08;大部分字段都相同&#xff09;&#xff0c;在这种情况下&#xff0c;我们可以利用对已有对象…

5 类型转换

类型转换是变量与变量之间的&#xff0c;变量与常量之间是赋值。 5.1 自动类型转换 小转大。如下图所示&#xff0c;注意&#xff0c;byte不能自动转为char类型&#xff0c;因为类型不匹配&#xff0c;但是可以通过强转来转。 代码如下&#xff1a; byte a 10;int b a; 5.2…

测试流程实战(1)

目录&#xff1a; 测试流程梳理业务架构分析实战测试用例管理实战Bug 录入与管理实战如何写 Bug 报告编写 Bug 报告 1.测试流程梳理 2.业务架构分析实战 使用 plantuml 完成登录流程时序图plantuml 官网&#xff1a;使用简单的文字描述画UML图的开源工具。plantuml 在线绘图…

【HDC.Cloud 2023】华为开发者大会2023来了!这份PaaS参会指南请查收!

了不起的开发者们&#xff0c;我们来啦&#xff01; 7月7日&#xff0c;华为开发者大会2023 ( Cloud )将拉开帷幕 PaaS诚邀您参加这场不容错过的年度开发者盛会&#xff0c;让我们一起开启探索之旅。 我们将为开发者们提供PaaS生态资源工具、学习成长、分享交流、生态实践等…

Docker 安装Flowable-ui

查询镜像 docker search flowable-ui 拉取镜像 docker pull flowable/flowable-ui 使用默认数据库&#xff08;默认H2数据库&#xff09; docker run --name flowable-ui \ -p 8080:8080 \ -d --restartalways \ flowable/flowable-ui 使用MySQL数据库 docker run --name…

Windows环境Jmeter调优

在windows环境下搭建jmeter的压测实验环境&#xff0c;需要对操作系统默认的一些个参数进行设置&#xff0c;以提高并发能力。特别是作为压力机的时候。 Socket 编程时&#xff0c;单机最多可以建立多少个 TCP 连接&#xff0c;受到操作系统的影响。 Windows 下单机的TCP连接数…

simulink while/if/switch case

目录 while if Switch case while 循环设置100次 if Switch case 子模块可以用法和if一样

vue3混入mixins

Vue中混入的作用是分发组建中可复用的功能 新建mixins文件夹&#xff0c;新建mixins.ts文件 import { ref } from vue;export default function () {const num ref(0);const fav ref(false);const fvbtn () > {num.value 1;fav.value true;setTimeout(() > {fav.va…

六西格玛在服务业的案例:如何通过过程改进提高客户满意度?

六西格玛是一种质量管理方法&#xff0c;旨在通过减少缺陷和提高效率用以改善业务流程。在服务行业&#xff0c;六西格玛可以帮助企业提高客户满意度&#xff0c;缩短服务周期&#xff0c;降低成本。下面张驰咨询给大家分享一个服务行业的六西格玛案例。 1、背景介绍 这家服务…

Docker学习笔记26

Docker stack应用&#xff1a; 1&#xff09;Docker 层级关系中的最高层次——stack&#xff0c;一个stack就是一组有关联的服务的组合&#xff0c;可以一起编排&#xff0c;一起管理。 早期&#xff0c;使用service来发布服务。但是service每次只能发布一个service。 yaml可…

(Windows版)PostgreSQL - TimescaleDB插件的2种安装方法

一&#xff1a;下载pgsql相对应的timescaledb插件包 下载地址&#xff1a;https://github.com/timescale/timescaledb/releases/tag/2.10.1 二&#xff1a;开始安装 注意&#xff1a;在安装前&#xff0c;先关闭PostgreSQL 服务 方法一 1.【控制面板\系统和安全\管理工具\…

手把手教-单片机和w5500模块基于rt-thread中wiznet软件包的使用

一、开发环境 硬件&#xff1a;stm32f407野火开发板&#xff0c;w5500模块 软件&#xff1a;rt-thread操作系统&#xff0c;wiznet软件包&#xff0c;基于正点原子stm32f407的bsp包&#xff08;需要根据实际修改系统时钟&#xff09; 引脚连接方式&#xff1a; 单片机引脚&…

镀金积分球——激光红外功率测量

中波红外激光作为干扰光源&#xff0c;可对红外导引头及红外观瞄设备实施压制式干扰&#xff0c;激光器远场功率密度分布是评价其作战效能的重要指标。通用的激光功率测量设备多基于近场&#xff0c;并且因接收口径有限&#xff0c;只能检测到很小特定区域内的激光功率&#xf…

专心当个工程师,也挺好~

正文 大家好&#xff0c;我是bug菌~ 今天主要是分享一些职场上的小小领悟吧&#xff1a; 1 双向思维 一个人有他的缺点&#xff0c;也必定存在其优点&#xff0c;不要动不动就局限的认为这个人这也不行&#xff0c;那也不行&#xff0c;再说当初你咋就把他招进来了呢&#xff1…

【Redis 】Redis 的脑裂现象和解决方案

文章目录 Redis 中的脑裂是什么&#xff1f;脑裂有什么影响&#xff1f;数据丢失一定是发生了脑裂吗&#xff1f;如何解决脑裂问题&#xff1f; Redis 中的脑裂是什么&#xff1f; 从名字分析&#xff0c;脑裂现象就是大脑裂开了&#xff0c;一个人如果有两个大脑&#xff0c;…

Golang每日一练(leetDay0116) 路径交叉、回文对

目录 335. 路径交叉 Self-crossing &#x1f31f;&#x1f31f;&#x1f31f; 336. 回文对 Palindrome Pairs &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/…

【PCIE】链路训练的TS训练序列解释

TS码流解释 TS1和TS2有序集合是PCIe&#xff08;Peripheral Component Interconnect Express&#xff09;协议中使用的一种特殊数据包格式。这些有序集合用于在PCIe链路的训练阶段进行通信和控制。 TS1&#xff08;Training Sequence 1&#xff09;有序集合&#xff1a;TS1有…