惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
C
Cybersecurity and Infrastructure Security Agency CISA
N
News and Events Feed by Topic
C
Cisco Blogs
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
Scott Helme
Scott Helme
P
Palo Alto Networks Blog
S
Schneier on Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
量子位
G
Google Developers Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
B
Blog RSS Feed
NISL@THU
NISL@THU
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
AWS News Blog
AWS News Blog
爱范儿
爱范儿
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
L
LINUX DO - 最新话题
Security Archives - TechRepublic
Security Archives - TechRepublic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Secure Thoughts
Cloudbric
Cloudbric
aimingoo的专栏
aimingoo的专栏
L
Lohrmann on Cybersecurity
TaoSecurity Blog
TaoSecurity Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Hacker News: Ask HN
Hacker News: Ask HN
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
S
Security @ Cisco Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
G
GRAHAM CLULEY
P
Proofpoint News Feed
V
V2EX
Martin Fowler
Martin Fowler
C
CERT Recently Published Vulnerability Notes
Attack and Defense Labs
Attack and Defense Labs
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Cloudflare Blog
SecWiki News
SecWiki News
罗磊的独立博客
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
小众软件
小众软件
The Last Watchdog
The Last Watchdog

博客园 - 一线码农

记一次 .NET 某光电成像后端解析系统 内存暴涨分析 记一次 .NET 某电力后台监控系统 内存暴涨分析 记一次 .NET 某注塑模具系统 CPU爆高分析 记一次 .NET 某集群管理软件 内存暴涨分析 记一次 .NET 某智慧工厂视觉程序 崩溃分析 记一次 .NET 某低代码开发框架 内存暴涨分析 记一次 .NET 某MES上位机拍照系统 内存暴涨分析 记一次 .NET 某RFID标签打印客户端 崩溃分析 对 .NET FileSystemWatcher引发内存碎片化的 反思 DotMemory系列:5. 如何实现自动化抓取和应用自托管 DotMemory系列:4. 如何分析进程的转储文件 DotMemory系列:3. 堆碎片化引发的内存暴涨分析 DotMemory系列:2. 事件泄露引发的内存暴涨分析 DotMemory系列:1. 终结队列积压引发的内存暴涨分析 记一次 .NET 某理财管理客户端 OOM溢出分析 记一次 .NET 某医联体管理系统 崩溃分析 记一次 .NET 某光放测试系统 崩溃分析 记一次 .NET 某药品缺陷高速检测系统 卡慢分析 聊一聊 .NET超高内存故障分析方法 的反思 记一次 .NET 某企业ECM内容管理系统 内存暴涨分析 记一次 .NET 某跨境物流系统 内存暴涨分析 记一次 .NET 某中医药附属医院门诊系统 崩溃分析 聊一聊 .NET 中的 CompositeChangeToken 聊一聊 .NET 中的 CancellationTokenSource
记一次 .NET 某CRM物流行业管理系统 崩溃分析
一线码农 · 2025-09-04 · via 博客园 - 一线码农

一:背景

1. 讲故事

微信上有位朋友找到我,说他们部署在linux上的 .net 程序会隔几天崩溃一次,一直找不到原因,让我帮忙看下怎么回事,让朋友用 procdump 抓了一个dump下来,然后就是正式的分析啦。

二:崩溃分析

1. 为什么会崩溃

拿到dump之后,双击dump打开,会看到程序崩溃的原因,参考如下:


(1.d): Signal SIGSEGV (Segmentation fault) code SEGV_MAPERR (Address not mapped to object) at 0x108

libc_so!wait4+0x57:
00007f44`37aa5c17 483d00f0ffff    cmp     rax,0FFFFFFFFFFFFF000h

从卦中可以看到如下几点信息:

  1. 1.d 表示 d 号线程出现了崩溃。
  2. SIGSEGV 表示经典的 段错误,用 Windows 的话术就是访问违例。
  3. SEGV_MAPERR 表示mapper错误,即当前地址无法映射到有效内存。
  4. 0x108 当前访问的地址。

既然都说到 d 号线程了,接下来就是切过去看看,参考输出如下:


0:007> ~~[d]s
libc_so!wait4+0x57:
00007f44`37aa5c17 483d00f0ffff    cmp     rax,0FFFFFFFFFFFFF000h
0:007> k
 # Child-SP          RetAddr               Call Site
00 00007f44`367d1cd0 00007f44`37851c05     libc_so!wait4+0x57
01 00007f44`367d1d00 00007f44`37852b40     libcoreclr!PROCCreateCrashDump+0x275 [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2307] 
02 00007f44`367d1d60 00007f44`3782518e     libcoreclr!PROCCreateCrashDumpIfEnabled+0x770 [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2524] 
03 00007f44`367d1df0 00007f44`37824765     libcoreclr!invoke_previous_action+0x10e [/__w/1/s/src/coreclr/pal/src/exception/signal.cpp @ 397] 
04 00007f44`367d1e30 00007f44`37a0e050     libcoreclr!sigsegv_handler+0x1d5 [/__w/1/s/src/coreclr/pal/src/exception/signal.cpp @ 631] 
05 00007f44`367d2ac0 00007f44`37754e2a     libc_so!_sigaction+0x40
06 00007f44`368d2830 00007f44`375109c3     libcoreclr!CustomAssemblyBinder::PrepareForLoadContextRelease+0xa [/__w/1/s/src/coreclr/binder/customassemblybinder.cpp @ 222] 
07 00007f44`368d2850 00007f44`1adbe9c5     libcoreclr!AssemblyNative_PrepareForAssemblyLoadContextRelease+0x93 [/__w/1/s/src/coreclr/inc/clrtypes.h @ 1263] 
08 00007f44`368d28e0 00007f44`224242cd     System_Private_CoreLib!System.Runtime.Loader.AssemblyLoadContext.InitiateUnload+0xe5 [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs @ 151] 
09 00007f44`368d29b0 00007f44`376de496     System_Private_CoreLib!System.Runtime.Loader.AssemblyLoadContext.Finalize+0x2d [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs @ 124] 
0a 00007f44`368d29d0 00007f44`3749b8a3     libcoreclr!FastCallFinalizeWorker+0x6 [/__w/1/s/src/coreclr/vm/amd64/calldescrworkeramd64.S @ 31] 
0b (Inline Function) --------`--------     libcoreclr!FastCallFinalize+0x4a [/__w/1/s/src/coreclr/vm/methodtable.cpp @ 4771] 
0c 00007f44`368d29e0 00007f44`375540f5     libcoreclr!MethodTable::CallFinalizer+0x253 [/__w/1/s/src/coreclr/vm/spinlock.h @ 4889] 
0d (Inline Function) --------`--------     libcoreclr!CallFinalizer+0x58 [/__w/1/s/src/coreclr/vm/finalizerthread.cpp @ 75] 
0e 00007f44`368d2a40 00007f44`37554345     libcoreclr!FinalizerThread::FinalizeAllObjects+0xc5 [/__w/1/s/src/coreclr/inc/volatile.h @ 104] 
0f 00007f44`368d2a80 00007f44`374e5b75     libcoreclr!FinalizerThread::FinalizerThreadWorker+0x95 [/__w/1/s/src/coreclr/inc/volatile.h @ 354] 
10 (Inline Function) --------`--------     libcoreclr!ManagedThreadBase_DispatchInner+0x2 [/__w/1/s/src/coreclr/vm/threads.cpp @ 7222] 
11 (Inline Function) --------`--------     libcoreclr!ManagedThreadBase_DispatchMiddle+0x3d [/__w/1/s/src/coreclr/vm/util.hpp @ 7266] 
12 (Inline Function) --------`--------     libcoreclr!<unnamed-class>::operator()+0x3d [/__w/1/s/src/coreclr/vm/util.hpp @ 7424] 
13 (Inline Function) --------`--------     libcoreclr!<unnamed-class>::operator()+0xa9 [/__w/1/s/src/coreclr/vm/util.hpp @ 7426] 
14 00007f44`368d2cd0 00007f44`374e619d     libcoreclr!ManagedThreadBase_DispatchOuter+0x135 [/__w/1/s/src/coreclr/vm/util.hpp @ 7450] 
15 (Inline Function) --------`--------     libcoreclr!ManagedThreadBase_NoADTransition+0x18 [/__w/1/s/src/coreclr/vm/threads.cpp @ 7494] 
16 00007f44`368d2de0 00007f44`375545e8     libcoreclr!ManagedThreadBase::FinalizerBase+0x2d [/__w/1/s/src/coreclr/vm/threads.cpp @ 7514] 
17 00007f44`368d2e10 00007f44`3785476e     libcoreclr!FinalizerThread::FinalizerThreadStart+0x58 [/__w/1/s/src/coreclr/vm/finalizerthread.cpp @ 403] 
18 00007f44`368d2e30 00007f44`37a5b1f5     libcoreclr!CorUnix::CPalThread::ThreadEntry+0x1fe [/__w/1/s/src/coreclr/pal/inc/pal.h @ 1763] 
19 00007f44`368d2ee0 00007f44`37adab00     libc_so!pthread_condattr_setpshared+0x515
1a 00007f44`368d2f80 ffffffff`ffffffff     libc_so!_clone+0x40
1b 00007f44`368d2f88 00000000`00000000     0xffffffff`ffffffff

从卦象看是终结器线程正在调用 AssemblyLoadContext 的析构函数,在coreclr层的 PrepareForLoadContextRelease 函数中抛出了访问违例,这段代码很明显犯了编程的一个大忌,即不手工调用Dispose,而是依赖终结器线程的兜底,导致灾难的发生,

不过按理说这些代码都是固若金汤,抛异常也是有点奇葩。。。

2. 为什么会抛出异常

要想找到这个答案,可以借助可视化的VS面板,将 dump 拖到 VS 中,在线程面板中找到 终结器线程,然后观察 InitiateUnload 方法的代码逻辑,可以清楚的看到然来是 _nativeAssemblyLoadContext 字段为 null 导致的,截图如下:

观察源代码发现 _nativeAssemblyLoadContext 是 coreclr 对外提供操作的句柄,它的赋值是在 AssemblyLoadContext 初始化构造时,截图如下:

说实话看到这个源头就蒙圈了,_nativeAssemblyLoadContext 居然还有null的情况,这也就说明 InitializeAssemblyLoadContext 函数有为null的情况,签名如下:


	[DllImport("QCall", CharSet = CharSet.Unicode)]
	private static extern IntPtr InitializeAssemblyLoadContext(IntPtr ptrAssemblyLoadContext, bool fRepresentsTPALoadContext, bool isCollectible);

3. 接下来怎么办

针对 _nativeAssemblyLoadContext=null 这种奇葩情况,我个人提供两种方案。

1) 使用 using 替代 兜底线程

如果调用线程执行了错误的 _nativeAssemblyLoadContext,那最多就是抛个异常,不会导致程序崩溃,相反如果让终结器线程崩溃了,那就是大大的一个灾难。无法挽回。

2) 使用 harmony 跟踪

如果你是一个极客,一定要抓到 _nativeAssemblyLoadContext=null 时的调用栈,可以使用 harmony 进行实时跟踪,即对 AssemblyLoadContext 构造函数进行注入,在后缀补丁中获取 _nativeAssemblyLoadContext 值即可,这里借助上一篇的 CustomAssemblyLoadContext 代码例子,参考代码如下:


    [HarmonyPatch(typeof(AssemblyLoadContext), MethodType.Constructor, new Type[] { typeof(string), typeof(bool) })]
    public class AssemblyLoadContextHook
    {
        // 后缀补丁 - 在原始方法执行后运行
        public static void Postfix(AssemblyLoadContext __instance, IntPtr ____nativeAssemblyLoadContext)
        {
            Console.WriteLine("----------------------------");

            long addr = (____nativeAssemblyLoadContext == IntPtr.Zero) ? 0 : ____nativeAssemblyLoadContext.ToInt64();

            Console.WriteLine($"____nativeAssemblyLoadContext: 0x{addr:X}");
            Console.WriteLine(JsonConvert.SerializeObject(__instance));
            Console.WriteLine("----------------------------");
            Console.WriteLine(Environment.StackTrace);
        }
    }

从卦中可以清晰的看到 new AssemblyLoadContext 之后的类型信息,并记录了当前的调用栈,一旦有 null 出现的时候,是不是一下子就缩小了包围圈哈。。。

三:总结

这次生产事故也强烈的警示了大家,能用 using 就不要让 终结器线程 兜底,后者一旦崩溃就会酿成灾难性后果。

图片名称