当前位置: 首页 > news >正文

记一次 .NET某工业设计软件 崩溃分析

一:背景

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:   noLock  ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception0    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 scopeREAD_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  0c7460120: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     esi0: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 lazy0: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 TableEntry 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某工业设计软件 崩溃分析

一&#xff1a;背景 1. 讲故事 前些天有位朋友找到我&#xff0c;说他的软件在客户那边不知道什么原因崩掉了&#xff0c;从windows事件日志看崩溃在 clr 里&#xff0c;让我能否帮忙定位下&#xff0c;dump 也抓到了&#xff0c;既然dump有了&#xff0c;接下来就上 windbg …...

2020 6.s081——Lab5:Lazy page allocation

再来是千年的千年 不变是眷恋的眷恋 飞越宇宙无极限 我们永不说再见 ——超兽武装 完整代码见&#xff1a;SnowLegend-star/6.s081 at lazy (github.com) Eliminate allocation from sbrk() (easy) 顾名思义&#xff0c;就是去掉sbrk()中调用growproc()的部分。1s完事儿。 Laz…...

华为认证学习笔记:生成树

以太网交换网络中为了进行链路备份&#xff0c;提高网络可靠性&#xff0c;通常会使用冗余链路。但是使用冗余链路会在交换网络上产生环路&#xff0c;引发广播风暴以及MAC地址表不稳定等故障现象&#xff0c;从而导致用户通信质量较差&#xff0c;甚至通信中断。为解决交换网络…...

leetcode 97.交错字符串

思路&#xff1a;LCS 其实也是同一个类型的题目&#xff0c;一般涉及到这种子序列的字符串问题的时候&#xff0c;状态的设置基本上都应该是以...结尾为状态的。这里同样&#xff0c;设置用dp[i][j]为s1,s2字符以i&#xff0c;j结尾能否拼接成s3[ij]。 那么&#xff0c;首先就…...

The Missing Semester ( Shell 工具和脚本 和 Vim)

管道符号 &#xff08;1&#xff09;管道符号 | 将前一个命令的输出作为下一个命令的输入 例如&#xff1a; 以下为 ./semester输出中提取包含 "Last-Modified" 的行并写入文件 last-modified.txt./semester | grep "Last-Modified" > ~/last-modif…...

【Uniapp微信小程序】自定义水印相机、微信小程序地点打卡相机

效果图 template 下方的image图片自行寻找替换&#xff01; <template><view><camerav-if"!tempImagePath && cameraHeight ! 0":resolution"high":frame-size"large":device-position"device":flash"f…...

SimPO: Simple Preference Optimization with a Reference-Free Reward

https://github.com/princeton-nlp/SimPO 简单代码 class simpo(paddle.nn.Layer):def __init__(self):super(OrPoLoss, self).__init__()self.loss paddle.nn.CrossEntropyLoss()def forward(self,neg_logit, neg_lab, pos_logit, pos_lab,beta,gamma):neg_logit paddle.n…...

CDH6.3.2安装文档

前置环境&#xff1a; 操作系统&#xff1a; CentOS Linux release 7.7 java JDK &#xff1a; 1.8.0_231 1、准备工作 准备以下安装包&#xff1a; Cloudera Manager: cloudera-manager-agent-6.3.1-1466458.el7.x86_64.rpm cloudera-manager-daemons-6.3.1-1466458.el…...

Java实战入门:深入解析Java中的 `Arrays.sort()` 方法

文章目录 一、方法定义参数说明返回值 二、使用场景三、实现原理四、示例代码示例一&#xff1a;对整型数组排序示例二&#xff1a;对字符串数组排序示例三&#xff1a;对自定义对象数组排序 五、注意事项六、总结 在Java编程中&#xff0c;Arrays.sort() 方法是一个非常常用的…...

JavaScript的垃圾回收机制

No.内容链接1Openlayers 【入门教程】 - 【源代码示例300】 2Leaflet 【入门教程】 - 【源代码图文示例 150】 3Cesium 【入门教程】 - 【源代码图文示例200】 4MapboxGL【入门教程】 - 【源代码图文示例150】 5前端就业宝典 【面试题详细答案 1000】 文章目录 一、垃圾…...

小程序使用Canvas设置文字竖向排列

在需要使用的js页面引入js文件,传入对应参数即可 /** * 文本竖向排列 */ function drawTextVertical(context, text, x, y) {var arrText text.split();var arrWidth arrText.map(function (letter) {return 26; // 字体间距,需要自定义可以自己加参数,根据传入参数进行…...

GPT-4o:重塑人机交互的未来

一个愿意伫立在巨人肩膀上的农民...... 一、推出 在人工智能&#xff08;AI&#xff09;领域&#xff0c;自然语言处理&#xff08;NLP&#xff09;技术一直被视为连接人类与机器的桥梁。近年来&#xff0c;随着深度学习技术的快速发展&#xff0c;NLP领域迎来了前所未有的变革…...

大语言模型拆解——Tokenizer

1. 认识Tokenizer 1.1 为什么要有tokenizer&#xff1f; 计算机是无法理解人类语言的&#xff0c;它只会进行0和1的二进制计算。但是呢&#xff0c;大语言模型就是通过二进制计算&#xff0c;让你感觉计算机理解了人类语言。 举个例子&#xff1a;单1&#xff0c;双2&#x…...

Linux自动挂载服务autofs讲解

1.产生原因 2.配置文件讲解 总结&#xff1a;配置客户端&#xff0c;先构思好要挂载的目录如&#xff1a;/abc/cb 然后在autofs.master中编辑&#xff1a; /abc&#xff08;要挂载的主目录&#xff09; /etc/qwe&#xff08;在这个文件里去找要挂载的副目录&#xff0c;这个名…...

堆结构知识点复习——玩转堆结构

前言:堆算是一种相对简单的数据结构&#xff0c; 本篇文章将详细的讲解堆中的知识点&#xff0c; 包括那些我们第一次学习堆的时候容易忽略的内容&#xff0c; 本篇文章会作为重点详细提到。 本篇内容适合已经学完C语言数组和函数部分的友友们观看。 目录 什么是堆 建堆算法…...

JS数据类型运算符标准库

目录 数据类型运算符标准库对象Object对象属性描述对象Array对象包装对象Boolean对象Number对象String对象Math对象Date对象...

单片机之从C语言基础到专家编程 - 4 C语言基础 - 4.13数组

C语言中&#xff0c;有一类数据结构&#xff0c;它可以存储一组相同类型的元素&#xff0c;并且可以通过索引访问这些元素&#xff0c;没错&#xff0c;这类数据结构就是数组。数组可以说是C语言中非常重要的数据结构之一了。使用数组可以是程序逻辑更加清晰&#xff0c;也更加…...

【码银送书第二十期】《游戏运营与出海实战:策略、方法与技巧》

市面上的游戏品种繁杂&#xff0c;琳琅满目&#xff0c;它们是如何在历史的长河中逐步演变成今天的模式的呢&#xff1f;接下来&#xff0c;我们先回顾游戏的发展史&#xff0c;然后按照时间轴来叙述游戏运营的兴起。 作者&#xff1a;艾小米 本文经机械工业出版社授权转载&a…...

String 类

目录&#xff1a; 一. 认识 String 类 二. String 类的基本用法 三. String对象的比较 四.字符串的不可变性 五. 认识 StringBuffer 和 StringBuilder 一. 认识 String 类&#xff1a; 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能使用字符数组或者…...

Chromebook Plus中添加了Gemini?

Chromebook Plus中添加了Gemini&#xff1f; 前言 就在5月29日&#xff0c;谷歌宣布了一项重大更新&#xff0c;将其Gemini人工智能技术集成到Chromebook Plus笔记本电脑中。这项技术此前已应用于谷歌的其他设备。华硕和惠普已经在市场上销售的Chromebook Plus机型&#xff0c;…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...

elementUI点击浏览table所选行数据查看文档

项目场景&#xff1a; table按照要求特定的数据变成按钮可以点击 解决方案&#xff1a; <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

【C++】纯虚函数类外可以写实现吗?

1. 答案 先说答案&#xff0c;可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...

上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式

简介 在我的 QT/C 开发工作中&#xff0c;合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式&#xff1a;工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...