記一次 .NET 某CRM物流行業管理系統崩潰分析
一、背景
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從卦中可以看到如下幾點信息:
- d 表示 d 號線程出現了崩潰。
- SIGSEGV 表示經典的 段錯誤,用 Windows 的話術就是訪問違例。
- SEGV_MAPERR 表示mapper錯誤,即當前地址無法映射到有效內存。
- 0x108 當前訪問的地址。
既然都說到 d 號線程了,接下來就是切過去看看,參考輸出如下:
0:007> ~~[d]s
libc_so!wait4+0x57:
00007f44`37aa5c17 483d00f0ffff cmp rax,0FFFFFFFFFFFFF000h
0:007> k
# Child-SP RetAddr Call Site
0000007f44`367d1cd0 00007f44`37851c05 libc_so!wait4+0x57
0100007f44`367d1d00 00007f44`37852b40 libcoreclr!PROCCreateCrashDump+0x275 [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2307]
0200007f44`367d1d60 00007f44`3782518e libcoreclr!PROCCreateCrashDumpIfEnabled+0x770 [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2524]
0300007f44`367d1df0 00007f44`37824765 libcoreclr!invoke_previous_action+0x10e [/__w/1/s/src/coreclr/pal/src/exception/signal.cpp @ 397]
0400007f44`367d1e30 00007f44`37a0e050 libcoreclr!sigsegv_handler+0x1d5 [/__w/1/s/src/coreclr/pal/src/exception/signal.cpp @ 631]
0500007f44`367d2ac0 00007f44`37754e2a libc_so!_sigaction+0x40
0600007f44`368d2830 00007f44`375109c3 libcoreclr!CustomAssemblyBinder::PrepareForLoadContextRelease+0xa [/__w/1/s/src/coreclr/binder/customassemblybinder.cpp @ 222]
0700007f44`368d2850 00007f44`1adbe9c5 libcoreclr!AssemblyNative_PrepareForAssemblyLoadContextRelease+0x93 [/__w/1/s/src/coreclr/inc/clrtypes.h @ 1263]
0800007f44`368d28e0 00007f44`224242cd System_Private_CoreLib!System.Runtime.Loader.AssemblyLoadContext.InitiateUnload+0xe5 [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs @ 151]
0900007f44`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]
0f00007f44`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 (InlineFunction) --------`-------- 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 (InlineFunction) --------`-------- 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`368d2f80ffffffff`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. 接下來怎么辦
針對 _nativeAssemblyLoadCnotallow=null 這種奇葩情況,我個人提供兩種方案。
1) 使用 using 替代 兜底線程
如果調用線程執行了錯誤的 _nativeAssemblyLoadContext,那最多就是拋個異常,不會導致程序崩潰,相反如果讓終結器線程崩潰了,那就是大大的一個災難。無法挽回。
2) 使用 harmony 跟蹤
如果你是一個極客,一定要抓到 _nativeAssemblyLoadCnotallow=null 時的調用棧,可以使用 harmony 進行實時跟蹤,即對 AssemblyLoadContext 構造函數進行注入,在后綴補丁中獲取 _nativeAssemblyLoadContext 值即可,這里借助上一篇的 CustomAssemblyLoadContext 代碼例子,參考代碼如下:
[HarmonyPatch(typeof(AssemblyLoadContext), MethodType.Constructor, new Type[] { typeof(string), typeof(bool) })]
publicclassAssemblyLoadContextHook
{
// 后綴補丁 - 在原始方法執行后運行
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 就不要讓 終結器線程 兜底,后者一旦崩潰就會釀成災難性后果。




































