一個主程序卡死的跟蹤
阿新 • • 發佈:2019-01-10
原因:一開始想查詢由於ipc初始化順序的問題導致tray卡死的原因,但恰好遇到主程序彈出退出確認框後也卡死了,於是開始查詢原因.
首先是跟蹤程式碼,發現訊息迴圈是活著的,但整個訊息迴圈只能取到timer和paint訊息,使用訊息工具抓視窗,可以看到也可以取到GetItemText等訊息.
(一般來講這時已經可以定位是由於attachthreadinput的原因了,但這時候我還不知道);
仔細想,最可能的情況就是當前執行緒所有視窗都已經被disable掉了,於是仔細檢查程序的所有視窗,沒有發現問題.
回想一下,視窗輸入訊息佇列是這樣的,使用者點選某視窗後系統將該視窗設定為forground視窗,接著擁有該視窗的執行緒成為前臺執行緒,此時系統的原始輸入執行緒內儲存的分發訊息佇列地址切換到改執行緒的虛擬輸入佇列地址上,接著系統接下來將所有輸入訊息投放到這個佇列中去.
視窗收不到滑鼠和鍵盤訊息,首先確定該視窗是不是前臺視窗,是用工具檢測發現,確實是前臺視窗:
但此時的active視窗為null,這是肯定是無法收到鍵盤訊息,於是將其強制設定為active視窗,主程序依然活不起來. 從另一個角度,即使這是的active視窗為0,但理論上應該還是能接收到滑鼠訊息,滑鼠訊息特殊在,系統找到當前滑鼠下的視窗,並將訊息分發到改視窗的執行緒中.此時與forgound和active視窗都沒有關係. 直觀猜測,應該是某個api使用不當造成的,所以接下來就是找到所有和視窗訊息佇列有關的api函式,影響最大的就是attachthreadinput函式,該函式會將兩個執行緒的輸入佇列合併,並共享所有關於前臺視窗等狀態,因此這個函式嫌疑最大. 測試發現,若此函式沒有配對使用則其中一個執行緒的等待必然導致另一個執行緒卡死 使用程式碼搜尋了所有這個函式的呼叫,發現都是配對使用,故排除了attachthreadinput使用不當這個原因. 野史記載,KTHREAD有一個user32thread資料介面記錄了一個THREADINFO來儲存訊息佇列,於是嘗試核心除錯來觀察之,但此變數為void型別,公共符號沒有找到THREADINFO的定義,最終已失敗告終. 另外一個猜想是既然和執行緒有關係,那就殺執行緒吧,於是開始將主程序的所有非主執行緒殺掉,但依然沒有啟用視窗. 鬱悶之後開始殺程序,發現殺死tray程序後主程序被啟用,後測試發現tray send完ipc訊息後馬上返回也不會導致主程序 卡死,焦點再次回到了attachthreadinput使用上.懷疑是某個我們沒有原始碼的模組使用沒有配對造成的bug.於是還是選擇除錯來查詢
由於操作步需要tray啟動主程序在右鍵退出,且操作流暢才可復現bug,所以採用如下辦法來檢查attachthreadinput函式到底在哪個模組沒有配對,故採用如下方法:
將windbg的所有斷點事件禁用,同時:
啟用程序初始斷點並關聯命令:
bp USER32!NtUserAttachThreadInput"|.;.printf \"src:%N,to:%N,isatt:%N\",poi(@esp+4),poi(@esp+8),poi(@esp+c);.echo;kc;gc";gc;
這樣新程序會在呼叫NtUserAttachThreadInput時就會終端並打印出引數:
bug復現後發現:
兩次執行緒的attach和detach並不配對,第二次會直接失敗.程式碼如下:
經過測試發現: 兩個執行緒attach以後,若一個執行緒卡死,則另一個執行緒也會卡住,此時所有滑鼠和鍵盤訊息在兩個執行緒都無法收到,另外那個執行緒會阻塞在GetMessage或者類似的等待函式中,但該執行緒仍然可以接受諸如timer之類的訊息,原因如下: 當兩個執行緒attach以後輸入佇列合併為一個,此時要想取出硬體輸入訊息,必須兩個執行緒同事到場,也就是說兩個執行緒的getmessage同步去取才會取得出來,否則無法取出,相當於一把需要兩把鑰匙才能開啟的鎖,持鑰匙的 兩人同時在場才能開門,而timer等訊息在另外一個訊息佇列中,則可以順利取出. 原因確定就好復現bug了,從tray啟動主程序後不斷的右鍵點選tray,使焦點保持在menu上,此時主程序啟動會將自己與tray的主執行緒attach,之後從tray中sendipc訊息到主程序,然後tray會等待主程序的回覆因此tray終止了訊息迴圈,等待在sendipcmessage上,因此導致了baiduaan也無法接受滑鼠訊息從而卡住. 這同時也說明了為什麼主程序除錯的時候經常卡住vs,windbg或者工作列等的原因. 總結: 1,若發生接受不到滑鼠訊息,且訊息迴圈或者則考慮1,是否所有視窗都被diable,2,是否與另外一個執行緒有attach關係 2,不要輕易排除一個問題,需要有確切的理由和仔細的檢查才可以排除. 3,慎用attachthreadinput. 4,其實如果剛開始死盯住attachthreadinput會很快找到原因,馬虎的測試和粗略的程式碼檢查導致了後來的糾結 參考 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644927(v=vs.85).aspx
但此時的active視窗為null,這是肯定是無法收到鍵盤訊息,於是將其強制設定為active視窗,主程序依然活不起來. 從另一個角度,即使這是的active視窗為0,但理論上應該還是能接收到滑鼠訊息,滑鼠訊息特殊在,系統找到當前滑鼠下的視窗,並將訊息分發到改視窗的執行緒中.此時與forgound和active視窗都沒有關係. 直觀猜測,應該是某個api使用不當造成的,所以接下來就是找到所有和視窗訊息佇列有關的api函式,影響最大的就是attachthreadinput函式,該函式會將兩個執行緒的輸入佇列合併,並共享所有關於前臺視窗等狀態,因此這個函式嫌疑最大. 測試發現,若此函式沒有配對使用則其中一個執行緒的等待必然導致另一個執行緒卡死 使用程式碼搜尋了所有這個函式的呼叫,發現都是配對使用,故排除了attachthreadinput使用不當這個原因. 野史記載,KTHREAD有一個user32thread資料介面記錄了一個THREADINFO來儲存訊息佇列,於是嘗試核心除錯來觀察之,但此變數為void型別,公共符號沒有找到THREADINFO的定義,最終已失敗告終. 另外一個猜想是既然和執行緒有關係,那就殺執行緒吧,於是開始將主程序的所有非主執行緒殺掉,但依然沒有啟用視窗. 鬱悶之後開始殺程序,發現殺死tray程序後主程序被啟用,後測試發現tray send完ipc訊息後馬上返回也不會導致主程序
兩次執行緒的attach和detach並不配對,第二次會直接失敗.程式碼如下:
經過測試發現: 兩個執行緒attach以後,若一個執行緒卡死,則另一個執行緒也會卡住,此時所有滑鼠和鍵盤訊息在兩個執行緒都無法收到,另外那個執行緒會阻塞在GetMessage或者類似的等待函式中,但該執行緒仍然可以接受諸如timer之類的訊息,原因如下: 當兩個執行緒attach以後輸入佇列合併為一個,此時要想取出硬體輸入訊息,必須兩個執行緒同事到場,也就是說兩個執行緒的getmessage同步去取才會取得出來,否則無法取出,相當於一把需要兩把鑰匙才能開啟的鎖,持鑰匙的 兩人同時在場才能開門,而timer等訊息在另外一個訊息佇列中,則可以順利取出. 原因確定就好復現bug了,從tray啟動主程序後不斷的右鍵點選tray,使焦點保持在menu上,此時主程序啟動會將自己與tray的主執行緒attach,之後從tray中sendipc訊息到主程序,然後tray會等待主程序的回覆因此tray終止了訊息迴圈,等待在sendipcmessage上,因此導致了baiduaan也無法接受滑鼠訊息從而卡住. 這同時也說明了為什麼主程序除錯的時候經常卡住vs,windbg或者工作列等的原因. 總結: 1,若發生接受不到滑鼠訊息,且訊息迴圈或者則考慮1,是否所有視窗都被diable,2,是否與另外一個執行緒有attach關係 2,不要輕易排除一個問題,需要有確切的理由和仔細的檢查才可以排除. 3,慎用attachthreadinput. 4,其實如果剛開始死盯住attachthreadinput會很快找到原因,馬虎的測試和粗略的程式碼檢查導致了後來的糾結 參考 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644927(v=vs.85).aspx