印萍 发表于 9 小时前

记一次 .NET 某企业审批系统 崩溃分析

一:背景

1. 讲故事

今年年初有位朋友在微信上找到我,说他们的系统在客户这边崩掉了,在代码中也加了全局异常处理但还是崩,不知道咋回事,让朋友在客户那边拿程序dump,拿到dump之后开始分析。
二:崩溃分析

1. 为什么会崩溃

既然是崩溃,那就用 !analyze -v 命令观察下windbg给我们整理的崩溃信息,看看可有蛛丝马迹。
0:000> !analyze -v
*******************************************************************************
*                                                                           *
*                        Exception Analysis                                 *
*                                                                           *
*******************************************************************************

CONTEXT:(.ecxr)
eax=006fdb40 ebx=00000005 ecx=00000005 edx=00000000 esi=006fdc04 edi=00000001
eip=768f9132 esp=006fdb40 ebp=006fdb9c iopl=0         nv up ei pl nz ac po nc
cs=0023ss=002bds=002bes=002bfs=0053gs=002b             efl=00000212
KERNELBASE!RaiseException+0x62:
768f9132 8b4c2454      mov   ecx,dword ptr ss:002b:006fdb94=005b1570
Resetting default scope

EXCEPTION_RECORD:(.exr -1)
ExceptionAddress: 768f9132 (KERNELBASE!RaiseException+0x00000062)
   ExceptionCode: e0434352 (CLR exception)
ExceptionFlags: 00000001
NumberParameters: 5
   Parameter: 80131604
   Parameter: 00000000
   Parameter: 00000000
   Parameter: 00000000
   Parameter: 72e90000

PROCESS_NAME:xxx.exe

EXCEPTION_CODE_STR:8007000e

FAULTING_THREAD:ffffffff

STACK_TEXT:
006fde30 07a0941c System_Drawing!System.Drawing.Graphics.FromHdcInternal+0x4c
006fde40 1173bbdb System_Drawing!System.Drawing.Font.GetHeight+0x5b
006fde70 1173bb4b System_Drawing!System.Drawing.Font.get_Height+0xb
006fde80 178658d4 DevExpress_Utils_v13_1!DevExpress.Utils.Frames.NotePanel.CalcSizes+0x304
006fdf4c 17864f4f DevExpress_Utils_v13_1!DevExpress.Utils.Frames.NotePanel.OnPaint+0x9f
006fe0b0 0705ab89 System_Windows_Forms!System.Windows.Forms.Control.PaintWithErrorHandling+0x89
006fe0e0 07058d75 System_Windows_Forms!System.Windows.Forms.Control.WmPaint+0x47d
006fe1d8 07a003db System_Windows_Forms!System.Windows.Forms.Control.WndProc+0x39b
006fe218 0707f821 System_Windows_Forms!System.Windows.Forms.Control+ControlNativeWindow.OnMessage+0x11
006fe220 0707f7f8 System_Windows_Forms!System.Windows.Forms.Control+ControlNativeWindow.WndProc+0xa0
006fe234 0707f227 System_Windows_Forms!System.Windows.Forms.NativeWindow.Callback+0x5f从卦中可以看到 ExceptionCode: e0434352,这很明显是一个 托管异常,既然是 托管异常 那为什么朋友的代码拦截不到呢?这就比较有意思了,那到底是什么类型的托管异常,用 !t 观察一下。
0:000> !t
ThreadCount:      13
UnstartedThread:0
BackgroundThread: 11
PendingThread:    0
DeadThread:       1
Hosted Runtime:   no
                                                                         Lock
       ID OSID ThreadOBJ    State GC Mode   GC Alloc ContextDomain   Count Apt Exception
   0    1 68b4 00970248   26020 Preemptive2BB06608:00000000 0096ad08 1   STA System.Reflection.TargetInvocationException 2baf5884 (nested exceptions)
   2    2 8b90 0098a220   2b220 Preemptive00000000:00000000 0096ad08 0   MTA (Finalizer)
   5    41cc 05e3f308   202b220 Preemptive00000000:00000000 0096ad08 0   MTA
   ...从卦中的 System.Reflection.TargetInvocationException 可以看到这是一个 调用目标异常,由于卦象上有 (nested exceptions) 标记,使用 !pe -nested命令观察异常链走势。
0:000> !pe -nested
Exception object: 2baf5884
Exception type:   System.Reflection.TargetInvocationException
Message:          调用的目标发生了异常。
InnerException:   System.ComponentModel.Win32Exception, Use !PrintException 96e70c07 to see more.
StackTrace (generated):
    SP       IP       Function
    00000000 00000001 mscorlib_ni!System.RuntimeMethodHandle.SerializationInvoke(System.IRuntimeMethodInfo, System.Object, System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext ByRef)+0xffffffff8d074f41
    xxx
    006FDFF4 71891968 mscorlib_ni!System.Resources.RuntimeResourceSet.GetObject(System.String, Boolean, Boolean)+0x1e8
    006FE074 7189B263 mscorlib_ni!System.Resources.RuntimeResourceSet.GetObject(System.String, Boolean)+0x13
    006FE07C 7189331B mscorlib_ni!System.Resources.ResourceManager.GetObject(System.String, System.Globalization.CultureInfo, Boolean)+0x22b
    006FE0DC 7194FD5B mscorlib_ni!System.Resources.ResourceManager.GetObject(System.String)+0xf
    006FE0E0 07CB92F8 xxx.frmBase.InitializeComponent()+0x68
    006FE0F0 0707308A xxx.frmBase..ctor()+0x42
    006FE100 05202B3D xxx.frmErrorMessage..ctor()+0xd
    006FE10C 05202A03 xxx.ExceptionHandler.HandlerErr(System.Exception)+0x23
    006FE11C 052029C9 xxx.UnhandledExceptionManager.Application_ThreadException(System.Object, System.Threading.ThreadExceptionEventArgs)+0x9
    006FE120 052027E8 System_Windows_Forms!System.Windows.Forms.Application+ThreadContext.OnThreadException(System.Exception)+0x88
    006FE15C 05202746 System_Windows_Forms!System.Windows.Forms.Control.WndProcException(System.Exception)+0x16
    006FE168 0520271A System_Windows_Forms!System.Windows.Forms.Control+ControlNativeWindow.OnThreadException(System.Exception)+0xa
    006FE16C 0707F256 System_Windows_Forms!System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)+0x8e

StackTraceString: <none>
HResult: 80131604

Nested exception -------------------------------------------------------------
Exception object: 2baeb3b0
Exception type:   System.OutOfMemoryException
Message:          内存不足。
InnerException:   <none>
StackTrace (generated):
    SP       IP       Function
    006FDE30 07A0941C System_Drawing!System.Drawing.Graphics.FromHdcInternal(IntPtr)+0x4c
    006FDE40 1173BBDB System_Drawing!System.Drawing.Font.GetHeight()+0x5b
    006FDE70 1173BB4B System_Drawing!System.Drawing.Font.get_Height()+0xb
    006FDE80 178658D4 DevExpress_Utils_v13_1!DevExpress.Utils.Frames.NotePanel.CalcSizes(System.Drawing.Graphics)+0x304
    006FDF4C 17864F4F DevExpress_Utils_v13_1!DevExpress.Utils.Frames.NotePanel.OnPaint(System.Windows.Forms.PaintEventArgs)+0x9f
    006FE0B0 0705AB89 System_Windows_Forms!System.Windows.Forms.Control.PaintWithErrorHandling(System.Windows.Forms.PaintEventArgs, Int16)+0x89
    006FE0E0 07058D75 System_Windows_Forms!System.Windows.Forms.Control.WmPaint(System.Windows.Forms.Message ByRef)+0x47d
    006FE1D8 07A003DB System_Windows_Forms!System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)+0x39b
    006FE218 0707F821 System_Windows_Forms!System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)+0x11
    006FE220 0707F7F8 System_Windows_Forms!System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)+0xa0
    006FE234 0707F227 System_Windows_Forms!System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)+0x5f

StackTraceString: <none>
HResult: 8007000e从卦象看,程序是先抛出了 OutOfMemoryException 异常,在全局异常处理中再次抛出 TargetInvocationException 引发程序崩溃,接下来看下为什么会抛 OutOfMemoryException 异常呢?导出源代码观察,简化后如下:
public float GetHeight()
{
    IntPtr dC = UnsafeNativeMethods.GetDC(NativeMethods.NullHandleRef);
    float num = 0f;
    try
    {
      using Graphics graphics = Graphics.FromHdcInternal(dC);
      return GetHeight(graphics);
    }
    finally
    {
      UnsafeNativeMethods.ReleaseDC(NativeMethods.NullHandleRef, new HandleRef(null, dC));
    }
}

public static Graphics FromHdcInternal(IntPtr hdc)
{
    IntPtr graphics = IntPtr.Zero;
    int num = SafeNativeMethods.Gdip.GdipCreateFromHDC(new HandleRef(null, hdc), out graphics);
    if (num != 0)
    {
      throw SafeNativeMethods.Gdip.StatusException(num);
    }
    return new Graphics(graphics);
}

internal static Exception StatusException(int status)
{
    return status switch
    {
      2 => new ArgumentException(SR.GetString("GdiplusInvalidParameter")),
      3 => new OutOfMemoryException(SR.GetString("GdiplusOutOfMemory")),
      ...
    };
}从卦象看,应该就是 GdipCreateFromHDC 方法返回 num=3,继而代码上手工 throw OutOfMemoryException,这里不是引发程序崩溃的直接原因,所以暂时就不深究了,转头关注核心的 HandlerErr 方法。
2. 全局异常处理崩溃探究

接下来回头看为什么 Application_ThreadException -> HandlerErr 全局异常处理中会再次抛异常?观察源代码发现是在初始化 frmErrorMessage 的时候抛错的。。。无语了。。。难怪拦截不到,截图如下:

细心的朋友会发现上面还有一句话 System.ComponentModel.Win32Exception, Use !PrintException 96e70c07 to see more.,即 TargetInvocationException 的内部还有一个异常 Win32Exception,可以用 !pe 显示出来。
0:000> !PrintException /d 2baf5360
Exception object: 2baf5360
Exception type:   System.ComponentModel.Win32Exception
Message:          操作成功完成。
InnerException:   <none>
StackTrace (generated):
    SP       IP       Function
    006FDA9C 07CBA3BA System_Drawing!System.Drawing.Icon.Initialize(Int32, Int32)+0x83a
    006FDB6C 07CB9B49 System_Drawing!System.Drawing.Icon..ctor(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)+0xc1

StackTraceString: <none>
HResult: 80004005
There are nested exceptions on this thread. Run with -nested for details到这里逻辑基本就搞清楚了。

[*]业务代码在 System.Drawing.Graphics.FromHdcInternal 处抛了一个 OutOfMemoryException 进入全局异常拦截。
[*]在全局异常拦截中,又不幸在 new frmErrorMessage() 下 System.Drawing.Icon.Initialize 处抛出了 Win32Exception 异常。
最后就是 Icon.Initialize 函数为什么会抛异常?说实话我也搞不清楚,在 stackoverflowhttps://stackoverflow.com/questions/2356580/system-drawing-icon-constructor-throwing-operation-completed-successfully-exce 上找到了这样的解读,截图如下:

最后给到朋友的建议:

[*]设置多尺寸的 icon 图片,再观察效果。
[*]修改 frmErrorMessage,避免双异常。
三:总结

本次事故是 多异常的联合作战 成功在高压的全局异常拦截方法Application_ThreadException中逃逸,是不是非常的有意思,示警大家,警惕被偷家!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 记一次 .NET 某企业审批系统 崩溃分析