欧美成人精品手机在线观看_69视频国产_动漫精品第一页_日韩中文字幕网 - 日本欧美一区二区

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

淺析 C# Console 控制臺為什么也會卡死

freeflydom
2023年10月28日 10:15 本文熱度 542

一:背景

1. 講故事

在分析旅程中,總會有幾例控制臺的意外卡死導(dǎo)致的生產(chǎn)事故,有經(jīng)驗(yàn)的朋友都知道,控制臺卡死一般是動了 快速編輯窗口 的緣故,截圖如下:

雖然知道緣由,但一直沒有時(shí)間探究底層原理,市面上也沒有對這塊的底層原理介紹,昨天花了點(diǎn)時(shí)間簡單探究了下,算是記錄分享吧。

二:幾個(gè)疑問解答

1. 界面為什么會卡死

相信有很多朋友會有這么一個(gè)疑問?控制臺程序明明沒有 message loop 機(jī)制,為什么還能響應(yīng) 窗口事件 呢?

說實(shí)話這是一個(gè)好問題,其實(shí) Console 之所以能響應(yīng) 窗口事件,是因?yàn)樗_了一個(gè)配套的 conhost 窗口子進(jìn)程,用它來承接 UI 事件,為了方便闡述,上一段定時(shí)向控制臺輸出的測試代碼。

        static void Main(string[] args)
        {            for (int i = 0; i < int.MaxValue; i++)
            {
                Console.WriteLine($"i={i}");
                Thread.Sleep(1000);
            }
        }

將程序跑起來,再用 process explorer 觀察進(jìn)程樹即可。

接下來用 windbg 附加到 conshost 進(jìn)程上,觀察下有沒有 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. 進(jìn)程間如何通訊

這個(gè)問題再細(xì)化一點(diǎn)就是Client 端通過 Console.WriteLine($"i={i}"); 寫入的內(nèi)容是如何被 Server 端的conhost!ConsoleIoThread 方法接收到的。

熟悉 Windows 編程的朋友都知道:Console.WriteLine 的底層調(diào)用邏輯是 ntdll!NtWriteFile -> nt!IopSynchronousServiceTail ,前者是用戶態(tài)進(jìn)入到內(nèi)核態(tài)的網(wǎng)關(guān)函數(shù),后者是用戶將irp丟到線程的請求包隊(duì)列后進(jìn)入休眠(KeWaitForSingleObject),直到驅(qū)動提取并處理完之后喚醒。

說了這么多,怎么去驗(yàn)證呢?

  • 客戶端下斷點(diǎn)


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)

  • 服務(wù)端下斷點(diǎn)

conhost端的提取邏輯是在 conhost!ConsoleIoThread 方法中,它的內(nèi)部調(diào)用的是 kernelbase!DeviceIoControl 函數(shù),這個(gè)方法挺有意思,可以直接給驅(qū)動程序下達(dá)命令,方法簽名如下:

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

提取完了之后會通過 conhost!DoWriteConsole 向控制臺輸出,接下來可以下個(gè)斷點(diǎn)驗(yàn)證下。


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

可以看到果然有一個(gè) i=18,這里要提醒一下,要想看方法的順序邏輯,可以借助 perfview。

3. 為什么快捷編輯之后就卡死

conhost 的源碼不是公開的,不過可以感官上推測出來。

  1. 快速編輯窗口 被用戶啟用后, GetMessage 會感知到這個(gè)自定義的 MSG 消息。

  2. 這個(gè)消息的邏輯會讓 server 處理Client消息的流程一直處于等待中,導(dǎo)致 Client 的 IopSynchronousServiceTail 不能被喚醒,導(dǎo)致一直處于阻塞中,類似 Task 的完成狀態(tài)一直不被設(shè)置。

接下來可以驗(yàn)證下 快速編輯窗口 的處理消息碼是多少,只要在控制臺點(diǎn)一下鼠標(biāo)。參考腳本如下:


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 中對每個(gè)消息碼的介紹,可以看到會有一個(gè) 405 的自定義消息,這個(gè)就是和 快速編輯窗口 有關(guān)的。

三:總結(jié)

這篇就是我個(gè)人對窗口卡死的推測和記錄,高級調(diào)試不易,如果大家感興趣,歡迎補(bǔ)充細(xì)節(jié)。

作者:一線碼農(nóng)

來源:博客園

查看原文


該文章在 2023/10/28 10:15:11 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved