大招!如何把Gdb異常調用棧功能移植到任意平臺?

 

Hello,今天又是Q哥來給大家進行分享了~

 

 

當屏幕彈出“Segmentation fault”時,程序員該怎麼辦?之前文章已經教給大家一個通用的方法就是利用backtrace函數和addr2line命令定位問題,沒看過上一篇文章的小夥伴可以戳這裏查看如何輕鬆搞定“Segmentation fault”,看這篇就夠了!

 

如果你熟悉gdb,當程序跑飛,我們可以執行backtrace(簡寫bt)命令,可以dump出異常調用棧。如果你在大廠呆過,你應該知道,商用的定位方法是不允許使用gdb的,只能通過命令行和日誌定位問題。

 

所以,今天我們一起分析下gdb源碼中backtrace怎麼實現的,然後活學活用,把這個功能在任意硬件平臺(ARM/MIPS/X64/X32...)上移植出來。

 

首先回顧下,利用gdb在程序異常後執行bt命令的打印。

 

先從gdb官網下載gdb-8.3源碼,把backtrace命令當做字符串,可以查到其調用函數,主要功能是獲取異常時調用棧地址信息,然後對地址進行解析成文件名、函數名和行號。對代碼一層層分析,可以看到最核心的代碼段(地址解析),先貼出來,如下:

 

是不是看不懂?沒關係,很簡單的,一起分析下唄。

 

首先代碼最後一行pfilename, pfunctionname, plineno一眼就看出是出參,所以地址解析最核心的函數是bfd_find_nearest_line,該函數參數mypc就是待解析的地址,那麼下面的任務就變成給bfd_find_nearest_line函數的參數賦值就行了。

 

bfd_find_nearest_line函數入參主要包括,bfd節點、所屬section、符號表和偏移地址,出參則是文件名、函數名和行號。

 

Bfd節點的獲取,即current_bfd,直接可以調用bfd模塊open函數即可。

 

通過上面代碼可以知道Code_section,是通過遍歷所有section段,找到text這個section段賦值給Code_section,並把text section段的基地址給code_base。例如用readelf讀取section信息,code_base值就是光亮的0X4005C0值。

 

有小夥伴會問,什麼是section?

 

這是Linux ELF相關的基礎知識,ELF相關的基礎知識之前文章已經講過,可查閱。

 

符號表的獲取,方法有很多,例如bfd_canonicalize_symtab、bfd_read_minisymbols函數都可以獲取到,可以參考上面的源碼。

 

最後需要說的是bfd_find_nearest_line依賴於bfd庫,如果你需要把這個異常調用棧功能移植到特定平臺(arm/mips),你需要自己編譯bfd庫。

 

特定平臺的bfd庫哪裏來呢?

 

需要下載binutils源碼,可以到官網下載最新的binutils-2.32.tar.gz。

先配置CC=XX ./configure --host==XX,再make生成libbfd.a和libiberty.a。

 

現在開始對之前文章中介紹的backtrace代碼進行優化,主要是重構backtrace_symbols函數,利用backtrace和dump_backtrace實現異常調用棧的打印。

 

我們已經知道調用backtrace函數可以獲取調用棧地址,考慮到可執行文件包含動態庫,下面的分析,是隻dump不包含動態庫的異常調用棧,這樣,如果調用棧地址非本模塊本身,則不會做解析。

 

核心代碼如下,首先以可執行文件本身作爲file,獲取fd,然後根據addr,調用dump_current_pc_location函數,傳入fd和addr,解析出文件名、函數名和行號。

 

通過gcc example.c  -L./lib -lbfd -liberty -g -ldl -lz  -o example 命令編譯,執行example後,可以看到異常調用棧信息。

 

好了,至此,我們已經把gdb的backtrace功能移植出來了。

 

但是如果異常調用棧中還包括動態庫中的函數,這個我們目前代碼是沒有做解析的,這個代碼能不能做?

 

答案是肯定的,只要對現有代碼做兩個地方改動就行了。

 

怎麼搞?

 

不急,慢慢來,先消化,後面再深聊。

 

如果想直接獲取本文完整代碼,可關注公衆號,回覆”bt”即可下載。

 

歡迎掃碼關注,一起學習Linux

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章