棧呼叫關係跟蹤

發表於2016-12-05

在發生段錯誤的時候,列印函式的呼叫棧資訊是定位問題很好的手段,一般來講,我們可以捕獲SIGSEGV訊號,在訊號處理函式中將函式呼叫棧的關係列印出來。gdb除錯中的backtrace,簡稱bt就是這個作用。

CU的二娃子前兩天寫了個Linux下程式崩潰時定位原始碼位置,這篇文章寫的很好,呼叫的GNU的backtrace函式,列印了函式的呼叫棧資訊。我想補充一些內容,把這個話題補充的更加豐富一些。

我們遇到的很多難題,前輩都會遇到,很多有分享精神的前輩會寫很多精彩的總結。國外布法羅大學的一個牛人總結了跟蹤棧呼叫關係的文章,寫了一篇部落格。英文水平高的筒子,不要聽我JJYY,直接跳轉到http://www.acsu.buffalo.edu/~charngda/backtrace.html,去看這篇博文,當然本文提到的第二種方法還是值得一看的。作者提到了4種方法來解決棧呼叫關係其中二娃子用的是第二種方法。我閱讀了self-service linux這本書,這本書也很詳盡的描述了棧的結構。我們補充一種方法,自己實現backtrace。

我的棧呼叫關係如下:

第一種方法 : glibc 提供的 backtrace 函式

先說二娃子的方法: GNU提供的backtrace函式

編譯的時候,我們需要加上 -rdynamic 選項,否則的話,符號表資訊列印不出來。

這種方法好是好,不過,需要加上-rdynamic選項。否則會出現如下列印:

第二種方法,自己動手豐衣足食的方法。

下面的圖來自雨夜聽聲的部落格,函式呼叫如下圖所示。如果有N個引數,將N個引數壓棧(順序也很有意思,希望瞭解這個的可以看程式設計師的自我修養),然後是將返回地址壓棧,最後是將ebp壓棧儲存起來。

如果我們只傳遞一個引數個某個函式,那麼我們完全可以根據引數的地址推算出ebp存放的地址,進而得到ebp的值。引數地址-4(32位系統指標的長度為4Byte)可以得到返回地址的位置。引數的地址-8 得到ebp在棧存放的地址。我們一旦得到ebp,我們就可以回朔出整個棧呼叫。

24774106_1357023120qz5e

先看第一步:getEBP

原理很簡單,就是入參的地址下面是返回地址,返回地址的下面是被儲存的ebp的地址。

第二步,有了ebp, 我們可以一步一步前回退,得到呼叫者的棧的ebp,呼叫者的呼叫者的棧的ebp,。。。。直到NULL

對這個過程不太理解的筒子可以看下我下面的實驗:

光有這個也是不行的,只能拿到棧的資訊,和返回地址的資訊,拿不到函式名也是白扯。這時候我們可以利用libdl.so,我們用dladdr這個函式可以得到距離入參地址最近的符號表裡面的symbol。

把整個函式書寫一下:

注意兩點:
1 標頭檔案 dlfcn.h
2 編譯的時候加上-rdynamic ,同時連結libdl.so 即加上-ldl選項

執行效果如下:

3 第三種是 libunwind。

編譯的時候加上 -lunwind -lunwind-x86 ,如果是X86_64,則是 -lunwind -lunwind-x86_64
優點是不需要-rdynamic選項,不需要-g選項。

執行結果如下:

參考文獻1 提到了改進的backtrace,同時給出了cario的相關程式碼,很有意思,感興趣的可以去讀一下。

參考文獻
1 http://www.acsu.buffalo.edu/~charngda/backtrace.html (強烈推薦)
2 程式設計師的自我修養
3 CU 二娃子的部落格
4 Self-Service Linux chapter 5 :Stack(推薦)

相關文章