《Windows 用戶態程序高效排錯》市場價
元 特價
元 購買>>
不可思議一個API同時打開了兩個文件
問題描述
有一天一個電話打進來客戶非常氣憤地抱怨調用ShellExecute這個API傳入本地的一個文本文件的路徑有時候會在打開這個TXT的同時還打開另一個不相干的文件!客戶非常明確地告訴我所有的參數肯定沒有傳錯而且ShellExecute的返回值也正確
第一印象
我仔細思考了兩分鐘後告訴客戶不可能發生上述問題如果參數正確API的行為肯定是唯一的(在往下面閱讀以前請您花兩分鐘思考這有可能嗎?)
我的第一感覺是用戶的參數傳遞錯了比如打開的是一個BAT文件或者打開方式被用戶修改過這樣才有可能同時打開兩個文件但如果真的是這樣程序的行為也應該是唯一的而不會有時候才出現
事實是我錯了在查看了用戶的截圖並且用客戶的代碼在本地重現了一次問題後我不得不相信一次ShellExecute調用會偶爾打開兩個文件
在下面的分析中你會看到該問題是如何發生的但是請先記住第一點相信事實不要相信經驗如果我胸有成竹地告訴客戶這種問題肯定跟代碼不相關唯一的可能性是出自一些防病毒之類的程序(防病毒程序的確是很多問題的根源但是有時也是擋箭牌)那麼就失去了找到真相的機會
深入分析
背景是這樣客戶用MFC開發了一個Dialog Application 上面放了一個HTMLView Control這個Control會顯示本地的一個HTML文件這個HTML文件會顯示一個GIF圖片當用戶在這個GIF圖片上點鼠標右鍵默認的IE右鍵菜單被用戶自定義的菜單代替當用戶點擊其中一個菜單命令後在客戶的消息響應函數中調用ShellExecute打開本地的一個TXT文件TXT文件類型跟UltraEdit綁定所以文件會被UltraEdit打開當問題發生的時候UltraEdit會在打開TXT的同時打開另外一個二進制的文件經過分析這個二進制的文件就是那個 HTML中顯示的GIF文件
客戶在PreTranslateMessage函數中用下面的代碼彈出自定義菜單代替IE默認菜單
if(pMsg>message==WM_RBUTTONDOWN)
{CMenu menu;
menuLoadMenu(IDR_MENU_MSG_OPEN);
CMenu *pMenu = menuGetSubMenu();
if (pMenu)
{
CPoint pt;
GetCursorPos(&pt);
pMenu>TrackPopupMenu(TPM_LEFTALIGN ptx pty this);
}
return FALSE;}
有了這樣的背景問題跟用戶的代碼就聯系起來了額外打開的文件(GIF)的確是跟這個程序(HTMLView Control)相關的那下一步我們該做什麼呢?
當時我的思路是這樣的ShellExecue能打開這個GIF文件肯定在某種程度上這個GIF跟ShellExecue的調用有聯系 至少ShellExecute要知道這個GIF的路徑才可以打開它為了進一步分析可以選擇的步驟是
打開Windbg(和Visual Studio Debugger類似的調試工具後面有詳細介紹)在ShellExecute上設斷點然後開始調試一步一步走下去看這個GIF文件是如何打開的
通過閱讀客戶的代碼和做進一步的測試來縮小問題的范圍
仔細思考後我放棄了第一種原因是首先打開文件的操作不是在ShellExecute中完成的而是在UltraEdit中完成的目標和范圍都太大操作起來很難其次問題是偶爾發生的所以無法保證每次調試都能夠重現問題
通過上面的分析和測試沒有花費太多的精力就找到了解決問題的方法但是這個時候就開始高興未免太早這裡把False修改成True給客戶帶來了新問題上面的分析也不足以揭示為什麼簡單的True/False就給ShellExecute帶來這麼大的影響讓我們繼續走下去
首先解釋一下帶來的新問題是什麼在菜單的消息響應函數中客戶除了用ShellExecute打開文本文件外還需要用HTMLDocument的一些屬性和操作來獲取當前用戶是在HTML頁面中的哪一個tag上點的鼠標比如是在<img>標簽呢還是<div>標簽如果修改成True上面的功能就無法正常工作這個問題很好解釋由於HTMLView無法繼續處理鼠標消息當然得不到用戶的操作信息
問題的另一方面還是在於為何會打開兩個文件PreTranslateMessage的返回值帶來兩種不同的表現所以我考慮分析ShellExecute在兩種情況下表現的差異首先反匯編ShellExecute發現裡面的實現非常復雜不可能首先讀懂代碼後再進行調試一個高效的辦法是通過Windbg的wt命令(這裡不詳細介紹這些命令的用法留到下一章)觀察ShellExecute內部的調用棧(callstack)研究後發現ShellExecute是通過DDE的方法來打開目標文件的在研究了MSDN的說明以後了解到DDE其實內部是依靠WindowsMessage來完成進程之間通信的於是又在PostMessageW/SendMessageW上設定條件斷點每次調用這兩個函數的時候就把WindowsMessage的詳細信息和callstack在windbg中打印出來以便比較不同情況下的差異最後看到發生問題時候的callstack是這樣的您能想清楚原因嗎?
From:http://tw.wingwit.com/Article/os/xtgl/201311/10174.html