你碰到過的最難調試的 Bug 是什麼樣的?

在 Quora 上有一個和 Bug 相關的熱門問答帖:《What』s the hardest bug you』ve debugged? | 你調試過的最難 Bug 是?我大中國的程序員攻城師們遇到最難調試的bug是什麼呢?歡迎吐槽!


2017年1月17更新

好像最近這個帖子又被翻出來了,又陸續有一些朋友點贊或者留下評論,謝謝大家!有一些大家共同關心的問題,我在這裡做一些統一的回復,謝謝!

1. 那哥們後來怎麼樣了?

沒怎麼樣,混的不錯。去Intel繼續禍害大眾了,哈哈

2. 同行啊,請問答主目前哪裡高就

現在混互聯網。搞了家小公司。不寫BIOS很多很多很多年了

3. 300萬行代碼如何編譯成不足8m的rom?

這個問題問得很好。首先300萬行的規模是整個項目的規模,裡面包含有幾乎所有硬體的platform code,事實上在每個特定的主板上,是要做一些裁剪的,把一些這個主板沒有用到的硬體代碼去掉;其次這300萬行裡面還包含所有的工具代碼,makefile,配置文件等等等等,尤其是工具類的代碼,除去編譯以及連接工具,大概有幾十個自行開發的工具要參與構建過程,整個BIOS的構建過程首先就是先構建這些工具,然後再用這些工具去處理配置文件,創建總的makefile,在一步步的逐漸的去創建各個模塊的makefile,最後再根據最上一級的模塊配置文件來逐步的構建每一個組件。當這些組件都生成了,再根據預先配置好的FLASH的存儲結構,按照相關的規範來打包成ROM文件,壓縮格式是略微調整過的LZMA,按照FFS規範來進行存儲

我那個時候(2005年 - 2010年),編譯工具用的是VS2003 + MASM;後來聽說他們升級到了VS2010,貌似也可以用GCC了,好像也可以用Intel C Compiler


2015年8月23更新

評論里有幾位朋友對於我提到的BIOS有上百萬行源代碼表示不可能,甚至有一位朋友提到BIOS就是個boot loader,要那麼多代碼幹什麼?我想我有必要在這裡做一些簡單的說明。

在說明之前,我首先要申明一下由於我2010年就已經離開BIOS行業了,並且之後的日子我並沒有持續的去跟蹤最新的技術趨勢,所以我對於目前的最新情況並不了解,事實上這個行業的知識刷新速度看起來非常快。所以我這裡說的其實還是基於我當年的知識構成。

首先,目前的BIOS都是基於UEFI的新一代BIOS。這類系統本身就具備十分強大的功能。完全可以視作一個小型的操作系統,有自己的shell,自己的drivers,自己的app,甚至自己的圖形環境。這樣的系統的代碼量自然不會小。

其次是因為x86系統的歷史包袱非常非常嚴重,而bios作為最核心的系統固件承擔了太多的歷史兼容性的責任。舉個例子,就是對於usb鍵盤的支持,大家可能會簡單的認為,支持一個usb鍵盤那不是太簡單的事情么?的確是這樣,但是個人電腦有一個非常非常重要的原則就是兼容性。具體到鍵盤上,如果你現在找一個古董級的PS/2介面的鍵盤,然後把它接到現代的主板上,假如你的主板已經沒有PS/2介面了,那麼買一個轉換頭,然後再接上去你會發現這個古董級的硬體仍然可以使用。是的!對於我們用戶而言,這是完全符合邏輯的一件事情。但是對於bios開發者而言,這就是一個很要命的問題了。原因很簡單,我們暫時穿越到幾十年前,那個年代的電腦主板上都有一個叫做8042的晶元,用來控制諸如鍵盤這樣的外設,那個時代的開發者通過讀寫60H以及64H埠來訪問鍵盤,然後那個時代的彙編BIOS則提供了INT 9H中斷來為應用程序提供鍵盤服務,在幾十年前那個時代,這一切是很美好的。那個時代的操作系統DOS就是這樣來訪問鍵盤的。現在讓我們回到更加美好的現代,由於歷史兼容性原則,所以現在的每一台計算機還必須可以安裝DOS,還必須讓DOS或者運行在DOS之上的應用程序可以無差別的運行在現代的計算機之上 - 可是,大家是否知道,現代的計算機壓根沒有8042這塊晶元!更要命的是,後來人們發明了一個叫做USB的新玩意兒,基於這個新玩意兒的鍵盤根本不會接到60/64埠上,而且這個新介面的鍵盤採用的編碼與過去PS/2介面的編碼完全不一樣!那麼如果不做任何處理的情況下,那些過去年代的軟體壓根不會認識新的鍵盤,所謂的歷史兼容性根本無從談起!所以我們偉大的bios這個時候就扮演了救世主的角色了,bios會做很多處理,以現在的角度看,現代bios模擬了一個PS/2鍵盤:現代bios一邊讀取來自USB鍵盤的信息,一邊將其轉換成那些古老軟體能夠識別的鍵盤編碼,然後再中斷系統,寫入內部的60/64埠的緩衝區。大家也許會發現,所有的現代bios里會有一個設置項,一般叫做legacy USB Support,默認值就是Enable,打開這個選項你才可以在DOS下使用usb鍵盤。當然,具體的實現過程異常複雜,涉及x86處理器最神秘的SMM模式,我們就不展開講了,這已經遠遠超出本文的初衷。所以一句話,bios的代碼非常複雜,還要包含大量的歷史兼容性代碼,除了我們上面談及的鍵盤問題,還包括比如INT 10H的屏幕服務,據說直到Windows 7的安裝程序,還有一小部分使用INT 10H來寫屏。那麼我們的bios就必須包含這些可能有些用戶一輩子也用不上的服務。代碼量刷刷的就上去了。

--------------------------

每次想起這個bug,雖然很多很多年了,我仍然滿臉都是淚水啊!

當年做x86 BIOS,客戶是長城電腦。有一回我們的新版本發布給他們後進行系統重啟測試,就是安裝好操作系統後反覆不停的重啟機器,看看重啟幾百上千次後情況如何。原因是客戶買了電腦每天用,至少得保障人家用個倆三年沒事吧。

結果我們的新版本重啟到一百多次的時候掛了,現象就是開機黑屏,沒有任何輸出,就和當年的CIH病毒發作一模一樣,經驗判斷系統壓根還沒有boot OS就跑飛了,我們自己測試也是這樣,而且一旦出現問題就只能重新刷BIOS

這個bug非常難調,因為當時我們的版本將近300萬行源代碼,大概2%的彙編與98%的C,幾千個源文件,光是用來參與build過程的工具就有十幾個。而且這些工具都是自己寫的,構建項目的時候先編譯這些工具,再去用這些工具加編譯器來生成最後的ROM文件

並且更加惱人的是,我們當時沒有source level的debug tool,甚至連彙編級別的單步調試工具也沒有,壓根沒法對代碼做step into/over,更沒法加個斷點。。。當時可以用來調試BIOS的工具有兩個,一個是Intel自己內部用的ITP,這個是人家公司自己的,一般不給外面人用,當時我們公司與I公司的關係尚處蜜月期,給了我們兩個,但是當時被Chipset team霸佔著做porting用;另一個工具就是American Arium(這家鳥公司不知道現在還活著不),這個東西說白了就是商品化的ITP,因為目標客戶少,所以價格巨貴巨貴!一套系統價格幾萬美金,而且每一代CPU都要換一個插座上的適配器,這個適配器又是一萬美金好像,還不太穩定,用著用著就掛了。。。我們公司當時有倆,但是因為沒有買新一代處理器的適配器,於是只能吃灰了

於是我們唯一的調試手段就是serial debug,就是系統啟動的時候會通過port 80把一些重要信息打出來,然後我們根據這些信息判斷執行到哪裡了,系統的情況如何。這類似原始的printf列印。如果要看一個變數的值或者驗證一下我們的判斷,就得重新寫代碼,在需要的地方加入調試語句,然後花上半個小時rebuild bios,再重新燒錄,再上電運行看看打出來的到底是啥。如果有疑問,或者發現這裡沒有問題,又或者有了新的思路,重複上述過程。記憶中整整一個禮拜,我們都在不停的看debug info,反覆燒錄bios 哭啊!簡直不是人過的日子!

最後發現系統可以成功的跑過PEI,到了DXE階段的某個環節,突然就像心臟驟停一樣,跑飛了!去看疑似跑飛的DXE Driver,是個很普通的平台硬體初始化程序,沒什麼疑點,壓根沒有頭緒。那段時間,幾乎每時每刻都在想著這個bug,實在是茶飯不思,根本沒心情做任何事!

就這樣差不多過了倆禮拜,經過了無數次的重啟與燒錄bios,以及猜測,驗證,被否定,再猜測,再驗證,再否定。。。。。的過程後,我們終於發現了問題的原因:

大家可能還記得電腦主板上有個CMOS,傳統上用來存bios設置,但是現代的系統已經逐漸棄用這個東西。我們現在的bios晶元都是可擦寫的,也就是用程序可編程。bios大小是8MB,裡面會規劃好,哪裡是code,哪裡放設置等等,然後代碼里有專門寫flash的函數,讓大家可以保存一些東西,比如你想用硬碟還是光碟機啟動等等。同時系統每次啟動也都會自己寫一點沒什麼鳥用的信息進來。

問題就出在這個寫flash的函數上,我們後來發現,這哥們算錯了存儲區域的地址,導致寫很多次後終於越界,誤寫到了人家代碼區,把人家好端端的代碼給寫的亂七八糟,就如同當年CIH破壞系統的方法一模一樣,照這樣哪個機器能點亮才怪呢!又因為每次系統寫的信息不一樣,比如啟動時間就不太一樣,所以越界需要的次數不是恆定,更加重了我們排錯的難度,淚啊!

第一次寫這麼長的回答,還是手機打的,累!


實在忍不住了,第一次答題。

08年的時候,我所在的公司調試三星的一款新的arm9 CPU,型號是S3C2416,是S3C2450的簡配版。開發板剛入手的時候還是熱乎的,因為三星的這個晶元剛剛出來,國內的代理商一共就幾塊開發板。各公司評估開發板都是分時使用的,只能預約幾天。開發板入手的時候,三星那面連BSP都沒有準備好,沒有test code,沒有u-boot,沒有linux-kernel,甚至連Spec都是錯誤百出。還好我公司雖然小,研發能力在本地區還算不差,沒有的東西可以自己移植。
公司急著要出新品,在沒有完全驗證處理器的情況下,已經layout好了PCB,並且去打樣了(當時競爭確實比較激烈,400M主頻處理器而且這麼低的價格絕對非常有誘惑力,所以公司決定冒這個險了)。在沒黑沒白的工作兩周後,硬體和軟體做的都差不多穩定了。這時候經理說,功能上問題不大了,我們來調一調休眠時的功耗吧(我們的產品一直以待機時極低功耗作為產品的賣點之一)。然而這卻是噩夢的開始……
公司的指標是待機時休眠電流500uA~800uA(電源電壓4V)之間。以前所有的產品都在這個範圍之內,三星方面的技術支持也明確表示,他們的解決方案達到這個指標。
在我們調試過程中發現,整個系統休眠時的功耗在1800uA左右,一直降不下來。我們重新核對了所有的IO和外圍電路的所有連接,以及IO口的電平配製,都沒有問題。這時,我們決定測試每一個單元的功耗,用電流表分別串聯進每一個外圍電路,每個單元都很正常,就是系統總體偏大1000uA。
我們連flash和ram的待機電流都測過了,仍然正常。好了,通過排除法已經確定了就是CPU的功耗過大。但是在開發板上調試休眠的時候,CPU功耗卻是正常的。
我們懷疑是開發板上CPU批號和我們自己拿到的CPU樣品的批號之間有區別導致的,因為三星那面也在同步修正CPU的BUG,所以我們「大膽地」把開發板上的CPU用風槍吹下來,換到我們的PCB上,把我們的CPU貼到了開發板上進行交叉驗證。結果是開發板仍然功耗正常,我們自己的板子上功耗偏大,還是大了1000uA。
CPU周邊的核心電路設計出現了問題!這是我們一致的判斷!但是問題出在哪裡,我們反覆核對開發板的原理圖和我們自己板子的原理圖,簡直就是一模一樣!因為整個核心電路這部分就是從開發板上抄過來的,實在沒有什麼可比對的。我們轉而又去懷疑PCB的問題了。
我是做系統移植和軟體的,純電氣的問題我就無能為力了。閑著沒事,我就反覆檢查我在linux中對系統休眠的IO引腳配置。然後掛著電流表做反覆測試。電流表也對的起我,每次都是那個數。在一次系統待機的時候,我實在忍無可忍,一把抓起了板子。突然之間,電流表的讀數飛快下降,降到了300uA!我鬆開手電筒流表的讀數就又爬回來了。我把我這個驚奇的發現告訴了同事——一個硬體工程師。同事說可能是哪兒摸短路了,讓我試試還能不能喚醒系統。我給了一個外部中斷,系統神奇的正常喚醒了!
「難道這就是問題?」,我想重現一下。但是再次在待機的時候抓起電路板的時候,讀數並沒有顯著發生變化。「可能是手法不好」,我這麼想著,用手在板子上繼續撫摸著。果然!當我的手指按到PCB中的某一個位置時,電流又降了下來!反覆試了幾次,都是這樣,就是在我手指按壓的這一片,只要是用手指按著,電流就正常!
這回同事開始重視了,打開PCB圖,拿著電路圖和萬用表,查查我摸的到底是那塊電路。硬體工程師覺得不可思議,因為我摸的部分並沒有連接任何的電路——焊盤是空的。他於是用萬用表的表筆去檢查是不是PCB製版的問題,測一下這些空焊盤到底哪一個有電壓。但是萬用表中沒有讀數,這塊都沒有電。但是當萬用表的表筆落在一處空焊盤的時候,電流表的讀數又降下來了!
這可是重大發現,我們對照了一下電路圖。這處空焊盤是CPU中USB-Host模塊的D+信號。由於我們的產品不需要USB的主機功能,所以這一塊兒沒有做任何處理。多虧了畫原理圖和PCB的同事,多留了一手,把USB Host的引腳都在PCB上做了個引出。誰也沒想到是這個引腳出現了問題,辛虧這個信號引出來了,要是沒有引出來,一輩子也查不出問題。我們給D+信號加了一個下拉電阻後,系統的功耗瞬間正常了。
事後分析,三星自己開發板上有USB-Host的功能,所以USB-Host的外圍電路也是完備的,所以功耗不會有問題。但是我們自己的產品上不使用USB-Host功能,沒有相關外圍電路,所以出了問題。這是因為在CPU休眠的時候,D+信號內部被懸空了!一句話,是三星CPU自己的BUG。我們修改了我們的PCB,增加了一個下拉電阻,同時將問題反饋給了三星。
一個月後,當我們的產品量產時,三星也及時的解決了這個問題。那個下拉電阻也不需要再貼上去了。

最後用手指頭找到了CPU的BUG,不知道這算不算是最難調的。
反正這麼多年了,這個經歷留給我的印象是最深的。


以前做windows技術支持,一直調試crash dump。就我個人的體驗,有關線程安全的dump是最難調試的,來無影去無蹤,看到的就是一坨已經被破壞的現場(dump),然後你需要在大腦中還原案發經過。

有一天香港某大公司(名字不透露了)上了一個case,他們自己的一個應用在生產環境中會莫名奇妙地crash。當時我就想:你自己應用crash找我們幹什麼,肯定是你自己代碼問題,而你還不給我看你的代碼!

所以幾個難點:
第一,沒有客戶代碼。
第二:客戶用的系統是NT4!NT4什麼概念?就是沒有pdb文件的,符號文件只能對應到函數入口,對應不到具體的源代碼行號。你只能把整個函數的彙編都讀懂才能知道crash的地點是在做什麼事。
第三:只有dump,不可能設斷點調式,因為根本不知道如何重現。

反正就這麼讀了幾百上千行的彙編(此處省略兩千字),最後定位crash的地點,能看到進到EnterCriticalSection的api之後發現這個CRITICAL_SECTION結構其實已經壞了,然後就掛了。從heap結構可以看出似乎那個heap block已經被用作它用了,所以有可能那個CRITICAL_SECTION已經被delete了。

然後看誰管理這個CRITICAL_SECTION的, 發現是msvcrt,還是VC5的。好吧去找代碼,還好那個代碼是找得到的。然後就把所以處理這個CRITICAL_SECTION的代碼全部找出來,把所有代碼在不同線程中的不同執行順序都排列出來,最後發現在某一個特殊的執行次序下會有一個race condition,導致這個CRITICAL_SECTION會被過早delete掉。還好一開始就懷疑是線程安全問題,入手方向沒錯。

好吧,最後居然是vc runtime的bug。當初錯怪客戶了。

調完這個bug的副作用就是之後看到彙編就想吐。看看現在c#的調試那根本不是事。


遠遠算不上最難,但是很有意思。來自我的知乎專欄文章。
背景,我給饑荒遊戲寫了一個LuaJIT PATCH,替換掉了原有的LUA引擎以解決卡頓的問題。在編寫過程中解決了很多BUG,全文很長長長長長長,下面這個是比較有意思的,就節選出來了。完整的部分請點擊下面的文章鏈接。

此PATCH源碼及下載在GITHUB有:GitHub - paintdream/DontStarveLuaJIT: LuaJIT bridge for Don"t Starve (compatible with DS, RoG, SW, DST [only for test] )

===================================================
作者:paintsnow
鏈接:饑荒遊戲LuaJIT掃雷筆記 - paintsnow的文章 - 知乎專欄
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

0x0F 人間蒸發的海盜鸚鵡

新的版本持續用了很久,新的BUG反饋與很少了,而且基本上都是已經使用了舊的patch或者自己不會按要求改代碼導致的。

安逸的日子一直持續到有用戶開始報告說:陷阱類物品使用滑鼠點擊後捕捉到的動物消失,並且物品的耐久沒有減少。

這看起來很難理解,這個bug是如此的精緻,精緻到不像是腳本引擎更換時引起的bug,反而更像是lua代碼本身的bug。若是PATCH引起的bug,為什麼遊戲中的其他邏輯能精確地運行,唯獨陷阱不行呢?這是怎麼做到的呢?

(前方高能提醒:這個是製作此Patch時遇到的最詭異,邏輯鏈最長,但同時也最好玩的一個)


為此我打開遊戲(啟用PATCH),建了個新檔,用控制台刷出一個捕鳥器和一隻海盜鸚鵡。拾取後果然鸚鵡沒了,捕鳥器耐久也沒掉。

那麼,問題出在哪裡呢?通過仔細比對啟用PATCH前後的畫面,發現一件怪事:

(啟用前)

(啟用後)

所以問題應該是出在原本應該是Check的操作變成了Pick up,導致玩家撿起了捕鳥器而不是收穫捕到的鳥。但是如果使用空格撿的話,則會正確地執行Check操作。

所以問題應該是出在原本應該是Check的操作變成了Pick up,導致玩家撿起了捕鳥器而不是收穫捕到的鳥。但是如果使用空格撿的話,則會正確地執行Check操作。

接下來目標就很明確了:這個Pick up是哪裡來的?

通過檢索lua源碼中"Pick up",可以發現在strings.lua中有其定義:


STRINGS = { --ACTION MOUSEOVER TEXT
ACTIONS =
{
REPAIR = "Repair",
REPAIRBOAT = "Repair",
PICKUP = "Pick up",
CHOP = "Chop",
FERTILIZE = "Fertilize",
SMOTHER = "Extinguish",
...
}
}

再看看哪些地方引用了STRINGS.ACTIONS.PICKUP這個值,並沒有找到,再找ACTIONS.PICKUP,發現一處有價值的線索:

也就是說,所有操作都是通過構造一個BufferedAction來封裝的,從名字上來看既然是Buffered,應該會有一個隊列之類的來存儲Actions,繼續找"BufferedAction"可以發現在scripts/components/playeractionpicker.lua里有些奇怪的東西:

也就是說,所有操作都是通過構造一個BufferedAction來封裝的,從名字上來看既然是Buffered,應該會有一個隊列之類的來存儲Actions,繼續找"BufferedAction"可以發現在scripts/components/playeractionpicker.lua里有些奇怪的東西:


function PlayerActionPicker:SortActionList(actions, target, useitem)
if #actions &> 0 then
table.sort(actions, function(l, r) return l.priority &> r.priority end)
local ret = {}
for k,v in ipairs(actions) do
if not target then
table.insert(ret, BufferedAction(self.inst, nil, v, useitem))
elseif target:is_a(EntityScript) then
table.insert(ret, BufferedAction(self.inst, target, v, useitem))
elseif target:is_a(Vector3) then
local quantizedTarget = target
local distance = nil

--If we"re deploying something it might snap to a grid, if so we want to use the quantized position as the target pos
if v == ACTIONS.DEPLOY and useitem.components.deployable then
distance = useitem.components.deployable.deploydistance
quantizedTarget = useitem.components.deployable:GetQuantizedPosition(target)
end

local ba = BufferedAction(self.inst, nil, v, useitem, quantizedTarget)
if distance then
ba.action.distance = distance
end
table.insert(ret, ba)
end
end
return ret
end
end

function PlayerActionPicker:GetSceneActions(targetobject, right)
local actions = {}

for k,v in pairs(targetobject.components) do
if v.CollectSceneActions then
v:CollectSceneActions(self.inst, actions, right)
end
end

if targetobject.inherentsceneaction and not right then
table.insert(actions, targetobject.inherentsceneaction)
end

if targetobject.inherentscenealtaction and right then
table.insert(actions, targetobject.inherentscenealtaction)
end

if #actions == 0 and targetobject.components.inspectable then
table.insert(actions, ACTIONS.WALKTO)
end

return self:SortActionList(actions, targetobject)
end

仔細看了半天,這裡似乎是一個比較關鍵的地方。所有的BufferedAction在這裡通過table.sort按priority排了個序。我猜想遊戲邏輯應該是先收集各種可選的操作選項,然後把操作們按優先順序排個序,頂端的即為優勝者,將會被選作默認的操作(親,你直接選個最大值不就得了嗎)。

因此,要麼是排序前的actions有已經有問題了,要麼是排序本身出的問題。通過這裡我們應該可以縮小檢查的範圍。

0x10 不穩定天平

為了驗證我的想法,在SortSceneActions里寫點LOG。看看好不好玩:


local mapActionToName = {}
for k, v in pairs(STRINGS.ACTIONS) do
local m = ACTIONS[k]
if (m) then
mapActionToName[m] = k
end
end

local function PrintList(actions)
for i, v in ipairs(actions) do
print("ACTION [" .. i .. "] = " .. (mapActionToName[v] or "NULL"))
end
end

function PlayerActionPicker:SortActionList(actions, target, useitem)
if #actions &> 0 then
print("-----------------------")
print("Before sorting ... ")
PrintList(actions)
table.sort(actions, function(l, r) return l.priority &> r.priority end)
print("After sorting ... ")
PrintList(actions)

local ret = {}
for k,v in ipairs(actions) do
if not target then
table.insert(ret, BufferedAction(self.inst, nil, v, useitem))
elseif target:is_a(EntityScript) then
table.insert(ret, BufferedAction(self.inst, target, v, useitem))
elseif target:is_a(Vector3) then
local quantizedTarget = target
local distance = nil

--If we"re deploying something it might snap to a grid, if so we want to use the quantized position as the target pos
if v == ACTIONS.DEPLOY and useitem.components.deployable then
distance = useitem.components.deployable.deploydistance
quantizedTarget = useitem.components.deployable:GetQuantizedPosition(target)
end

local ba = BufferedAction(self.inst, nil, v, useitem, quantizedTarget)
if distance then
ba.action.distance = distance
end
table.insert(ret, ba)
end
end
return ret
end
end

在排序前後我都把actions數組中的值列印出來,看看這個排序做了什麼:

(啟用PATCH前)

(啟用PATCH後)

(啟用PATCH後)

注意看圖中藍框的部分,排序後的結果果然變成PICKUP第一了。於是一個直接的想法就是,饑荒作者並不知道table.sort排序是不穩定的,LuaJIT中的快排演算法很可能和原版的不一樣,在最大元素不唯一的情況下,二者的結果就會有差異!!

注意看圖中藍框的部分,排序後的結果果然變成PICKUP第一了。於是一個直接的想法就是,饑荒作者並不知道table.sort排序是不穩定的,LuaJIT中的快排演算法很可能和原版的不一樣,在最大元素不唯一的情況下,二者的結果就會有差異!!

於是我打開actions.lua,一切都似乎明白了:


Action = Class(function(self, priority, instant, rmb, distance, crosseswaterboundary)
self.priority = priority or 0
self.fn = function() return false end
self.strfn = nil
self.testfn = nil
self.instant = instant or false
self.rmb = rmb or nil
self.distance = distance or nil
self.crosseswaterboundary = crosseswaterboundary or false
end)

ACTIONS=
{
REPAIR = Action(),
...
PICKUP = Action(2),
...
CHECKTRAP = Action(2),
BUILD = Action(),
PLANT = Action(),
...
}

非常明顯,PICKUP和CHECKTRAP的優先順序都是2,那麼排序就有可能出現PICKUP在CHECKTRAP前面的情況。

想要解決也很容易,把CHECKTRAP的優先順序調大些(如2.5),就好了。事實證明調整後,bug也確實消失了。

然而問題就到這裡完結了嗎?

0x11 依賴於錯誤的正確

這個bug的神奇之處在於,並不僅僅如此。

在發布了修補辦法之後,吧友表示問題解決了,但是還有其他的類似問題,比如船隻不能Inspect,MOD人物翼語的蓮花台不能右鍵拾取,烤箱MOD異常等等。

確實,這些都是原版饑荒Actions優先順序設置導致的,那麼有兩種可能:

1. 作者們修改了排序演算法,使之變成穩定的(如冒泡排序),所以在優先順序相同的時候,原序列中排前面的排序後也排前面。

2. 作者們壓根就不知道快排還有不穩定一說,出現結果異常的時候就調調優先順序,結果要是符合預期,就不管了。lua5.1.4中快排實現和LuaJIT中不一樣導致了這個問題。

最初我以為是1的問題,於是手寫了個穩定排序,掛入後果然解決了陷阱的問題(即使優先順序都是2)。但是其餘的bug不能都解決,此路不通。

那麼如果按2來說,快排實現不一樣,那麼我換一個lua5.1.4原版的DLL試試呢?

於是我把lua51.dll改名成luajit.dll,運行遊戲,果然一切正常。看來就是排序演算法的問題了。

於是我打開lua5.1.4的源碼,對比luajit的源碼,卻發現了神奇的事情:排序演算法除了報錯部分有點區別以外,竟然是一模一樣!!

那這個就奇怪了,排序演算法也是一樣的,為什麼結果不一樣呢?我漏掉了什麼東西嗎?

再仔細檢查這兩張圖,終於發現了問題所在:

(啟用PATCH前)

(啟用PATCH後)

(啟用PATCH後)

之前我一直關注排序後的結果,卻沒發現排序前的數據順序也是不一樣的(紅框所示)。也就是說,問題本身與排序演算法沒有關係,與錯誤的priority雖有關係,但不是致命的。

致命的是排序前數據的順序是為何不同的!

沿著SortActionList往上找,果然,剛剛就在眼皮底下錯過了:


function PlayerActionPicker:GetSceneActions(targetobject, right)
local actions = {}

for k,v in pairs(targetobject.components) do
if v.CollectSceneActions then
v:CollectSceneActions(self.inst, actions, right)
end
end

if targetobject.inherentsceneaction and not right then
table.insert(actions, targetobject.inherentsceneaction)
end

if targetobject.inherentscenealtaction and right then
table.insert(actions, targetobject.inherentscenealtaction)
end

if #actions == 0 and targetobject.components.inspectable then
table.insert(actions, ACTIONS.WALKTO)
end

return self:SortActionList(actions, targetobject)
end

不管你信不信,問題就出在這個函數里的for循環上。

如果您看過前文的話,就能明白我的意思——for循環的枚舉順序是與string HASH演算法有關的!而v:CollectSceneActions是順序往actions中添加ACTION的,那麼,不同的枚舉順序就會導致ACTION在actions里的順序不一致。

而LuaJIT的string HASH演算法和原版lua的並不一樣,這也是前文聯機版RPC出bug的原因。

那麼,我們把邏輯整理一下,完整的bug觸發流程是:

string HASH演算法不一致 =&> table里key的遍歷順序不一致 =&> actions里的ACTION順序不一致 + 排序演算法不穩定且待排序數組中存在鍵相等(priority相等)的問題 =&> 排序結果不一致 =&> 選中的操作不一致。

事已至此,所有的謎團都已經解開了。回頭來看,如果饑荒作者在發現actions排序後順序奇怪的時候能想到這是排序演算法的穩定性,那麼就絕不會只調整個別ACTION的priority來解決,而是會重新為所有的ACTION明確不同的priority。如果他們這麼做了,整個問題就完全不會出現。

而現在,程序能夠正確運行完全依賴於特定排序演算法對特定數據的排序結果。試想如果有一個mod手工添加了一個ACTION;或者隨著版本更新,作者又在targetobject.components里添加了一個默認components,都會導致排序的結果與預期的不一致,而且這種不一致會導致大面積的邏輯錯誤,極難排除。

更麻煩的在於,已經有不少第三方MOD使用了ACTION。如果隨便改掉默認ACTION的priority值可能會導致這些MOD出錯。

因此這個bug就慢慢地變成了feature,且無人敢動。

那麼怎麼解決呢?我沒辦法,只能把lua5.1.4的string HASH演算法複製出來,替換掉luajit的那份實現了。這個同時也解決了之前RPC的問題,不用再修改代碼了。我其實不想這麼改,因為這樣的設計將會面臨更高的安全風險。但是沒辦法,將錯就錯吧。


評論里信誓旦旦要小護士的兄弟,你真覺得長著一張工程師臉的你能搞定么? 木有高富帥的命,卻得了高富帥的病……洗洗睡吧親,一個成功的工程師是注孤生的……

XXXXXXXXXXXXXXXXXXX
網路硬體相關
現象:
某醫院部署的網路,不定期會有半夜斷網或者不穩定情況,但天亮就會恢復,客戶投訴抱怨。

調試過程:
現場查看全部網路硬體正常,查看log發現有一台匯聚交換機有反覆重啟動作,在重啟前有高溫告警。於是重點關注該機器。

該機器放在一個機櫃中,機櫃在一個小儲藏間的角落裡,儲藏間不大,一邊還擺著張破沙發,正好可以坐著用電腦調機器,但是實在查不出什麼可疑情況會導致過熱,因為投訴等級較高,於是連夜蹲守。

第一夜無事。
第二夜無事,到半夜,忽然進來個小護士,嚇一跳,說,喲怎麼有人啊,然後就走了。一夜無事。
第三夜無事,到半夜,又來個小護士,探頭看一眼走了。一夜無事。
第四夜無事。
於是告訴院方,發現問題馬上打電話,回家。
第五夜出事,趕到時已是早上,網路已經正常,查看log發現還是過熱告警重啟,時間在半夜3點多。聯想到前幾天的小護士,於是問院方半夜是否有人進入,答一些值夜班的護士會偶爾在裡面休息。

於是找到進去的小護士,問是否動交換機,答沒有,問進去後做了些什麼動作,答只是睡覺。再追問,除此之外呢?答:就是那個排風扇太吵,睡覺的時候把電源拔了。

她把機櫃的冷卻排風扇電源拔了!
她把機櫃的冷卻排風扇電源拔了!
她把機櫃的冷卻排風扇電源拔了!
她以為就是個通氣風扇!

居然睡醒走了還知道再插回去 〒_〒
你有膽拔插頭你倒是別插回去啊…

EEEEEEEEEEE分EE割EEEEEEEEEEEEE

再說一個吧。
研發的一塊新電路板,調試正常,往機箱裡面裝,裝上螺絲擰好後不上電了,沒有電壓,確認是電源短路保護。

把板子拆下來,又能用了。
裝上去,又不能用了。
跟白鹿原里白孝文在窯洞里穿褲子一樣。

機箱是金屬並且接地的,檢查了全部連接,電源肯定木有碰到地,但是用萬用表量的明明就是電源地短路,而且就是裸板能用,帶機殼就短路,於是懷疑螺絲。

螺絲都擰上就短路,都拆下來就正常。
然後挨個擰螺絲,定位到某個螺絲。
那個螺絲一擰上就短路。

但是電路板正面反面都是地,螺絲本來擰上去就是為了接地用的,怎麼會把電源短路了呢……
這tmd不科學啊。

仔細端詳該螺絲孔,發現內壁有些黑,湊近聞略有焦味。心裡大概有數了,一查pcb圖,果然,6層電路板,內層電源層的鋪銅幾乎直接鋪到了螺絲孔,安全距離只留了一點點。

其實本來也沒什麼,螺絲只是固定用的,不會和螺絲孔內側有什麼觸碰,好死不死的那塊板子那個螺絲孔公差偏大,螺絲擰上去是沒有完全對齊的,直接卡到了螺絲孔內壁……使勁一擰,就像刀一樣切了進去,碰到了內層電源。

所以,所有災難,都是一連串小概率事件的巧合扎堆,搞科學,也得信命。

------------------------------------------------------------------
補充備註一下,這都是很多年前的事情了,以現在的技術而言,第一個case即使機櫃風扇關幾天都不會有問題,晶元的可靠性和工作溫度範圍已經有很大的改善。第二個case則是安規設計規則的低級錯誤問題,只要正常按照安規的規範審查,是不會有問題的。


寫一個熱乎的,剛發生的:
寫JS,自己手機沒電了,拿同事老張的安卓機調試,很簡單的獲取用戶微信昵稱,結果死活獲取不到,一直顯示為null。應該是跨平台問題,因為之前在自己iPhone上是沒有bug的,拚命看api文檔,但是都沒提到這方面。急死我了。

———————8.21更新—————————
剛剛老張告訴我他的昵稱就是null。

———————8.22更新—————————
老張已被打死

————————說明——————————
前面誇張修辭,老張最後當然沒死,腿打斷了而已。

答主的其他回答:

有哪些聽起來高大上實際上很普通的東西? - 條件狀語從句
在網吧寫代碼是怎樣一種體驗? - 條件狀語從句
PPAP 為什麼突然之間就火了? - 條件狀語從句
哪個瞬間讓你突然覺得讀書真有用? - 條件狀語從句
為什麼喬布斯和扎克伯格都有傳記式個人電影,而蓋茨沒有? - 條件狀語從句
為什麼總覺得日本的很多地名比中國的聽起來有味道? - 條件狀語從句


分享剛看到的一則新聞——
思科交換機複位鍵設計問題成了網路工程師最可怕的噩夢

2013年,思科針對旗下價格昂貴的3650和3850系列交換機發布了「問題通報」。世界各地的許多數據中心正在使用這2個系列的交換機。通報當中詳述了這2個系列交換機當中複位鍵存在設計錯誤,導致用戶插入網線之後,可能在短短几秒內讓整個網路癱瘓。

如果有人在這個埠插上一根網線,在不知情的情況下就會按下複位鍵,他們甚至沒有意識到整個網路已經因此癱瘓。

如果有人在這個埠插上一根網線,在不知情的情況下就會按下複位鍵,他們甚至沒有意識到整個網路已經因此癱瘓。


歪個樓,說個SCADA工程投產的關鍵時刻突發的bug,論技術含量,它無疑是最low的,但絕對夠刺激。

N年前的一天,晚10點,甘肅戈壁灘上的某處工藝站場,現場一片忙碌,根據工藝人員的測算,工藝介質還有半個小時就要進站了,介質進站就表示站場正式投入運行。我和我們部門經理,作為站場核心控制系統——SCADA系統的集成商,2人蹲守在站控室的主控電腦前,做好了72小時保投產的準備。

隨著時間一點點逼近,控制室的氣氛越來越凝重,對講機里絲絲拉拉的呼叫聲、調度電話的鈴聲,此起彼伏,我和經理也一臉嚴肅地坐在站控台旁,內心既輕鬆又緊張,經過一個多月的緊張安裝與調試,站控SCADA系統早已準備就位,該忙活的都忙活完了,剩下的就看它的了!

10點05分,報警器突然傳出沉悶的報警聲,三條報警出現在SCADA軟體的監控屏幕上,三條報警都指向同一個設備,我快速瀏覽了一下控制流程圖,其它設備一切正常,單只這台設備的狀態出現了變化,我的第一反應是,又是哪個傻逼在現場亂動設備了。

這套站控SCADA系統是由我親手組態安裝的,每一個信號迴路、每一個信號、每一個控制命令,我都親自測量調試過,我敢保證系統里顯示的所有數據,一定是對現場工況的真實反映,所有的數據和操作,一定是準確可靠的。

我猜測,這台狀態發生扭轉的設備,肯定是現場人員手動操作的結果,沒有調度令瞎特么動,找罵呢,也不看看現在是什麼時候,我還幸災樂禍呢。

10秒鐘不到,幾千公里之外的控制中心的調度電話就打過來了,什麼情況?報告原因?

離設備最近的工作人員迅速趕過去,用對講機彙報了檢查結果:設備一切正常,沒人動它,現場指示一切正常!

現場指示一切正常,我心裡咯噔一下,不會吧,設備沒問題,那就是SCADA系統有問題呀,數據錯誤那可是重大失責,極有可能導致生產事故的,這可不是鬧著玩的。

騰的一下我就站起來了,抓起防爆對講機就向200米開外的工藝區衝去,我必須親自確認一遍設備狀態,果然,機械指示沒問題,電子指示也沒問題,各種裝置也在原來的位置上,沒錯,設備沒問題,完了完了,回去查系統吧。

我以最快速度飛奔回站控室,一頭扎進機房,迅速檢查了一遍上、下位機的數據,PLC、實時資料庫、HMI三者數據一致,數據流正確,系統沒問題呀,我在腦子裡頭快速過了一遍數據流程,究竟是哪個環節出問題了呢?

PLC程序中設備的控制邏輯是按模塊封裝的,要出錯早就該出錯了,不會拖到現在,即使真出錯了,也得是現場幾十個同類設備一塊出錯呀,單是一台設備出問題,肯定不是PLC程序的問題,排除。

實時資料庫出錯的可能性也不大,數據點在資料庫中是哈希分布的,不存在邏輯關聯,同一設備的所有數據點同時出現錯誤的概率太小了,我還不至於點背到這個程度,再者說,實時庫中的數值與PLC是一致的,說明數據採集程序運轉也是正常的,排除。

HMI顯示的都是實時庫發布的數據,我手動修改了幾個故障數據點,HMI立馬跟著變化,數據發布和顯示功能也正常,排除。

上位機和下位機都排除了,剩下的就是PLC IO模板及其之後的環節了,先排除IO模板吧,短接測試最簡單有效,半分鐘之後得出結論,IO模板也沒問題,排除。

問題肯定出在現場設備與機櫃之間的信號迴路上,沒得跑了。

我咽了口唾沫,轉身抄起了萬用表,在PLC機櫃縝密排列、密密匝匝的電纜與信號線中,找到該設備的所有輸入迴路,逐一量過,果然,信號迴路的電流電壓與現場設備的狀態嚴重不符,與HMI顯示的錯誤結果一致,問題找到了,電纜傳過來的信號亂套了。

不會是信號電纜斷了吧,這是我腦子裡閃過的第一個念頭,現場設備與機櫃之間的所有線路我都親手調校過,每一台設備我都開蓋測試確保接線規範牢固,單體調試和邏輯聯動也是反覆做了很多次,這些基本功課我做的一絲不苟,完全可以排除掉接線錯誤、接線自動脫落等低級錯誤,出問題的是台進口設備,這類設備之前的表現一直很穩定,信號源出錯的可能性貌似不大,現在擺在面前的唯一的可能就是,這條電纜現在斷了,或者它曾經斷過,後被施工人員只是草草地焊接上了,現在脫焊了。

按照施工規範,信號電纜是不允許出現斷點的,然而,獻禮趕進度,難免出現加班施工、違規操作的情況,哪個土方施工隊開著推土機,冷不丁來上一鏟子,干斷幾根電纜的事情也是不可避免的,畢竟從控制室與設備之間有好幾百米的距離,中間隔著很多地面施工點。(電信機房的光纜都會被人挖斷,更何況繁忙的施工現場敷設的電纜,每次想到獻禮我就蛋疼)

一種不詳的預感向我襲來,要真是焊接的電纜,哪我就太TM倒霉了,電纜敷設和儀錶安裝的活也是我們公司乾的呀,儘管不是我的工作,但他們挖的坑埋的雷,最後都得由我們來拆包填坑,而且還特么是在這種關鍵的時刻,一萬頭草泥馬從我耳邊呼嘯而過,我不敢往下細想了,一股涼氣從後脊樑一路串到頭頂。

經理趕過來了,見我正舉著萬用表,看著機櫃發獃,還以為我嚇傻了呢,一把搶過萬用表就要親自上陣,他是趕過來保投產的,未參與前期調試,他以為是我的信號迴路接線有問題呢,我攔住他,把情況跟他一說,他也傻了,這種狗血的事情,他之前遇到過。

經理趕緊把在站控室外待命的儀錶安裝負責人叫了進來,瞪紅了眼,指著電纜問道,老張(化名),這根纜被人弄斷過,你們給偷摸焊上了,是不是,你跟我說實話。

老張憋紅了臉,拍著胸脯保證,這根纜絕對是一條完整的電纜,沒出過任何問題,無論我們怎麼逼問,老張都一口咬定,電纜絕對沒問題。

我和經理看老張說的這麼決絕,姑且相信了他,這種時候,諒他也不敢死扛。

可是電纜沒問題,設備也沒問題,那會是什麼問題呢,我抬頭看了一眼時間,10點19。

冷靜,冷靜,我擦了擦鼻子上的冷汗,走到機房的小窗口,漫無目的地掃射了一遍廠區,夜色中,幾十台相同型號的設備散布在工藝區的各個角落,深藍色的電子屏如群獸的眼睛,透著幽蘭的光芒,死死地盯著我,那台故障的設備就蹲在巨大廠房的陰影之中,眼睛忽明忽暗地眨巴著,彷彿在嘲笑著試圖馴服它的主人。

我突然一個機靈,不對勁,亮度不對勁,其它設備電子屏的亮度是固定不變的,很少出現閃爍,只有這台故障設備的電子屏,如鬼火般輕微跳動,在夜晚黑色的背景下,越來越刺眼。

電壓波動,肯定是380伏動力電網的電壓波動,導致設備的主電路版出現了故障,我拆過這種設備,它採用的是獨立的顯示模塊,儘管顯示模塊與IO模塊是隔離的,但電子屏都出現了畫面抖動,IO模塊也不會好到哪裡去。

我轉身正要往控制室跑,這才發現,站場的主要負責人已經把經理給圍住了,經理憋紅了臉,應對著各種質詢與責難,我趕緊上前,把我的發現與想法告訴了他們,他們半信半疑,這種進口設備,項目上用了這麼多,從未出現過這種問題,你確定是設備的問題不是你們的問題,信與不信已經不重要了,不試怎麼知道,時間不等人,趕緊請示控制中心,要求重啟設備,控制中心回復:現場自行決定。

還決定個屁呀,我抹頭就往廠區跑,把這幫人甩在了身後,開閘,斷電,等待10秒,合閘,上電,報警自動恢復,數據顯示正常。

10點28,故障終於解除了,憋在胸口的一口悶氣,終於可以吐出來了。

控制工程和軟體工程一樣,都是系統工程,一環扣一環,一層疊一層,系統越複雜風險越大,系統工程出現的問題,絕大部分都是多米諾骨牌效應造成的,小故障導致大事故。

多米諾骨牌的觸發和傳導過程是外顯的,小牌推大牌,傻子都能一眼看出問題的端倪,但系統工程是隱蔽性工程,系統內部的結構不是顯現的,問題的發生與傳導也是不可見的,當故障發生時,大家看到的是倒下去的最後一張牌,看不見故障的源頭,也看不清坍塌的全過程,這也是為什麼工程師愛說「不是我的問題」的原因,很多錯可能真的不是他們造成的。

愚蠢的管理者,會責怪倒下去的最後一張牌,聰明的管理者,認為每一張牌都有作案的嫌疑。

當你被懷疑時,能做的就是自證清白,往下深挖,如果你自己的功課一團糟糕,又沒本事找出推倒你的上一張牌,那麼很不幸,你就成了那口黑鍋,only you。

很多時候,工程師什麼都愛學一點,並不一定是求知慾太強烈,或許他只是想看清楚,站在我身後的,究竟是些什麼鬼。

----------------------------------------------
相關話題:
石油,天然氣的管道運輸程序是怎樣的? - SCADA 的回答
不同行業的真實工作是怎樣的? - SCADA 的回答
現代工業的存在是依賴於工業軟體的支持嗎?我國有世界級的工業工程、控制軟體企業嗎? - SCADA 的回答
為什麼自動化專業後來轉成 CS 的這麼多? - SCADA 的回答


最難調的是在菊花廠做分散式資料庫Taurus,遇到的數據不一致的bug.
為什麼說難調呢?
1.從發現bug 到最終解決耗時一個月,當時項目已經馬上要發版本,所以期間每天搞到12點。
2.耗人力,累計投入1個專職測試,最高峰時期有一個10人的攻關小組,這很菊花廠:-),其中一半以上的都是senior developer,一個價值上百萬的bug。

從技術上來說難度主要體現在

1. 系統複雜,涉及到上百萬行代碼,系統分散式部署

2. Bug 難重現,並且沒有規律

3. 系統代碼複雜度較高,涉及資料庫事物核心代碼。

吹水結束,下面是技術乾貨

1,什麼是數據不一致,怎麼測試出來的?

測資料庫一致性的用例一般用轉賬的例子,例如A給B轉10塊錢,那就需要把A的賬戶餘額減10,再給B的賬戶餘額+10,然後再判斷一下A和B總的賬戶餘額不變。當然你也可以擴展到多個表,但是基本原理類似。我們的測試是用TPCC,所以一致性使用的是TPCC的業務模型,模擬往TPCC的warehouse之間出貨,然後查庫存是否一致。

為了盡量模擬用戶的真實使用場景,當然得導入大量的數據,加大並發,中間還會故意模擬各種故障。 所謂的各種故障,說白了也無非是殺進程,下電,模擬磁碟故障,因為Taurus又是一個分散式的資料庫,網路故障必不可少,模擬網路抖動,丟包,網路割裂。

不過,出現數據不一致的場景反而沒那麼複雜,就是導入300G基礎數據,然後起大量並發在上面跑TPCC測試用例,跑個3,4個小時,一致性檢查語句就能發現數據不一致。(想想挺後怕,要是這個bug是由各種故障的注入導致的,定位的工作量估計又要翻倍)

2,怎麼定位和解決的?

最開始接手這個問題的時候,首先當然是看測試測的對不對了,因為之前也烏龍過幾次,測試一開始導入的數據就有部分不成功,導致一致性檢查一開始就沒通過 :(。確認問題後,就開始定位。

首先我們想的是找到重現條件,好吧,那就開測吧,搭個環境,導個數據,1天下來也是能夠重現一次的。

那不一致的日誌有沒有什麼特徵呢?插入的數據有上千萬條,每次測試出來的不一樣的記錄,連不一樣的表都不重樣。

那日誌里有沒有啥異常?日誌全開,還好跑幾個小時也就幾G的日誌,大菊花廠的硬體還是捨得花錢的,都是300G內存的機器,mount一塊來放日誌,grep查出來的warning,error看的眼睛疼。

那隻能從讀寫流程來定位了。這裡需要首先交代一下Taurus的背景。

Taurus是一個基於MySQL改的計算和存儲分裂的分散式資料庫,計算層支持1寫多讀,存儲層通過raft協議來同步redo log。

那寫入的流程就涉及到:

  1. 計算層的讀寫節點解析執行SQL語句,最終執行引擎會生出redo日誌,
  2. redo日誌發送給存儲層的leader節點,
  3. 存儲層的leader節點再通過raft,把redo日誌同步給其他的存儲節點(副本)
  4. 存儲副本再解析redo,重放redo
  5. 存儲副本的數據是需要做checkpoint的,把臟頁寫入磁碟
  6. 計算層的讀節點讀的時候首先在自己的緩存裡面找需要的頁面,如果對應版本的page在內存中,直接返回,否則是需要去存儲節點拿的
  7. 存儲節點接收到請求的頁面,會根據頁面號,頁面版本信息,返回對應的頁面。

說了這麼多,最想說的其實就是,這個流程中任何一個步驟出差都可能會導致數據不一致,我們的想法是,首先要定位到在上面的整個流程中,是哪一個處理流程出錯了。

我們的辦法是按流程倒著排查。

首先第7步請求的頁面和磁碟上的頁面是否一致?

我們根據出錯的記錄,找到記錄所在的page,把都出來的page dump出來,然後和磁碟上的數據做對比。除了page頭的數據,其他一樣,ok,那可以排除計算節點讀到的數據和寫入的數據不一致。

然後再排查第3步,同步的redo日誌有丟數據?

我們對raft同步的數據做了很多優化,其中之一就是raft同步的數據做了batch,然後我們開發了一個raft日誌的解析工具,把raft日誌解開,列印成可讀的文本方便分析。

然後還真通過這個工具發現raft日誌裡面的redo日誌不連續,我們很開心,覺得問題已經定位的差不多了,但是raft那塊代碼是相當穩定的,因為我們團隊在raft演算法積累了好幾年的經驗,在cockroach db和etcd社區有5,6個committer,並且,raft出錯好歹也會在leader頻繁切換,磁碟故障或者網路丟包嚴重的情況下,測試場景一切都正常,當我們回過頭再來看我們的raft日誌分析工具,發現對應的分析工具正好有幾個我們添加的redo日誌類型沒有解析到,並且沒解析到日誌也沒報錯。。。這個時候,時間已經過去了2周。無奈之下我們只好去北京,找當初寫塊代碼的兄弟一起來攻關。這裡順便吐槽一下菊花廠的開發模式,經常是一群人做了一個原型,然後丟給另一群人做產品,然後可能最後會丟給其它的團隊來做維護,同一個項目團隊成員流動太大。

讀上來了一個老版本的頁面,然後在老版本的頁面上再做修改,導致把中間做的修改的數據覆蓋?

果然還是親爹靠譜,直接就說了一個他覺得可能性最大的地方,然後這哥們就回去啃代碼去了。。。

這裡交代一下背景,MySQL本身有一個mvcc機制來實現多版本管理,在我們的實現中計算層的只讀節點是按頁面來請求數據的,為了滿足不同的事務請求請求同一個頁面在不同時刻的數據,我們在存儲層實現了一個多版本的page管理機制,會根據讀page請求的版本號,來返回一個特定版本的page給用戶。

問題就在「請求特定版本的page」這裡,如果計算層請求的page版本是一個老版本的page,那後面的update是會基於這個老版本的page來做修改的,舉個例子:比如存儲層的page版本是10,上面有2條記錄,記錄了插入的2條數據,但是計算層讀到的是版本為9的page,版本為9的page沒有在事務10插入的2條數據,這個時候事務11為page插入另外2條數據,生成的redo剛好就覆蓋了事務10增加的那2條數據,這個和innodb的page機制有關,插入數據的時候會從空閑空間那個offset往後寫數據。對應到我們的一致性檢查就會看到轉賬的時候,只看到A扣錢,B的餘額沒有增加。

那我們的想法是先通過測試來驗證是這個問題。但是重現一次需要半天時間,我們想到的是盡量構建重現的條件:

  1. 縮小導入數據的規模,把導入300G數據改成100M。
  2. 把buffer pool設置的很小,這樣計算層就需要頻繁的去存儲層讀page,讀的越多就越有可能會讀錯老版本頁面。
  3. 加大客戶端的並發度,構建盡量多的讀寫請求。
  4. 把存儲節點的buffer pool設置的很小,啟動另外一個寫磁碟的線程,不停的搶佔磁碟IO,這樣的好處可以讓存儲節點讀磁碟慢,返回頁面慢,增加讀寫頁面的衝突程度。

結果20分鐘就重現了這個問題,一下子把定位的問題從上百萬行代碼縮小到我們寫的1w行代碼。

這個時候時間已經過去了3周,後面我們花了1周時間改了2行代碼,修復了bug,並通過驗證。導致出現bug的原因很簡單,計算層在把redo日誌提交給存儲層做持久化的時候使用了非同步和pipeline的方式通訊,在非同步處理裡面,我們保護redo日誌提交的鎖釋放晚了,導致讀線程還是拿老的事務號去讀page,從而讀到了老的page,從而導致修改的時候基於老的page修改。

3,怎麼保證後續的開發不會再引入同樣的問題

首先想到的肯定是把重現的用例增加到自動化的測試用例集中,在每次發布代碼前都需要校驗。

增加測試用例,模擬各種極端情況,例如基於jepson模擬各種網路和節點故障。

除此之外,我們還在代碼中增加了幾個檢查點,例如在每次替換頁面的時候保存頁面的checksum來校驗讀上來的頁面是否是寫下去的頁面。當然這個開關只在內部測試版本中才會打開。


**************************************************************************************
2016.8.29 咋最近這麼多贊,莫非來了很多考古學家?
既然這樣,順便發一個剛剛發生的真實案例,絕對真實,有血有肉!!(Debug 案例2發在原答案下方)
================================================================

原答案:(Debug 案例1)
======================================================================
差不多10年前,我們做了一個ARM核的晶元,據說還是國內第一批用ARM7做的,還挺高端,帶有很多安全功能,當然安全就意味著難以調試,整個系統全部打散,不能分塊。俺負責前端設計,系統,硬體軟體驅動等雜七雜八一堆工作。

然後晶元流出來了,封裝回來,幾天幾夜的調試,功能都正常了,那個高興呀,第一個晶元就成功,獎金有了!

不過做穩定性測試時候有一個問題一直困擾著,這系統總是莫名其妙的有時候啟動不起來,概率有個百分之幾左右。上電就是不LOAD。而一旦起來之後,就很容易了。

反正功能設計,硬體,驅動都是俺的,那就調唄,軟體,硬體,電路,模擬,研究了好幾天,抓狂,無解。又整個系統不能分塊,我都開始懷疑是不是ARM核的問題。。

又做了一個不斷重啟的測試系統,不斷啪啪響上電斷電,針對上電的情況作了統計。得出結論就是,上午不啟動的概率高,下午不啟動的概率低,晚上不啟動的概率高,深夜不啟動的概率低。。。。。和飢餓程度快掛鉤了。。。

那時候那個抓狂啊,懷疑是什麼干擾的,連屏蔽房,隔離電源啥都整出來了。就是沒頭緒,而公司給客戶演示的時間快到了,要是現場掛掉就丟臉了,心裡那個急啊。那段時間,每個深夜,公司里就是我座位上啪啪啪的聲音------繼電器的啪啪聲。

接下來一個周日測試,公司空調壞了,汗流浹背,脾氣極壞,幾乎就要摔板子了。不過發現這天運氣非常好,成功概率很高。沒頭緒,直接抽上煙,看著板子發獃,不知那根神經搭錯,直接把煙頭對著晶元戳上去!咱第一個親生晶元!!如果不行了就掐死它!!!結果發現怪了,戳了煙頭,啟動嘩嘩的,每次都OK。遂懷疑是尼古丁過敏或者是溫度原因。拿著烙鐵燙著它,每次必成。於是送進高低溫箱,做溫度曲線測試,發現環境溫度40度以上,成功概率極高,剛好碰見今天加班沒空調,平均溫度高,所以表現良好。而啟動起來因為系統一發熱,所以後面啟動就容易了,溫度一涼下來表現慘不忍睹,敢情這晶元是非洲來的。

有了方向就好說,先解決DEMO,給領導好看。遂做了一個電熱絲髮熱電路,貼在晶元上,用單穩開關控制,一上電就加熱,然後不斷自動啪啪啪對晶元重啟,一旦晶元重啟成功了就斷開發熱電路和重啟電路。進入正常運行情況。系統搭起來一測,效果杠杠的!!!基本都能保證幾秒鐘內就能啟動,公司上下一片讚譽。 於是,領導拿著這套帶著電爐絲的系統去做報告,銷售拿著這個電爐絲Demo去給客戶演示,取得極好成功,老闆都在準備後續的銷售計划了。俺心裡急啊,總不能出貨產品也帶著電爐絲吧。。。。

靜下心好好分析,和溫度有關,又是隨機故障,應該很可能是哪個地方懸空,存在不定態的問題,外面的電路是不可能了,前端模擬加入隨機量也不能重現,那很大可能是後端的人搞的鬼,遂拉來後端人員(暫且稱為C公司),檢查掃描鏈和測試電路,果然發現有一個寄存器沒有初始化複位。於是後面的情況就簡單了,往掃描鏈中灌入一串數據,把未知量洗出來。成功!!!

所以我們第一代的產品,主晶元旁有一個奇怪的晶元。據線人報告,有競爭對手和盜版者都認為這是安全反盜版電路,因為拆掉這一塊,系統工作就時不時的異常,抓不到規律,可能包含短時間正版驗證,長時間正版驗證,隨機正版驗證等高精尖反盜版措施。反正無法破解。。。。

俺笑而不語。圖樣圖森破。:)

---------------------------------------------------
至於說為什麼寄存器沒有初始化複位沒檢查出來,我也不知道,這是人家C公司做的後端,他們的軟體自己加進去的電路。而據說這C公司雖然牛,但那時候後端服務還是新的,軟體也是新的,剛進國內,給我們一個特惠價做白老鼠。。。

=====================================================--
=
=(Debug 案例2)
=
=====================================================--
最近把家裡的台式機搬到了客廳,雖然台式機和電視機都很少用了,但本著發揮餘熱的原則,接了一根HDMI線到電視機,於是可以雙屏顯示了,用奇藝投放視頻到電視機中,一家人一起看,效果杠杠的,俺還可以在邊上顯示器上一邊刷知乎,各得其樂。


不過呢,原來用的是天威寬頻,看大網站的視頻速度還行,因為天威有CDN. 但看不熱門的網站,或者720p的節目,就相當的卡頓了。於是說服老婆大人,改為電信光纖,在沙發的地方放光纖貓, 因不好拉網線跨過整個客廳,於是用了一個TPLINK的多頻無線路由器,通過5G WIFI連接電腦。這下載速度相當的滿意。看視頻也是可以選擇最高清晰度的。不過呢最近都是下載藍光大片,不看在線了,原因無他,爽。

這兩天,LP大人想看一些新片,於是俺用奇藝找到電影,然後打開電視機,拉到電視機中播放。LP大人很滿意。 可是蹊蹺的事情發生了。無論是什麼電影,只要放上去幾分鐘,馬上就卡住,然後就不能放了,網路全斷,PING 路由器也出現大量的掉包率。。。。然後再也不行了,瀏覽器也上不了了,切換其他視頻也不行了。 重啟電腦偶爾能連上,也是斷斷續續。 但是平時上網或者在顯示器看視頻,又是從來不出問題。
又有一天,不死心,打開瀏覽器,開始播放,然後暫停,然後等LP回來了投到電視準備一起看,結果看幾分鐘後就斷網。。。
又有一天,LP大人自己找了視頻,看了一段,然後暫停,準備一起看,結果投到了電視,過幾分鐘又斷網了。。。
不過只用顯示器看視頻貌似沒問題。。。。
每次只要LP大人想要看視頻就看不成,看著領導敗興的樣子,亞歷山大。。。

於是查硬體,查軟體,挪天線位置,切換2.4G, 5G, 似乎都沒有反應。
查了網路上的信息,據說這個INTEL AC-7260 無線網卡似乎確實有網路兼容性問題,貌似我還真的是MSI主板:


這下中彩了,按照網上的信息修改設置也沒用!據說最好的方法是換個網卡。於是向LP申請了2萬塊換一台一體機,未遂。

於是作為一個技術宅,擺了一個亮騷的機器,卻無法讓LP舒服的坐在沙發上看電影,俺多年來修理水龍頭遙控器電燈泡積攢的權威性日漸消失。地位一天不如一天。。。

今晚不死心,又折騰了好久,重啟電腦,路由器,拔掉所有外設。故障照舊。。。

現象總結:
1.用網線沒問題。
2.只用電腦上網看視頻沒問題。
3.上網的時候家人看電視沒問題。
4.把電腦的視頻投上電視機,剛開始幾分鐘沒問題,接下來就幾乎連不上網了。然後再怎麼重啟,也很難連上,連上也是斷斷續續的。
(感覺就像家裡有個幽靈,一看到我獻殷勤,或者看到電視機被電腦佔用就耍脾氣。。。。莫非是貞子??)

突然,靈犀一動,仔細分析了坐著沙發看視頻和趴在電腦前看網頁的區別。發現這主要區別應該在於電視機的問題,於是開命令行一直PING路由器,同時切換電視機,發現只要電視機在顯示PC內容,必然會斷PING,關掉電視或者切換到別的源,就沒有問題。看了應該是多屏或者輸出的問題,試著修改HDMI解析度,從720P60 改為1080P60,PING非常流暢! 問題順利解決!!!俺又可以在家裡當大爺啦!!

問題分析:
HDMI線有干擾,影響到了WIFI信道,加上這個AC7260無線網卡本身設計不良,容易串入干擾。由於HDMI工作頻率是根據視頻信號碼率決定的,通過修改解析度,改變HDMI工作頻率,使干擾諧波信號跳開了2.4G和5G信道。另外通過HDMI傳輸信號會有一個介面協商初始化過程,只有電視機切換到這個HDMI源,完成初始化,才會在HDMI線上有數據,這點和VGA,YPBPR等模擬信號不同。

之前分析問題沒有往HDMI方面想,主要是視頻播放會有一個緩衝,因此,剛開電視切換到HDMI的時候,一切看起來是正常的(但此時後台網路已斷),過了幾分鐘緩衝讀完了才停頓。因此分析問題時很難和HDMI線聯繫上。

此問題其實做音視頻類產品的項目經常遇見,由於HDMI頻率高,傳輸長,因此很多輸出源有意加重輸出信號,導致EMI干擾嚴重,又由於很多HDMI線材質低劣,偷工減料,缺少屏蔽措施,因此HDMI介面往往成為電磁干擾的重災區,也導致大量HDMI介面的兼容性問題(我這個破電視就挑信號源,有些1080P不顯示)。因此能用YPBPR模擬線,或者能用DP介面,我都是躲開HDMI的。

****************************************************************************************
****************************************************************************************
****************************************************************************************


9月21日增加說明
謝謝各位程序員捧場……
我要講的這個bug一點都不難,可能大家也都遇到過,但是當時卡了我一上午,所以印象極為深刻
在這跟大家分享一下
--------------------分割線-------------------------

我來講一個吧,也是我親身經歷的
在傳輸數據的時候,需要對字元串a做判斷,如果a為空,則不下發配置,非空下發配置
在我獲取到a之後,我把它列印出來發現是""
嗯,什麼都沒有,為空,應該不下發,但當我去檢查配置的時候,很奇怪的下發了
我當然認為是我的判斷問題,去檢查,發現沒問題,判斷條件和邏輯都是正確的
我覺得應該是字元串類型的問題,我又把所有判斷項都轉換成了unicode編碼,問題仍然存在
我又更改了判斷條件為a的長度為零,則不下發,問題仍然存在
當時我的心情大約是這樣的


為什麼
為什麼明明a的內容什麼都沒有,代碼卻一直認為a非空呢
a你為什麼如此任性的表現你的存在感呢!
我覺得下發的配置在嘲笑我,雖然是一個bug,但是他卻表露出一股王霸哥之氣


我憤怒了,身為測試轉研發的我一定要解決這個bug,人定勝天……額,bug

我又重新寫了一個程序,專門測試這段代碼,代碼邏輯仍然沒問題,但是只要傳輸數據,他就認為a是非空的

我逐行print各種東西,數據全對,問題存在
我查了一下字元串那部分的python源代碼,代碼全對,問題存在
我更換別人的機器執行這段代碼,全都正常,問題存在……
最後胡亂嘗試,甚至期盼掌控代碼的神明或者魔法少女代碼子能夠出現幫我解決這個問題,
畢竟這個簡單的不能再簡單的問題已經卡了我將近4個小時了

直到我在一次瞎試的時候
print len(a)
屏幕結果是:2

我才恍然大悟,原來字元串a的內容,就是""
當時我大概是保持這樣的動作持續了十分鐘吧


而且,發生這一切的時候,我才剛剛看了@條件狀語從句的回答,還在跟同時調侃哈哈哈怎麼會有傳輸名字為null這種事發生

人生真奇妙


怒答!!
答主不如樓上這些硬體大牛,乃軟體狗一隻,去年做 Android 開發的時候遇到一個極其搞笑的 BUG ,自我感覺有必要說出來讓大家開心開心。

當時我們在做 Egret Runtime 的第一個版本,可以理解為他就是一個 Android GLSurfaceView 渲染器。

當我從開發工程師手中拿到第一個新鮮出爐熱乎的開發版的時候,我發現一個現象,就是遊戲整個 App 會不時崩潰掉,crash日誌大概描述的是 C++ 掛掉了,當時我由於經驗不足,還不會根據 C++ 的 dumpstack 去檢查是哪個模塊的問題。所以我決定先嘗試重現一下這個問題。

重現步驟:

1、把手機放在桌子上,打開 App,觀察5分鐘,未崩潰
2、5分鐘的時候接了個電話,接完後 App 崩潰
3、意識到可能是 Android 生命周期問題導致的,於是重新打開 App,反覆在前台 / 後台切換,未崩潰
4、由於長時間彎腰盯著桌子上的手機,有些不舒服,於是把手機拿起來,想舒服的後仰坐在工位上繼續重現,然後在把手機拿起來的一瞬間,崩潰。
5、嚇一跳之後,小心翼翼的重新打開 App,思索自己剛才到底做了什麼,同時緊握著手中的手機繼續盯著屏幕,觀察5分鐘,未崩潰
6、心情暴躁,把手機扔到桌子上,崩潰
7、驚愕......
8、重新把手機拿起來,再次扔到桌子上,崩潰
9、給開發的同事講,我重現出了這個BUG,只要用力把手機往桌子上砸一下,就會崩潰
10、開發同事匪夷所思的表示沒有調用過陀螺儀之類的 API,只是個簡單的渲染。
11、現場演示狠狠的砸了一下我的手機,崩潰重現
12、開發同事狠狠的砸了一下他自己的測試機,未重現,於是拿過來我的手機,進行真機調試( 每次調試就要砸一下我的手機....請自行腦補我的表情)
13、最後發現,原來是當手機被狠狠砸一下的時候,屏幕旋轉方向發生了改變,然後觸發了 OpenGL 底層渲染的問題沒有 Handle 住 ......
14、用3分鐘修復了此問題,然後對著被砸了20多次的手機淚流滿面.....


不算是最難,但是很有意思的一個bug。
我們系統里有大量的VR (virtual route),每個VR都有一堆的防火牆規則,大約幾百條吧。所以總共有幾十W條防火牆規則要在系統啟動的時候寫入到系統裡面去。
你要問為什麼不提前寫好,那是困為我們用的是cluster,都沒有硬碟的。沒地方寫啊。所有的軟體都要在啟動的時候安裝一遍,系統配置要在啟動的時候寫入一次。
以為背景。

平時用著好好的,大家都很開心。直到有一天香港的有個客戶心血來潮一下子配了幾百個VR,配了就配了吧,用的好好的。大家照樣很開心。

直到有一天他們做了個作死的事情。

他們決定要把系統重啟下。

話說我也不知道他們為毛閑的要重啟系統,因為我們的設計目標是除非升級是不需要重啟的。可能只是有某個人手賤想試試這個從來沒用過的功能吧。


然後我們系統就啟動不起來了。 啟動持續了好幾個小時都啟動不起來。

這不正常。

於是抓log,看現場。
各種narrow down,最後發現erlang gen_server (你可以認為是一個dispatcher)的隊列里留著幾W條未處理的消息,這個gen_server是被活生生堵死的。然後查查查,發現 iptables寫規則實在太慢了。我們的代碼又是一條一條寫的,每次寫iptables都要初始化一下,結果幾百個VR,幾十W條firewall規則的寫入請求堵在了gen_server的隊列裡面,然後掛了。

知道了原因以為好修了,一個進程寫不動,我們為每個VR開個進程不就行了?我們為自己的聰明才智感動不以。
事實證明我們too young too simple,
我們為幾百個VR開了幾百個gen_server進程大家一起努力往linux裡面寫。
然後


iptables掛了。再然後

linux內核掛了。

內核掛了
掛了

於是只好想辦法把iptables寫個driver集成到erlang裡面去,去restore的方法來做。這樣就跳過了每條規則初始化的時間。雖然還是需要幾個小時再啟動,但是好歹能啟動了。

什麼再後來?


再後來我們把VR的驅動改了個底朝天,搞定了。


答個前段時間剛發生的,不是最難調的bug,但是確實比較虐心。
之前用xilinx一塊比較高端的開發板驗證一個高速信號的功能,發現有一路輸出幅度是其他的1/10,感覺很詫異,於是和師弟翻了一天手冊文檔,難不成這貨還有配置幅度的功能。最後無解,用萬用表在BGA焊盤和走線上一點一點地量,過了一個小時,發現....


(Bug微距圖)
尼瑪一萬多的板子 表貼SMA接頭漏焊!中間大概有0.5mm的距離,高速信號空間耦合過去10%。
於是默默用熱風槍吹上,一切正常了。

這個bug的恐怖之處在於,高速信號可以從斷點發射出去,然後讓人誤以為卧槽這有輸出啊,不會想到根本就是個直流的斷路,直到你用了萬用表。


也談談自己遇到的一個bug吧,我之前是做電商的,某較大的電商平台,突然有一天,C2C的店主反饋,看到的訂單不是自己的,看到後台的商品列表也不是自己的

當時在睡午覺,看到這個問題,立馬嚇醒了,平時5個投訴就是一個故障單,那還都是一點體驗上的小問題,這種訂單混亂,商品混亂的錯誤,真是要緊急死了

於是,主管,總監都來看這個問題,一群大佬在後面看著,趕緊找最近幾天的發布,測試情況,一個個回退,一個個檢查,最後都無法解決問題,要知道時間一分一秒過去,半個小時還解決不了就要出大事了

後續又有用戶來投訴,直接電話聯繫,遠程控制電腦,發現操作起來巨慢,於是順口問了一下用戶的網路是什麼網路。

結果他說是:「某城寬頻」,一瞬間,有點感覺了,繼續問其他幾個投訴的客戶都是「某城寬頻」,然後我們打電話到那個寬頻的運營商,得到的回復是「年底了,為了省流量,他們做了一部分緩存」

他們做了緩存
做了緩存
緩存

可是為毛TM的動態請求還做緩存啊,修改商品和訂單的時候,隨機返回成功或者失敗 。。。。

=========回答下面的一些質疑===============
1.這個和時間戳也沒關,我們都加了token的,他們也忽略了
2.你沒猜錯,他們把POST和GET動態請求也緩存了,就是說你提交了一個POST修改商品的請求,他從環緩存裡面隨便丟個回復給用戶,用戶感覺修改成功了,其實請求根本沒到我們這邊
是的,就是這麼喪心病狂。


剛看其他人的回答,又想起來一個。

現象:
很簡單,有段時間學校宿舍電信的網路,經常大面積集體掉線,時間不固定,但發現最常掉線的時間是晚上10點多,各種投訴抱怨,問候學校網管家人的。(心塞呀,學校其實真的維護設備的都是我們這幫兼職的學生,那幫老師就管收錢和發錢,工資那麼低,累成汪還被罵。。。)

調試:

天去看了下機房設備硬體一切正常,當時以為是認證伺服器配置太low,晚上上線人太多就卡死,導致心跳包中斷,集體被下線,後來發現發現雖然到5000多
人後會有部分心跳包沒回應,但是客戶端允許部分掉包,人再多也不至於丟包到集體下線,最後查log發現這台機架上的所有交換機到那個時候都會重啟一下,於
是重點關注。

機房是5號宿舍樓1樓的一間宿舍改造的,加了空調,另外從配電箱里接了不限電的電源(宿舍的電嘛,晚上會斷電,還有功率限制),聯通電信移動每家一個宿舍做機房,於是我在機房裡蹲守。

第一晚是周六,一晚無事。

第二晚,到10點多一點,咔嚓一下機架上所有交換機的燈一下全滅了,過了幾秒後通電,重啟。誒,我什麼都沒做呀,我就在機櫃前坐著,也沒人碰機櫃呀!腫么就斷電重啟了,

然後又看了log,發現其他機櫃切換到UPS電源供電了一小段時間,交換機的機櫃是沒UPS的,明顯是斷電了,但是我人在機房,沒人碰設備呀
懷疑是插頭接觸不良,換了機櫃里的排插。(其實想想就不太可能,接觸不了還能定時發生?)

第三晚還這樣,
第四夜還這樣,
誒~神奇了,這是什麼超自然現象?

難道是進戶線有問題了?想起電線是從隔壁聯通機房進來的。
於是跑聯通的機房看看他們到點會有問題沒?發現這邊也是每到那個點,就自動切換到UPS供電。(聯通給交換機也配置UPS了)懷疑是電路有問題,時不時斷電。


於是,第五晚守在隔壁了。。。一夜無事!!!
誒??!!!這是神馬情況?
然後就再也沒事了,斷電情況再也不出現了。。
足足好了1個月。直到下個月的運行商派人來例行檢查了以下設備後,又出現上述問題了。

額,這是神馬情況嘛!!
我去兩個機房看了一下,誒,又好了,電信不掉線了!
抓狂了都!!完全是玄學呀這。
只要我進過聯通的機房一次,電信的掉線問題就能解決。。
我居然還有防掉線的功能?

最後只好挨個問機房隔壁的幾個宿舍有沒有發現在那個點機房有沒有奇怪的現象。
他們有人說感覺會掉線的日子裡,機房不是那麼吵!!

然後在我努力研究調查下,終於知道事情的真相:

========真相的分隔線==========
機房的供電是這樣的,紅色的是電線,紫色的是一排插座

電線是後改造的從室外的橋架上引入的明線,隔壁是電信的機房,因為進門的牆是承重牆巨厚還有鋼筋,布線的時候為了省力,就只從聯通這屋進了線,電線到插座高度後,打洞到隔壁。

電線是後改造的從室外的橋架上引入的明線,隔壁是電信的機房,因為進門的牆是承重牆巨厚還有鋼筋,布線的時候為了省力,就只從聯通這屋進了線,電線到插座高度後,打洞到隔壁。

聯通機房隔壁宿舍的嫌睡覺時機架散熱風扇太吵,就用偷偷用卡把門捅開(那種A級鎖,很好撬的),睡前就偷偷把機櫃的風扇電源拔了,早上再偷偷插上。。因為機
房有空調,拔了也沒導致設備過熱。但是。。。插機櫃風扇的那個插座是進戶第一個插座,就是那個孔的位置,他拔插時插頭會帶動插座,導致裡邊沒接好的電線在
連接處斷開一下。


為什麼周五周六有時沒事,因為他們可能周末集體出去了,晚上不在宿舍沒人拔插頭!
為什麼是10點多,因為快睡覺了,才去拔插頭。
為什麼其他時間不定時,因為他們不一定什麼時候想起來插回去。
為什麼我進去一次就沒事,因為我出機房後,會用鑰匙反鎖門,他們捅不開了,而其他人出來不反鎖,只是關門,他們能捅開。

其實現在回想,最詭異的就是,我第一次蹲在聯通機房的那一晚(第五晚)他沒來撬門拔插頭,不然也不至於追查這麼久。

又想起來一個:
【關於銳捷和交換機的事】

學校某一天開始,大量用戶正常上網情況下突然掉線,再次撥號時認證停頓在「尋找認證伺服器」,之後提示框顯示「認證失敗」。但是有人發現銳捷認證失敗後,「禁用」、「啟用」網卡後,多次嘗試銳捷撥號能夠成功認證,但是在上網幾分鐘後會再次掉線。

銳捷的認證過程就不寫了,看這個個問題的估計都大概知道。

先考慮,銳捷認證伺服器故障,看了下,CPU佔用率60%左右波動,內存佔用2G左右。(這渣渣伺服器啊。。佔用這麼高也是醉了)RG-SMP正常,加密狗也沒問題。看來伺服器沒事。

那估計就是網路環境故障,看了下核心交換機到認證伺服器之間的網,物理連接沒問題,流量看起來也挺正常。
那估計就是核心交換機和用戶終端交換機的問題。

然後發現,核心交換機到物理選課平台的伺服器間最高峰時一秒快3萬的封包。。。這腫么可能嘛,結果聯繫了那邊,到伺服器上一看,恩,伺服器被人拿下了,被放了好幾個成人和賭博網站在上邊,還發現有挖流量礦的代碼,還被拿來D別人,還被放了個SMTP伺服器在發垃圾郵件。裡邊的學生個人信息被下載都不算事了。

斷開與他們的鏈接後,CPU負載降低,但是認證還是失敗。

繼續查,發現另一個到行政樓的介面流量也很高,大約每秒1.5W的封包
show arp一下查看,發現一堆arp未完成報文,在Incomplete狀態在IP地址無法找到其對應的MAC地址時進行轉發,佔用了交換機內緩存表。於是猜,要麼中毒,要麼出現環線,然後就查呀查,發現基礎交換機上WLAN102、103流量異常,那就繼續查唄,最後發現,某逗比做了這麼一件事。

這個基礎交換機是直接給這層供網的,大辦公室留兩個口,小辦公室一個口,他們自己用交換機再分。
學校機構調整,部分辦公室從新分配了,本來有個大辦公室是,兩個部門同時用的,他們每個部門自己各佔用了一個介面用。後來換給另一個大部門了,他們搬家時,看到有兩個介面,於是把這兩個同時插到自己的集線器(居然連交換機都不是)上。

始作俑者還特肯定的和我說,這樣可以加快網速,可以有雙倍的帶寬。
我和他說這樣不行,他還不行,非說我不懂電腦了,他是在XX之家看到的,還說之前辦公室里就這麼接,就沒事,網速還快了。(後來發現,他原來辦公室其中一個介面,交換機端的插頭鬆了,插了等於沒插,網速什麼的,只能說是心理作用了)

什麼時候集線器能當路由器用了。。
什麼時候,帶負載均衡的雙WAN口路由器,只賣20塊錢了。。
想多玩多播起碼你也要設置下吧,直接插有個毛用。。
不怕遇到小白用戶,就怕遇到半懂不懂還裝懂的。。


小的是和另外幾千人給一個工業母機做軟體的。這個母雞差不多5億人民幣一台,一般大宗生產需要個百十來台滿足產量需求。


一台母雞停產一秒就虧150多人民幣。一個軟體bug造成一個母雞停產一天一套上海內環三居室就虧沒了。所以我們每寫一行代碼都是戰戰兢兢…


有天一線客服反饋一個客戶怒得拍桌子的問題,一個母雞精度會突然下降100%,檢測精度的感測器報警,導致母雞停止下蛋。按照故障查處協議,發現重啟系統就好了。然後這個情況一周出現一次,不固定。每次出現就損失一台賓士E系…

開發部門緊急安排人手排查。虛擬母雞模擬,根本不會出現這麼極端的問題。各種靜態動態分析控制精度的源代碼,都沒有問題。硬體部門排查相關感測器,也沒問題。在內部真實母雞上還是無法重現。絕望之下還查閱了當地地震紀錄…

於此同時,派我到客戶那兒,在出事母雞上結合總部大牛遠程會診,仍然沒有頭緒。一環套一環的程序上,都沒問題,每個輸出輸入,都正常。然後哪天,duang,讀出的精度就跳了。(當然物理上沒有duang這一聲),一周後我灰頭土臉回來。


算了,最後客服建議客戶重新安排維護時間來避免獨立重啟。賠送了客戶付費應用。並說開發部門對這個問題需要從長計議。

三個月過去了,某天在食堂偶然聽到坐對桌的硬體部門人扯淡他們有個供應商剛彙報了他家寄存器發現了bug,在某一個地址上寫不進去,但也不報警啊!聽了真是要報警了!

然後我們軟體這邊就電光火石般開竅了…一看出事母雞果然用的是那個批次的板子…別的母雞雖然也用那個板子,但不會遇上那個地址…這個倒霉催的母雞恰巧累計一段時間就會遇上一次…一個數字正常進去出來就給截了一半啊…
後來給那個母雞升級了那個板子的固件就再沒問題了…


想當年在日本,我們公司承接了Nintendo 3DS中的一部分軟體開發。

為了提高測試效率,使用了如下圖所示一種叫Quality Commander的測試機器人。


簡單說就是把掌機放在一個箱子里,箱子里有機器手,可以模擬人的手指操作掌機的觸屏。然後可以在電腦的專用軟體里寫腳本,機器手會按腳本里指定的步驟操作掌機。最後,箱子里的攝像頭會把掌機的屏幕拍下來,和事先準備好的圖片用複雜的圖像處理技術進行比較,最後得出測試成功或失敗的結果並存入資料庫。

這東西的好處是可以夜間測試。白天寫程序,夜裡跑一遍測試腳本,如有新bug立刻可以察覺。可以看成是系統級的unittest。測試團隊再也不用熬夜搞測試了,最重要可以把人從大量重複性測試中解放出來,love and peace!

我當時是測試組組長,負責腳本的編寫,維護,每天的報告,bug錄入。

理論上不用熬夜,但那些都是騙人的。

--------------------------------------------

有一天,腳本出bug了。

我當時判斷應該是Quality Commander本身機械手和攝像頭安裝出了問題,於是給廠家打電話,日本人批星戴月,從王子飛奔至我的公司,看得出他已經有些疲憊了。

可是問題來了,任天堂對所有承包項目公司的要求是,所有和項目無關的人,都不得進入團隊所在的房間,就算公司其他部門的人,也一律不得進入。項目開發中使用的樣機,代碼,文檔,甚至項目代號,也都不能讓與項目無關的人知道。否則3000年之前休想再從任天堂拿到第二個項目。

項目相關人員都被叫到京都簽署了保密協議。

這樣一來,為了讓不能進屋的廠家的人調試,首先就要把大箱子和機械手攝像頭拆開搬到另一間屋,重新組裝,重新校準。精密儀器校準可費事了啊。

然後最難的事情來了,樣機不能讓廠家看到,而且樣機的尺寸顏色,有幾個按鍵,位置,屏幕大小,內置攝像頭的位置,樣機調試器的形狀介面等都是機密,不能泄露!甚至都不能說這是個任天堂的遊戲機。說了立刻會被踢出項目,就這麼嚴。

好在對這種狀況,廠家的人原來也碰到過,表示了理解,也幫我們想了很多辦法。我們自己也摸索了很多條路,都走不通。

最後天快亮了,沒辦法只能讓廠家的人先回去,又跟他們要了大量關於Quality Commander的文檔,我們自己反覆讀後用了一天時間自己解決了那個bug。時隔40多個小時,終於可以在會議室睡個覺了。

---------------------------------------

再過了半個月,就地震了。。。


講一個不是自己遇到的,是從實驗室小夥伴嘴裡聽來的。
隔壁某實驗室的一個PhD童鞋quit走人了,在他走了以後,他參與開發的一個軟體出現了一個神奇的bug。bug的表現和原理很普通,但神奇之處在於,每次修改完了出問題的部分,編譯出來的軟體還是有這個bug,而且源文件也會抽成原來那個有bug的版本。
然後這個實驗室特地從外面請了專業人士來debug,搞了個把月愣是沒弄明白這個靈異現象咋回事。。然後實驗室大佬說,錢我們照付,不用繼續了,請來的程序猿說,不不不!我自願留下來接著修bug!我就不信我調不出來了!這是職業操守!(╯‵□′)╯︵┻━┻
然後這哥們把整個系統翻了個底朝天,花了N久,最後發現這個軟體使用的編譯器里被人隱秘地注入了一小段代碼,使得每次編譯時,編譯器都會先反過來編譯出現bug的那段代碼,強行改成有bug的版本,然後再進行正常的編譯。
於是我們可以想像這個PhD在這個實驗室里一定過得不太愉快→_→
這什麼仇什麼怨啊(╯‵□′)╯︵┻━┻
以及這個程序猿大哥真敬業。。


從前做一個嵌入式的項目,調試工具巨難用而且隨機掛掉,也沒有core dump,只能手工添加printf看日誌調試bug。有一天出現了一個bug,查看列印日誌,修改,第二天同樣的bug又出現了,但是在不同的源碼處。繼續添加日誌,查看,修改,這過程重複了n次,每次出現bug的位置都不一樣。我突然醍醐灌頂,查看了下printf的源碼............tnnd誰把printf函數給重載了,寫入還不做校驗,會寫到別的內存位置上去。就像是我拿個鎚子到處敲地鼠,地鼠其實住在鎚子里.........


推薦閱讀:

TAG:程序員 | 編程 | 計算機 | 軟體調試 |