歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

高級語言裡的函數在匯編裡的實現方式

一、 學習過程

在高級語言中我們為什麼要用變量呢?因為我們要存儲數據,而且因為要使用循環等語法結構,存儲的數據需要不斷地變化,變量的特性可以很好地解決這個問題。在前面我已經討論過了,變量的聲明實際上就是在內存中開辟一個內存空間,我們在匯編語言裡使用循環,主要是把數據存在si、di等寄存器中來進行操作,存儲數據是把數據放在寄存器、內存空間(普通的和棧)裡面。編寫程序ur1.c,並編譯連接:

 

用debug加載ur1.exe,用u命令查看編譯後的機器碼和匯編代碼:

 

發現main函數中的代碼沒有出現,用t命令單步執行發現程序在一個死循環內,為什麼這時顯示的代碼不是main函數裡的代碼呢?可能的原因有兩個:(1)我以為main函數裡的代碼編譯後會是這樣:mov ax,1;mov bx,1;mov cx,1;mov ax,bx;add ax,cx……但是c語言編譯成的匯編代碼是通過別的方式實現代碼的功能的,與我從匯編的角度來實現功能的方式不一樣。(2)main函數本身經過編譯後會有許多匯編代碼,或者是main函數前面還要加載其他程序,導致main函數裡的代碼被放在一個我們不知道的地方,所以找不到。那麼main函數的代碼在什麼段中?用debug怎樣找到ur1.exe中main函數的代碼?我覺得應該在code段中,但是程序裡沒有標號,看不到main的地址。那麼就寫一個main到程序裡,看看main的地址是多少。

編譯程序,顯示main函數的偏移地址:

運行發現,main函數的偏移地址為1fa:

那麼,用debug運行ur1.exe,找到main函數的地址:

 

由圖可知,ur1.c中對應的代碼是mov ax,1;mov bx,1;mov cx,2;mov ax,bx;

add ax,cx;mov ah,bl;add ah,cl;mov al,bh;add al,ch.這裡還應注意到開頭和結尾的三條指令:push bp;mov bp,sp;pop bp.是對bp進行了保護,並且在程序中把sp賦給了bp,為什麼要這樣做呢?書本上的解釋是:這是C編譯器安排的為函數中可能使用到bp寄存器而設置的。那麼為什麼函數中可能使用到bp寄存器呢,也可能使用到別的寄存器啊,為什麼只對bp寄存器進行了保護?

還有在代碼結尾處用ret進行了處理,即對ip出棧,一般用到ret時會與call指令配合使程序段成為可調用的字程序段,那麼main函數是以call-ret的方式來實現成為子程序段的嗎?將以下程序編譯連接:

 

用debug查看main函數中的內容:

 

發現果然f()的位置被call 020b代替。而20b的位置上是f()函數的內容:

 

所以我們的想法是正確的,不僅main函數在匯編語言裡是以call-ret程序段的方式實現的,甚至C語言是將所有函數都實現為匯編語言裡的call-ret子程序段。

二、 解決的問題

(1) 為什麼用debug查看ur1.exe文件找不到main函數裡的內容?

答:因為main函數及其內容是作為子程序段被放在其他位置,所以找不到。

(2) 用debug怎麼查找函數的偏移地址?

答:寫一個程序,用printf打印函數的地址。

(3) C語言裡的函數在匯編語言裡是怎樣實現的?

答:用call-ret作為子程序段來實現。

三、 未解決的問題

(1) 為什麼一個函數在匯編語言中實現要對並且只對bp寄存器進行保護?

(2) 為什麼用debug加載ur1.exe用u命令首先看到的不是main函數及其內容,而是一大堆無關的指令?

(3) 其他高級語言函數的實現方式和C語言一樣嗎?

四、學習感想

你的想法到底正不正確呢?試一試就知道了,實踐是檢驗真理的唯一標准。很多時候我們都會覺得嘗試是一件很麻煩的事,所以面對還不太懂的問題,就想當然地給它下一個判斷,或者根本不去想,等別人來告訴你,久而久之,我們對一件事物的整個認知就會出現偏差,從而犯錯。函數名能夠直接寫在printf函數裡把地址打印出來嗎?c語言裡的函數是用什麼形式在匯編語言裡實現的呢?這些都是需要親自嘗試才能解決的問題。在平時的學習和生活中,我們經常會遇到諸如此類的問題,但是有時候因為自身的惰性,想當然地把它忽視掉了,要用到的時候才感歎自己知道的真的太少。所以,保持一個積極強烈的求知心態是學習過程中很重要的。

Copyright © Linux教程網 All Rights Reserved