gdb調試之棧幀信息

前言

這篇文章,是爲了介紹緩衝區溢出攻擊做準備。

在開始之前,我們希望具備以下背景知識。

(gdb我用的比較少,只會簡單的,還沒熟練。我們的界面或許會有些不一樣,因爲插件的緣故,但不影響)

看起來,略微有些複雜,需要自己調試一遍。

環境
ubuntu18.04+gcc7.5+gdb8.1


摘要和總結

我們先給出gdb的調試方法。

然後結合棧幀信息和彙編代碼,分析函數調用過程中的棧(棧幀)變化情況。



調試過程

調試的代碼

//我們用一個最簡單的程序:memery_layout.c
/**
 * 使用gdb調試該程序,展示內存佈局
*/
#include <stdio.h>
void func(int a,int b){
	int x,y;
	x = a + b;
	y = a - b;
}
int main(void){
	int x=0;
	int y=0;
	func(4,3);
	return 0;
}

調試過程

  1. 在開始調試之前,我們先進行編譯:gcc -g -o memery_layout memery_layout.c

  2. 開始調試:gdb -q memery_layout

  3. 顯示程序:l

在這裏插入圖片描述

  1. 打斷點:break funcbreak 10

  2. 運行到斷點:run

  3. 顯示程序的調用棧信息:bt (backtrace)

  4. 顯示當前棧幀的詳細信息:info frame(關於這個棧幀信息的解讀,我們放在後面)

在這裏插入圖片描述

  1. 跳轉到上一個棧幀:up

  2. 顯示當前棧幀的詳細信息:info frame

在這裏插入圖片描述

  1. 跳轉到下一個棧棧:down

  2. 繼續運行到下一個斷點:c

  3. 查看全部局部變量信息:info locals

  4. 查看x變量內容、地址:print xprint &x

在這裏插入圖片描述

  1. 查看寄存器內容:info registers

  2. 查看指定寄存器(rbp)內容:print $rbp

  3. 查看彙編代碼:disassemble /rm

  4. 查看內存內容(0x7fffffffdc70地址,5個單元/16十六進制格式顯示/g表示八字節):x /5xg 0x7fffffffdc70

在這裏插入圖片描述



棧幀信息分析

棧幀結構

關於調試過程上面已經介紹,下面便不再贅述。

參考文章:x86_64架構下的函數調用及棧幀原理

因爲調試的現象和它的不一樣。所以我在下面畫出我的棧幀圖片。

因爲參數只有兩個,通過寄存器傳參,所以沒有畫參數壓棧的情況。

我按照info frame 的信息劃分棧幀。

我們先看下棧幀的基本結構。

在這裏插入圖片描述

寄存器介紹

  • %rax :通常存儲函數調用的返回結果,也被用在idiv (除法)和imul(乘法)命令中。

  • %rsp :堆棧指針寄存器,指向棧頂位置。pop操作通過增大rsp的值實現出棧,push操作通過減小rsp的值實現入棧。

  • %rbp :棧幀指針。標識當前棧幀的起始位置。 。並不是當前棧幀開始的地方

  • %rdi, %rsi, %rdx, %rcx,%r8, %r9 :六個寄存器,當參數少於7個時, 參數從左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9;當參數爲7個以上時,前 6 個與前面一樣, 但後面的依次從 “右向左” 放入棧中,即和32位彙編一樣。


棧幀信息分析

在這裏插入圖片描述

  1. 藍色方框:標明我們現在在第0層。

  2. 綠色方框,是上一個棧幀的信息,保存在當前棧幀。

    • called by frame at 0x7fffffffdcb0 :由0x7fffffffdcb0 位置開始的棧幀調用
    • saved rip = 0x555555554640 :當前棧幀要保存上一個棧幀將要執行的地址0x555555554640
    • rip at 0x7fffffffdc88 :返回地址(上一個棧幀將要執行的地址),保存在0x7fffffffdc88
    • rbp at 0x7fffffffdc80:上一個棧幀指針內容保存在0x7fffffffdc80
    • Previous frame's sp is 0x7fffffffdc90 :上一個棧幀的棧頂指針。(這我畫成紅框,框錯了。。)
  3. 紅色方框是當前棧幀信息

    • frame at 0x7fffffffdc90 :當前棧幀的起始地址0x7fffffffdc90
    • Locals at 0x7fffffffdc80:局部變量從0x7fffffffdc80 開始。


代碼分析

那麼當前的rbp值是什麼?局部變量爲甚從上面 0x7fffffffdc80的地方開始?

僅從棧幀的信息,是無法驗證。這時候,我們來看看它的彙編代碼。

在這裏插入圖片描述


補充下壓棧:push指令可以分解爲=%rsp棧頂指下移+mov 源數據

  1. 綠色方框

    • 通過壓棧方式,保存上一個棧幀指針。由上一節,我們知道,它被保存在0x7fffffffdc80
    • 將當前棧頂,保存在rbp中。即,設置棧幀指針。所以如上面結構圖所示,當前棧幀位置,是保存上一個棧幀的內存位置。
  2. 藍色方框

    從寄存器中取出參數,放入內存。

  3. 紅色方框

    進行計算,計算的結果放入局部變量局部變量的起始地址,從rbp指向的內存的下面開始。

  4. 還原

    • 將上一個棧幀地址,還原到rbp中
    • ret 返回調用(下面介紹)

函數的調用與返回

這裏,我們僅僅着重看下callret

我們也看下main函數的彙編代碼。

在這裏插入圖片描述
我們可以看到,兩個參數,從右向左,傳遞給寄存器。

然後,調用0x5555555545fa位置的func 函數。

通過上一張圖,我們能看出0x5555555545fa是func函數的起始位置。

call 還幹了一件事,把下一條要執行的地址,壓入棧中。我們可以通過內存查看。

在這裏插入圖片描述

  1. 綠色方框 —> 兩個局部變量的值

  2. 藍色方框 —> 上一個棧幀的值

  3. 紅色方框 —> 返回地址


棧幀指針-不是棧幀開始的地方

棧幀寄存器存儲的不是棧幀開始的地方。

我們用第0層的棧幀來看:frame at 0x7fffffffdc90 + Saved registers: rbp at 0x7fffffffdc80

當前棧幀開始的地方是0x7fffffffdc90,而棧幀寄存器裏面的值是0x7fffffffdc80

而且,我傾向與將返回地址,劃分在當前棧幀,而不是上一個棧幀。

0x7fffffffdc90 這個內存中的內容,我不知道代表什麼。不是寄存器的值

我們可以看出0x7fffffffdc98裏面是兩個int 0,是上一個棧幀的內容。

我們可以通過,frame 0x7fffffffdc90 跳轉到當前棧幀。

How to interpret GDB “info frame” output?

info frame command

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