记一次 .NET 某拍摄监控软件 卡死分析
一:背景
1. 讲故事
今天本来想写一篇 非托管泄露
的生产事故分析,但想着昨天就上了一篇非托管文章,连着写也没什么意思,换个口味吧,刚好前些天有位朋友也找到我,说他们的拍摄监控软件卡死了,让我帮忙分析下为什么会卡死,听到这种软件,让我不禁想起了前些天 在程序员桌子上安装监控 的新闻,参考如下:
我在想我这不是尼玛作恶吗... 和朋友确认了下还好不是干这个事的。
二:WinDbg 分析
1. 为什么会卡死
因为这种监控软件是窗体程序,所以它的卡死理应看主线程的调用栈即可, 在windbg中有一个 k
命令。
0:000:x86> kb 8
# ChildEBP RetAddr Args to Child
00 00dbedc0 77835329 0fd54c08 00000000 0fd54c08 ntdll_777d0000!NtWaitForAlertByThreadId+0xc
01 00dbedc0 7783505c 00000000 00000000 0fd54c08 ntdll_777d0000!RtlpWaitOnAddressWithTimeout+0x64
02 00dbee60 77813fd8 0fd543f0 0fd54c04 0000000c ntdll_777d0000!RtlpWaitOnCriticalSection+0x1ac
03 00dbeea8 77813d99 00000000 00dbef04 09d72f87 ntdll_777d0000!RtlpEnterCriticalSectionContended+0x228
04 00dbeeb4 09d72f87 0fd54c04 09d38131 ee66de6e ntdll_777d0000!RtlEnterCriticalSection+0x49
WARNING: Stack unwind information not available. Following frames may be wrong.
05 00dbef04 09d38036 ee66de46 000001fd 00000111 scvncctrl!DllUnregisterServer+0x4ed7
06 00dbef2c 09d3304d 00000111 000001fd 00000111 scvncctrl+0x48036
07 00dbef50 09d341f3 00000111 000001fd 00000001 scvncctrl+0x4304d
从卦象来看,程序在 scvncctrl!DllUnregisterServer+0x4ed7
方法中等待 临界区锁
,即 RtlEnterCriticalSection
处。
可能有些朋友有疑问,为什么 scvncctrl
后面的偏移值那么大,这是因为 scvncctrl 没有提供公有和私有符号,所以无法对应函数名,windbg 只能以 module 为参考点设置偏移,这对 dump 分析产生了很大的阻碍!
接下来继续看,既然主线程在等待锁,那必然有人在持有锁,那到底是谁在持有呢?
2. 寻找持有线程
要想找到持有者,可以提取 RtlEnterCriticalSection
方法中的第一个参数 0fd54c04
,我们使用 dt _RTL_CRITICAL_SECTION 命令即可。
0:000:x86> dt _RTL_CRITICAL_SECTION 0fd54c04
ntdll_777d0000!_RTL_CRITICAL_SECTION
+0x000 DebugInfo : 0x07ba4428 _RTL_CRITICAL_SECTION_DEBUG
+0x004 LockCount : 0n-6
+0x008 RecursionCount : 0n1
+0x00c OwningThread : 0x0000621c Void
+0x010 LockSemaphore : 0xffffffff Void
+0x014 SpinCount : 0x200064a
上面的 OwningThread
就是当前的持有线程,找到了之后切过去看下它的线程栈,它到底在干嘛?
0:005:x86> ~~[0x0000621c]s
ntdll_777d0000!NtWaitForSingleObject+0xc:
7784619c c20c00 ret 0Ch
0:005:x86> kb
CvRegToMachine(x86) conversion failure for 0x14f
X86MachineInfo::SetVal: unknown register 0 requested
# ChildEBP RetAddr Args to Child
00 0a8cf1ac 747ccfd5 00000924 00000001 00000000 ntdll_777d0000!NtWaitForSingleObject+0xc
01 0a8cf1ac 747ddb12 00000002 00000006 ae23e128 mswsock!SockWaitForSingleObject+0x125
02 0a8cf220 75c05fe5 000007e8 0a8cf258 00000001 mswsock!WSPRecv+0x232
03 0a8cf26c 09ddd32f 000007e8 011a5a30 00002000 ws2_32!recv+0x95
WARNING: Stack unwind information not available. Following frames may be wrong.
04 0a8cf3b4 09ddd0a6 011a5a30 00002000 00000003 scvncctrl!DllUnregisterServer+0x6f27f
05 0a8cf4d4 09ddd625 00000001 00000001 07ac4ae0 scvncctrl!DllUnregisterServer+0x6eff6
06 0a8cf5f0 09ddd72f 0fd1f350 07ac4ae0 00000000 scvncctrl!DllUnregisterServer+0x6f575
07 0a8cf708 09d70626 00000003 00000001 0fd543f0 scvncctrl!DllUnregisterServer+0x6f67f
08 0a8cf958 09d71b56 00000075 000001f7 0000070b scvncctrl!DllUnregisterServer+0x2576
09 0a8cf9a4 09d3140c 00000075 000001f7 0000070b scvncctrl!DllUnregisterServer+0x3aa6
0a 0a8cfa18 09d35b89 e431cbea 0fd5fbf0 0fd543f0 scvncctrl+0x4140c
0b 0a8cfa80 09d73189 00000000 09d73120 0a8cfacc scvncctrl+0x45b89
0c 0a8cfa90 09e09434 0fd543f0 e431cba6 09e093dd scvncctrl!DllUnregisterServer+0x50d9
0d 0a8cfacc 75c77ba9 0fd5fbf0 75c77b90 0a8cfb34 scvncctrl!DllUnregisterServer+0x9b384
0e 0a8cfadc 7783b79b 0fd5fbf0 c738a5e9 00000000 kernel32!BaseThreadInitThunk+0x19
0f 0a8cfb34 7783b71f ffffffff 778689f7 00000000 ntdll_777d0000!__RtlUserThreadStart+0x2b
卦中的 ws2_32!recv
是一个win32体系内的方法,用于 接收客户端发送数据
,可能有些朋友对 recv
方法不是很清楚,方法签名大概如下:
int recv(
SOCKET s,
char *buf,
int len,
int flags
);
因为是主控端,我在网上找了一段 win32 实现的 server 版的 recv 完整代码。
#define _WINSOCK_DEPRECATED_NO_WARNINGS
//1.头文件
#include <stdio.h>
#include <Winsock2.h>
#pragma comment (lib,"ws2_32.lib")
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("请求版本失败!\n");
return -1;
}
printf("请求版本成功!\n");
SOCKET serverScoket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == serverScoket)
{
printf("创建套接字失败!\n");
WSACleanup();
return -1;
}
printf("创建套接字成功!\n");
SOCKADDR_IN serverAddr = { 0 };
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8888);
serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.0.107");
if (SOCKET_ERROR == bind(serverScoket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)))
{
printf("绑定失败!\n");
closesocket(serverScoket);
WSACleanup();
return -1;
}
printf("绑定成功!\n");
if (SOCKET_ERROR == listen(serverScoket, 10))
{
printf("监听失败!\n");
closesocket(serverScoket);
WSACleanup();
return -1;
}
printf("监听成功!\n");
SOCKADDR_IN clientAddr = { 0 };
int len = sizeof(clientAddr);
SOCKET clientSocket = accept(serverScoket, (sockaddr*)&clientAddr, &len);
if (INVALID_SOCKET == clientSocket)
{
printf("接受链接失败!\n");
closesocket(serverScoket);
WSACleanup();
return -1;
}
printf("接受客户链接成功!\n");
printf("客户ip为:%s", inet_ntoa(clientAddr.sin_addr));
//8.开始通讯
char recvbuff[1024] = {};
char sendbuff[1024] = {};
//参数一:代表客户端的socket,表示从客户端进行收取数据
//参数二:接受的数据存放地址
//参数三:接受数据的长度
//参数四:表示收发方式,0表示默认,一次收完
while (true)
{
//保存数据清空
memset(recvbuff, 0, sizeof(recvbuff));
//从客户端接受数据
if (recv(clientSocket, recvbuff, sizeof(recvbuff) - 1, 0) > 0)
{
printf("客户说:%s\n", recvbuff);
}
else
{
break;
}
memset(sendbuff, 0, sizeof(sendbuff));
printf("我说:");
scanf_s("%s", sendbuff, sizeof(sendbuff) - 1);
//发送数据给客户端
send(clientSocket, sendbuff, strlen(sendbuff), 0);
}
//9.关闭链接
closesocket(clientSocket);//关闭客户端socket
closesocket(serverScoket);//关闭服务端socket
WSACleanup(); //关闭套接字请求
return 0;
}
结合上面的完整代码,业务逻辑应该是 while (true)
里的 send
和 recv
区间内的某句代码持有了锁,但因为某种异常导致持有的 临界区锁
没有释放,出现了一种 锁污染
的情况。
朋友提供的信息也进一步佐证了这种说法。
- 大截图
- 受控端偶发断网
这些情况组合在一起导致了 send
和 recv
之间的某处代码异常污染了 临界区锁
。
本来想提取下 recv 中的 socket 信息,结果发现是一个网络句柄号,真正的socket信息在内核层,没法提出来只能作罢,截图如下:
也即线程栈上的 000007e8
字段。
0a8cf26c 09ddd32f 000007e8 011a5a30 00002000 ws2_32!recv+0x95
那这个问题怎么解决呢? 通篇分析下来应该就是 scvncctrl 的 bug,能做的就是升级到最新版本,毕竟程序里还是 2020 年的。
0:005:x86> lmvm scvncctrl
Browse full module list
start end module name
09cf0000 09f06000 scvncctrl (export symbols) scvncctrl.dll
Loaded symbol image file: scvncctrl.dll
Image name: scvncctrl.dll
Browse all global symbols functions data
Timestamp: Sat Oct 10 15:14:33 2020 (5F815F59)
CheckSum: 001CA728
ImageSize: 00216000
File version: 3.9.2.0
Product version: 3.9.2.0
File flags: 0 (Mask 3F)
File OS: 4 Unknown Win32
File type: 2.0 Dll
File date: 00000000.00000000
Translations: 0409.04b0
Information from resource tables:
CompanyName: SmartCode Pte. Ltd.
ProductName: SmartCode VNC Viewer ActiveX
OriginalFilename: scvncctrl.dll
ProductVersion: 3.9.2.0
FileVersion: 3.9.2.0
FileDescription: SmartCode VNC Viewer ActiveX
LegalCopyright: Copyright (c) 2003-2020 SmartCode Pte. Ltd. All rights reserved.
Comments: https://www.s-code.com
三:总结
这次卡死事故还是挺有教育意义的,告诉我们第三方插件尽量应升尽升,同时也考察了对 临界区锁 和 socket 的基础知识。
记一次 .NET 某拍摄监控软件 卡死分析的更多相关文章
- 记一次 .NET 某药品仓储管理系统 卡死分析
一:背景 1. 讲故事 这个月初,有位朋友wx上找到我,说他的api过一段时间后,就会出现只有请求,没有响应的情况,截图如下: 从朋友的描述中看样子程序是被什么东西卡住了,这种卡死的问题解决起来相对简 ...
- 记一次 .NET 某金融企业 WPF 程序卡死分析
一:背景 1. 讲故事 前段时间遇到了一个难度比较高的 dump,经过几个小时的探索,终于给找出来了,在这里做一下整理,希望对大家有所帮助,对自己也是一个总结,好了,老规矩,上 WinDBG 说话. ...
- 记一次 .NET 某数控机床控制程序 卡死分析
一:背景 1. 讲故事 前段时间有位朋友微信上找到我,说它的程序出现了卡死,让我帮忙看下是怎么回事? 说来也奇怪,那段时间求助卡死类的dump特别多,被迫训练了一下对这类问题的洞察力 ,再次声明一下, ...
- 现在的SQLSERVER数据库监控软件有哪些?
现在的SQLSERVER数据库监控软件有哪些? 收集了一下当前SQLSERVER数据库监控软件,发现开源免费的真的是“没有” Questsoftware Quest's spotlight(收费) ...
- Android手机监控软件设计实现
一.需求分析: 随着IT信息技术的飞速发展,手机的普及,伴随着智能手机的出现及快速的更新换代,手机已不仅仅是一个通信工具,更是一个多功能的应用平台. 手机监控软件则是基于电脑监控软件的原理,植入手机平 ...
- 普及windows流氓程序和监控软件
win7下载更改后无黑屏windows7激活程序v1.0 一个立即安装 美女主播节目,和流行的色情垃圾邮件 安装程序,结果装了很多垃圾节目,输入.日历.文件等. 重新启动机器后,,会弹出广告. .他的 ...
- CEPH监控软件
概述 目前主流的Ceph开源监控软件有:Calamari.VSM.Inkscope.Ceph-Dash.Zabbix等,下面简单介绍下各个开源组件. Calamari 概述 Calamari对外提供了 ...
- 删除新版UniAccess Agent 办公室监控软件的方法
UniAccess Agent 是在由LeagSoft开发的监控软件,老版本的一般安装在C:\Program Files\LeagSoft\UniAccess Agent这个目录下,一般找到这个目录点 ...
- Greenplum-cc-web监控软件安装
一环境列表 操作系统 centos6.5 64 Greenplum版本: greenplum-db-4.3.5.3-build-2-RHEL5-x86_64.tar Greenplum集群环境搭建: ...
- 开源监控软件ganglia
开源监控软件ganglia安装手册 Ganglia是一个监控服务器,集群的开源软件,能够用曲线图表现最近一个小时,最近一天,最近一周,最近一月,最近一年的服务器或者集群的cpu负载,内存,网络,硬盘等 ...
随机推荐
- CHAT-GPT初使用
拿chatgpt去试验了一下,一个挺小的需求,但是前后还是更改了三次,体验就是它可以不断改进之前实现的代码,但需要提需求的人比较清楚需求内的细节,差不多类似于,我有想法,它来实现,还是可以提高不少效率 ...
- Elasticsearch 6.x 配置search-guard 插件
前言 es之前版本一直无用户验证功能,不过官方有提供一x-pack,但是问题是付费.在es的6.3.2版本中,已经集成了x-pack,虽然es团队已经对x-pack开源,但是在该版本中如果需要使用 ...
- @Retention元注解的使用
@Retention注解标记其他的注解用于指明标记的注解保留策略:先看Java SE 8中@Target是如何声明的: package java.lang.annotation; public enu ...
- RPA自动化如何帮助企业提高业务业务洞察力
目录 1. 引言 2. 技术原理及概念 2.1 基本概念解释 2.2 技术原理介绍 2.3 相关技术比较 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.2 核心模块实现 3.3 集成 ...
- 规则引擎 ice
目录 项目介绍 服务安装 创建数据库(MySQL) 下载安装 服务(启动.停止.重启) 打开后台 Client接入(Spring Boot) 示例 添加配置 新增 ICE liteflow 更适应我们 ...
- Nacos服务发现与注册源码剖析
为什么要看源码: 1.提升技术功底:学习源码里的优秀设计思想,比如一些疑难问题的解决思路,还有一些优秀的设计模式,整体提升自己的技术功底2.深度掌握技术框架:源码看多了,对于一个新技术或框架的掌握速度 ...
- 聊一聊Java中的Steam流
1 引言 在我们的日常编程任务中,对于集合的制造和处理是必不可少的.当我们需要对于集合进行分组或查找的操作时,需要用迭代器对于集合进行操作,而当我们需要处理的数据量很大的时候,为了提高性能,就需要使用 ...
- allure报告生成
allure生成测试报告 1.生成xml文件 pytest 测试文件所在路径 --alluredir 生成的测试结果数据保存的目录 pytest --alluredir=resport/xml/ D ...
- 【转载】AF_XDP技术详解
原文信息 作者:rexrock 出处:https://rexrock.github.io/post/af_xdp1/ 目录 1. 用户态程序 1.1 创建AF_XDP的socket 1.2 为UMEM ...
- PerfView专题 (第十三篇):洞察 .NET程序 的非托管句柄泄露
一:背景 1. 讲故事 前几天写了一篇 如何洞察 .NET程序 非托管句柄泄露 的文章,文中使用 WinDbg 的 !htrace 命令实现了句柄泄露的洞察,在文末我也说了,WinDbg 是以侵入式的 ...