一種處理棧越界的方法

做者:吉林小夥
連接:http://zhuanlan.zhihu.com/p/20642841
來源:知乎
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

在linux下,棧越界寫壞返回地址會致使調用棧沒法回溯,這就致使咱們直接使用bt沒有辦法查看崩潰時調用棧,今天我講一下我最近研究出來的一種方法(雖然是原創,但可能互聯網上早有人發佈過此種方法,只不過我沒有查到而已).linux

廢話少說,步入正題,首先我寫了個簡單的程序來構造一個棧溢出的狀況,爲了使效果更加明顯,我使用了一些遞歸來增長調用棧的深度,代碼以下:c++

不要吐槽命名方式,我也知道很醜,棧都能越界的程序,必定漂亮不到哪去,哈哈!函數

簡單描述下這個代碼的功能,func2裏遞歸調用了func2,這樣能有效增長調用棧深度,而後調用func3的時候,因爲func3裏寫buf越界了,致使棧被破壞了,而後段錯誤崩潰.崩潰後生成了core文件,咱們用gdb打開,輸入bt,效果以下:學習

因爲棧破壞有不少種方式,bt也有可能顯示出一排??,總之棧破壞頗有可能致使bt沒法回溯就是了.那咱們如何應對呢?咱們首先來看一下調用棧的一些知識:測試

通常狀況下,在調用函數以前,(部分)參數會放入棧內,而後執行彙編指令call, 執行call後會自動將返回地址壓入棧中,而後執行被調用函數,被調用函數開頭的兩條彙編指令極可能是:
push rbp
mov  rbp,rsp
這兩條指令的做用就是把rbp壓入棧中,把棧頂指針rsp賦值給rbp,這樣在棧內就會造成一個 鏈表,以便咱們回溯調用棧.
注意:
在開啓優化的時候,默認是 -fomit-frame-pointer,這樣可能致使不少函數開頭不會出現那兩條彙編指令,-fno-omit-frame-pointer選項開啓後就會生成以上兩條指令.

好了,詳細的棧資料請你們自行查閱資料學習,我就再也不贅述了.你只要知道調用棧在棧內是以鏈表的方式保存便可.那棧越界寫壞的地方咱們能夠認爲是鏈表的頭部,因爲鏈表的頭部被寫壞了,致使gdb的bt指令沒法回溯調用棧了.優化

既然如此,那咱們能夠再找一個節點做爲鏈表的頭部.只不過回溯的調用棧可能比"完美"的調用棧少那麼一兩條,不過半個麪包總比沒有好,說幹就幹:指針

上圖的代碼是gdb的擴展,我擴展了一個bts(backtrace stack)指令,其做用是打印給定addr後的count條內容.gdb中可使用source指令來加載擴展,也能夠在home路徑下新建.gdbinit文件,將腳本內容寫入,這樣在gdb啓動時就會自動生效,咱們使用source .gdbinit來加載一個這個bts擴展指令.而後在gdb裏輸入i r rsp指令:調試

,rsp的值通常狀況下是棧頂,不過不排除這個值是不對的,只是不對的機率比較小而已,而後鍵入指令:code

(gdb) set pagination off
(gdb) set loggin on ./log
Copying output to ./log.
(gdb)bts 0x7fff81f7c250 1000

第一條指令的做用是關閉"超過一屏內容等待鍵入回車"功能,第二條是打開log,這樣gdb裏的輸出就會寫入log文件內,第三條就是咱們寫的擴展指令了,執行一下,等待結果寫入到文件中吧.因爲咱們的測試程序很短,棧也沒用多大,因此1000應該就能夠了,實際程序中這個1000可能要變得很大,可能要跑幾分鐘,不過我很享受這幾分鐘,由於我就喜歡敲入一條命令而後屏幕刷刷滾的感受,逼格高,哈哈!!遞歸

跑題了,好了,輸出結束咱們去看看./log文件,執行head ./log命令,結果以下:

這正是咱們想要的內容,第一列是棧地址,第二列是該地址的內容,既然調用棧在棧內是鏈表,那咱們就能夠寫個代碼把棧內全部的鏈表都暴力搜出來,而後看下哪一個最多是調用棧.

這是我寫的暴力搜索棧中鏈表的程序,其實你們徹底能夠用腳本寫,比c++方便多了.好了,g++ stack.cpp -o stack編譯一下,而後執行:

./stack log 100 > symbol

log就是咱們的log文件了,100呢是調用棧的深度,當你指定100的時候,會把全部調用棧深度爲100的鏈表打印出來,因爲咱們遞歸超過100次了,因此這裏我就指定100了,若是你在實際應用中,100沒有結果,那能夠嘗試逐漸減少這個數值,而後咱們看看symbol裏的內容:

大概是這樣的,還有好多條,我只截取了部分,之因此有info symbol,是由於我要在gdb中加載這個symbol文件,加載後會自動執行info symbol address,做用就是打印出這條地址附近的符號:

(gdb)source ./symbol

此處應該有掌聲!!!

從下往上看,依次是__libc_start_main()->main()->func1()->func2()...,這的確是咱們程序中的調用棧,只不過丟失了func3而已.

最後總結一下,此片專欄只是提供一種解決方法而已,具體可否成功,要看運氣了.我一直以爲調試找bug是要看運氣的,尤爲是那些偶然出現的crash,在你不知道緣由沒法重現時只能從core dump裏尋找蛛絲馬跡了.

經驗:通常棧越界頗有多是字符串越界,此時能夠查看rsp附近的內存,說不定有很明顯的字符串,一下就定位問題了呢.

因爲水平有限,文章中有錯誤在所不免,請你們包含並指教,謝謝!

-----------------------------------------------

更新一下暴力搜索調用棧的那個代碼,其中有一處bug可能致使在遍歷調用棧的時候死循環.更新後如上圖所示,這樣就不怕棧裏有迴路了,還有一處修改,這個地方用sizeof(void*),就兼容32bit 64bit的程序了,之前寫的8只支持64bit的程序

相關文章
相關標籤/搜索