浅析 C# Console 控制台为什么也会卡死

这篇具有很好参考价值的文章主要介绍了浅析 C# Console 控制台为什么也会卡死。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一:背景

1. 讲故事

在分析旅程中,总会有几例控制台的意外卡死导致的生产事故,有经验的朋友都知道,控制台卡死一般是动了 快速编辑窗口 的缘故,截图如下:

浅析 C# Console 控制台为什么也会卡死

虽然知道缘由,但一直没有时间探究底层原理,市面上也没有对这块的底层原理介绍,昨天花了点时间简单探究了下,算是记录分享吧。

二:几个疑问解答

1. 界面为什么会卡死

相信有很多朋友会有这么一个疑问?控制台程序明明没有 message loop 机制,为什么还能响应 窗口事件 呢?

说实话这是一个好问题,其实 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 观察进程树即可。

浅析 C# Console 控制台为什么也会卡死

接下来用 windbg 附加到 conshost 进程上,观察下有没有 GetMessageW


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 端通过 Console.WriteLine($"i={i}"); 写入的内容是如何被 Server 端的conhost!ConsoleIoThread 方法接收到的。

熟悉 Windows 编程的朋友都知道:Console.WriteLine 的底层调用逻辑是 ntdll!NtWriteFile -> nt!IopSynchronousServiceTail ,前者是用户态进入到内核态的网关函数,后者是用户将irp丢到线程的请求包队列后进入休眠(KeWaitForSingleObject),直到驱动提取并处理完之后唤醒。

说了这么多,怎么去验证呢?

  • 客户端下断点

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端的提取逻辑是在 conhost!ConsoleIoThread 方法中,它的内部调用的是 kernelbase!DeviceIoControl 函数,这个方法挺有意思,可以直接给驱动程序下达命令,方法签名如下:


BOOL DeviceIoControl(
  HANDLE       hDevice,
  DWORD        dwIoControlCode,
  LPVOID       lpInBuffer,
  DWORD        nInBufferSize,
  LPVOID       lpOutBuffer,
  DWORD        nOutBufferSize,
  LPDWORD      lpBytesReturned,
  LPOVERLAPPED lpOverlapped
);

提取完了之后会通过 conhost!DoWriteConsole 向控制台输出,接下来可以下个断点验证下。


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.."

可以看到果然有一个 i=18,这里要提醒一下,要想看方法的顺序逻辑,可以借助 perfview。

浅析 C# Console 控制台为什么也会卡死

3. 为什么快捷编辑之后就卡死

conhost 的源码不是公开的,不过可以感官上推测出来。

  1. 快速编辑窗口 被用户启用后, GetMessage 会感知到这个自定义的 MSG 消息。

  2. 这个消息的逻辑会让 server 处理Client消息的流程一直处于等待中,导致 Client 的 IopSynchronousServiceTail 不能被唤醒,导致一直处于阻塞中,类似 Task 的完成状态一直不被设置。

接下来可以验证下 快速编辑窗口 的处理消息码是多少,只要在控制台点一下鼠标。参考脚本如下:


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

浅析 C# Console 控制台为什么也会卡死

从 chaggpt 中对每个消息码的介绍,可以看到会有一个 405 的自定义消息,这个就是和 快速编辑窗口 有关的。

三:总结

这篇就是我个人对窗口卡死的推测和记录,高级调试不易,如果大家感兴趣,欢迎补充细节。文章来源地址https://www.toymoban.com/news/detail-711418.html

浅析 C# Console 控制台为什么也会卡死

到了这里,关于浅析 C# Console 控制台为什么也会卡死的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Postman进阶篇(二)-console控制台(查看接口日志、调试代码)

    如果你觉得这篇内容对你有所帮助,有所启发,我想邀请你帮我个忙: 关注我的公众号【软件测试必备技能】,经常会分享软件测试干货。 搜索公众号名称,或点击文章底部卡片即可加我。 ———————————————————————————————— 进入postm

    2024年02月02日
    浏览(73)
  • Unity入门系列之Inspector检查窗口和Console控制台窗口

    目录 1.基本内容 2.Inspector检查窗口 3.Console控制台窗口 Inspector检查窗口:查看场景中游戏对象关联的C#脚本信息。 Console控制台窗口:用于查看调试信息的窗口,报错警告,测试打印都可以显示在其中。 不选择场景中游戏对象或不进行任何相关设置,该界面不会显示任何信息。

    2024年02月05日
    浏览(44)
  • 【IDE 小程序】小程序控制台 不打印 console.log问题

    全局搜索compress.drop_console(一般在config文件中),设置为false,再重新打开小程序即可

    2024年02月13日
    浏览(94)
  • FISCO-BCOS 命令交互控制台 Console-命令大全(超详细)

    引言 此文章基于fisco-bocs官方技术文档进行整理并加以解释,在这里可以快速理解命令的含义和更快地上手,可以当作命令词典使用。 前提条件 部署好区块链网络 配置好console,即拷贝配置文件等等 console 目录如下: 使用账户生成脚本生成账户(非国密版) PEM格式 p12格式 启动控

    2024年02月04日
    浏览(65)
  • PyCharm 调试过程中控制台 (Console) 窗口内运行命令 - 实时获取中间状态

    ​​​ [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

    2024年02月20日
    浏览(69)
  • UE4,UE5虚幻引擎,Command Console控制台命令,参数集

    1、Command Console控制台命令,虚幻官方文档 https://docs.unrealengine.com/5.0/zh-CN/unreal-editor-interface/ 2、在cmd控制台 help 并按 Enter 3、自动跳转到网页,在网页中,可以查找所有的命令行参数。

    2024年02月15日
    浏览(87)
  • macbook Safari 如何打开F12 Console 控制台 开发者工具 Developer Tools

    首先要启用开发者模式,然后就可以打开开发者工具。 Safari–Preferences呼出首选项面板(或用快捷键 command+, 直接呼出)。 在 Advanced 菜单面板下,勾选 Show Develop Menu in menu Bar 。 顶部菜单栏在 勾选这个选项之前 : 勾选后 ,在 Bookmarks 和 Window 之间多了一个 Develop : 点击这个

    2024年02月11日
    浏览(78)
  • C# 控制台进度条

    https://github.com/Mpdreamz/shellprogressbar

    2024年01月23日
    浏览(76)
  • C# 在控制台整齐的输出 DataTable

    效果: 在 Winform 平台,可以用 DataGridView 这样的控件来显示数据库的表单数据,但在 C# 控制台项目中,如果有用到数据库查询,我们想看看查询语句的效果,就比较困难了,比如,我随意写了一个控制台输出,代码如下: 效果: 在 Navicat 16 for MySQL 软件中的查询结果 由于没

    2024年02月12日
    浏览(64)
  • C#控制台应用程序如何添加窗口关闭事件?

    公司有一个控制台应用程序,在关闭控制台应用程序窗口前,想处理一下业务逻辑。还有比如误操作关闭了,或者像消息队列启动了发送消息提醒,那关闭了窗口代表控制台应用销毁了,也需要发送消息通知。那这个时候添加关闭窗口事件就派上用场了。 很多小伙伴问,有没

    2024年02月13日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包