一:背景

1.讲故事

前段时间有位朋友找到我,说他的程序在客户的机器上跑着跑着会出现偶发卡死,然后就崩掉了,但在本地怎么也没复现,dump也抓到了,让我帮忙看下到底怎么回事,其实崩溃类的dump也有简单的,也有非常复杂的,因为大多情况下都是非托管层面出现的各种故障,非常考验对 C, C++, Win32 API 以及 汇编 的理解,所以能不能解决看运气吧, 不管怎么说,先上 WinDbg。

二:WinDbg分析

1. 查找崩溃点

WinDbg 非常牛的地方在于它拥有一个自动化崩溃分析命令 !analyze -v,它的输出信息非常有参考价值,所以尝试一下看看。


0:136> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
CONTEXT: (.ecxr)
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=00000003 edi=00000003
eip=777cf04c esp=22dfd678 ebp=22dfd808 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
ntdll!NtWaitForMultipleObjects+0xc:
777cf04c c21400 ret 14h
Resetting default scope EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 0174ba6d
ExceptionCode: 00000000
ExceptionFlags: 00000000
NumberParameters: 0 PROCESS_NAME: xxx.exe STACK_TEXT:
22dfd808 75b23b10 00000003 22dfdc68 00000001 ntdll!NtWaitForMultipleObjects+0xc
22dfd808 75b23a08 00000003 22dfdc68 00000000 KERNELBASE!WaitForMultipleObjectsEx+0xf0
22dfd824 672ff11a 00000003 22dfdc68 00000000 KERNELBASE!WaitForMultipleObjects+0x18
22dfdca4 672ff3ac 672dd150 672d0000 00000003 Faultrep!WerpReportFaultInternal+0x59e
22dfdcc4 672dd17d 22dfdcec 708d0479 22dfdd60 Faultrep!WerpReportFault+0x9e
22dfdccc 708d0479 22dfdd60 00000000 22dfdd60 Faultrep!ReportFault+0x2d
22dfdcec 708d07e9 ec030e28 1c8f7728 00000003 clr!DoReportFault+0x43
22dfdd44 709f3c7e 00000003 22dfe140 2e954594 clr!WatsonLastChance+0x19a
22dfe090 709f3d90 ec0333f0 22dfe140 2e954594 clr!DoWatsonForUserBreak+0xc2
22dfe120 6fdc690f 00000000 00000000 00000000 clr!DebugDebugger::Break+0xc9
22dfe148 0174ba6d 00000000 00000000 00000000 mscorlib_ni!System.Diagnostics.Debugger.Break+0x57
WARNING: Frame IP not in any known module. Following frames may be wrong.
22dfe194 0174b58b 00000000 00000000 00000000 0x174ba6d
22dfe1e8 0174b525 00000000 00000000 00000000 mscorlib_ni!System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start<<xxxAsync>d__10>+0x43
22dfe1e8 0174b525 00000000 00000000 00000000 mscorlib_ni!System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start<<xxxAsync>d__10>+0x43
22dfe22c 0174b3bd 00000000 00000000 00000000 0x174b525
22dfe27c 0174b33b 00000000 00000000 00000000 0x174b3bd
22dfe2d0 0174b2d5 00000000 00000000 00000000 0x174b33b
... SYMBOL_NAME: faultrep!WerpReportFaultInternal+59e MODULE_NAME: Faultrep IMAGE_NAME: Faultrep.dll STACK_COMMAND: ~136s; .ecxr ; kb ...

从卦中的调用栈看,有二个非常重要的信息。

  1. Debugger.Break()

这个是 C# 对 int 3 的封装,即 断点中断异常,目的就是将程序的所有线程中断。

  1. Faultrep!ReportFault()

这个是 WER 2.0 ,全称为 Windows Error Reporting Service,用来抓崩溃dump的,前身是 Waston 医生,在 Windows 服务列表中可以看到。

还有一点, Faultrep.dll 是 WER 的一个组件,会在抓取过程中自动加载,我们用 lm 观察进程中的 dll 列表。


0:136> lm
start end module name
00fe0000 01034000 xxx C (service symbols: CLR Symbols without PDB)
0c100000 0c123000 WINMMBASE (deferred)
662d0000 662ef000 clrcompression (deferred)
672d0000 67327000 Faultrep (pdb symbols) c:\mysymbols\FaultRep.pdb\E16126C7FB9849A8B9AC57D8D62CABB01\FaultRep.pdb
...

汇总以上信息,大概就能推测出代码中用了 Debugger.Break() 函数,因为无catch处理,Windows 启动了 WER 2.0,程序代码在 ntdll!NtWaitForMultipleObjects 处等待第三方组件处理完毕,因为各种原因出现了问题导致无法返回最后崩溃。

通过卦中的信息我们大概知道了前因后果,但代码中为什么会出现 Debugger.Break() 呢?这就需要我们继续深挖。

2. 为什么会有 Debugger.Break()

刚才的输出中有这么一段话: STACK_COMMAND: ~136s; .ecxr ; kb ,它可以让我们找到异常前的调用栈,为了能看到托管栈,这里将 kb 改成 !clrstack


0:136> ~136s; .ecxr ; !clrstack OS Thread Id: 0x13ec (136)
Child SP IP Call Site
22dfe0ac 777cf04c [HelperMethodFrame: 22dfe0ac] System.Diagnostics.Debugger.BreakInternal()
22dfe128 6fdc690f System.Diagnostics.Debugger.Break() [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 65]
22dfe150 0174ba6d xxx.xxx+d__10.MoveNext()
22dfe19c 0174b58b System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[[xxx.xxx+d__10, xxx.Abstractions]](d__10 ByRef) [f:\dd\ndp\clr\src\BCL\system\runtime\compilerservices\AsyncMethodBuilder.cs @ 316]
22dfe1f0 0174b525 xxx.xxxAsync(System.String, System.String)
22dfe238 0174b3bd xxx.xxxProducer+d__7.MoveNext()
22dfe284 0174b33b System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[[xxx.xxx+d__7, xxx.Abstractions]](d__7 ByRef)
22dfe2d8 0174b2d5 xxx.xxx.xxx(System.String, System.String)

从卦中看,貌似是在一个异步方法中手工调用了 Deubgger.Break() 方法,接下来我们观察下源码,由于比较隐私,这里就简化一下。


internal async Task xxxAsync(string x1, string x2)
{
if (string.IsNullOrEmpty(x1))
{
Debugger.Break();
return;
}
if (string.IsNullOrEmpty(x2))
{
Debugger.Break();
return;
}
...
}

这代码果然有意思,在防御性编程中居然用 Debugger.Break() 来处理,比较少见。

找到了问题源头,解决方法就简单了,大概有两种做法。

  1. 去掉 Debugger.Break() 语句

  2. 关闭 WER 2.0 服务

3. 对 Debugger.Break() 的题外话

在 clr 源码中有对 Debugger.Break() 非常详细的注释。


// This does a user break, triggered by System.Diagnostics.Debugger.Break, or the IL opcode for break.
//
// Notes:
// If a managed debugger is attached, this should send the managed UserBreak event.
// Else if a native debugger is attached, this should send a native break event (kernel32!DebugBreak)
// Else, this should invoke Watson.
//
// Historical trivia:
// - In whidbey, this would still invoke Watson if a native-only debugger is attached.
// - In arrowhead, the managed debugging pipeline switched to be built on the native pipeline.
FCIMPL0(void, DebugDebugger::Break)
{
...
}
FCIMPLEND

注释文本: Else, this should invoke Watson 中的 Watson 其实就是本篇聊到的 WER,观察反汇编其实就是对 int 3 的封装。


0:136> uf kernelBase!DebugBreak
KERNELBASE!DebugBreak:
75ba5e40 8bff mov edi,edi
75ba5e42 cc int 3
75ba5e43 c3 ret

在很多反调试机制中,经常会用 int 3 来检测当前程序是否被附加了调试器,参考如下 C++ 代码。


#include <iostream> int isAttach = 0; int main()
{
__try
{
__asm {
int 3
} isAttach = 1;
}
__except(1)
{
isAttach = 0;
} if (isAttach) {
printf("不好,发现有调试器 ...");
}
else {
printf("哈哈,一切正常!");
} getchar();
}

如果你用 WinDbg 附加上去, 就会被程序检测到,截图如下:

如果是正常运行,会是如下界面

可以在 C# 中通过 Pinvoke 引入,这种动态方式,反反调试会有不小的难度。

三:总结

这次事故是朋友在开发过程中为了方便调试,使用了 Debugger.Break() 方法,但在生产环境下并没有删除,导致在某些客户机器上因为 WER 的开启,被 Waston 捕获导致的事故。

本次教训是:发给客户的版本,内含的调试信息该关闭的一定要关闭,以免生出此乱。

记一次 .NET 某自动化集采软件 崩溃分析的更多相关文章

  1. 记一次 .NET 某教育系统API 异常崩溃分析

    一:背景 1. 讲故事 这篇文章起源于 搬砖队大佬 的精彩文章 WinDBg定位asp.net mvc项目异常崩溃源码位置 ,写的非常好,不过美中不足的是通览全文之后,总觉得有那么一点不过瘾,就是没有 ...

  2. “深度评测官”——记2020BUAA软工软件案例分析作业

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任建) 这个作业的要求在哪里 个人博客作业-软件案例分析 我在这个课程的目标是 完成一次完整的软件开发经历并以博客的方式记录开发 ...

  3. centos环境自动化批量安装软件脚本

    自动化安装jdk软件部署脚本 准备工作: 1.在执行脚本的服务器上生成免密码公钥: 安装expect命令 yum install -y expect ssh-keygen 三次回车 2.将jdk-7u ...

  4. Deinstall卸载RAC之Oracle软件及数据库+GI集群软件

    Deinstall卸载Oracle软件及数据库+GI集群软件 1. 本篇文档应用场景: 需要安装新的ORACLE RAC产品,系统没有重装,需要对原环境中的RAC进行卸载: #本篇文档,在AIX 6. ...

  5. Storm集群启动流程分析

    Storm集群启动流程分析 程序员 1.客户端运行storm nimbus时,会调用storm的python脚本,该脚本中为每个命令编写了一个方法,每个方法都可以生成一条相应的Java命令. 命令格式 ...

  6. 个人作业2——集大通APP案例分析

    个人作业2——集大通APP案例分析 产品:集大通 我认为这个是我们学校的APP,我们应该支持一下. 一.个人体验 1.下载并使用,描述最简单直观的个人第一次上手体验. ①界面美观,可以感受到丰富的校园 ...

  7. 记一次ZOOKEEPER集群超时问题分析

    CDH安装的ZK,三个节点,基本都是默认配置,一直用得正常,今天出现问题,客户端连接超时6倍时长,默认最大会话超时时间是一分钟.原因分析:1.首先要确认网络正确.确认时钟同步.2.查看现有的配置,基本 ...

  8. 记我的第二次自动化尝试——selenium+pageobject+pagefactory实现自动化下单、退款、撤销回归测试

    需求: 系统需要做下单.退款.撤销的回归测试,有下单页面,所以就想到用selenium做WEB UI 自动化 项目目录结构: common包上放通用的工具类方法和浏览器操作方法 pageobject包 ...

  9. 记一次zookeeper单机伪集群分布

    zookeeper的各版本(历史版本)下载地址:http://apache.org/dist/zookeeper/ 环境>:linux 下载的zookeeper解压成3个

  10. ElasticSearch集群故障案例分析: 警惕通配符查询

    最近ElasticSearch集群出现了 https://elasticsearch.cn/article/171 文章中描述的情况,现在转载全文警示下自己. 许多有RDBMS/SQL背景的开发者,在 ...

随机推荐

  1. 阿色全息脑图,及制作软件AHMM

    阿色全息脑图 AHMM 全息脑图是按照大系统观原理开发的新型思维工具,用于升维思考. 让您以系统的观点看待世界,专注系统的结构信息--全息,抓住事物的本质,透过表象和数据发现规律. 世间每项事物都是一 ...

  2. Java基础(标识符,数据类型,数据转换,变量)

    注释 Java中的注释有3种: 单行注释 // 多行注释 /**/ 文档注释 /***/ 注释不会被执行,是给我们写代码的人看的 书写注释是一个非常好的习惯 标识符 Java所有的组成部分都需要名字, ...

  3. 路径参数和数值校验: Path_Parameters_and_Numeric_Validations

    官方文档地址: https://fastapi.tiangolo.com/zh/tutorial/path-params-numeric-validations/ # -*- coding: UTF- ...

  4. 第六章:Django 综合篇 - 7:网站地图sitemap

    网站地图是根据网站的结构.框架.内容,生成的导航网页,是一个网站所有链接的容器.很多网站的连接层次比较深,蜘蛛很难抓取到,网站地图可以方便搜索引擎或者网络蜘蛛抓取网站页面,了解网站的架构,为网络蜘蛛指 ...

  5. mysql8数据库修改root密码,以及创建用户遇到的坑,开启远程登录,用navicat进行mysql的远程连接,mysql8.0默认编码方式,部分参数配置查询命令

    yum 安装MySQL8 echo "删除系统默认或之前可能安装的其他版本的 mysql" for i in $(rpm -qa|grep mysql);do rpm -e $i ...

  6. 使用docker-compose方式部署es和kibana以及cerebro

    使用的镜像可以从这个网站查看最新的:https://hub.docker.com/ 参考极客时间上的教程转发来的 使用步骤:安装docker和docker-compose 运行: docker-com ...

  7. ERP系统都能给企业带来什么好处?

    ERP系统但如果用得好,自然可以提高企业内部资源的计划和控制能力,提质增效降成本,提升企业竞争力,加速数字化转型步伐,但不是所有的企业使用ERP都能带来好处的,尤其是对于一些小微企业,带来的可能是灾难 ...

  8. Node.js(二)express

    npm init -y(初始化项目) npm install express(引入express) npx express-generator -e(自动生成模板.添加对 ejs 模板引擎的支持) a ...

  9. 内存映射IO(MMIO)

    端口I/O 介绍: 一种I/O编址方式是端口映射I/O(port-mapped I/O), CPU使用专门的I/O指令对设备进行访问, 并把设备的地址称作端口号. 在执行其中的一条指令时,CPU使用地 ...

  10. uoj221【NOI2016】循环之美

    前面部分比较简单,就是无脑化式子,简单点讲好了. 首先肯定在\((x,y)=1\)时才考虑这个分数,要求纯循环的话,不妨猜猜结论,就是y必须和K互质.所以答案是\(\sum_{i=1}^n \sum_ ...