记一次 .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 的基础知识。

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/141705.html<

(0)
运维的头像运维
上一篇2025-03-04 12:57
下一篇 2025-03-04 12:58

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注