An analysis of UI hanging of a lithium battery detection program for a new energy vehicle based on. Net

More high quality dry goods : See my GitHub: dotnetfly

One : background

1. Tell a story

Things in this world are strange , In the last two months, three friends came to me , Let me help analyze his program hangon The phenomenon , These three dump They are respectively concerned with : Medical care , New energy ,POS System . The screenshot is as follows :

Then why take one of them New energy What's up ? Because this friend solved it most smoothly , After providing some clues, the problem code is found out smoothly .

Say something out of the question , I personally winform It's unfamiliar , How can it appear in my vision again and again , So I decided to write an article to summarize , There are not many references , Limited ability , I can only try to interpret it myself .

Two : Windbg analysis

1. Procedural phenomena

Make complaints about it before you start. , These bosses caught it dump Files are wow64, Also is to use 64bit The task manager caught it 32bit The program , See output below :

00000000`756d2e09 c3 ret
 Copy code 

So it's not easy to use windbg preview To analyze , First of all, use !wow64exts.sw take 64bit To 32bit , This article uses windbg10, Okay , Since it is UI stuck , The first thing to bear the brunt is to have a look UI The thread is stuck by something , You can use the command !clrstack to glance at .

0:000:x86> !clrstack
OS Thread Id: 0x1d90 (0)
Child SP IP Call Site
0019ee6c 0000002b [HelperMethodFrame_1OBJ: 0019ee6c] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
0019ef50 6c4fc7c1 System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)
0019ef68 6c4fc788 System.Threading.WaitHandle.WaitOne(Int32, Boolean)
0019ef7c 6e094e7e System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
0019efbc 6e463b96 System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
0019efc0 6e09722b [InlinedCallFrame: 0019efc0]
0019f044 6e09722b System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
0019f078 6e318556 System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback, System.Object)
0019f090 6eef65a8 Microsoft.Win32.SystemEvents+SystemEventInvokeInfo.Invoke(Boolean, System.Object[])
0019f0c4 6eff850c Microsoft.Win32.SystemEvents.RaiseEvent(Boolean, System.Object, System.Object[])
0019f110 6eddb134 Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(Int32, IntPtr, IntPtr)
0019f130 6f01f0b0 Microsoft.Win32.SystemEvents.WindowProc(IntPtr, Int32, IntPtr, IntPtr)
0019f134 001cd246 [InlinedCallFrame: 0019f134]
0019f2e4 001cd246 [InlinedCallFrame: 0019f2e4]
0019f2e0 6dbaefdc DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef)
0019f2e4 6db5e039 [InlinedCallFrame: 0019f2e4] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
0019f318 6db5e039 System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
0019f31c 6db5dc49 [InlinedCallFrame: 0019f31c]
0019f3a4 6db5dc49 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
0019f3f4 6db5dac0 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
0019f420 6db4a7b1 System.Windows.Forms.Application.Run(System.Windows.Forms.Form)
0019f434 003504a3 xxx.Program.Main()
0019f5a8 6f191366 [GCFrame: 0019f5a8]
 Copy code 

From the call stack , The code is due to Microsoft.Win32.SystemEvents.OnUserPreferenceChanged Be triggered , And then in System.Windows.Forms.Control.WaitForWaitHandle Stuck at , You can see from the name of the former ,OnUserPreferenceChanged( User preferences ) Is a system level Microsoft.Win32.SystemEvents event , What exactly caused this system event to be triggered , So I checked the information , It's about : If the application's Control These system level events are registered , So when windows issue WM_SYSCOLORCHANGE, WM_DISPLAYCHANGED, WM_THEMECHANGED( The theme , Preferences , Interface display ) When the news , These register system level events Control Of handle Will be executed , For example, refresh yourself .

If you think the words are awkward , I'll try to draw a picture to illustrate .

essentially , It is an observer model , But this sum UI It doesn't matter if it gets stuck , At best, it is the background knowledge you need to know before solving the problem , There is another important concept that has not been mentioned , That's it : WindowsFormsSynchronizationContext .

2. understand WindowsFormsSynchronizationContext

Why do you have to understand WindowsFormsSynchronizationContext Well ? Understand it , You'll understand why it gets stuck , We know winform Of UI A thread is a thread STA Model , One of its characteristics is single thread , Other threads want to update Control, All need to be dispatched to UI Thread Queue In line , Does not exist and does not allow concurrent updates Control The situation of , Refer to the following :

0:000:x86> !t
ThreadCount: 207
UnstartedThread: 0
BackgroundThread: 206
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 1d90 003e2430 2026020 Preemptive 00000000:00000000 003db8b8 0 STA
2 2 2804 003f0188 2b220 Preemptive 00000000:00000000 003db8b8 0 MTA (Finalizer)
 Copy code 

Winform There is another feature : It will create Control Thread with one WindowsFormsSynchronizationContext Synchronization context , That is, if other threads want to update that Control, Then the updated value must be passed through WindowsFormsSynchronizationContext Schedule to the thread that created it , Threads here are not just UI Thread , With this basic knowledge , Let's analyze why it got stuck .

3. The real reason for the seizure

Look again at the call stack of the main thread , It's going this way : OnUserPreferenceChanged -> WindowsFormsSynchronizationContext.Send -> Control.MarshaledInvoke -> WaitHandle.WaitOneNative , ha-ha , Any questions ???

Sharp eyed friends will find , Why does the main thread call WindowsFormsSynchronizationContext.Send Methods? ? Is that registration handler Of Control Wasn't it created by the main thread ? To answer this question , Need to see WindowsFormsSynchronizationContext Class destinationThreadRef field value , Source code is as follows :

public sealed class WindowsFormsSynchronizationContext : SynchronizationContext, IDisposable
private Control controlToSendTo;
private WeakReference destinationThreadRef;
 Copy code 

It can be used !dso The command puts the thread on the stack WindowsFormsSynchronizationContext Find it out , The simplified output is as follows :

0:000:x86> !dso
OS Thread Id: 0x1d90 (0)
ESP/REG Object Name
0019ED70 027e441c System.Windows.Forms.WindowsFormsSynchronizationContext
0019EDC8 112ee43c Microsoft.Win32.SafeHandles.SafeWaitHandle
0019F078 11098b74 System.Windows.Forms.WindowsFormsSynchronizationContext
0019F080 1107487c Microsoft.Win32.SystemEvents+SystemEventInvokeInfo
0019F08C 10fa386c System.Object[] (System.Object[])
0019F090 1107487c Microsoft.Win32.SystemEvents+SystemEventInvokeInfo
0019F0AC 027ebf60 System.Object
0019F0C0 10fa386c System.Object[] (System.Object[])
0019F0C8 027ebe3c System.Object
0019F0CC 10fa388c Microsoft.Win32.SystemEvents+SystemEventInvokeInfo[]
0:000:x86> !do 11098b74
Name: System.Windows.Forms.WindowsFormsSynchronizationContext
MT Field Offset Type VT Attr Value Name
6dbd8f30 4002567 8 ...ows.Forms.Control 0 instance 11098c24 controlToSendTo
6c667c2c 4002568 c System.WeakReference 0 instance 11098b88 destinationThreadRef
0:000:x86> !do 11098b88
Name: System.WeakReference
MT Field Offset Type VT Attr Value Name
6c66938c 4000705 4 System.IntPtr 1 instance 86e426c m_handle
0:000:x86> !do poi(86e426c) Name: System.Threading.Thread Fields: MT Field Offset Type VT Attr Value Name 6c663cc4 40018a5 24 System.Int32 1 instance 2 m_Priority 6c663cc4 40018a6 28 System.Int32 1 instance 7 m_ManagedThreadId 6c66f3d8 40018a7 2c System.Boolean 1 instance 1 m_ExecutionContextBelongsToOuterScope  Copy code 

Sure enough , From the perspective of divination Thread=7 On thread Control System events registered , that Thread=7 What thread is it ? Can pass !t see .

0:028:x86> !t
ThreadCount: 207
UnstartedThread: 0
BackgroundThread: 206
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 1d90 003e2430 2026020 Preemptive 00000000:00000000 003db8b8 0 STA
2 2 2804 003f0188 2b220 Preemptive 00000000:00000000 003db8b8 0 MTA (Finalizer)
28 7 27f0 0b29cd30 3029220 Preemptive 00000000:00000000 003db8b8 0 MTA (Threadpool Worker)
 Copy code 

From the perspective of divination : ID=7 It's a thread pool thread , And it's MTA Pattern , Logically, it should Create controls Logical scheduling for UI Threads , Instead of creating it yourself , therefore UI The thread has been WaitOneNative Waiting for 7 Thread message pump response , So it leads to indefinite waiting .

4. 7 What control did thread No. create

This is another question to test the underlying knowledge , It also bothers me so far , It's too hard , I tried to UserPreferenceChangedEventHandler All on the event handles Pull it out , I wrote a script, which is roughly as follows :

"use strict";
// 32bit
let arr = ["xxxx"];
function initializeScript() { return [new host.apiVersionSupport(1, 7)]; }
function log(str) { host.diagnostics.debugLog(str + "\n"); }
function exec(str) { return host.namespace.Debugger.Utility.Control.ExecuteCommand(str); }
function invokeScript() {
for (var address of arr) {
var commandText = ".printf \"%04x\", poi(poi(poi(poi(" + address + "+0x4)+0xc)+0x4))";
var output = exec(commandText).First();
if (parseInt(output) == 0) continue; //not exists thread info
commandText = ".printf \"%04x\", poi(poi(poi(poi(poi(" + address + "+0x4)+0xc)+0x4))+0x28)";
output = exec(commandText).First();
//thread id
var tid = parseInt(output);
if (tid > 1) log("Thread=" + tid + ",systemEventInvokeInfo=" + address);
 Copy code 

Output results :

||2:2:438> !wow64exts.sw Switched to Guest (WoW) mode Thread=7,systemEventInvokeInfo=1107487c
 Copy code 

Found from output 7 Number thread Corresponding processing event systemEventInvokeInfo , Then trace it as follows :

0:028:x86> !do 1107487c
Name: Microsoft.Win32.SystemEvents+SystemEventInvokeInfo
MT Field Offset Type VT Attr Value Name
6c65ae34 4002e9f 4 ...ronizationContext 0 instance 11098b74 _syncContext
6c6635ac 4002ea0 8 System.Delegate 0 instance 1107485c _delegate
0:028:x86> !DumpObj /d 1107485c
Name: Microsoft.Win32.UserPreferenceChangedEventHandler
MT Field Offset Type VT Attr Value Name
6c66211c 40002b0 4 System.Object 0 instance 110747bc _target
6c66211c 40002b1 8 System.Object 0 instance 00000000 _methodBase
6c66938c 40002b2 c System.IntPtr 1 instance 6ebdc00 _methodPtr
6c66938c 40002b3 10 System.IntPtr 1 instance 0 _methodPtrAux
6c66211c 40002bd 14 System.Object 0 instance 00000000 _invocationList
6c66938c 40002be 18 System.IntPtr 1 instance 0 _invocationCount
0:028:x86> !DumpObj /d 110747bc
Name: DevExpress.LookAndFeel.Design.UserLookAndFeelDefault
 Copy code 

As you can see from the output , The last control is DevExpress.LookAndFeel.Design.UserLookAndFeelDefault , I thought I found the answer , Take this result google, result devExpress Play the ball , The screenshot is as follows :

cough , It seems that we can't check it here , There are other materials that say Control Register across threads handler It will pass by MarshalingControl , So in this control bp Breakpoints can be caught , The reference command is as follows :

bp xxx ".echo MarshalingControl creation detected. Callstack follows.;!clrstack;.echo  Copy code 

I can't verify it here .

3、 ... and : summary

Although I know that all three accidents are due to Not UI Thread creation Control Caused by , But it is a pity that I have tried my best to find the most important culprit , But happily, based on the existing clues, a friend finally found the problem code , I'm so happy for him , The solution is simple , take Create controls adopt Invoke Dispatch to UI Threads perform . The screenshot is as follows :

Through this case , I found that advanced debugging is really a hard journey , Tune and cherish !

Please bring the original link to reprint ,thank
Similar articles