记一次 .NET 某CRM物流行业管理系统 崩溃分析

记一次 .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 

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

  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 导致的,截图如下:

记一次 .NET 某CRM物流行业管理系统 崩溃分析

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

记一次 .NET 某CRM物流行业管理系统 崩溃分析

说实话看到这个源头就蒙圈了,_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); } } 

记一次 .NET 某CRM物流行业管理系统 崩溃分析

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

三:总结

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

记一次 .NET 某CRM物流行业管理系统 崩溃分析
  • 本文作者:WAP站长网
  • 本文链接: https://wapzz.net/post-27797.html
  • 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
本站部分内容来源于网络转载,仅供学习交流使用。如涉及版权问题,请及时联系我们,我们将第一时间处理。
文章很赞!支持一下吧 还没有人为TA充电
为TA充电
还没有人为TA充电
0
0
  • 支付宝打赏
    支付宝扫一扫
  • 微信打赏
    微信扫一扫
感谢支持
文章很赞!支持一下吧
关于作者
2.8W+
9
1
2
WAP站长官方

国产化浪潮下,Gitee如何成为技术团队的项目管理新基建?

上一篇

【URP】[投影Projector]解析与应用

下一篇
评论区
内容为空

这一切,似未曾拥有

  • 复制图片
按住ctrl可打开默认菜单