二、Linux彙編——函數的工做原理

第四章 函數的工做原理

一、函數的組成部分

    函數主要由如下幾個成分組成:函數名、函數參數、局部變量、靜態變量、全局變量、返回地址、返回值架構

(1)函數參數及幾個變量:這是在邏輯上對函數的涉及到的數據進行規劃,實際上當前運行的指令只能經過直接、間接、當即數三種方式訪問數據。函數

(2)返回地址:在彙編語言中,其實是某個指令的地址,即IP寄存器、程序段標號等存儲或表明的地址。oop

(3)返回值:程序只能有一個返回值,具體表現爲該返回值:存儲在某個寄存器中,存儲在某個內存單元中。spa

二、C調用約定的彙編語言函數

       一、關於棧

        在內存的具體實現中,「棧底」位於「高地址」區域,「棧頂」位於「低地址」區域,其中%ess爲棧的段基址,%esp爲棧頂指針(注意:棧頂非空)。當進行「壓棧」操做時,%esp的值由大到小變化;當進行「彈棧」操做時,%esp的值由小到大變化。指針

        同時,因爲當前機器爲32 位機,所以,每次%esp都會跨過4 byte。
code

    二、關於如何調用函數

     C約定的彙編調用其實是利用「棧」暫存信息:被調用函數的地址、被調用函數的參數、調用函數的地址。當前執行的程序調用某個函數時,進行以下操做:遞歸

(0)爲了防止寄存器中的數據被破壞,在進入「被調用函數」以前,須要在當前執行的函數中,將全部寄存器的值壓棧保存。待返回當前函數時,再從新加載這些值,即常說的「恢復現場」。
索引

(1)逆序將被調用函數的「參數」壓棧:如函數fun(para 1, para 2, para 3, para 4,....),則para 4最早入棧,para 1最後入棧。此時棧頂元素爲para 1。
內存

(2)將當前的IP地址壓棧:該地址爲返回地址,當被調用函數結束執行後,利用ret(return的縮寫)指令,返回到調用函數ci

(3)將當前%ebp(基址指針寄存器)值壓棧

(4)movl %esp , %ebp:此時,正如「基址指針寄存器」代表的,能夠經過%ebp來根據「基址尋址」方式對「被調用函數」中的局部變量進行訪問。如:-8(%ebp),在x86架構中,採用%ebp進行基址尋址較使用其餘寄存器快。

(5)修改%esp值,爲「被調用函數」開闢棧空間,存儲局部變量——能夠獲得被調函數的局部變量空間的範圍爲:%ebp~%esp。

#示例:演示進入被調函數的代碼
#說明: 一、被調函數的參數保存在數據域的item標籤下,此處爲索引,實際狀況不會這樣
#        二、被調函數的標籤爲called_func
.section .data
    item: .long 1, 2, 3 #使用long類型,是爲了配合%esp每次移動都爲4 byte,不然須要作其餘處理
                        #函數即爲:called_func(1,2,3)
.section .text
.globl _start
_start:     #(0)保存「上下文環境」
            pushl %eax  #假設"調用函數"只涉及到%eax和%ebx的使用
            pushl %ebx    
            movl  3, %ebx
            #(1)對參數進行逆序壓棧,此處採用「索引尋址」 
load_data:  subl 1,%ebx             #將函數參數壓入棧中
            pushl item(,%ebx,4)
            cmpl 0,%ebx
            jne  load_data
            
            #(2)將當前函數的指令地址壓入棧中,並跳轉
            call called_func
called_func:    pushl %ebp          #(3)暫存%ebp的值於棧中
                movl %esp , %ebp    #(4)改變%ebp的值
                subl 8,%ebp        #(5)爲「被調函數」開闢2個字的空間,存儲局部變量
                
                #若是須要對「局部變量」進行訪問,則利用%ebp進行「基址訪址」形式便可
                movl $2, -4(%ebp)
                ......
                ......
                #使用下面的指令,返回到「調用函數」中

以上即爲進入一個「被調用函數」須要作的準備工做。當退出「被調函數」時,須要作以下工做:

(1)將返回值存入%eax中

(2)清除「被調函數」棧內數據

(3)返回「調用函數」。從「被調函數」返回的代碼以下:

movl %ebp, %esp
popl %ebp
ret

三、對於寄存器

        在進入「被調函數」前,必定要將當前的寄存器值暫存在棧中,從而保證「被調函數」有充足的寄存器可使用。若是要在「被調函數」保存寄存器,則破壞了函數之間的封閉性。調用函數和被調函數之間,必定只能經過全局變量、被調函數的參數進行通訊,不然,將不易於程序的管理。

三、程序1:

#目的:本程序計算2^3+5^2
#程序全部內容存入寄存器中,數據段無數據
#變量說明:%eax存儲函數power的計算結果,並做爲返回值,返回給「調用函數」

.section .data
.section .text
.globl _start
_start:    #計算第一個加數
           pushl $3     #壓入第二個參數,指數
           pushl $2     #壓入第一個參數,底數
           call power   #調用函數
           
           addl $8, %esp #清空「被調函數」存儲局部變量的棧空間
           pushl %eax     #將第一個結果壓入棧中
           
           #計算第二加數
           pushl $2
           pushl $5
           call power
           
           addl $8, %esp     #清空「被調函數」的棧空間
           popl %ebx        #將第一個結果彈棧,存入%ebx中。第二個結果已經存入%eax中
           
           addl %eax, %ebx  #兩個結果相加,做爲返回給系統的狀態之,存儲在%ebx中
           
           movl $1, %eax    #調用中斷,退出程序
           int 0x80
    
#目的:計算一個整數的冪
#輸入:參數1:底數a
#      參數2:冪b
#輸出:a^b
#注意:指數爲不小於1的整數
#變量:%ebx:存底數
#       %ecx:存指數
#       -4(%ebp):存當前結果
#       %eax:臨時存儲
.type power, @function
power:    pushl %ebp         #暫存%ebp的值於棧中
          movl %esp, %ebp    #將%ebp指向局部變量存儲區域的開始
          subl $4, %esp      #開闢局部不變量存儲區域
          
          #從棧中獲取參數,注意4(%ebp)存儲「調用函數」的地址
          movl 8(%ebp), %ebx      #底數
          movl 12(%ebp), %ecx     #指數
          movl %ebx, -4(%ebp)    #存儲結果,注意:因爲棧空間是從「高地址」區域向「低地址」區域移動
power_loop_start:    cmpl $1, %ecx   #若是是1次方,則結束循環乘法
                     je end_power
                     
                     movl -4(%ebp), %eax
                     imull %ebx, %eax      # %eax = %eax * %ebx
                     movl %eax, -4(%ebp)    #存回棧中
                     
                     decl %ecx            #指數遞減
                     jmp power_loop_start
end_power:    movl -4(%ebp), %eax
              movl %ebp, %esp
              popl %ebp
              ret
說明:

一、因爲返回給程序的狀態碼需不大於255,因此計算的結果不能過大

二、.type power, @function指令告訴鏈接器:將符號power做爲函數處理。

四、遞歸函數——程序2

問題背景:計算某個整數的階乘。因爲每一個函數都有本身的「棧幀」,因此當函數調用本身的時候,使用局部數據空間不會互相干擾。

#目標:計算某個給定數字的階乘。程序將使用遞歸思想
#變量:%ebx做爲臨時變量
.section .data
.section .text
.globl _start
.globl factorial #經過該項,可將該函數共享給其餘程序調用

_start :    pushl $4    #須要計算階乘的整數
            call factorial  #調用函數,計算階乘
            addl $4, %esp   #清空「被調函數」開闢的存儲局部變量的空間
            movl %eax, %ebx #將存儲在%eax中的factiorial的返回值,做爲狀態字存儲在%ebx中
            
            movl $1, %eax
            int 0x080
#此爲實際的函數定義
.type factorial, @funciton
factorial:    pushl %ebp          #初始化局部存儲空間   
              movl %esp, %ebp
              
              movl 8(%ebp), %eax  #4(%ebp)存返回地址,8(%ebp)存第一個參數,即某個整數
              
              cmpl $1, %eax       #爲1,則退出階乘的計算
              je end_factorial
              
              decl %eax            #大於1,則遞歸調用該函數
              
              pushl %eax            #與上面的指令——pushl $4相呼應
              call factorial
              
              #核心計算代碼
              movl 8(%ebp), %ebx
              imull %ebx, %eax     #%ebx存儲上一次運算的結果,%eax存儲當前整數,
end_factorial:    movl %ebp, %esp
                  popl %ebp
                  ret
程序說明:

.type指令告訴連接器factorial爲一個函數。

相關文章
相關標籤/搜索