函數調用過程分析


函數調用過程分析

1. 靜態變量和初始化

int static_variable = 5;
--------------------------------------------
        .data ;進入程序的數據區
        .even ;確保變量開始於內存的偶數地址
        .global _static_variable ;聲明變量為全局類型
 _static_variable:
        .long 5 ;創建空間,初始化

2. 堆棧幀

一個函數分為:函數序、函數體、函數跋

函數序:執行啟動工作,如:為局部變量保存堆棧中的內存

函數跋:在函數即將返回之前清理堆棧。

函數體:執行工作的地方

void f()
{
    register int i1, i2, i3, i4, i5,
                 i6, i7, i8, i9, i10;
    register char *c1, *c2, *c3, *c4, *c5,
                  *c6, *c7, *c8, *c9, *c10;
    extern int a_very_long_name_to_see_...
    double dbl;
    int func_ret_int();
    double func_ret_double();
    char *func_ret_char_ptr();
-------------------------------------------------------
        .text    ;進入程序代碼段
        .global _f    ;函數的全局聲明
_f:     link a6, #-88     ;創建堆棧幀,堆棧幀是堆棧中的一個區域,存儲變量和其他值
        moveml 0x3cfc, sp@    ;選定寄存器中的值復制到堆棧中

0x3cfc表示寄存器d2至d7、a2到a5中的值需要被保存

局部變量聲明和函數原型不會產生任何匯編代碼。但局部變量聲明時進行了初始化,也會出現指令用於賦值操作

3. 寄存器變量

i1 = 1; i2 = 2; i3 = 3; i4 = 4; i5 = 5;
i6 = 6; i7 = 7; i8 = 8; i9 = 9; i10 = 10;
c1 = (char *)110; c2 = (char *)120;
c3 = (char *)130; c4 = (char *)140;
c5 = (char *)150; c6 = (char *)160;
c7 = (char *)170; c8 = (char *)180;
c9 = (char *)190; c10 = (char *)200; 
-------------------------------------------
moveq #1,d7
moveq #2,d6
moveq #3,d5
moveq #4,d4
moveq #5,d3
moveq #6,d2
movl #7,a6@(-4)
movl #8,a6@(-8)
movl #9,a6@(-12)
movl #10,a6@(-16)
movl #110,a5
movl #120,a4
movl #130,a3
movl #140,a2
movl #150,a6@(-20)
movl #160,a6@(-24)
movl #170,a6@(-28)
movl #180,a6@(-32)
movl #190,a6@(-36)
movl #200,a6@(-40)

值1至6被放在數據寄存器,7至10放在其他地方

指針變量前4個存放在寄存器

其他變量,機器執行間接尋址和索引操作。a6稱為幀指針,指向堆棧幀內部的一個引用位置

這台機器(motorola 68000),a6用作幀指針,a7是堆棧指針sp,d0和d1用於存函數返回值

4. 堆棧幀的布局

運行時堆棧保存了每個函數運行時所需要的數據,包括它的自動變量和返回地址。

4.1 傳遞函數參數

i2 = func_ret_int(10, i1, i10);
---------------------------------
movl a6@(-16),sp@-
movl d7,sp@-
pea  10
jbsr _func_ret_int

前3條指令把函數的參數壓入堆棧中。以參數列表相反的次序逐個壓入棧。

接下來跳轉子程序,把返回地址壓入堆棧中,並跳轉到_func_ret_int的起始位置,當被調用函數結束任務返回調用位置時,就需要用到壓入堆棧的返回地址。

5d3178bbba95520405

4.2 函數序

int func_ret_int(int a, int b, register int c)
{
    int d;
---------------------------------------------------
        .global  _func_ret_int
_func_ret_int:
        link    a6,#-8
        moveml  #0x80,sp@
        movl    a6@(16),d7

link指令分成幾個步驟:

  • a6的內容被壓入堆棧中

  • 堆棧指針的當前值被復制到a6

  • link指令從堆棧指針中減去8

  • 這將創建空間用於保存局部變量和被保存的寄存器值*

    5d317bbfca55072652

下一條指令把單一寄存器保存到堆棧幀,操作數0x80指定寄存器d7。寄存器存儲在堆棧的頂部,剩余部分必然是局部變量存儲的地方。如上圖右邊。

函數序最后才能夠堆棧復制一個值到d7,函數把第三個參數聲明為寄存器變量。

4.3 堆棧中的參數次序

被調用函數使用幀指針加一個偏移量來訪問參數,當參數以反序壓入到堆棧時,參數列表的第一個參數便位於堆棧中這堆參數的頂部,它距離幀指針的偏移量是一個常數。

4.4 最終的堆棧幀布局

5d317e8fc517c97106

    d = b - 6;
    return a + b + c;
--------------------------------
movl    a6@(12),d0
subl    #6,d0
movl    d0,a6@(-4)
movl    a6@(8),d0
movl    a6@(12),d0
addl    d7,d0
moveml  a6@(-8),#0x80
unlk    a6
rts    

4.5 函數跋

  • mveml恢復以前被保存的寄存器值

  • unkl(unlink)把a6的值復制給堆棧指針並把從堆棧中彈出的a6舊值裝入a6中。清除堆棧幀中返回地址以上的那部分內容

  • rts指令通過把返回地址從堆棧中彈出到程序計數器,從而從該函數返回。

現在,執行流從調用程序的地點繼續。堆棧尚未完全清理

i2 = func_ret_int(10, i1, i10);
------------------------------------
lea    sp@(12),sp
movl    d0,d6

第1條指令把參數從堆棧中彈出,此時堆棧的狀態就和調用前狀態完全一樣了。

4.6 返回值

函數跋沒有使用d0,因此依然保存函數的返回值。第二條指令把d0復制給d6,后者存放變量i2的存放位置。

dbl = func_ret_double();
c1 = func_ret_char_ptr();
-------------------------------------
jbsr    _func_ret_double
movl    d0,a6@(-48)
movl    d1,a6@(-44)

pea    a5@
jbsr    _func_ret_char_ptr
addqw   #4,sp
movl    d0,a5

第一個函數double是8字節,無法放入一個寄存器中,因此返回值需要d0和d1兩個寄存器。

最后那個函數調用說明了指針變量時如何從函數中返回的:也是通過d0進行傳遞的。

參考:C和指針-第18章 運行時環境


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM