議棧緩沖區(qū)溢出后的異常表現(xiàn))
一個(gè)深夜調(diào)試的崩潰現(xiàn)場(chǎng)那年做一個(gè)工業(yè)網(wǎng)關(guān)的協(xié)議轉(zhuǎn)換項(xiàng)目。設(shè)備跑在現(xiàn)場(chǎng),每隔幾天就莫名其妙重啟。日志里沒有任何崩潰記錄,看門狗復(fù)位標(biāo)志倒是置上了。起初懷疑電源紋波,換了濾波電容,沒用。又懷疑溫升導(dǎo)致晶振頻偏,加散熱片,還是復(fù)現(xiàn)。后來把現(xiàn)場(chǎng)抓回來的網(wǎng)絡(luò)報(bào)文在實(shí)驗(yàn)室里回放,發(fā)現(xiàn)只要某個(gè)特定設(shè)備發(fā)來一條稍長(zhǎng)的配置幀,網(wǎng)關(guān)跑十幾分鐘到幾小時(shí)不等,就會(huì)死。那條配置幀的長(zhǎng)度,剛好比協(xié)議棧里某個(gè)接收緩沖區(qū)大出幾個(gè)字節(jié)。那次之后我才真正把“緩沖區(qū)溢出”這件事從教科書概念,變成了手上能摸到的故障模式。協(xié)議棧的緩沖區(qū)溢出和普通應(yīng)用層溢出最大的不同在于:它發(fā)生在中斷上下文、發(fā)生在協(xié)議狀態(tài)機(jī)內(nèi)部、發(fā)生在你不知道的某個(gè)回調(diào)里。你看到的異常表現(xiàn),往往和溢出這件事本身隔了好幾層。溢出不一定立刻崩很多人以為緩沖區(qū)溢出就是立刻HardFault。實(shí)際項(xiàng)目里,協(xié)議棧的緩沖區(qū)溢出有相當(dāng)一部分是“靜默越界”——寫進(jìn)去的字節(jié)數(shù)超了,但恰好覆蓋的是相鄰的另一個(gè)緩沖區(qū),或者恰好落在對(duì)齊填充區(qū),沒有觸發(fā)任何硬件異常。這種時(shí)候你會(huì)看到什么?現(xiàn)象一:協(xié)議解析結(jié)果隨機(jī)錯(cuò)亂。比如某個(gè)校驗(yàn)字段偶爾不對(duì),重傳一次又對(duì)了。因?yàn)橐绯龅膸讉€(gè)字節(jié)把相鄰緩沖區(qū)的狀態(tài)標(biāo)志改了,但改的值恰好是合法的枚舉范圍?,F(xiàn)象二:連接偶爾建立失敗,但重試就成功。溢出的字節(jié)污染了連接控制塊里的某個(gè)計(jì)數(shù)器,導(dǎo)致握手階段超時(shí)判斷出錯(cuò)?,F(xiàn)象三:內(nèi)存池水位緩慢上漲,最終分配失敗。溢出的寫操作把內(nèi)存管理塊的鏈表指針改了一位,釋放時(shí)沒有正確歸還,每次溢出泄漏一點(diǎn)。這些現(xiàn)象的共同特征是:不可穩(wěn)定復(fù)現(xiàn),和流量大小、報(bào)文順序、中斷時(shí)