《軟體除錯分析技術》學習筆記(十)

2021-06-04 03:06:59 字數 4397 閱讀 8675

函式呼叫 

許多程式語言中,可以將一段經常需要使用的**封裝起來,在需要使用時可以直接呼叫,這就是程式中的函式(也被稱為過程)。其實程式中的函式和數學中所說的函式是很相似的,都是通過輸入自變數(有些函式可能不需要自變數),然後經過一系列的運算,最後得出函式的值。 

在組合語言中對於函式的呼叫一般用乙個call指令來完成,當過程返回時用retn指令來完成。call指令有乙個運算元,指向被呼叫過程的位址,當程式需要呼叫乙個過程的時候,程式會跳轉到被呼叫過程的位址處去執行**。當過程**執行完畢的時候,程式需要返回到原呼叫位址處,這個時候就需要使用到retn指令。retn指令是沒有運算元的,那麼程式怎麼知道函式要返回的原呼叫處的位址?顯然,在呼叫乙個過程的時候應該儲存返回的原呼叫處的位址,具體怎麼操作,先看一段c語言**: 

#include void function() 

int main()

這裡定義了乙個空函式function(),然後在主函式裡呼叫了這個空函式function(),這裡需要修

改一下編譯器的選項,在對語言的優化處理中把對內聯函式展開的選項修改為「只使用

__inline」,如圖2.3.1.1,

否則編譯器會把**比較少的函式當作inline函式處理。然後進行編譯,編譯完成後使用ida進行分析,主函式反彙編**如下:

text:00401010    push    ebp 

.text:00401011 mov ebp, esp

.text:00401013 call ?function@@yaxxz ; function(void)

.text:00401018 xor eax, eax

.text:0040101a pop ebp

.text:0040101b retn

這裡可以看到0x00401013處使用了乙個call指令呼叫呼叫過程,call指令的運算元說明了被呼叫過程,ida已經分析出這裡被呼叫的過程為function(void)。再看看函式function(void)的**:

.text:00401000 ; attributes: bp-based frame 

.text:00401000

.text:00401000 ; void __cdecl function()

.text:00401000 ?function@@yaxxz proc near ; code xref: _main+3 p

.text:00401000 push ebp

.text:00401001 mov ebp, esp

.text:00401003 pop ebp

.text:00401004 retn

.text:00401004 ?function@@yaxxz endp

.text:00401004

這裡是ida對function(void)的整個分析結果。在c語言中定義的函式function()是乙個空函式,什麼都沒有做就直接返回了,但是在組合語言中這裡除了retn指令還有另外的三句。這三句**的作用是保護棧楨。ebp是棧基址暫存器,它通常用來對區域性變數進行定址,那麼在呼叫乙個過程後,要使用過程內的全域性變數,就要重新定義ebp暫存器的值,一般用這兩句**來完成:

push    ebp 

mov ebp, esp

這兩句指令,把原ebp的值壓入棧中,然後把ebp的值修改為當前棧頂。一般把這兩句指令作為函式開頭。那麼函式的結尾就是這樣的:

pop     ebp 

retn

一般把這兩句指令作為函式的結尾,從棧中彈出原來儲存的ebp暫存器的值,恢復ebp後呼叫retn指令返回原呼叫處。 

用od載入這個程式,除錯看看:

00851010  /$  55            push    ebp 

00851011 |. 8bec mov ebp, esp

按f8步過這兩句**,然後注意od的暫存器視窗,如圖2.3.1.2,可以看到這時ebp寄存哭已經被賦於esp暫存器的值,這時它們都指向0x0029f910。

00851013  |.  e8 e8ffffff   call    00851000

這裡使用call指令呼叫函式function(),od就沒有ida那麼智慧型了,這裡沒有分析出被呼叫過程的名稱。按f7跟進這個過程: 

00851000  /$  55            push    ebp 

00851001 |. 8bec mov ebp, esp

00851003 |. 5d pop ebp

00851004 \. c3 retn

跟進這個過程後不要執行任何**,先看看當前的狀態。暫存器視窗如圖2.3.1.3,很明顯這裡棧頂esp所指向的位址比剛

才低了4個位元組,值為0x0029f90c,那麼說明棧裡被寫入了4位元組的資料,看看現在棧裡的0x0029f90c處的資料,如圖

2.3.1.4。0x0029f90c裡的資料為0x00851018,這到底是什

麼東西,先待定,按f8步過函式function()中的**。 

00851004  \.  c3            retn 

執行到這裡的時候停住,再看看這時候暫存器的狀態,如圖2.3.1.5,

0029f90c      00851018    返回到 acm.00851018  來自 acm.00851000 

0x0029f90c的值既然為0x00851018。再按一次f8步過

retn指令,再看暫存器資訊,如圖2.3.1.6,現在eip暫存器指向0x00851018,而esp暫存器的值變回了0x0029f910。棧頂esp抬高了四個位元組,原來棧頂0x0029f90c指向的資料0x00851018被彈出,變成了現在eip暫存器的值。看這時的**:

00851013  |.  e8 e8ffffff   call    00851000 

00851018 |. 33c0 xor eax, eax

0085101a |. 5d pop ebp

0085101b \. c3 retn

0x00851018為原呼叫處的下一句指令的位址。經過這樣的分析,過程的呼叫和返回過程就很明顯了,當執行call指令時會把call指令的下一句指令的位址壓入棧中當作返回址,然後再執行過程的**,過程**執行結束以後使用retn指令返回,當執行retn指令時彈出儲存在棧中的原呼叫指令的下一句指令的位址,把它放到eip中使程式轉而執行原呼叫指令的下一句指令。 

再來看看使用指標呼叫函式的方法:

#include void function() 

int main()

在這裡function()不變,在主函式main()中定義了乙個函式指標fn,把function()函式的位址傳給函式指標fn,然後通過函式指標fn呼叫函式function(),使用ida分析:

.text:00401010    push    ebp 

.text:00401011 mov ebp, esp

.text:00401013 push ecx

指標變數裡裝的是乙個32位的位址,它用占用的記憶體空間為4位元組,因此這裡向棧中壓入乙個32位暫存器ecx為指標變數fn開闢空間。 

.text:00401014    mov     [ebp+fn], offset ?function@@yaxxz ; function(void) 

.text:0040101b call [ebp+fn]

這裡獲取函式function(void)的位址放到函式指標fn裡,然後通過函式指標fn呼叫函式。這種呼叫叫做間接呼叫,通過動態計算函式位址,然後呼叫。這種呼叫方式很像c++中的虛函式,關於虛函式的內容,會在後面的章節中講到。 

.text:0040101e    xor     eax, eax 

.text:00401020 mov esp, ebp

.text:00401022 pop ebp

.text:00401023 retn

這種間接呼叫的方式要比前面看到的直接呼叫方式麻煩很多。

《軟體除錯分析技術》學習筆記(一)

今天開始寫寫一些心得體驗。軟體除錯分析技術 是好友monster的 作品。作為一直以的好夥伴,他是我看著長大的,嘻嘻 之所以有今天這樣的成績,是與他的努力和天賦脫不了關係的。他大方地給了我pdf版的,我也大方的給了我們全班。但我們班有同學說,這是撒子呦,看不看不懂 我決心寫一些學習筆記,和我班的同學...

《軟體除錯分析技術》學習筆記(二)

1.暫存器 暫存器m講的比較透徹。暫存器是 處理器cpu的組成部分,是有限存貯容量的高速存貯部件,它們可用來暫存指 令 資料和位址,是記憶體階層中的最頂端,也是系統獲得操作資料的最快速途徑。1.1資料暫存器 資料暫存器主要用來儲存運算元和運算結果等資訊,從而節省讀取運算元所需占用匯流排和訪問 儲存器...

《軟體除錯分析技術》學習筆記(三)

m給出乙個c程式 include include int a int main 這段 定義了乙個整型全域性變數a,在主函式main 中定義了乙個整型區域性變數b和乙個整形指標變數c,然後呼叫malloc 函式申請大小為1個整形變數的記憶體並把申請到的記憶體位址賦值給指標變數c,再依次給變數a b和c...