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

一:背景

1. 讲故事

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

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

二:几个疑问解答

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 观察进程树即可。

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


0:005> ~* k0  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 ffffe001b5e51840SessionId: 1  Cid: 0e8c    Peb: 7ff7ab226000  ParentCid: 09d4DirBase: 18079000  ObjectTable: ffffc00036965200  HandleCount: <Data Not Accessible>Image: ConsoleApp2.exe0: 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     0x53: 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=00000095d627f8f9r8=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。

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

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

三:总结

这篇就是我个人对窗口卡死的推测和记录,高级调试不易,如果大家感兴趣,欢迎补充细节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/168406.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot2.x简单集成Flowable

环境和版本 window10 java1.8 mysql8 flowable6 springboot 2.7.6 配置 使用IDEA创建一个SpringBoot项目 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.…

网络协议--IP选路

9.1 引言 选路是IP最重要的功能之一。图9-1是IP层处理过程的简单流程。需要进行选路的数据报可以由本地主机产生&#xff0c;也可以由其他主机产生。在后一种情况下&#xff0c;主机必须配置成一个路由器&#xff0c;否则通过网络接口接收到的数据报&#xff0c;如果目的地址不…

原型与原型链

一、原型&#xff1a;prototype 1.什么是原型&#xff1f; javascript常被描述为一种基于原型的语言&#xff08;每个对象都拥有一个原型对象&#xff09; 当访问一个对象的属性时&#xff0c;它不仅在该对象上寻找&#xff0c;还会寻找该对象的原型&#xff0c;以及该对象原…

Git学习笔记——超详细

Git笔记 安装git&#xff1a; apt install git 创建版本库&#xff1a; git init 添加文件到版本库&#xff1a; git add 文件 提交文件到仓库&#xff1a; git commit -m “注释” 查看仓库当前的状态信息&#xff1a; git status 查看修改内容和之前版本的区别&am…

阿里云服务器x86计算架构ECS实例规格汇总

阿里云企业级服务器基于X86架构的实例规格&#xff0c;每一个vCPU都对应一个处理器核心的超线程&#xff0c;基于ARM架构的实例规格&#xff0c;每一个vCPU都对应一个处理器的物理核心&#xff0c;具有性能稳定且资源独享的特点。阿里云服务器网aliyunfuwuqi.com分享阿里云企业…

STM32 HAL高级定时器正交编码模式案例

STM32 HAL高级定时器正交编码模式案例 &#x1f516;基于stm32F030RBT6单片机采用高级定时器1&#xff0c;编码器模式&#xff0c;测试EC11编码器。 &#x1f3ac;EC11测试效果&#xff1a; &#x1f33f;STM32定时器编码器有3种映射模式: ✨本次采用的是上面的模式3&#x…

【网络】HTTPS讲解(侧重于加密、秘钥、证书的讲解)

HTTPS讲解 前言正式开始安全HTTP和HTTPS的关系什么是加密和解密为什么要加密运营商劫持中间人 常⻅的加密⽅式对称加密⾮对称加密 数据摘要数字签名HTTPS 的⼯作过程⽅案 1 - 只使⽤对称加密&#xff08;不可靠&#xff09;⽅案 2 - 只使⽤⾮对称加密&#xff08;不可靠&#x…

Node编写用户登录接口

目录 前言 服务器 编写登录接口API 使用sql语句查询数据库中是否有该用户 判断密码是否正确 生成JWT的Token字符串 配置解析token的中间件 配置捕获错误中间件 完整的登录接口代码 前言 本文介绍如何使用node编写登录接口以及解密生成token&#xff0c;如何编写注册接…

【Qt】消息机制和事件

文章目录 事件event()事件过滤器案例&#xff1a;检测鼠标事件案例&#xff1a;定时器 事件 事件&#xff08;event&#xff09;是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘&#xff0c;或者是窗口需要重新绘制的时候&#xff0c;都会发出一个相应的事…

Docker Swarm 集群搭建

Docker Swarm Mode Docker Swarm 集群搭建 Docker Swarm 节点维护 Docker Service 创建 1.准备主机 搭建一个 docker swarm 集群&#xff0c;包含 5 个 swarm 节点。这 5 个 swarm 节点的 IP 与暂 时的角色分配如下&#xff08;注意&#xff0c;搭建完成后会切换角色&#xff…

Linux绝对路径和相对路径

在 Linux 中&#xff0c;简单的理解一个文件的路径&#xff0c;指的就是该文件存放的位置。 只要我们告诉 Linux 系统某个文件存放的准确位置&#xff0c;那么它就可以找到这个文件。指明一个文件存放的位置&#xff0c;有 2 种方法&#xff0c;分别是使用绝对路径和相对路径。…

​​​​​​​Python---练习:打印直角三角形(利用wihle循环嵌套)

案例&#xff1a; 打印直角三角形&#xff0c;特征&#xff1a;一共有5行&#xff0c;第1行&#xff0c;有1列。第2行&#xff0c;有2列&#xff0c;第3&#xff0c;有3列。 思考&#xff1a; pycharm里面&#xff0c;输出三角形&#xff0c;因为本来控制台就是长方形&#…

Python桌面应用之XX学院水卡报表查询系统(Tkinter+cx_Oracle)

一、功能样式 Python桌面应用之XX学院水卡报表查询系统功能&#xff1a; 连接Oracle数据库&#xff0c;查询XX学院水卡操作总明细报表&#xff0c;汇总数据报表&#xff0c;个人明细报表&#xff0c;进行预览并且支持导出报表 1.总明细报表样式 2.汇总明细样式 3.个人明细…

硬件信息查看工具 EtreCheckpro mac中文版功能介绍

etrecheckpro mac中文版是一款专业的硬件信息查看工具&#xff0c;它能够快速的检测Mac电脑的软硬件信息&#xff0c;加强用户对自己计算机的了解&#xff0c;EtreCheckPro for Mac下载首先会对电脑的软硬件信息进行扫描收集&#xff0c;之后才会显示出来。EtreCheck Mac版报告…

Python深度学习进阶与应用丨注意力(Attention)机制、Transformer模型、生成式模型、目标检测算法、图神经网络、强化学习详解等

目录 第一章 注意力&#xff08;Attention&#xff09;机制详解 第二章 Transformer模型详解 第三章 生成式模型详解 第四章 目标检测算法详解 第五章 图神经网络详解 第六章 强化学习详解 第七章 深度学习模型可解释性与可视化方法详解 更多应用 近年来&#xff0c;伴…

mac安装+配置python3环境

一、python3下载 官网下载 https://www.python.org/ 二、python3安装 打开下载好的.pkg文件一直继续确认即可。 三、验证是否安装成功 打开终端&#xff0c;输入python3&#xff0c;如果返回python对应的版本信息&#xff0c;则安装成功。 四、python配置 如果需要输入…

`SQL`编写判断是否为工作日函数编写

SQL编写判断是否为工作日函数编写 最近的自己在写一些功能,遇到了对于工作日的判断,我就看了看sql,来吧!~(最近就是好疲惫) 我们一起看看(针对ORACLE) 1.声明: CREATE OR REPLACE PACKAGE GZYW_2109_1214.PKG_FUN_GETDAY_HDAY AS /** * 通过节假日代码获取指定的日期[查找基…

Synchronized同步锁

synchronized 一&#xff0c;介绍 Java中的synchronized关键字用于实现线程同步&#xff0c;可以修饰方法或代码块。 1. 修饰方法&#xff1a;当一个方法被synchronized修饰时&#xff0c;只有获得该方法的锁的线程才能执行该方法。其他线程需要等待锁的释放才能执行该方法。…

Python+requests+pytest+excel+allure 接口自动化测试实战

定义&#xff1a; Unittest是Python标准库中自带的单元测试框架&#xff0c;Unittest有时候也被称为PyUnit&#xff0c;就像JUnit是Java语言的标准单元测试框架一样&#xff0c;Unittest则是Python语言的标准单元测试框架。 Pytest是Python的另一个第三方单元测试库。它的目的…

Linux C语言开发-D2vi编辑器使用

三种模式基础 命令行模式、插入模式和底行模式 :! [命令]&#xff1a;可在命令行模式下执行命令 :w test1.cc可备份当前编辑的文件 :r head.h可为当前文件引入头文件 u&#xff1a;撤销 命令行进入插入模式&#xff1a; i&#xff1a;从当前光标位置之前开始插入 …