一:背景

1. 讲故事

前些天有位朋友找到我,说他的软件在客户那边不知道什么原因崩掉了,从windows事件日志看崩溃在 clr 里,让我能否帮忙定位下,dump 也抓到了,既然dump有了,接下来就上 windbg 分析吧。

二:WinDbg 分析

1. 为什么崩溃在 clr

一般来说崩溃在clr里都不是什么好事情,这预示着 clr 在执行自身代码的时候抛了异常,即灾难的 ExecutionEngineException,可以用 !t 验证下。


0:000> !t
ThreadCount: 18
UnstartedThread: 0
BackgroundThread: 7
PendingThread: 0
DeadThread: 11
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 52e8 18998d50 24220 Preemptive 639B0D58:00000000 18c361f0 0 STA System.ExecutionEngineException 1f421120
...

既然是灾难性异常,那为什么会出现呢?可以用 !analyze -v 观察下。


0:000> !analyze -v
CONTEXT: 0115a98c -- (.cxr 0x115a98c)
eax=00000000 ebx=00000000 ecx=00000000 edx=18c364a4 esi=00030000 edi=18998d50
eip=552bfff1 esp=0115ae6c ebp=0115af24 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
clr!VirtualCallStubManager::ResolveWorker+0x33:
552bfff1 8bb968020000 mov edi,dword ptr [ecx+268h] ds:002b:00000268=????????
Resetting default scope READ_ADDRESS: 00000268 STACK_TEXT:
0115af24 552c0698 0115afdc 1f4222c0 00030000 clr!VirtualCallStubManager::ResolveWorker+0x33
0115affc 552c070b 0115b010 1f4222c0 00030000 clr!VSD_ResolveWorker+0x1d2
0115b024 28a3a949 639b0d38 00000000 00000000 clr!ResolveWorkerAsmStub+0x1b
0115b0a4 28a3a8bd 00000000 00000000 00000000 xxxx!xxx
...

我去,真无语了,我卦中数据看,这是一个接口Stub调用的崩溃,在这里崩溃真的是少之又少,从汇编代码 edi,dword ptr [ecx+268h] ds:002b:00000268=???????? 上看就是因为 ecx =0 导致的,接下来观察下方法的汇编代码。

从汇编上看这个 ecx 其实就是这个方法的 this 指针,那为什么 this =null 呢?这就很奇葩了。

2. 为什么 this =null

要想找到这个答案,只能看clr源代码,简化后如下:


PCODE VSD_ResolveWorker(TransitionBlock* pTransitionBlock,
TADDR siteAddrForRegisterIndirect,
size_t token
)
{
...
VirtualCallStubManager::StubKind stubKind = VirtualCallStubManager::SK_UNKNOWN;
VirtualCallStubManager* pMgr = VirtualCallStubManager::FindStubManager(callSiteTarget, &stubKind); ...
target = pMgr->ResolveWorker(&callSite, protectedObj, representativeToken, stubKind);
}

从卦中代码看,问题就是 pMgr=null 导致的,无语了,这个 VirtualCallStubManager::FindStubManager 方法的本意就是根据 callSite的stub的前缀找到对应的 虚调用管理器,它的核心逻辑如下:


StubKind getStubKind(PCODE stubStartAddress, BOOL usePredictStubKind = TRUE)
{
StubKind predictedKind = (usePredictStubKind) ? predictStubKind(stubStartAddress) : SK_UNKNOWN;
...
if (predictedKind == SK_LOOKUP)
{
if (isLookupStub(stubStartAddress))
return SK_LOOKUP;
}
...
return SK_UNKNOWN;
} VirtualCallStubManager::StubKind VirtualCallStubManager::predictStubKind(TADDR stubStartAddress)
{
StubKind stubKind = SK_UNKNOWN; WORD firstWord = *((WORD*)stubStartAddress); if (firstWord == 0x05ff)
{
stubKind = SK_DISPATCH;
}
else if (firstWord == 0x6850)
{
stubKind = SK_LOOKUP;
}
else if (firstWord == 0x8b50)
{
stubKind = SK_RESOLVE;
} return stubKind;
}

接下来需要找到 stubStartAddress 的地址是多少?这个只需要提取 ResolveWorker 方法的第一个参数 callSite 即可。


0:000> dp poi(0115afdc) L1
0c740040 0c746012 0:000> u 0c746012
0c746012 50 push eax
0c746013 6800000300 push 30000h
0c746018 e9d3a6b748 jmp clr!ResolveWorkerAsmStub (552c06f0)
0c74601d 0000 add byte ptr [eax],al
0c74601f 0000 add byte ptr [eax],al
0c746021 005068 add byte ptr [eax+68h],dl
0c746024 0000 add byte ptr [eax],al
0c746026 46 inc esi 0:000> dp 0c746012 L1
0c746012 00006850

对比刚才的代码既然都返回来了 SK_LOOKUP 那为什么还是 SK_UNKNOWN 呢? 这个也可以通过在线程栈上找到 &stubKind 变量得到验证。

0:000> uf 552c0698
...
clr!VSD_ResolveWorker+0x1ab:
552c065f 8b85e0ffffff mov eax,dword ptr [ebp-20h]
552c0665 83a5ecffffff00 and dword ptr [ebp-14h],0
552c066c 8d95ecffffff lea edx,[ebp-14h]
552c0672 8b08 mov ecx,dword ptr [eax]
552c0674 e858feffff call clr!VirtualCallStubManager::FindStubManager (552c04d1)
552c0679 ffb5ecffffff push dword ptr [ebp-14h]
552c067f 51 push ecx
552c0680 8bcc mov ecx,esp
552c0682 8931 mov dword ptr [ecx],esi
552c0684 ffb5e8ffffff push dword ptr [ebp-18h]
552c068a 8d8de0ffffff lea ecx,[ebp-20h]
552c0690 51 push ecx
552c0691 8bc8 mov ecx,eax
552c0693 e823f9ffff call clr!VirtualCallStubManager::ResolveWorker (552bffbb)
552c0698 8bf0 mov esi,eax
... 0:000> dp 0115affc-0x14 L1
0115afe8 00000000

我感觉这逻辑也只有clr团队帮忙解释,我已经搞不清楚了,接下来我们回头看托管方法,看能不能继续下去。

3. 在托管层寻找突破口

高级调试就是这样,一个方向走不通就需要在另一个方向上突破,接下来使用 !clrstack 观察一下。


0:000> !clrstack
OS Thread Id: 0x52e8 (0)
Child SP IP Call Site
0115af50 775c2aac [GCFrame: 0115af50]
0115afac 775c2aac [StubDispatchFrame: 0115afac]xxx.GetListDrawerType(System.String)
0115b02c 28a3a949 xxx.PluginInvoker.InvokeMothod[[System.__Canon, mscorlib]](System.String, System.Object[])
0115b0b0 28a3a8bd xxx.xxx.OnFinishSizeCheck(Int64)
...

从调用栈来看,貌似是用反射来实现功能增强,不管怎么说先看下xxxCheck 方法干了什么?简化后的代码如下:


public string OnFinishSizeCheck(long uuid)
{
return PluginInvoker.InvokeMothod<string>("xxxCheck", new object[1] { uuid });
} public static T InvokeMothod<T>(string methodName, params object[] args)
{
IPluginInvoker pluginInvoker = GetPluginInvoker(); return (T)pluginInvoker.InvokeMothod(methodName, args);
}

从代码上可以看到原来是使用 (T)pluginInvoker.InvokeMothod(methodName, args); 实现的接口调用,在coreclr层面也能观察得到,找到对象 1f4222c0 之后按图索骥即可。


0:000> !do 1f4222c0
Name: xxx.xxx.BusinessAppDomainInvoker
MethodTable: 0c73a144
EEClass: 0c6d6f0c
Size: 12(0xc) bytes
File: E:\xxx\xxx.dll
Fields:
MT Field Offset Type VT Attr Value Name
0c73a4e8 400000a 4 ....AppDomainManager 0 instance 1f42236c appDomainManager
0c73a2dc 4000009 18 ..., xxx]] 0 static 1f422214 lazy 0:000> !dumpmt -md 0c73a144
EEClass: 0c6d6f0c
Module: 0c7383dc
Name: xxx.xxx.BusinessAppDomainInvoker
mdToken: 02000006
File: E:\xxx\xxx.dll
BaseSize: 0xc
ComponentSize: 0x0
Slots in VTable: 10
Number of IFaces in IFaceMap: 1
--------------------------------------
MethodDesc Table
Entry MethodDe JIT Name
...
0c6c3400 0c73a110 JIT xxx.xxx.InvokeMothod(System.String, System.Object[]) 0:000> !do poi(0c73a144+0x24)
Name: xxx.IPluginInvoker
MethodTable: 0c739f30
EEClass: 0c6d6d34
Size: 0(0x0) bytes
File: E:\xxx\xxx.dll
Fields:
None
ThinLock owner 1 (18998d50), Recursive 0

对比那个 token=30000h 发现什么地方都没有问题,奇葩的就是一个简单接口调用就出现了问题,仔细观察代码之后发现了两个和别人不一样的地方。

4. 与众不同的地方在哪里

第一个是他的程序是多 AppDomain 的,可以用 !dumpdomain 观察。


0:000> !dumpdomain
--------------------------------------
System Domain: 55a6caa0
...
--------------------------------------
Shared Domain: 55a6c750
LowFrequencyHeap: 55a6cdc4
Stage: OPEN
--------------------------------------
Domain 1: 18b04690
LowFrequencyHeap: 18b04afc
Name: DefaultDomain
--------------------------------------
Domain 2: 18c361f0
LowFrequencyHeap: 18c3665c
...

第二个是我发现托管调用栈上还有很多 托管C++,这种混合编程真的是无语了。

到这里我想到了三个办法:

1)如果可以先把接口方法预热,clr会直接把方法入口塞到汇编里,就不会再走clr底层逻辑了。

2)能否将 托管C++ 和 C# 隔离,不要混合编程。

3)重点观察下多Domain下这个托管调用是不是有什么问题。

三:总结

这种 多domain + 托管C++混合C# 编程,真出问题了基本上就是无解,一般人hold不住,无语了。

记一次 .NET某工业设计软件 崩溃分析的更多相关文章

  1. 记一次 .NET 某自动化集采软件 崩溃分析

    一:背景 1.讲故事 前段时间有位朋友找到我,说他的程序在客户的机器上跑着跑着会出现偶发卡死,然后就崩掉了,但在本地怎么也没复现,dump也抓到了,让我帮忙看下到底怎么回事,其实崩溃类的dump也有简 ...

  2. 记一次 .NET 某医疗器械 程序崩溃分析

    一:背景 1.讲故事 前段时间有位朋友在微信上找到我,说他的程序偶发性崩溃,让我帮忙看下怎么回事,上面给的压力比较大,对于这种偶发性崩溃,比较好的办法就是利用 AEDebug 在程序崩溃的时候自动抽一 ...

  3. 记一次 .NET 某医疗住院系统 崩溃分析

    一:背景 1. 讲故事 最近收到了两起程序崩溃的dump,查了下都是经典的 double free 造成的,蛮有意思,这里就抽一篇出来分享一下经验供后面的学习者避坑吧. 二:WinDbg 分析 1. ...

  4. 【知名的3D造型设计软件】犀牛 Rhinoceros 5.5.2 for Mac

    [简介] 今天和大家分享最新的3D设计软件 犀牛 Rhinoceros for Mac 5.5.2 版本,支持中文界面,这是一款Mac上知名的3D造型软件,犀牛可以广泛地应用于三维动画制作.工业制造. ...

  5. mac设计师系列 Adobe “全家桶” 15款设计软件 值得收藏!

    文章素材来源:风云社区.简书 文章收录于:风云社区 www.scoee.com,提供1700多款mac软件下载 Adobe Creative Cloud 全线产品均可开放下载(简称Adobe CC 全 ...

  6. 读<<领域驱动设计-软件核心复杂性应对之道>>有感

    道可道,非常道. 名可名,非常名. 无名天地之始,有名万物之母. ---老子 关于标题 好久没写东西了,动笔的动机是看完了一本书,想写点总结性的东西,一是为了回顾一下梳理知识点,二是为了日后遗忘时能有 ...

  7. 免费的ER 设计软件调研

    目标: 找到一个免费的ER 设计软件, 适合数据仓库项目开发. 结果: 经初步调研, Oracle的 SQL Developer Data Modeler基本满足需求. 但在功能和操作性等方面, 较P ...

  8. 打造属于自己的Altium Designer 3D封装库,不需要懂专门的三维设计软件

    看到Andy_2020发的帖子“Altium Designer专题”之后,对Altium Designer的3D功能很感兴趣,着手自己做一个AD的3D封装库.刚开始按照Andy介绍的方法,学了两天So ...

  9. 中国澳门sinox很多平台CAD制图、PCB电路板、IC我知道了、HDL硬件描述语言叙述、电路仿真和设计软件,元素分析表

    中国澳门sinox很多平台CAD制图.PCB电路板.IC我知道了.HDL硬件描述语言叙述.电路仿真和设计软件,元素分析表,可打开眼世界. 最近的研究sinox执行windows版protel,powe ...

  10. 【优秀的艺术文字和图标设计软件】Art Text 3.2.3 for Mac

      [简介] Art Text 3.2.3 版本,这是一款Mac上简单易用的艺术文字和图标设计软件,今这款软件内置了大量的背景纹理和特效,能够让我们非常快速的制作出漂亮的图标,相比专业的PS,Art ...

随机推荐

  1. SQLite总结

    废话不多说: 优点,高并发读快速度读取超越所有主流大中型数据库 缺点,缺少同步机制,读写不能同时,且同时只能有一个写入线程 用途,硬盘式缓存 另附一SQLite工具类: import java.io. ...

  2. js 连接数据库 提示:ActiveXObject is not defined

    ActiveXObject is not defined 最近比较闲,上班瞎捣鼓一下,没想到报错了,提示ActiveXObject is not defined 大概是在js连接数据库时new对象使用 ...

  3. 牛客网-SQL专项训练23

    ①假设创建新用户nkw,现在想对于任何IP的连接,仅拥有user数据库里面的select和insert权限,则列表语句中能够实现这一要求的语句是(B) 解析: 考察知识点-数据库授权命令: GRANT ...

  4. 云服务器ECS共享标准型S6全新发布,行业内最具性价比

    近日,阿里云弹性计算发布全新一代云服务ECS共享标准型S6,性能相对上一代实例提升15%以上,价格相对上一代最高降低42%,是目前国内云计算厂商更能够提供的最具性价比的云服务器产品.一些中小型网站.轻 ...

  5. 高效使用Java构建工具|Maven篇|云效工程师指北

    ​简介:高效使用Java构建工具|Maven篇.众所周知,当前最主流的Java构建工具为Maven/Gradle/Bazel,针对每一个工具,我将分别从日常工作中常见的场景问题切入,例如依赖管理.构建 ...

  6. 基于容器服务 ACK 发行版打造 CNStack 社区版

    ​简介:本文将介绍如何使用 ACK Distro 作为基础镜像打造 CNStack 社区版以及CNStack 社区版中的容器服务 ACK 敏捷版产品如何帮助用户更好的使用容器平台能力. 作者:临石 C ...

  7. 如何构建企业出海的"免疫力"?深入解读阿里云CDN安全能力

    简介: 随着信息技术快速发展与应用,产业数字化和智能化趋势正日益加深,企业信息安全与防护被提升到前所有未有的高度.阿里云CDN经过10多年的技术发展时间,已逐步构筑一个边缘+云的安全网络立体防护体系, ...

  8. 重磅 | 数据库自治服务DAS论文入选全球顶会SIGMOD,领航“数据库自动驾驶”新时代

    简介: 近日,智能数据库和DAS团队研发的智能调参ResTune系统论文被SIGMOD 2021录用,SIGMOD是数据库三大顶会之首,是三大顶会中唯一一个Double Blind Review的,其 ...

  9. [FAQ] Edge/Chrome 网络请求的编辑并重发

    1. 在网络请求上面右键,复制为fetch. 2. 切换到Console控制台,粘贴并回车. fetch 是javascript中一个网络请求的函数或者工具,Chrome在我们 Copy as fet ...

  10. 修复 GitLab 的 CI Runner 提示找不到 pwsh 执行文件

    本文告诉大家如何修复使用 GitLab 的 Runner 做 CI 时提示 "pwsh": executable file not found in %PATH% 错误 有两个方法 ...