繼續說那些奇葩的Bug。
靠不住的系統元件:Vista和Speech Recogition
我們遊戲使用了語音辨識,使用了DirectX裏面的XAudio來采集聲音。Windows Vista裏面有一個語音辨識的元件,啟動那個程式,然後玩我們的遊戲,玩了一段時間,因為沒有使用麥克風,那個程式會自動關閉使用麥克風,然後我們的遊戲就Crash了。又是一個跨行程的奇葩Bug。
從現場來看,Crash在使用XAudio的庫程式碼裏面。看不出什麽線索,最後發現在日誌裏面,Crash前一會兒,XAudio的dll被Unload掉了。Vista那個語音元件很強悍,也會跨行程追殺大法,在自己行程把我的行程dll給Unload掉了。
雖然遇事先要懷疑自己的程式碼有問題,但這個情況太匪夷所思,所以我試圖推卸責任,我跑了幾個其他DirectX程式和Demo,但凡用到XAudio元件的,在Vista Speech Recognition這一大招前無一幸免,全部Crash。
問題不在我們這裏,但是作為一個職業殺蟲人,我有著「只解沙場為國死,何須bug裹屍還」的覺悟,決定還是要想辦法搞定它。
我猜這個元件結束的時候,廣播了點什麽訊息,讓系統所有行程去解除安裝這個Dll。我找了一個微軟提供的庫Detours Express,專做Hook API的勾當。它會反組譯API的入口程式碼,然後動態替換入口,插入jmp指令去執行我們自己的函數。先看了文件,搞懂這個東西怎麽用,然後猜測是一個OS內部的訊息導致Unload DLL,我們就攔截了RegisterWindowMessage。一通翻箱倒櫃,啥有意義的東西都沒發現。
再用spy++來攔截訊息,發現有一個MSUIM.msg.rpcsendreceive的訊息面目猙獰,很可疑。就在收到這個訊息以後,Dll就被Unload了。於是我們用Detours截住這個訊息,直接返回。
以為搞定了,但結果還是不行,有幾條別的訊息也會觸發Unload Dll,我一一用Detours攔截返回。攔截的訊息越來越多,看來不是個解決辦法。於是我懷疑我們沒有攔截到正確的訊息,懷疑OS啟動的時候便註冊了一堆內部用的訊息,而不是在執行行程的時候才註冊。那些訊息裏肯定有我想要的,我便異想天開地Hook了User32.dll,攔截下所有訊息,在安全模式下把修改過的user32.dll覆蓋原來的檔,然後滿懷希望的重新開機動Vista的時候。結果不出所料的藍屏了...User32.dll是凡人能隨便碰的嗎?這不是一個User friendly的庫,應該改名叫God32.dll。
這個bug搞了1周,最後才想到最基本的道理,說不定只是Dll內部參照計數被清掉了,所以系統就卸掉Dll了。當Vista語音元件Unload XAudio的時候,系統去看看XAudio庫有沒有什麽別人參照,結果發現居然沒有,那就順手清理了。就像地上有100塊錢,你東張西望,發現沒有人宣布擁有那張錢,沒有一點點猶豫,你隨手就把它放進了褲兜,進行了回收。嗯,就是這樣的,我喜歡比喻這種修辭手法。
理論上建立XAudio器材的時候沒有做什麽額外的加Dll參照計數的事情,懷疑是MS的bug,建立D3D器材、DInput器材的時候都不需要做什麽特別的事情的,但他們的參照計數都沒有問題。
於是我惡搞了一下,在初始化XAudio完成以後,手動做了一次LoadLibrary,把XAudio2_1.dll強行Load一遍,相當於自行增加了參照計數。於是再無Crash。
從此我們的遊戲,在Vista 32上過上了幸福的生活。
可得結論:Vista靠不住
Takeaway: 不知道該說什麽了,這個bug太奇葩了。
效能的迷思
臨近最終版本釋出的時候,測試人員發現一個問題,當在Vista上連續打遊戲過7關以後,遊戲幀數一下子變成原來一半了。大家覺得很詭異,都不相信,之前整天玩遊戲都沒問題的,於是責令測試人員再重現幾次,否則拖出去刨坑埋了。我也沒當回事,