您當前的位置:首頁 > 動漫

Bug往事(3)

作者:由 顧煜 發表于 動漫時間:2016-05-15

時間有關的Bug

所有的console平臺,都有一個邪惡的要求:需要做Aging Test。簡單來說,就是這些遊戲機遊戲是要放在店裡面賣的,可能商家會把試玩遊戲放在外面,很久都沒有人玩,你的遊戲不能Crash,否則顧客想玩的時候發現遊戲Crash了,不會怪罪開發者,而是會認為這個主機不穩定。所以遊戲機廠商都要求開發商的遊戲要長時間執行不能Crash,通常都要求8個小時左右。我們一般的做法就是,在很久沒有檢測到玩家的手柄輸入資訊以後,就進入一個小的死迴圈,把畫面暗下來作屏保狀,迴圈裡面只檢測輸入,有輸入就退出這個狀態。因為主邏輯不動,複雜的程式碼都不執行,自然就安全。

看似簡單的aging test,被時間溫柔的手撫過,宕機了執行的遊戲,愁白了開發者的頭髮。

那天來了這麼一個bug,描述便很科幻很文學,說在某一個場景,有山有水有敵人的地方,主角躲在某個敵人無法攻擊的地方,敵人和坦克都會不停地開火。測試兄弟做完這一切便無情地離去,留遊戲主角在那裡看一夜雲起雲落,流彈共長天一色。第二天早上一來,操作主角一轉身,就Crash了。有詞為證:驀然回首,bug卻在燈火闌珊處。

我也不驚慌,開啟VC,Attach到Crash的Xbox360 kit,一看,是更新某個旗幟(softbody做的)的時候,旗幟的Bounding Box太大,被assert掉了。Bounding Box本不該這麼大的,看了看所有的頂點,位置都離原點很遠。為什麼呢?Softbody旗幟更新的時候,會有約束處理,每幀都會把頂點往中間拉一下,讓頂點們團結在原點周圍。

第一步自然是試著重現,每天下班就照描述方法重現,機器開著回家,試了幾天,終於可以穩定重現了,每次Crash也是同一個地方。

靜下心來,認認真真讀了一遍Softbody的頂點更新程式碼,覺得沒什麼問題,只好加了些日誌,列印幾個頂點的座標。第二天果然又Crash了,輸出了1G多的文字日誌,在pc檢視日誌。發現中間部分頂點位置都很正常,直到Crash前最後幾幀才急劇變大,最後觸發assert。

下班前又設好環境,第二天在觸發Crash前,加好了條件斷點,當頂點x座標大於特定值的時候就斷下來。然後操作主角轉身,VC馬上停下了程式碼執行。仔細看看,找到了原因。原來Softbody會受到外力,比如手雷或炮彈的爆炸,然後把外力加到頂點上面,改變一下頂點的位置。另外,我們有最佳化,對於看不見的Softbody,我們不會去更新它的頂點,這樣可以快一些。問題在於,每一次那輛坦克開炮,都會炸到那面旗幟,我們就不停地把那個衝量加到了旗幟受力上面。通常每次更新Softbody我們會把受力加到頂點上面,然後把那個變數清零準備接受下一幀的衝量。但這次很不幸,由於更新的最佳化,這個Softbody在主角背後,沒有被更新,那個記錄受力的向量沒有清零。經過一個晚上的炮轟,積累了很大的值,主角一轉身,Softbody被看見,就更新,巨大沖量的向量呼嘯而來,更新了頂點資料,導致頂點被更新到很遙遠的地方了。

知道原因就好辦了,在加衝量的時候,設個上限,超過上限部分一律忽視即可。改動,測了幾個晚上,似乎正常,收工。

可得結論:最佳化靠不住

造成最大損失的bug

2003年,PS2時代,我們用Unreal做一個FPS遊戲。PS2效能太低,導致我們遊戲多人玩的時候,作Server的那個玩家幀數很低,幾乎無法玩。

我們針對這個問題作了AI高層、物理底層、網路通訊層的程式碼最佳化,最後,在開發人員這裡,似乎能夠支援8個玩家了。

於是我們讓測試人員來測4v4,可是他們每次都說效能不理想。在釋出最終版本前兩天,我們緊急決定,不支援4v4了,改成3v3,這樣效能就沒問題了。程式設計師總是這麼睿智,老闆總是這麼英明。

在做Master版本前的最後一天晚上,遠處傳來某個程式設計師撕心裂肺的慘叫:“Release版本下面的程式碼最佳化沒有開啟!”原來如此,某人某次為了便於除錯,把Release裡面的程式碼最佳化關了,然後他不小心又上傳了這個專案配置檔案。我們測試的版本比較老,都是開著最佳化的,所以4v4沒有問題,做版本給測試人員測的時候用了最新版本,效能就不行了。

既然沒開啟最佳化,開啟不就皆大歡喜了?沒那麼簡單,開啟以後最佳化的執行檔案會多佔用2M的記憶體,PS2上面記憶體本來就很少,我們的執行時留的記憶體只有1M。如果早些發現,還可以讓關卡製作和美工去省點記憶體出來。都快發版本了,我們到哪兒找這多出來的2M?

最後,我們很屈辱地放出了一個本可以支援4v4,但陰差陽錯變成只支援3v3的版本,而且PS2的遊戲,你也沒有機會出Patch來彌補。

可得結論:程式設計師靠不住

機率

開篇說的Gamesutra上的Dirty Coding Tricks,裡面有一篇特別神。

他們有一次準備釋出版本了,臨發前手賤,加了一個文字檔案打包,結果就一直在遊戲載入時 Crash。查了一下,發現資源管理裡面的檔案識別符號衝突了,新改的文字檔案和另一個檔案識別符號一樣了。神奇的地方在於,那個識別符號是64位的,前32位是檔名和相對路徑的CRC32,後32位是整個檔案內容的CRC32,這都能衝突。。。修改方法很簡單,開啟文字檔案,加一個空格,存檔退出。

我看後深受啟發,得出結論是,機率也靠不住。

總結

綜上所述,我們順利得出了結論,什麼都靠不住。

但那也不意味著面對bug,程式設計師就手足無措。為了要修復bug,我們需要的,並不是愛和正義,而是你,最勤勞勇敢的程式設計師。對於Debug,我們有這樣一些看法:

不要懷疑工具、SDK和OS,先懷疑自己的程式碼有沒有問題。以前做PS1遊戲的時候,Sony的編譯器經常會編譯錯程式碼,搞得我們開發人員都迷信了,碰到詭異問題無法解釋就歸於迷信活動,燒個香再rebuild一下。。。可是這些年來這類情況越來越少,99。999的情況下那些奇怪的bug都事出有因。承認這一點,並耐心的去看這個bug,而不是到處亂改亂試,能省下好多時間。

善用工具 Vtune, Perfhud等等等等工具都能給你很大幫助。前面說的那個多執行緒降速Bug,如果有目前常用的Windows Performance Toolkit,加上GPUView,應該是秒殺的級別。很多時候問題不好解決,是沒有好的工具。

在遊戲裡多留Debug輔助程式碼和profile程式碼

日誌是你的好朋友 太多bug無法直接斷下程式來重現,等重現了往往現場又沒了,只有日誌才能幫助你追蹤bug。

多執行緒是魔鬼 我遇到過最難查的bug有80%和多執行緒有關,不要低估多執行緒程式的除錯難度。

易重現是Debug的第一步 很多bug之所以難查,是因為重現條件苛刻,可能是非常隨機,可能是要玩非常久才能重現。如果一個bug重現機率是25%,那修復它的難度比重現機率是100%的bug不是難4倍。對於100%重現bug,為了驗證一個想法,你只需要嘗試1次;可對於25%重現機率的bug,你嘗試4次是遠遠不夠的,萬一這4次都沒重現呢?你並不知道是你的改動生效了,還是根本這幾次就沒重現,所以你必須試上10多次,保證那25%的情況被觸發到了。如果可能,儘量試圖簡化bug重現條件。這是第一步,也往往是最重要的一步。

bug產生的地方和爆發地方越近越容易除錯,因為現場都容易看見。很多AI行為難除錯,就是出問題的地方離實際能看見的bug差很遠,時間無法回溯,現場已然消失。所以不要吝嗇assert,一個成功的Crash在現場會讓你省很多力。注意積累,多思考多總結。比如像我這篇文章,記下來,分享之。一點點運氣 運氣是必需的,運氣也是實力的一部分,而且運氣會光臨執著的程式設計師。

多人協同工作可以幫助你。叫幾個同事一起看,很多思路都是交談的時候整理出來的,哪怕你只是給別人複述一下某些程式碼的邏輯都有可能讓你豁然開朗。

瞭解彙編,底層,執行Script的虛擬機器 對於一個難重現的bug,一個Crash現場很難得,往往是自己或是測試同事數小時到一天的努力成果。對於這裡難重現的bug,每一個現場都要珍惜,很多資訊往往不容易找到,特別是Crash在Script虛擬機器裡或者在高度最佳化過的Release版本里面。平時要注意多積累底層的知識,這樣才能在每一次Crash後充分利用不完整的現場,不浪費重現bug的工作量。

保持良好心態 Debug是一件很艱苦的事情,尤其當你幾天都找不到線索的時候。以前Leader講過,他覺得Debug很有意思,因為每個Bug都是一個謎題,都有原因,把它看成是一個挑戰。追查一個問題多日,沉浸其中,終於有一天找到原因,由此而得到巨大成就感,這不就是吸引我們做程式設計師的一大原因?

我始終覺得,一個程式設計師Debug能力和最佳化能力的高低,很能體現他的水平,越強的程式設計師,越能在不可能處柳暗花明。Debug時那些靈光一閃的頓悟,需要長期浸淫在問題中,需要廣博的知識面,需要充分的想象力,長期除錯疑難bug後,你會發現你對系統底層,對效率,對其他模組,都會有更深入的瞭解。

希望大家都能在debug中有所收穫。

相關文章

Bug往事(1) - 遊戲開發隨筆 - 知乎專欄

Bug往事(2) - 遊戲開發隨筆 - 知乎專欄

Bug往事(3) - 遊戲開發隨筆 - 知乎專欄

標簽: bug  crash  重現  頂點  遊戲