浅析 C# Console 控制台为什么也会卡死
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
一:背景1. 讲故事在分析旅程中,总会有几例控制台的意外卡死导致的生产事故,有经验的朋友都知道,控制台卡死一般是动了 虽然知道缘由,但一直没有时间探究底层原理,市面上也没有对这块的底层原理介绍,昨天花了点时间简单探究了下,算是记录分享吧。 二:几个疑问解答1. 界面为什么会卡死相信有很多朋友会有这么一个疑问?控制台程序明明没有 说实话这是一个好问题,其实 Console 之所以能响应 窗口事件,是因为它开了一个配套的 conhost 窗口子进程,用它来承接 UI 事件,为了方便阐述,上一段定时向控制台输出的测试代码。 static void Main(string[] args) { for (int i = 0; i < int.MaxValue; i++) { Console.WriteLine($"i={i}"); Thread.Sleep(1000); } } 将程序跑起来,再用 process explorer 观察进程树即可。 接下来用 windbg 附加到 conshost 进程上,观察下有没有 0:005> ~* k 0 Id: 3ec8.2c20 Suspend: 1 Teb: 000000d2`92014000 Unfrozen # Child-SP RetAddr Call Site 00 000000d2`922ff798 00007fff`a3e45746 ntdll!NtWaitForSingleObject+0x14 01 000000d2`922ff7a0 00007fff`a60b5bf1 KERNELBASE!DeviceIoControl+0x86 02 000000d2`922ff810 00007ff6`9087a790 KERNEL32!DeviceIoControlImplementation+0x81 03 000000d2`922ff860 00007fff`a60b7614 conhost!ConsoleIoThread+0xd0 04 000000d2`922ff9e0 00007fff`a66a26a1 KERNEL32!BaseThreadInitThunk+0x14 05 000000d2`922ffa10 00000000`00000000 ntdll!RtlUserThreadStart+0x21 ... 2 Id: 3ec8.1b70 Suspend: 1 Teb: 000000d2`9201c000 Unfrozen # Child-SP RetAddr Call Site 00 000000d2`9227f858 00007fff`a4891b9e win32u!NtUserGetMessage+0x14 01 000000d2`9227f860 00007ff6`908735c5 user32!GetMessageW+0x2e 02 000000d2`9227f8c0 00007fff`a60b7614 conhost!ConsoleInputThreadProcWin32+0x75 03 000000d2`9227f920 00007fff`a66a26a1 KERNEL32!BaseThreadInitThunk+0x14 04 000000d2`9227f950 00000000`00000000 ntdll!RtlUserThreadStart+0x21 ... 2. 进程间如何通讯这个问题再细化一点就是Client 端通过 熟悉 Windows 编程的朋友都知道:Console.WriteLine 的底层调用逻辑是 说了这么多,怎么去验证呢?
0: kd> !process 0 0 ConsoleApp2.exe PROCESS ffffe001b5e51840 SessionId: 1 Cid: 0e8c Peb: 7ff7ab226000 ParentCid: 09d4 DirBase: 18079000 ObjectTable: ffffc00036965200 HandleCount: <Data Not Accessible> Image: ConsoleApp2.exe 0: kd> bp /p ffffe001b5e51840 nt!IopSynchronousServiceTail 0: kd> g Breakpoint 0 hit nt!IopSynchronousServiceTail: fffff802`a94f3410 48895c2420 mov qword ptr [rsp+20h],rbx 3: kd> k # Child-SP RetAddr Call Site 00 ffffd000`f6477988 fffff802`a94f2e80 nt!IopSynchronousServiceTail 01 ffffd000`f6477990 fffff802`a916db63 nt!NtWriteFile+0x680 02 ffffd000`f6477a90 00007ffc`2fed38aa nt!KiSystemServiceCopyEnd+0x13 03 0000009f`0743dbd8 00007ffc`2cd1d478 ntdll!NtWriteFile+0xa 04 0000009f`0743dbe0 00000000`00000005 0x00007ffc`2cd1d478 05 0000009f`0743dbe8 0000009f`0743dcf0 0x5 06 0000009f`0743dbf0 0000009f`0978c9b8 0x0000009f`0743dcf0 07 0000009f`0743dbf8 00007ffc`2986e442 0x0000009f`0978c9b8 08 0000009f`0743dc00 0000009f`0743dc30 0x00007ffc`2986e442 09 0000009f`0743dc08 0000009f`0743de00 0x0000009f`0743dc30 0a 0000009f`0743dc10 00000000`00000005 0x0000009f`0743de00 0b 0000009f`0743dc18 00000000`00000000 0x5 3: kd> tc nt!IopSynchronousServiceTail+0x70: fffff802`a94f3480 e8ebf1b5ff call nt!IopQueueThreadIrp (fffff802`a9052670)
conhost端的提取逻辑是在 BOOL DeviceIoControl( HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped ); 提取完了之后会通过 0:000> bp conhost!DoWriteConsole 0:000> g Breakpoint 0 hit conhost!DoWriteConsole: 00007ff6`90876ec0 48895c2410 mov qword ptr [rsp+10h],rbx ss:00000095`d627f738=0000000000000000 0:000> r rax=000000000000000c rbx=00000095d627f7b0 rcx=000002370df76cc0 rdx=00000095d627f768 rsi=00000095d627f7c0 rdi=00000095d627f7f0 rip=00007ff690876ec0 rsp=00000095d627f728 rbp=00000095d627f8f9 r8=000002370bedf010 r9=00000095d627f7b0 r10=000002370df76cc0 r11=000002370e0c9d00 r12=00000095d627f970 r13=000002370bedf010 r14=000002370bedf010 r15=0000000000000000 iopl=0 nv up ei pl zr na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 conhost!DoWriteConsole: 00007ff6`90876ec0 48895c2410 mov qword ptr [rsp+10h],rbx ss:00000095`d627f738=0000000000000000 0:000> du 000002370df76cc0 00000237`0df76cc0 "i=18.." 可以看到果然有一个 3. 为什么快捷编辑之后就卡死conhost 的源码不是公开的,不过可以感官上推测出来。
接下来可以验证下 0:004> bp win32u!NtUserGetMessage "dp ebp-30 L2 ; g" 0:004> g 00000095`d61ffae0 00000000`00130e6e 00000000`00000404 00000095`d61ffae0 00000000`00130e6e 00000000`00000404 00000095`d61ffae0 00000000`00130e6e 00000000`00000201 00000095`d61ffae0 00000000`00130e6e 00000000`00000405 00000095`d61ffae0 00000000`00130e6e 00000000`00000202 00000095`d61ffae0 00000000`00130e6e 00000000`00000200 从 chaggpt 中对每个消息码的介绍,可以看到会有一个 405 的自定义消息,这个就是和 三:总结这篇就是我个人对窗口卡死的推测和记录,高级调试不易,如果大家感兴趣,欢迎补充细节。 作者:一线码农 来源:博客园 该文章在 2023/10/28 10:15:11 编辑过 |
关键字查询
相关文章
正在查询... |