函數主要由如下幾個成分組成:函數名、函數參數、局部變量、靜態變量、全局變量、返回地址、返回值架構
(1)函數參數及幾個變量:這是在邏輯上對函數的涉及到的數據進行規劃,實際上當前運行的指令只能經過直接、間接、當即數三種方式訪問數據。函數
(2)返回地址:在彙編語言中,其實是某個指令的地址,即IP寄存器、程序段標號等存儲或表明的地址。oop
(3)返回值:程序只能有一個返回值,具體表現爲該返回值:存儲在某個寄存器中,存儲在某個內存單元中。spa
在內存的具體實現中,「棧底」位於「高地址」區域,「棧頂」位於「低地址」區域,其中%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
在進入「被調函數」前,必定要將當前的寄存器值暫存在棧中,從而保證「被調函數」有充足的寄存器可使用。若是要在「被調函數」保存寄存器,則破壞了函數之間的封閉性。調用函數和被調函數之間,必定只能經過全局變量、被調函數的參數進行通訊,不然,將不易於程序的管理。
#目的:本程序計算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做爲函數處理。
問題背景:計算某個整數的階乘。因爲每一個函數都有本身的「棧幀」,因此當函數調用本身的時候,使用局部數據空間不會互相干擾。
#目標:計算某個給定數字的階乘。程序將使用遞歸思想 #變量:%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爲一個函數。