偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

用gdb分析coredump的一些技巧

開發(fā) 前端
前幾天我們正在運(yùn)營的一款產(chǎn)品發(fā)生了崩潰,我花了兩天嘗試用 gdb 分析了 coredump ,雖然最后還是沒能找到 bug ,但還是覺得應(yīng)該做一些總結(jié)。

前幾天我們正在運(yùn)營的一款產(chǎn)品發(fā)生了崩潰,我花了兩天嘗試用 gdb 分析了 coredump ,雖然最后還是沒能找到 bug ,但還是覺得應(yīng)該做一些總結(jié)。

產(chǎn)品是基于 skynet 開發(fā)的,由于歷史原因,它基于的是 skynet 1.0 之前 2015 年中的一個(gè)版本,由于這兩年一直沒出過什么問題,所以維護(hù)人員懈怠而沒有更新。

崩潰的時(shí)候,關(guān)于 Lua 部分的代碼缺少調(diào)試符號信息,這加大了分析難度。現(xiàn)在的 skynet 在編譯 lua 時(shí),加入了 -g 選項(xiàng),這應(yīng)該可以幫助未來出現(xiàn)類似問題時(shí)更好的定位問題。

[[190796]]

導(dǎo)致代碼崩潰的直接原因是 rip 指向了一個(gè)數(shù)據(jù)段的地址,準(zhǔn)確的說,跳轉(zhuǎn)到了當(dāng)前工作線程擁有的 lua 虛擬機(jī)的主線程 L 那里。

發(fā)現(xiàn)這條線索很容易,skynet 的其它部分是有調(diào)試符號的,可以在崩潰的調(diào)用棧上看到,服務(wù)的 callback 函數(shù)的 ud 和崩潰地址一致,而 lua 服務(wù)的 ud 正是 L 。用 gdb 的 p ( lua_State *)地址 查看這個(gè)結(jié)構(gòu),也能觀察到這個(gè)數(shù)據(jù)結(jié)構(gòu)的內(nèi)容正是一個(gè) lua_State 。

由于用 bt 查看的調(diào)用棧是不正常的,所以可以斷定在函數(shù)調(diào)用鏈的過程中應(yīng)該是發(fā)生了某種錯(cuò)誤改寫了 C 棧的內(nèi)容。在這種情況下,gdb 多半靠猜測來重建調(diào)用鏈(就是用 bt 看到的那些)。

現(xiàn)代編譯器經(jīng)過優(yōu)化代碼之后, C 棧上已經(jīng)沒有 stack frame 的基地址了,所以現(xiàn)在不能簡單的看堆棧的數(shù)據(jù)內(nèi)容來推測 stack frame 。也就是經(jīng)過優(yōu)化的代碼不一定適用 rbp 來保存 stack frame ,它也不一定入棧。對于 gcc ,這個(gè)優(yōu)化策略是通過 -fomit-frame-pointer 開啟的,只要用 -O 編譯,就一定打開的。在 stack 本身出問題時(shí),gdb 的猜測很可能不準(zhǔn)確,人工來猜或手工補(bǔ)全或許更靠譜一些。方法就是先用 x/40xg $rsp 打印出 C stack 的內(nèi)容,然后觀察確定 stack 上的哪些數(shù)據(jù)落在代碼段上。所有有函數(shù)調(diào)用的地方,一定有處于代碼段上的某處返回地址指針。

主程序的代碼段一般都地址偏低,動(dòng)態(tài)鏈入的代碼段可以用 info sharedlibrary 來查看。返回地址肯定是落在函數(shù)代碼的內(nèi)部,而肯定不會(huì)是函數(shù)入口,而這些地址除了函數(shù)調(diào)用外,都不可能用正常的 C 代碼生成出來,所以識別性很強(qiáng),不會(huì)有歧義。

如果覺得某個(gè)指針是函數(shù)返回地址,可以用 x/10i 地址 來反匯編確認(rèn)。

但是需要注意的是,即使在 C stack 上發(fā)現(xiàn)一個(gè)函數(shù)返回地址,并不說明這個(gè)函數(shù)調(diào)用尚未返回。它只能說明這個(gè)函數(shù)至少被調(diào)用過。這是因?yàn)?,匯編在 call 一個(gè)函數(shù)時(shí),會(huì)把當(dāng)前調(diào)用處的地址壓棧。而調(diào)用結(jié)束后,ret 指令返回只是修改了 rsp 這個(gè)棧指針,而數(shù)據(jù)本身是殘留在棧上的。這也是為什么 gdb 有時(shí)候也會(huì)猜錯(cuò)。

在這次的案例里,崩潰發(fā)生在執(zhí)行跳轉(zhuǎn)到了數(shù)據(jù)段,這種情況多半是因?yàn)?call 指令調(diào)用的是一個(gè)間接引用,在 C 層面來看,就是調(diào)用了一個(gè)函數(shù)指針。這種情況下,跳轉(zhuǎn)地址肯定還在寄存器里。用 info registers 可以查看。(注:在 64bit 平臺下,查看寄存器內(nèi)容非常重要,因?yàn)?64bit 下,函數(shù)調(diào)用的前四個(gè)參數(shù)是通過寄存器 rdi rsi 傳遞而非堆棧,往往需要結(jié)合 disass 反匯編看代碼去推算。)

當(dāng)然,按 lua 自己的正常邏輯,是不可能把 L 作為一個(gè)函數(shù)指針來調(diào)用的。按我的猜測,這里出錯(cuò)比較大的可能是 longjmp 的時(shí)候數(shù)據(jù)出錯(cuò),恢復(fù)了錯(cuò)誤的寄存器。btw, setjmp 在生成 jmp_buf 時(shí),對于 rsp rbp 這類很可能用于地址的寄存器,crt 做了變形(mangling)處理,所以很難簡單的靠寫越界寫出一個(gè)巧合的錯(cuò)誤值。

對于調(diào)試崩潰在 lua 內(nèi)部的情況,比較關(guān)鍵的線索通常是 L 本身的狀態(tài)。因?yàn)闃I(yè)務(wù)的主流程其實(shí)是用 lua vm 驅(qū)動(dòng)的,L 的 callinfo 也就是 lua 的 stack frame 信息更多。

對于 skynet ,在正常運(yùn)行的時(shí)候通常會(huì)有兩個(gè)活動(dòng)的 L 。一個(gè)是主線程,用來分發(fā)消息;但消息本身是在一個(gè)獨(dú)立的 coroutine 中進(jìn)行的。以上可以確定主線程,而子線程的 L 可以在寄存器和 stack frame 里找。由于沒有調(diào)試符號,所以可以靠猜來尋找,這并不算太麻煩。要確定一個(gè)地址是否是 L ,只需要查看 L->l_G 看是否和前面找到的主線程 L 的對應(yīng)值是否相同。

在缺少調(diào)試符號的情況下,會(huì)發(fā)現(xiàn) lua 下的一些內(nèi)部數(shù)據(jù)結(jié)構(gòu) gdb 無法識別。這個(gè)時(shí)候可以用 add-symbol-file 來導(dǎo)入需要的結(jié)構(gòu)信息。方法是加上 -g 重新編譯一下 lua ,把一些包含這些結(jié)構(gòu)的文件,例如 ldo.o 加進(jìn)來。

我在分析這次的問題時(shí),寫了腳本查看兩個(gè) L 的 lua 調(diào)用棧,這些腳本只要對 lstate.h 里的 callinfo 數(shù)據(jù)結(jié)構(gòu)熟悉就很容易寫出來。lua 的調(diào)試信息很豐富,找到源文件名和行號都很容易。另外,L 棧頂?shù)臄?shù)據(jù)是什么也是重要的線索,可以推導(dǎo)出崩潰發(fā)生時(shí) Lua 的狀態(tài)。

這次我們崩潰的程序最后停在主線程的 resume 調(diào)用子線程上。子線程調(diào)用了 skynet.sleep ,也就是最后把 "SLEEP", session 通過 yield 傳給了主線程。這些要傳出的量可以在子線程的 L->top 上查到。雖然 lua 本身已經(jīng)把值 pop 出去了,但 pop 本身是不清空棧的,只是調(diào)整了棧頂指針,所以在 gdb 下依然可見。主線程也接收到了傳過來的數(shù)據(jù),數(shù)據(jù)棧上可見。

不過這次的吊詭之處在于,lua 線程間拷貝數(shù)據(jù)這個(gè)過程是在 lcorolib.c 中的 auxresume 函數(shù)中執(zhí)行的,在 luaB_coresume 里還需要在結(jié)果中插入一個(gè) boolean 。而我在 coredump 數(shù)據(jù)中發(fā)現(xiàn)了拷貝過程已經(jīng)完成,但是 boolean 卻沒有壓入。那么事故發(fā)生點(diǎn)只可能在兩者之間。不過在 auxresume 返回到后續(xù) push boolean 之間只有幾行匯編代碼,絕對不可能出錯(cuò)。

唯一能解釋的就是在 lua_resume 期間,子線程運(yùn)行的流程破壞了 C 的 stackframe ,讓 auxresume 沒能正確的返回到調(diào)用它的 luaB_coresume 中。但怎樣才能制造出這種情況,我暫時(shí)沒有想法。

在 C 層面制造出崩潰的可能性并不是很多,數(shù)據(jù)越界是一類常見的 bug (這次并不像);另一類是內(nèi)存管理出錯(cuò),比如對同一個(gè)指針 free 多次,導(dǎo)致內(nèi)存管理器出錯(cuò),把同一個(gè)地址分配給兩個(gè)位置,導(dǎo)致兩個(gè)對象地址重疊。后一類問題能干擾到 C 的 stack frame 可能性比較小,除非有堆上的對象指針指向了棧地址,然后并引用。這次的 bug 中,最打的線索是 L->errorJmp ,也就是 lua 線程中指向恢復(fù)點(diǎn)的 jmp_buf ,它是在 C 棧上的。

L 中有一些相關(guān)變量可以推測 resume/yield/pcall 等的執(zhí)行狀態(tài): L->nny L->nCcalls L->ci->callstatus 等都是。我分析的結(jié)果是在 auxresume 返回后,沒有繼續(xù)運(yùn)行 luaB_coresume 中的 push boolean 過程,卻又運(yùn)行了新的一輪 luaD_pcall ,導(dǎo)致了最終的崩潰。這可以通過 L 的 errorJmp 的 status 得到一定的佐證。不過 C stack 上沒有 luaB_coresume 的返回地址比較難解釋,只能說是可能被錯(cuò)誤的運(yùn)行流程覆蓋掉了。

gc 會(huì)是觸發(fā) bug 的多發(fā)點(diǎn),因?yàn)?gc 是平行于主流程同步進(jìn)行的。這次崩潰點(diǎn)的子線程的 lua 棧幀停留在 yield 函數(shù)上,在此之前也的確調(diào)用了 gc step 。但是,我們可以通過查閱 L 的 gcstate 變量查看 gc 處于什么階段。在這次的事發(fā)現(xiàn)場,可以看到 gcstate 為 GCSpropagate 也就是 mark 階段,所以并不會(huì)引發(fā)任何 __gc 流程,也沒有內(nèi)存釋放。

結(jié)論:對于 bug ,暫時(shí)沒有結(jié)論。不過對于調(diào)試 lua 編寫的程序,還是積累了一些經(jīng)驗(yàn):

  1. 一定要在編譯 lua 時(shí)加 -g ,雖然 lua 本身出嚴(yán)重 bug 的可能性極低,但可以方便在出問題時(shí)用 gdb 分析。
  2. 在 gdb 中查看 lua 的調(diào)用棧很有意義,分析 L->ci 很容易拿到調(diào)用棧信息。
  3. 記得查看一下 lua 的數(shù)據(jù)棧內(nèi)容,包括已經(jīng) pop 出去,但還殘留在內(nèi)存中的數(shù)據(jù),可以幫助分析崩潰時(shí)的狀態(tài)。
  4. 記得查看 L 中保留的 gcstate GCdebt 等 gc 相關(guān)變量,可以用于推斷 lua gc 的工作狀態(tài)。
  5. L 中的 nny nCcalls errorJmp 可以幫助確定 lua 到 C 的調(diào)用層次。注意:一個(gè) yield 狀態(tài)的 coroutine ,errorJmp 指針應(yīng)該為 NULL 。

另外,gdb 分析 skynet 可以從下面的線索入手:

  1. context 對象里能找到當(dāng)前服務(wù)的地址、最后一個(gè)向外提起的請求的 session 、接收過多少條消息等。結(jié)合 log 文件來看會(huì)有參考價(jià)值。
  2. 如果想找到內(nèi)存中其它的服務(wù)對象(非當(dāng)前線程上活動(dòng)的),可以試試 p *H 。 H 是個(gè)數(shù)組,定義在 skynet_handle.c 中,里面有所有服務(wù)的地址。
  3. 如果想找到內(nèi)存中待喚醒的 timer ,可以試試 p *TI 。它定義在 skynet_timer.c 中。
責(zé)任編輯:未麗燕 來源: 云風(fēng)的 BLOG
相關(guān)推薦

2013-03-29 13:17:53

XCode調(diào)試技巧iOS開發(fā)

2012-05-21 10:13:05

XCode調(diào)試技巧

2011-06-01 16:50:21

JAVA

2018-01-09 18:06:41

Python爬蟲技巧

2021-10-12 23:10:58

UnsafeJavaJDK

2011-05-23 18:06:24

站內(nèi)優(yōu)化SEO

2011-10-26 20:55:43

ssh 安全

2011-07-12 09:47:53

WebService

2022-12-02 14:58:27

JavaScript技巧編程

2022-02-17 13:58:38

Linux技巧文件

2018-05-07 08:22:19

LinuxImageMagick查看圖片

2017-09-20 15:07:32

數(shù)據(jù)庫SQL注入技巧分享

2020-04-14 09:22:47

bash腳本技巧

2009-11-26 10:32:57

PHP代碼優(yōu)化

2020-04-08 10:21:58

bash腳本語言

2024-03-11 15:08:26

Linux操作系統(tǒng)進(jìn)程

2021-06-18 07:35:46

Java接口應(yīng)用

2015-08-17 15:53:58

Linux桌面

2020-10-19 19:25:32

Python爬蟲代碼

2021-04-16 08:49:55

JavaScript技巧參數(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號