当前位置:首页 > 科技  > 软件

C#托管堆遭破坏问题溯源分析

来源: 责编: 时间:2024-06-12 17:31:31 281观看
导读一、背景1. 讲故事年前遇到了好几例托管堆被损坏的案例,有些运气好一些,从被破坏的托管堆内存现场能观测出大概是什么问题,但更多的情况下是无法做出准确判断的,原因就在于生成的dump是第二现场,借用之前文章的一张图,大家

一、背景

1. 讲故事

年前遇到了好几例托管堆被损坏的案例,有些运气好一些,从被破坏的托管堆内存现场能观测出大概是什么问题,但更多的情况下是无法做出准确判断的,原因就在于生成的dump是第二现场,借用之前文章的一张图,大家可以理解一下。BYz28资讯网——每日最新资讯28at.com

图片图片BYz28资讯网——每日最新资讯28at.com

为了帮助更多受此问题困扰的朋友,这篇来整理一下如何 快狠准 的抓取第一现场。BYz28资讯网——每日最新资讯28at.com

二、抓取第一现场

1. 思路分析

要想抓到第一现场,只需要让破坏托管堆的那个线程在修改完之后,回到 CLR Pinvoke 层的时候主动触发GC,因为这时候托管堆已经是损坏状态了,程序也就会立即崩溃,破坏线程也就被捉jian在床,画个图如下:BYz28资讯网——每日最新资讯28at.com

图片图片BYz28资讯网——每日最新资讯28at.com

那如何让 CLR:PInvoke 主动触发GC呢?这就需要借助微软的 MDA 托管调试助手,它有一个 gcUnmanagedToManaged 配置项就是专门做这件事情的,参考网址:https://learn.microsoft.com/zh-cn/dotnet/framework/debug-trace-profile/gcunmanagedtomanaged-mdaBYz28资讯网——每日最新资讯28at.com

2. 如何配置 MDA

MDA 的配置非常简单,大体上分两步:BYz28资讯网——每日最新资讯28at.com

  1. 提交注册表开启MDA

这里使用注册表的方式,需要注意的是,程序和操作系统位数一致的话采用如下方式。BYz28资讯网——每日最新资讯28at.com

Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/.NETFramework]"MDA"="1"

如果不一致,采用如下配置,比如 32bit 程序跑在 64bit 系统上。BYz28资讯网——每日最新资讯28at.com

Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE/SOFTWARE/Wow6432Node/Microsoft/.NETFramework]"MDA"="1"

这里我用的是第二段内容,按照官方文档描述,将内容保存到 MDAEnable.reg 中,然后在 注册表编辑器 上导入即可。BYz28资讯网——每日最新资讯28at.com

图片图片BYz28资讯网——每日最新资讯28at.com

开启应用程序级捕获

为了能够让 gcUnmanagedToManaged 生效,需要新建应用程序打头的配置文件,比如:Example_16_1_2.exe.mda.config,内容如下:BYz28资讯网——每日最新资讯28at.com

<mdaConfig> <assistants>  <gcUnmanagedToManaged/> </assistants></mdaConfig>

完整截图:BYz28资讯网——每日最新资讯28at.com

图片图片BYz28资讯网——每日最新资讯28at.com

这样就算配置好了,当程序在 PInvoke 时,CLR 会读取注册表的 MDA 值,如果开启的话就会读取 config 中 gcUnmanagedToManaged 子节做相应的逻辑。BYz28资讯网——每日最新资讯28at.com

tips:如果配置不生效,保守一点的话,建议重启下机器。BYz28资讯网——每日最新资讯28at.com

3. 一个托管堆破坏的测试案例

为了演示托管堆损坏,我准备将一个 string 传给 C++,然后让 C++ 溢出它来实现托管堆破坏。BYz28资讯网——每日最新资讯28at.com

C# 代码如下:BYz28资讯网——每日最新资讯28at.com

namespace Example_16_1_2{    internal class Program    {        [DllImport("Example_16_1_3.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]        public extern static void Alloc(string str);        static void Main(string[] args)        {            Test();            Task.Factory.StartNew(() =>            {                Thread.Sleep(3000);                GC.Collect();            });            Console.ReadLine();        }        static void Test()        {            var str = "hello";            var str2 = "world!";            Alloc(str);        }    }}

C++ 代码如下:BYz28资讯网——每日最新资讯28at.com

extern "C"{ _declspec(dllexport) void Alloc(wchar_t* c);}#include "iostream"#include <Windows.h>using namespace std;void Alloc(wchar_t* c){ for (size_t i = 0; i < 10; i++) {  *c++ = 'a'; } wprintf(L"%s /n", c);}

从代码逻辑看,只要 Alloc(str) 的线程栈上触发了 GC 就是第一现场,Task 下的 GC.Collect(); 是第二现场,如果是前者目的就达到了。BYz28资讯网——每日最新资讯28at.com

激动人心的时刻到了,把程序跑起来后,由于程序崩溃,procdump 立即给我抓了一个 crash dump,截图如下:BYz28资讯网——每日最新资讯28at.com

图片图片BYz28资讯网——每日最新资讯28at.com

接下来打开 windbg,从序幕信息看果然是 GC 清扫的时候出的问题,托管堆也是损坏状态,信息如下:BYz28资讯网——每日最新资讯28at.com

Debug session time: Sun Jan 29 10:14:21.000 2023 (UTC + 8:00)System Uptime: 0 days 1:14:11.423Process Uptime: not available.................................Loading unloaded module list..This dump file has an exception of interest stored in it.The stored exception information can be accessed via .ecxr.(4460.52ac): Access violation - code c0000005 (first/second chance not available)For analysis of this file, run !analyze -veax=00610060 ebx=00000000 ecx=02da23a4 edx=00000001 esi=02da2370 edi=02da2388eip=79a6f2d1 esp=00d3ef64 ebp=00d3f104 iopl=0         nv up ei pl nz na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206clr!WKS::gc_heap::plan_phase+0x79b:79a6f2d1 f70000000080    test    dword ptr [eax],80000000h ds:002b:00610060=????????0:000> !VerifyHeapCould not request method table data for object 02DA1228 (MethodTable: 0000000C).Last good object: 02DA121C.object 03da1020: bad member 02DA1228 at 03DA1098Last good object: 03DA1010.object 03da2338: bad member 02DA1228 at 03DA2340Last good object: 03DA2328.object 03da3568: bad member 02DA2364 at 03DA357CLast good object: 03DA3558.Failed to request SyncBlk at index 1.

那是不是主线程引发的GC呢?切过去便知。BYz28资讯网——每日最新资讯28at.com

0:000> ~0seax=00610060 ebx=00000000 ecx=02da23a4 edx=00000001 esi=02da2370 edi=02da2388eip=79a6f2d1 esp=00d3ef64 ebp=00d3f104 iopl=0         nv up ei pl nz na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206clr!WKS::gc_heap::plan_phase+0x79b:79a6f2d1 f70000000080    test    dword ptr [eax],80000000h ds:002b:00610060=????????0:000> !clrstackOS Thread Id: 0x52ac (0)Child SP       IP Call Site00d3f220 79a6f2d1 [HelperMethodFrame: 00d3f220] System.StubHelpers.StubHelpers.TriggerGCForMDA()00d3f294 02bc0aa7 DomainBoundILStubClass.IL_STUB_PInvoke(System.String)00d3f298 02bc09c9 [InlinedCallFrame: 00d3f298] Example_16_1_2.Program.Alloc(System.String)00d3f2e0 02bc09c9 Example_16_1_2.Program.Test() [D:/testdump/Example/Example_16_1_2/Program.cs @ 35]00d3f2f0 02bc0900 Example_16_1_2.Program.Main(System.String[]) [D:/testdump/Example/Example_16_1_2/Program.cs @ 19]00d3f490 7996f036 [GCFrame: 00d3f490] 0:000> k 10 # ChildEBP RetAddr      00 00d3f104 79a68153     clr!WKS::gc_heap::plan_phase+0x79b01 00d3f124 79a6847b     clr!WKS::gc_heap::gc1+0xbc02 00d3f13c 79a68585     clr!WKS::gc_heap::garbage_collect+0x36703 00d3f15c 79b1ddbd     clr!WKS::GCHeap::GarbageCollectGeneration+0x1bd04 00d3f16c 79b1de34     clr!WKS::GCHeap::GarbageCollectTry+0x7105 00d3f198 79d20aed     clr!WKS::GCHeap::GarbageCollect+0xac06 00d3f204 79d066c0     clr!TriggerGCForMDAInternal+0x7d07 00d3f28c 02bc0aa7     clr!StubHelpers::TriggerGCForMDA+0x61WARNING: Frame IP not in any known module. Following frames may be wrong.08 00d3f2d8 02bc09c9     0x2bc0aa709 00d3f2e8 02bc0900     Example_16_1_2!Example_16_1_2.Program.Test+0x39 [D:/testdump/Example/Example_16_1_2/Program.cs @ 35] 0a 00d3f318 7996f036     Example_16_1_2!Example_16_1_2.Program.Main+0x30 [D:/testdump/Example/Example_16_1_2/Program.cs @ 19] 0b 00d3f324 799722da     clr!CallDescrWorkerInternal+0x340c 00d3f378 7997859b     clr!CallDescrWorkerWithHandler+0x6b0d 00d3f3ec 79b1b11b     clr!MethodDescCallSite::CallTargetWorker+0x16a0e 00d3f510 79b1b7fa     clr!RunMain+0x1b30f 00d3f77c 79b1b727     clr!Assembly::ExecuteMainMethod+0xf7

从线程栈上的 clr!StubHelpers::TriggerGCForMDA 来看,在 Pinvoke 层果然主动触发了 GC,成功将 Program.Alloc 这个非托管方法捉jian在床。BYz28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-93367-0.htmlC#托管堆遭破坏问题溯源分析

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: PHP二进制 Swoole-Cli 了解一下

下一篇: Go 面试中的隐藏陷阱:SliceHeader 问题解析

标签:
  • 热门焦点
  • Redmi Buds 4开箱简评:才199还有降噪 可以无脑入

    在上个月举办的Redmi Note11T Pro系列新机发布会上,除了两款手机新品之外,Redmi还带来了两款TWS真无线蓝牙耳机产品,Redmi Buds 4和Redmi Buds 4 Pro,此前我们在Redmi Note11T
  • vivo TWS Air开箱体验:真轻 臻好听

    在vivo S15系列新机的发布会上,vivo的最新款真无线蓝牙耳机vivo TWS Air也一同发布,本次就这款耳机新品给大家带来一个简单的分享。外包装盒上,vivo TWS Air保持了vivo自家产
  • 得物效率前端微应用推进过程与思考

    一、背景效率工程随着业务的发展,组织规模的扩大,越来越多的企业开始意识到协作效率对于企业团队的重要性,甚至是决定其在某个行业竞争中突围的关键,是企业长久生存的根本。得物
  • 十个简单但很有用的Python装饰器

    装饰器(Decorators)是Python中一种强大而灵活的功能,用于修改或增强函数或类的行为。装饰器本质上是一个函数,它接受另一个函数或类作为参数,并返回一个新的函数或类。它们通常用
  • 使用LLM插件从命令行访问Llama 2

    最近的一个大新闻是Meta AI推出了新的开源授权的大型语言模型Llama 2。这是一项非常重要的进展:Llama 2可免费用于研究和商业用途。(几小时前,swyy发现它已从LLaMA 2更名为Lla
  • 从零到英雄:高并发与性能优化的神奇之旅

    作者 | 波哥审校 | 重楼作为公司的架构师或者程序员,你是否曾经为公司的系统在面对高并发和性能瓶颈时感到手足无措或者焦头烂额呢?笔者在出道那会为此是吃尽了苦头的,不过也得
  • JVM优化:实战OutOfMemoryError异常

    一、Java堆溢出堆内存中主要存放对象、数组等,只要不断地创建这些对象,并且保证 GC Roots 到对象之间有可达路径来避免垃 圾收集回收机制清除这些对象,当这些对象所占空间超过
  • iQOO Neo8 Pro抢先上架:首发天玑9200+ 安卓性能之王

    经过了一段时间的密集爆料,昨日iQOO官方如期对外宣布:将于5月23日推出全新的iQOO Neo8系列新品,官方称这是一款拥有旗舰级性能调校的作品。随着发布时
  • 回归OPPO两年,一加赢了销量,输了品牌

    成为OPPO旗下主打性能的先锋品牌后,一加屡创佳绩。今年618期间,一加手机全渠道销量同比增长362%,凭借一加 11、一加 Ace 2、一加 Ace 2V三款爆品,一加
Top