一:背景

1. 讲故事

昨晚给训练营里面的一位朋友分析了一个程序崩溃的故障,因为看小伙子昨天在群里问了一天也没搞定,干脆自己亲自上阵吧,抓取的dump也是我极力推荐的用 procdump 注册 AEDebug 的方式,省去了很多沟通成本。

二:WinDbg分析

1. 为什么会崩溃

windbg有一个非常强大的点就是当你双击打开后,会自动帮你切换到崩溃的线程以及崩溃处的汇编代码,省去了 !analyze -v 命令的龟速输出,参考信息如下:


................................................................
...................................................
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(10f4.f58): Access violation - code c0000005 (first/second chance not available)
For analysis of this file, run !analyze -v
eax=00000000 ebx=00000000 ecx=00000040 edx=00000000 esi=004c1b98 edi=07a8ed4c
eip=7008508f esp=07a8ec74 ebp=07a8ec80 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!Thread::GetSafelyRedirectableThreadContext+0x7c:
7008508f 8038eb cmp byte ptr [eax],0EBh ds:002b:00000000=??
...

从卦中可以看到,当前崩溃是因为 eax=0 导致的,那为什么 eax 等于 0 呢?要想寻找这个答案,需要观察崩溃前的线程栈上下文,可以使用命令 .ecxr;k 9 即可。


0:009> .ecxr;k 9
eax=00000000 ebx=00000000 ecx=00000040 edx=00000000 esi=004c1b98 edi=07a8ed4c
eip=7008508f esp=07a8ec74 ebp=07a8ec80 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!Thread::GetSafelyRedirectableThreadContext+0x7c:
7008508f 8038eb cmp byte ptr [eax],0EBh ds:002b:00000000=??
# ChildEBP RetAddr
00 07a8ec80 6fe7f6cd clr!Thread::GetSafelyRedirectableThreadContext+0x7c
01 07a8f030 6fe7f2f3 clr!Thread::HandledJITCase+0x31
02 07a8f0a4 6fee23da clr!Thread::SuspendRuntime+0x260
03 07a8f184 6fedf72d clr!WKS::GCHeap::SuspendEE+0x1fe
04 07a8f1b0 6fe309ca clr!WKS::GCHeap::GarbageCollectGeneration+0x168
05 07a8f1c0 6fe30a2e clr!WKS::GCHeap::GarbageCollectTry+0x56
06 07a8f1e4 6fe30a90 clr!WKS::GCHeap::GarbageCollect+0xa5
07 07a8f230 6f058b01 clr!GCInterface::Collect+0x5d
08 07a8f26c 055fa4b1 mscorlib_ni+0x3b8b01

从卦中信息看,尼玛,真无语了 GCInterface::Collect 说明有人用 GC.Collect() 手工触发GC,不知道为什么要这么做来污染GC内部的统计信息,不管怎么说这个肯定不是崩溃的原因。

2. GC正在干什么

我们继续观察线程栈,可以看到它的逻辑大概是这样的,通过 SuspendRuntime 把所有的托管线程进行逻辑上暂停,在暂停其中的一个线程时抛出了异常。

稍微提醒一下,这个 HandledJITCase 方法是用 ip 劫持技术将代码引入到 coreclr 中进行 GC完成等待,这种神操作有些杀毒软件会认为是病毒!!!

有些朋友肯定会说,有没有代码支撑。。。这里我就找一下 coreclr 的源码贴一下吧。


void ThreadSuspend::SuspendRuntime(ThreadSuspend::SUSPEND_REASON reason)
{
while ((thread = ThreadStore::GetThreadList(thread)) != NULL)
{
...
if (workingOnThreadContext.Acquired() && thread->HandledJITCase())
{
...
}
...
}
}

结合源码分析思路就非常清晰了,这里的 thread->HandledJITCase() 中的 thread 到底是哪一个线程? 可以观察 kb 输出然后用 !t 去做比对。

从卦中看,当前 GC 正在 Suspend 主线程,并且还看到了主线程有一个 System.AccessViolationException 异常,无语了。。。

3. 主线程到底怎么了

主线程进入到视野之后,那就重点关注一下它,可以用 k 看一下输出。


0:009> ~0s
eax=00000000 ebx=0029ea50 ecx=0029ea90 edx=00000000 esi=7efdb800 edi=000d0000
eip=00000000 esp=0029ea4c ebp=75146381 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210202
00000000 ?? ???
0:000> k
00 75146381 7efdb800 0x0
01 75146381 7517fa04 0x7efdb800
02 0029ea80 7736013a user32!__fnHkINLPKBDLLHOOKSTRUCT+0x28
03 0029eae4 7514908d ntdll!KiUserCallbackDispatcher+0x2e
04 0029eae4 076e3912 user32!CallNextHookEx+0x84
05 0029eb28 076e3064 0x76e3912
06 0029eb5c 0011d48f xxx!xxx.ScanerHook.KeyboardHookProc+0xe4
07 0029eb8c 75146381 0x11d48f
08 0029eba8 7517fa04 user32!DispatchHookW+0x38
09 0029ebd8 7736013a user32!__fnHkINLPKBDLLHOOKSTRUCT+0x28
0a 0029ec3c 751406eb ntdll!KiUserCallbackDispatcher+0x2e
0b 0029ec3c 75140751 user32!_PeekMessage+0x88
0c 0029ec68 6d8af3bf user32!PeekMessageW+0x108
...

从卦象看,这卦非常奇怪,有如下两点信息:

  • eip=00000000,这个很无语,线程已经疯了
  • KeyboardHookProc ,居然有键盘钩子

熟悉 eip 的朋友应该知道,它相当于一辆车的方向盘,一辆高速行驶的车突然没了方向盘,真的太可怕了,最后必然车毁人亡。

4. 是 eip=0 导致的崩溃吗

在汇编中是因为eax=0导致,而这里eip恰好也等于0,仿佛冥冥之中自有牵连,带着强烈的好奇心我们来反汇编下 GetSafelyRedirectableThreadContext 方法逻辑,简化后如下:


0:000> uf 7008508f
clr!Thread::GetSafelyRedirectableThreadContext:
6fe7f60e 55 push ebp
6fe7f60f 8bec mov ebp,esp
6fe7f611 53 push ebx
6fe7f612 56 push esi
6fe7f613 57 push edi
6fe7f614 8bf1 mov esi,ecx
...
7008506d ffe9 jmp rcx
7008506f fd std
70085070 c1daff rcr edx,0FFh
70085073 f6450801 test byte ptr [ebp+8],1
70085077 0f84efa5dfff je clr!Thread::GetSafelyRedirectableThreadContext+0xcc (6fe7f66c)
7008507d 8b8604010000 mov eax,dword ptr [esi+104h]
70085083 3987b8000000 cmp dword ptr [edi+0B8h],eax
70085089 0f85dda5dfff jne clr!Thread::GetSafelyRedirectableThreadContext+0xcc (6fe7f66c)
7008508f 8038eb cmp byte ptr [eax],0EBh

从上面的汇编代码看eax的取值链条是: eax <- esi+104h <- ecx ,很显然这里的 ecx 是 thiscall 协议中的 Thread=004c1b98 参数,可以用 dp 验证下。


0:000> dp 004c1b98+0x104 L1
004c1c9c 00000000

从卦中看果然是 0,有些朋友好奇这个 104 偏移到底是个什么东西,参考 coreclr 源码其实就是 m_LastRedirectIP 字段,参考如下:


BOOL Thread::GetSafelyRedirectableThreadContext(DWORD dwOptions, CONTEXT* pCtx, REGDISPLAY* pRD)
{
if (!EEGetThreadContext(this, pCtx))
{
return FALSE;
}
...
if (GetIP(pCtx) == m_LastRedirectIP)
{
const BYTE short_jmp = 0xeb;
const BYTE self = 0xfe; BYTE* ip = (BYTE*)m_LastRedirectIP;
if (ip[0] == short_jmp && ip[1] == self)
m_LastRedirectIP = 0;
return FALSE;
}
}

结合汇编代码其实我们崩溃在 ip[0] == short_jmp 这一句上,仔细分析上面的C++代码会发现一个很奇怪的信息,那就是为什么 GetIP(pCtx)= 0,接下来用 dt 观察下寄存器上下文。


0:009> kb 2
# ChildEBP RetAddr Args to Child
00 07a8ec80 6fe7f6cd 00000003 07a8ed4c 07a8ecf0 clr!Thread::GetSafelyRedirectableThreadContext+0x7c
01 07a8f030 6fe7f2f3 004c1b98 0b367326 76a016a1 clr!Thread::HandledJITCase+0x31 0:009> dt _CONTEXT 07a8ed4c
ntdll!_CONTEXT
+0x000 ContextFlags : 0x10007
...
+0x01c FloatSave : _FLOATING_SAVE_AREA
+0x08c SegGs : 0x2b
+0x090 SegFs : 0x53
+0x094 SegEs : 0x2b
+0x098 SegDs : 0x2b
+0x09c Edi : 0xd0000
+0x0a0 Esi : 0x7efdb800
+0x0a4 Ebx : 0x29ea50
+0x0a8 Edx : 0
+0x0ac Ecx : 0x29ea90
+0x0b0 Eax : 0
+0x0b4 Ebp : 0x75146381
+0x0b8 Eip : 0
+0x0bc SegCs : 0x23
+0x0c0 EFlags : 0x210202
+0x0c4 Esp : 0x29ea4c
...

从卦中看果然 eip=0,这是一个非常错误的信息,还有一点就是 m_LastRedirectIP 字段一般用来处理一些比较诡异的兼容性问题,所以这里两个字段都是 0 导致崩溃的产生。

有了上面的信息,我们就知道了前因后果,原来是主线程车毁人亡(eip=0),导致GC无法暂停它,在内部抛出了代码异常,你可以说是 CLR 的bug,也可以说是主线程的Bug,所以给到的解决方案就是:

  1. 屏蔽掉 键盘钩子 的业务逻辑,肯定是它造成的。
  2. 不去掉的话,要重点观察 键盘盘子 ,是否是代码改动引发的。

三:总结

说实话要想解释这个程序为什么会崩溃,需要分析者对GC的SuspendRuntime运作逻辑有一定的了解,否则真抓瞎了,所以.NET调试训练营中的GC理论知识一定是分析这些 dump 的基石。

记一次 .NET某防伪验证系统 崩溃分析的更多相关文章

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

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

  2. 记一次 .NET 某企业 ERP网站系统 崩溃分析

    一:背景 1. 讲故事 前段时间收到了一个朋友的求助,说他的ERP网站系统会出现偶发性崩溃,找了好久也没找到是什么原因,让我帮忙看下,其实崩溃好说,用 procdump 自动抓一个就好,拿到 dump ...

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

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

  4. 公共交通3D指纹验证系统解决方案

    为了响应国家关于老年人的优待政策,华本研发了退休老人乘公交车指纹认证系统.指纹认证系统不仅方便老人乘坐公交,还能为公共部门减压,杜绝伪造优待证乘坐公交的不法行为. 目前,优待证都是人工检查,缺乏有效的 ...

  5. 利用线上数据验证系统 Gor

    Web 应用性能和压力测试工具 Gor - 运维生存时间 http://hao.jobbole.com/gorhttp/ 要使用线上引流到测试环境的作用,需要做到以下几点: 1.新搭建一套测试环境,连 ...

  6. 景区3D指纹验证系统解决方案

    旅游业已成为全球经济中发展势头最强劲和规模最大的产业之一.旅游业在城市经济发展中的产业地位.经济作用逐步增强,旅游业对城市经济的拉动性.社会就业的带动力.以及对文化与环境的促进作用日益显现.指纹门票为 ...

  7. 让你一分钟认识电子身份验证系统EID

    什么是EID eID是英文"Electronic Identity"的英文简称,中文名为"电子身份证"或"网络电子身份证",由公安部第三研究 ...

  8. 洗礼灵魂,修炼python(81)--全栈项目实战篇(9)—— 购物商城登录验证系统

    都在线购物过吧?那么你应该体验过,当没有登录账户时,点开购物车,个人中心,收藏物品等的操作时,都会直接跳转到登录账户的界面,然后如果登录一次后就不用再登录,直到用户登出. 是的,本次项目就是做一个登录 ...

  9. django-用户验证系统

    django提供了一套用户验证系统,但是要使用这个系统,必须要使用django内置的用户模型:django.contrib.auth.models.User,这个模型中预先定义了一些字段,其中只有us ...

  10. django 使用其自带的验证系统 进行用户名有效性验证 登录状态验证 登入操作 登出操作

    from django.shortcuts import render, redirect from django.contrib.auth import authenticate, login, l ...

随机推荐

  1. std::vector<std::string> 与 char** 的转换

    vecotr 容器中插入多条字符串,再将 vector 的地址转换为 char** 代码如下: #include <stdio.h> #include <string> #in ...

  2. C++ 多线程的错误和如何避免(6)

    加锁的临界区要尽可能的紧凑和小型 问题分析: 当一个线程在临界区内执行时,所有其他试图进入临界区的线程都会被阻止,所以我们应该保证临界区尽可能的小.比如, void CallHome(string m ...

  3. C++ 多线程的错误和如何避免(1)

    在终止程序之前没有使用 join() 等待后台线程 前提分析:线程分为 joinable  状态和 detached 状态 添加 .join() 这句代码的时候,就表示主线程需要等待子线程运行结束回收 ...

  4. 【Android逆向】滚动的天空中插入smali日志

    1. 编写一个MyLog.java 放到一个android工程下,编译打包,然后反编译拿到MyLog的smali代码 package com.example.logapplication; impor ...

  5. h5页面在微信打开,ios底部存在返回横条的问题

    我的问题比较简单,一个处理链接的页面,二次跳转进入真正的页面,导致ios出现返回横条,点击后退回到了处理链接页面.因为这个后退不会重新加载,导致一直处在处理链接的这个空页面. 所以我用replace代 ...

  6. 教你如何用Keepalived和HAproxy配置高可用 Kubernetes 集群

    本文分享自华为云社区<使用 Keepalived 和 HAproxy 创建高可用 Kubernetes 集群>,作者:江晚正愁余. 高可用 Kubernetes 集群能够确保应用程序在运行 ...

  7. 矩池云上 git clone --recursive 出错,怎么解决

    遇到问题 有时候安装包教程里 git clone 的时候会出现以下错误: git clone --recursive https://github.91chi.fun/https://github.c ...

  8. 【Azure Developer】使用 Azure Python 查看 Azure 所有的 Alert rule

    问题描述 在Azure Alert 门户中,可以列举出所有Azure资源的Alert rule信息,如下图: 如果像通过Python SDK来获取所有的Alert Rule,有什么可以参考的代码吗? ...

  9. 【Azure 应用服务】"App Service"如何能判断自身网路没有问题?

    问题描述 当创建了一个App Service服务后,如何能判断服务自身网络链路路没有问题? 如果是用VM,通常用Ping可以判断.但是"网站应用App Service" 的Kudu ...

  10. Java UML类图

    在UML的静态机制中类图是一个重点,它不但是设计人员关心的核心,更是实现人员关注的核心.建模工具也主要根据类图来产生代码.类图在UML的9个图中占据了一个相当重要的地位.James Rumbaugh对 ...