GDB內存調試初探三

  • 調試準備

筆者在上一篇《GDB內存調試初探》中提到了一個簡單的調試應用(可執行文件名爲memory,並已上傳至CSDN的下載區),該應用會從標準輸入逐行讀取內存操作的相關“指令”,進行簡單的內存申請、釋放和讀寫的操作。本文仍將沿用此調試應用,側重點在於glibc的ptmalloc內存分配模塊初始化之後(即第一次申請內存之後)應用的幾次內存分配的執行流程。

首先,編寫簡單的文本文件,寫入memory的操作“指令”,保存至文件mscript.txt:

之後,使用gdb加載memory可執行文件,並在其main函數加入斷點,最後以run < mscript.txt命令運行該可執行文件。之前提到過,這是一個簡單的C代碼編寫的調試應用,它在main函數執行之後才進行第一次的內存分配請求:

如上圖,可能由於glibc檢測到應用的標準輸入指向一個普通的文件,而非一個tty文件,它第一次請求分配的內存從1024字節轉變爲4096個字節(參考筆者的前一篇博客文章),相應的第一次向Linux內核請求分配的內存大小也變爲136KB。

第一次應用向ptmalloc請求內存分配,是不可避免的:我們編寫的應用調用了fgets函數從標準輸入讀取簡單的操作“指令”。儘管如此,這對我們調試、分析ptmalloc模塊的影響並不大。在memory進程讀取了第一行“指令”之後,它會第二次請求分配內存,大小爲1024個字節(由第一行“指令”確定,0m0x400):

 此時我們也可以通過GDB查看一下ptmalloc的內存分配狀態結構體main_arena,如上圖所示。

 

  • _int_malloc(…)的執行流程

在筆者的前一篇博客文章中提到,在main函數運行後第一次向ptmalloc申請內存,_int_malloc函數會最終調用sysmalloc函數向Linux內核申請內存資源;而ptmalloc模塊向Linux內核申請的內存大小超過100KB,卻只向memory應用返回了1K(或4K)大小的內存,剩餘的內存就交給ptmalloc管理了,內存分配信號保存於main_arena結構體中。此時memory進程執行了標準輸入的“指令”,向ptmalloc第二次申請了1024個字節,_int_malloc函數會如何處理呢?一種方法是結合_int_malloc的源碼,使用GDB逐步調試;另一種方法也是結合_int_malloc源碼,但是要對該函數的處理流程進行預測。這裏筆者選擇了第二種方法,這可以簡化GDB的操作,因爲我們有足夠的調試信息。

此時在_int_malloc函數中,由於局部變量nb的值爲1032,有兩個條件分支均未執行,第一個條件分支是與get_max_fast()的比較,第二個條件分支是in_smallbin_range()宏的判定。之後,計算了idx局部變量的值:

我們手動計算的結果爲idx = 72。_int_malloc函數第一個重量級的代碼塊是一個for循環語句,其中嵌套了while循環。經分析,while循環不會被執行,如下圖:

接下來是對宏in_smallbin_range()的非判定,其中嵌套了一個條件分支,經分析可知,該條件分支中的代碼也不會被執行:

之後再次進入一個for循環體,不過很不幸,該循環體中的do … while循環很快就執行了goto跳轉:

至此,在_int_malloc函數中的主要執行流程就完成了:雖然沒有對main_arena結構體進行寫操作(對應函數中的av指針),但最終決定從top指針處分配內存了。使用top指向的內存來分配,這就簡化了內存分配的操作,下面對代碼的調試可以驗證這一點。

 

  • 使用top指針的剩餘空間分配內存

在use_top標籤之下有一長段的註釋說明信息,可以幫助我們理解ptmalloc的內存分配機制。在分析、預測_int_malloc的返回值之前,筆者決定讓memory進程執行到此處,查看main_arena結構體的信息,之後再分析預測:

如上圖,根據main_arena結構體中的ptmalloc內存分配信息,手動計算顯示_int_malloc函數應當返回的內存指針爲0x2a014010;讓此函數執行完成並返回,可以確認我們的計算結果是正確的:

讓應用執行完畢,也可以驗證這一點,memory可執行程序每分配一次內存,都會將分配的內存地址輸出到終端:

  • 多次分配內存的運行結果分析

上面調試用到的mscript.txt只分配了一次內存,筆者決定多次分配內存查看返回指針的規律。編寫連續5次分配1024個字節的“指令”,之後釋放第三次和第四次分配的內存,最後再分配一次大小爲2048字節的內存。運行memory可執行文件結果如下:

由上圖可知,分配1024字節的內存返回指針存在一定的規律:除了第一個返回指針與第二個返回指針相差0x810個字節外,其他的“相鄰”的返回指針都相差了0x408字節,也就是1032字節。而1032是一個我們比較熟悉的數值:它是用宏checked_request2size對1024作用得到的值,也就是實際分配的內存大小。在之前的博客文章中提到,多出來的8個字節其實用於了malloc_chunk結構體的前兩個成員變量,mchunk_prev_size及mchunk_size。同時也得知兩次釋放的內存很快被合併,作爲最後一次內存分配的返回內存空間了。

但第一次和第二次內存分配返回的指針間隔讓我們推測,fprintf(…)在第一次調用時,也分配了1024個字節的內存,可以再次啓用gdb調用驗證:

如上圖,斷點加入後,就可以調試了;調試結果表明,我們的推斷是正確的:

  • 總結

此次調試不是一個“成功”的調試案例;但筆者現在尚未找到通過gdb調試來分析glibc的內存分配模塊ptmalloc的實現機制。換句話說,理解ptmalloc的機制需要通過閱讀glibc源代碼,而gdb只是協助我們理解ptmalloc而己。在理解之後,可能會幫助我們調試一些嵌入式應用的內存異常問題——gdb在此只是一種輔助手段而己。筆者在工作中的一些調試內存異常的案例,因可能涉及公司的一些信息,不能在此分享;此類分析類的調試,不能側重於gdb調試手段,也不能側重於的內存相關的問題解決了。

從本次調試過程,筆者需要總結一下:

  1. 對於連續多次分配內存的請求,如果沒有內存的釋放過程,ptmalloc通常會從main_arena結構體中的top指針處分配內存;
  2. 內存被釋放後,ptmalloc會有一個“合併”相鄰的被釋放的內存的過程;
  3. 與前一篇博客文相同的結論,ptmalloc返回給應用的內存指針之前的8個字節對應着結構體malloc_chunk的前兩個成員變量。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章