无码人妻精品一区二区三18禁,影音先锋男人AV橹橹色,污污污污污污www网站免费,日韩成人av无码一区二区三区,欧美性受xxxx狂喷水

循跡追蹤令人頭禿的 Crash ,十倍程序員的 Debug 日常(2)

Jeff Tao

2021-03-19 / ,

原文首發于:

我們寫(xie) C 程(cheng)序,經(jing)常碰(peng)到 Crash,絕大(da)部分(fen)情況下都(dou)是空(kong)指(zhi)針或野(ye)指(zhi)針造成,從 call stack 來看,一(yi)(yi)般很(hen)容易找出(chu)(chu)問(wen)題(ti)(ti)。但(dan)是有(you)一(yi)(yi)類 Crash 很(hen)難(nan) debug,那就(jiu)是內存溢出(chu)(chu)。溢出(chu)(chu)的(de)部分(fen)的(de)內存空(kong)間正好覆(fu)蓋另外一(yi)(yi)個(ge)線程(cheng)訪問(wen)的(de)數據(ju)(比如一(yi)(yi)個(ge)結(jie)構體),那么另外一(yi)(yi)個(ge)線程(cheng)讀(du)取(qu)這塊數據(ju)時,獲取(qu)的(de)數據(ju)就(jiu)是無效的(de),往往導(dao)(dao)致不(bu)可預見(jian)的(de)錯誤,甚至 Crash。但(dan)因為(wei)造成數據(ju)溢出(chu)(chu)的(de)線程(cheng)已(yi)經(jing)離開現場(chang),導(dao)(dao)致問(wen)題(ti)(ti)很(hen)難(nan)定位。這是我在 2020 年 5 月寫(xie)的(de)一(yi)(yi)篇內部博客,以我當時碰(peng)到的(de)一(yi)(yi)個(ge)錯誤為(wei)例,將解(jie)決這類問(wen)題(ti)(ti)的(de)方(fang)法分(fen)享出(chu)(chu)來,供大(da)家參考,舉一(yi)(yi)反(fan)三。

具體問題

在feature/query分支上,在community倉庫,執(zhi)行以(yi)下(xia)腳本,出現Crash。

./test.sh -f general/parser/col_arithmetic_operation.sim

重現問題

我(wo)登錄到指定(ding)的機器(qi),查看了 core dump, 確實如此。Call Stack 截圖(tu)如下(xia):

Call Stack 截圖

第一步:看(kan)哪(na)個(ge)地方 crash。是 shash.c:250 行,使用 GDB 命令(ling) “f 1″ 查看(kan) stack 1,查看(kan)*pObj,一下就看(kan)到 hashFp 為 NULL,自然會 crash。但為什么會設置(zhi)為空?其他(ta)參(can)數呢?dataSize 設為 0 也(ye)一定是錯的。因(yin)此可以斷定,這個(ge)結構(gou)體是不對的。我們需要看(kan)上(shang)一層調用是否傳入了正確的參(can)數。

第二步:使(shi)用(yong)(yong) GDB “f 2″查看(kan) stack 2,rpcMain.c:605 行,查看(kan)*pRpc,這(zhe)些結構體里的(de)(de)參數(shu)顯得很正(zheng)常,包括指(zhi)向 hash 的(de)(de)指(zhi)針(zhen)值看(kan)不出有什么問題。那因(yin)此可(ke)以(yi)斷定(ding)調用(yong)(yong)是 OK 的(de)(de),調用(yong)(yong) taosGetStrHashData 應該(gai)提供了正(zheng)確的(de)(de)參數(shu)。

第三步:既然參(can)數對的,看 shash.c 的程序,那只可能(neng)是 SHashObj 這(zhe)(zhe)個結(jie)構(gou)體(ti)已經被釋放,訪(fang)問(wen)的時候,自然無效。再(zai)看代碼,只有(you)(you)(you)一(yi)(yi)個可能(neng),函數 taosCleanUpStrHash 被調用(yong),因此我在改函數里馬上(shang)加上(shang)一(yi)(yi)行打(da)印日志(zhi)(注意 TDengine 的日志(zhi)輸出(chu)控制,系(xi)統配置文件 taos.cfg 里參(can)數 asyncLog 要(yao)設(she)置為 1,否(fou)則 crash 時,有(you)(you)(you)可能(neng)日志(zhi)不打(da)印出(chu)來)。重(zhong)新運(yun)行腳本(ben),查看日志(zhi),發現(xian) taosCleanUpStrHash 沒(mei)有(you)(you)(you)被調用(yong)過。那么現(xian)在只有(you)(you)(you)一(yi)(yi)個可能(neng),這(zhe)(zhe)一(yi)(yi)塊數據的內存被其他線程寫壞。

第四步:萬幸,我(wo)們(men)有很棒的運(yun)行時(shi)內存(cun)檢查(cha)工具 valgrind, 可以通(tong)過運(yun)行它(ta)來找(zhao)找(zhao)蛛絲馬(ma)跡。一運(yun)行(valgrind 有很多選項,我(wo)運(yun)行的是 valgrind –leak-check=yes –track-origins=yes taosd -c test/dnode1/cfg),一下就發現有 invalid write,截圖如下:

發現有 invalid write 的截圖

第五步:一看(kan) valgrind 輸出,就(jiu)知道 rpcMain.c:585 有 invalid write, 這(zhe)里(li)是 memcpy。從編碼上來看(kan),這(zhe)應該沒有問題,因為拷(kao)貝的(de)(de)是固定大(da)小的(de)(de)結構體 SRpcConn,每次(ci)運行到這(zhe)里(li)都(dou)會執行的(de)(de)。那(nei)(nei)么唯一的(de)(de)可能就(jiu)是 pConn 指向(xiang)的(de)(de)是無(wu)(wu)效的(de)(de)內(nei)存(cun)區,那(nei)(nei) pConn 怎(zen)么可能是無(wu)(wu)效的(de)(de)?我們看(kan)一下程序:

pConn = pRpc->connList + sid

看 584 行(xing),pConn = pRpc->connList + sid。這(zhe)個 sid 是 taosAllocateId 分配出來的(de)。如果 sid 超過 pRpc->;sessions,那 pConn 毫無疑問就指(zhi)向了無效的(de)區(qu)域(yu)。那怎么確認呢?

第六步:加上日(ri)志(zhi) 578 行,分配出的 ID 打(da)印出來,編譯,重新(xin)運行測試腳本。

第七步:crash,看日志(zhi),可以看到(dao) sid 能輸(shu)出(chu)到(dao) 99(max 是 100),還一切正常,但之后(hou)就(jiu)(jiu)崩潰。因此可以斷言(yan),就(jiu)(jiu)是由于分(fen)配(pei)的(de) ID 超過了(le) pRpc→session。

第八步:查看(kan)分配 ID 的(de)程序 tidpool.c,一看(kan)就知(zhi)道原(yuan)因,ID 分配是從(cong) 1 到 MAX,而 RPC 模塊(kuai)只能使用 1 到 Max-1。這樣當 ID 返回為 max 時,RPC 模塊(kuai)自然(ran)會產生 invalid write。

解決方案

既(ji)然知道(dao)原因,那就很好辦,有兩套方(fang)法:

1. 在(zai) tidpool.c,taosInitIdPool 里,將 maxId 減一,這樣分配的 ID 只會是 1 到(dao) max-1。

2. 在 rpcMain.c 的 rpcOpen() 函數里,將(jiang)

pRpc->idPool = taosInitIdPool(pRpc->sessions);

改為

pRpc->idPool = taosInitIdPool(pRpc->sessions-1);

如果(guo)應用要(yao)求最(zui)多 100 個(ge) session,這么(me)改,RPC 至多創建 99 個(ge),為保(bao)證 100 個(ge),再將

pRpc->sessions = pInit->sessions;

改為

pRpc->sessions = pInit→sessions+1;

驗證

兩種方法,都重新(xin)編譯,運行測(ce)試(shi)腳本通過,crash 不(bu)再(zai)發生。

經驗總結

遇(yu)到內存(cun)被寫壞(huai)的(de)(de)場景,一(yi)定要用 valgrind 跑一(yi)次,看(kan)是(shi)否有(you) invalid write。因為它是(shi)一(yi)個動(dong)態檢查工具,報的(de)(de)錯(cuo)誤都應該是(shi)對的(de)(de)。只有(you)把 invalid write 先(xian)解決,再去(qu)看(kan) crash 問(wen)題。

怎么避免類似問題

這個 BUG 的(de)核心是(shi)(shi)由(you)于 tidpool.c 分(fen)配(pei)的(de) ID 范圍(wei)是(shi)(shi) 1 到 max,而(er) RPC 模(mo)塊(kuai)假設的(de) ID 分(fen)配(pei)是(shi)(shi) 1 到 max-1。因此(ci)是(shi)(shi)模(mo)塊(kuai)之間的(de)約定出(chu)了問題。

怎么(me)避免?每個模塊(kuai)對外 API 要(yao)做詳細(xi)說(shuo)明,如果 API 做了調(diao)整,要(yao)通知大家,而且(qie)要(yao)運(yun)行完整的(de)測試例,以免破(po)壞了某種約定。

本文(wen)為 TDengine Database 的一則真(zhen)實案(an)例,git commit id:89d9d62,GitHub 鏈接(jie)為