九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
深入理解C語(yǔ)言的函數(shù)調(diào)用過(guò)程
    本文主要從進(jìn)程??臻g的層面復(fù)習(xí)一下C語(yǔ)言中函數(shù)調(diào)用的具體過(guò)程,以加深對(duì)一些基礎(chǔ)知識(shí)的理解。
    先看一個(gè)最簡(jiǎn)單的程序:

點(diǎn)擊(此處)折疊或打開

  1. /*test.c*/
  2. #include <stdio.h>


  3. int foo1(int m,int n,int p)
  4. {
  5.         int x = m + n + p;
  6.         return x;
  7. }

  8. int main(int argc,char** argv)
  9. {
  10.         int x,y,z,result;
  11.         x=11;
  12.         y=22;
  13.         z=33;
  14.         result = foo1(x,y,z);
  15.         printf("result=%d\n",result);
  16.         return 0;
  17. }
    主函數(shù)main里定義了4個(gè)局部變量,然后調(diào)用同文件里的foo1()函數(shù)。4個(gè)局部變量毫無(wú)疑問(wèn)都在進(jìn)程的??臻g上,當(dāng)進(jìn)程運(yùn)行起來(lái)后我們逐步了解一下main函數(shù)里是如何基于棧實(shí)現(xiàn)了對(duì)foo1()的調(diào)用過(guò)程,而foo1()又是怎么返回到main函數(shù)里的。為了便于觀察的粒度更細(xì)致一些,我們對(duì)test.c生成的匯編代碼進(jìn)行調(diào)試。如下:

點(diǎn)擊(此處)折疊或打開

  1. .file "test.c"
  2.         .text
  3. .globl foo1
  4.         .type foo1, @function
  5. foo1:
  6.         pushl %ebp
  7.         movl %esp, %ebp
  8.         subl $16, %esp
  9.         movl 12(%ebp), %eax
  10.         movl 8(%ebp), %edx
  11.         leal (%edx,%eax), %eax
  12.         addl 16(%ebp), %eax
  13.         movl %eax, -4(%ebp)
  14.         movl -4(%ebp), %eax
  15.         leave
  16.         ret
  17.         .size foo1, .-foo1
  18.         .section .rodata
  19. .LC0:
  20.         .string "result=%d\n"
  21.         .text
  22. .globl main
  23.         .type main, @function
  24. main:
  25.         pushl %ebp
  26.         movl %esp, %ebp
  27.         andl $-16, %esp
  28.         subl $32, %esp
  29.         movl $11, 16(%esp)
  30.         movl $22, 20(%esp)
  31.         movl $33, 24(%esp)
  32.         movl 24(%esp), %eax
  33.         movl %eax, 8(%esp)
  34.         movl 20(%esp), %eax
  35.         movl %eax, 4(%esp)
  36.         movl 16(%esp), %eax
  37.         movl %eax, (%esp)
  38.         call foo1
  39.         movl %eax, 28(%esp)
  40.         movl $.LC0, %eax
  41.         movl 28(%esp), %edx
  42.         movl %edx, 4(%esp)
  43.         movl %eax, (%esp)
  44.         call printf
  45.         movl $0, %eax
  46.         leave
  47.         ret
  48.         .size main, .-main
  49.         .ident "GCC: (GNU) 4.4.4 20100726 (Red Hat 4.4.4-13)"
  50.         .section .note.GNU-stack,"",@progbits
    上面的匯編源代碼和最終生成的可執(zhí)行程序主體結(jié)構(gòu)上已經(jīng)非常類似了:

[root@maple 1]# gcc -g -o test test.s

[root@maple 1]# objdump -D test >testbin

[root@maple 1]# vi testbin

 //… 省略部分不相關(guān)代碼

80483c0:       ff d0                               call   *%eax

 80483c2:      c9                                   leave

 80483c3:      c3                                   ret

 

080483c4 :

 80483c4:       55                                  push  %ebp

 80483c5:      89 e5                               mov   %esp,%ebp

 80483c7:      83 ec 10                          sub    $0x10,%esp

 80483ca:      8b 45 0c                          mov    0xc(%ebp),%eax

 80483cd:      8b 55 08                         mov   0x8(%ebp),%edx

 80483d0:      8d 04 02                         lea    (%edx,%eax,1),%eax

 80483d3:      03 45 10                         add    0x10(%ebp),%eax

 80483d6:      89 45 fc                          mov    %eax,-0x4(%ebp)

 80483d9:      8b 45 fc                          mov    -0x4(%ebp),%eax

 80483dc:      c9                                   leave

 80483dd:      c3                                   ret

 

080483de

:

 80483de:      55                                     push   %ebp

 80483df:      89 e5                                 mov   %esp,%ebp

 80483e1:      83 e4 f0                            and    $0xfffffff0,%esp

 80483e4:      83 ec 20                           sub    $0x20,%esp

 80483e7:      c7 44 24 10 0b 00 00       movl   $0xb,0x10(%esp)

 80483ee:      00

 80483ef:      c7 44 24 14 16 00 00        movl   $0x16,0x14(%esp)

 80483f6:      00

 80483f7:      c7 44 24 18 21 00 00        movl   $0x21,0x18(%esp)

 80483fe:      00

 80483ff:      8b 44 24 18                      mov    0x18(%esp),%eax

 8048403:      89 44 24 08                    mov    %eax,0x8(%esp)

 8048407:      8b 44 24 14                    mov    0x14(%esp),%eax

 804840b:      89 44 24 04                    mov    %eax,0x4(%esp)

 804840f:      8b 44 24 10                     mov    0x10(%esp),%eax

 8048413:      89 04 24                         mov    %eax,(%esp)

 8048416:       e8 a9 ff ff ff                     call  80483c4

 804841b:      89 44 24 1c                     mov    %eax,0x1c(%esp)

 804841f:      b8 04 85 04 08                 mov    $0x8048504,%eax

 8048424:      8b 54 24 1c                     mov    0x1c(%esp),%edx

 8048428:      89 54 24 04                     mov    %edx,0x4(%esp)

 804842c:      89 04 24                         mov    %eax,(%esp)

 804842f:      e8 c0 fe ff ff                     call   80482f4

 8048434:      b8 00 00 00 00              mov    $0x0,%eax

 8048439:       c9                                  leave

 804843a:      c3                                  ret

 804843b:      90                                 nop

 804843c:      90                                 nop

 //… 省略部分不相關(guān)代碼

    用GDB調(diào)試可執(zhí)行程序test:

    在main函數(shù)第一條指令執(zhí)行前我們看一下進(jìn)程test的??臻g布局。因?yàn)槲覀冏罱K的可執(zhí)行程序是通過(guò)glibc庫(kù)啟動(dòng)的,在main的第一條指令運(yùn)行前,其實(shí)還有很多故事的,這里就不展開了,以后有時(shí)間再細(xì)究,這里只要記住一點(diǎn):main函數(shù)執(zhí)行前,其進(jìn)程空間的棧里已經(jīng)有了相當(dāng)多的數(shù)據(jù)。我的系統(tǒng)里此時(shí)棧頂指針esp的值是0xbffff63c,?;分羔?/span>ebp的值0xbffff6b8,指令寄存器eip的值是0x80483de正好是下一條馬上即將執(zhí)行的指令,即main函數(shù)內(nèi)的第一條指令“push %ebp”。那么此時(shí),test進(jìn)程的棧空間布局大致如下:
    然后執(zhí)行如下三條指令:

點(diǎn)擊(此處)折疊或打開

  1. 25 pushl %ebp         //將原來(lái)ebp的值0xbffff6b8如棧,esp自動(dòng)增長(zhǎng)4字節(jié)
  2. 26 movl %esp, %ebp    //用ebp保存當(dāng)前時(shí)刻esp的值
  3. 27 andl $-16, %esp    //內(nèi)存地址對(duì)其,可以忽略不計(jì)
   執(zhí)行完上述三條指令后棧里的數(shù)據(jù)如上圖所示,從0xbffff630到0xbffff638的8字節(jié)是為了實(shí)現(xiàn)地址對(duì)齊的填充數(shù)據(jù)。此時(shí)ebp的值0xbffff638,該地址處存放的是ebp原來(lái)的值0xbffff6b8。詳細(xì)布局如下:
   第28條指令“subl  $32,%esp”是在棧上為函數(shù)里的本地局部變量預(yù)留空間,這里我們看到main主函數(shù)有4個(gè)int型的變量,理論上說(shuō)預(yù)留16字節(jié)空間就可以了,但這里卻預(yù)留了32字節(jié)。GCC編譯器在生成匯編代碼時(shí),已經(jīng)考慮到函數(shù)調(diào)用時(shí)其輸入?yún)?shù)在棧上的空間預(yù)留的問(wèn)題,這一點(diǎn)我們后面會(huì)看到。當(dāng)?shù)?8條指令執(zhí)行完后??臻g里的數(shù)據(jù)和布局如下:

    然后main函數(shù)里的變量x,y,z的值放到棧上,就是接下來(lái)的三條指令:


點(diǎn)擊(此處)折疊或打開

  1. 29 movl $11, 16(%esp)
  2. 30 movl $22, 20(%esp)
  3. 31 movl $33, 24(%esp)

   這是三條寄存器間接尋址指令,將立即數(shù)11,22,33分別放到esp寄存器所指向的地址0xbffff610向高位分別偏移16、20、24個(gè)字節(jié)處的內(nèi)存單元里,最后結(jié)果如下:

   
注意:這三條指令并沒(méi)有改變esp寄存器的值。

   接下來(lái)main函數(shù)里就要為了調(diào)用foo1函數(shù)而做準(zhǔn)備了。由于mov指令的兩個(gè)操作數(shù)不能都是內(nèi)存地址,所以要將x,y和z的值傳遞給foo1函數(shù),則必須借助通用寄存器來(lái)完成,這里我們看到eax承擔(dān)了這樣的任務(wù):

點(diǎn)擊(此處)折疊或打開

  1. 32 movl 24(%esp), %eax
  2. 33 movl %eax, 8(%esp)
  3. 34 movl 20(%esp), %eax
  4. 35 movl %eax, 4(%esp)
  5. 36 movl 16(%esp), %eax
  6. 37 movl %eax, (%esp)



    當(dāng)foo1函數(shù)所需要的所有輸入?yún)?shù)都已經(jīng)按正確的順序入棧后,緊接著就需要調(diào)用call指令來(lái)執(zhí)行foo1函數(shù)的代碼了。前面的博文說(shuō)過(guò),call指令執(zhí)行時(shí)分兩步:首先會(huì)將call指令的下一條指令(movl  %eax,28(%esp))的地址(0x0804841b)壓入棧,然后跳轉(zhuǎn)到函數(shù)foo1入口處開始執(zhí)行。當(dāng)?shù)?8條指令“call foo1”執(zhí)行完后,棧空間布局如下:
   call指令自動(dòng)將下一條要執(zhí)行的指令的地址0x0804841b壓入棧,棧頂指針esp自動(dòng)向低地址處“增長(zhǎng)”4字節(jié)。所以,我們以前在C語(yǔ)言里所說(shuō)的函數(shù)返回地址,應(yīng)該理解為:當(dāng)被調(diào)用函數(shù)執(zhí)行完之后要返回到它的調(diào)用函數(shù)里下一條馬上要執(zhí)行的代碼的地址。為了便于觀察,我們把foo1函數(shù)最后生成指令再列出來(lái):

點(diǎn)擊(此處)折疊或打開

  1. 3 .globl foo1
  2. 4           .type foo1, @function
  3. 5 foo1:
  4. 6           pushl %ebp
  5. 7           movl %esp, %ebp
  6. 8           subl $16, %esp
  7. 9           movl 12(%ebp), %eax
  8. 10          movl 8(%ebp), %edx
  9. 11          leal (%edx,%eax), %eax
  10. 12          addl 16(%ebp), %eax
  11. 13          movl %eax, -4(%ebp)
  12. 14          movl -4(%ebp), %eax
  13. 15          leave
  14. 16          ret
  15. 17          .size foo1, .-foo1
    進(jìn)入到foo1函數(shù)里,開始執(zhí)行該函數(shù)里的指令。當(dāng)執(zhí)行完第6、7、8條指令后,棧里的數(shù)據(jù)如下。這三條指令就是匯編層面函數(shù)的“序幕”,分別是保存ebp到棧,讓ebp指向當(dāng)前棧頂,然后為函數(shù)里的局部變量預(yù)留空間:
   接下來(lái)第9和第10條指令,也并沒(méi)有改變棧上的任何數(shù)據(jù),而是將函數(shù)輸入?yún)?shù)列表中的的x和y的值分別轉(zhuǎn)載到eax和edx寄存器,和main函數(shù)剛開始時(shí)做的事情一樣。此時(shí)eax=22、edx=11。
然后用了一條leaf指令完成xy的加法運(yùn)算,并將運(yùn)算結(jié)果存在eax里。第12條指令“addl 16(%ebp), %eax”將第三個(gè)輸入?yún)?shù)p的值,這里是實(shí)參z的值為33,同樣用寄存器間接尋址模式累加到eax里。此時(shí)eax=11+22+33=66就是我們最終要得計(jì)算結(jié)果。

   
因?yàn)槲覀?/span>foo1()函數(shù)的C代碼中,最終計(jì)算結(jié)果是保存到foo1()里的局部變量x里,最后用return語(yǔ)句將x的值通過(guò)eax寄存器返回到mian函數(shù)里,所以我們看到接下來(lái)的第1314條指令有些“多此一舉”。這足以說(shuō)明gcc人家還是相當(dāng)嚴(yán)謹(jǐn)?shù)模?/span>C源代碼的函數(shù)里如果有給局部變量賦值的語(yǔ)句,生成匯編代碼時(shí)確實(shí)會(huì)在棧上為本地變量預(yù)留的空間里的正確位置為其賦值。當(dāng)然gcc還有不同級(jí)別的優(yōu)化技術(shù)來(lái)提高程序的執(zhí)行效率,這個(gè)不屬于本文所討論的東西。讓我們繼續(xù),當(dāng)?shù)?3、14條指令執(zhí)行完后,棧布局如下:
   將ebp-4的地址處0xbffff604(其實(shí)就是foo1()里的第一個(gè)局部參數(shù)x的地址)的值設(shè)置為66,然后再將該值復(fù)制到eax寄存器里,等會(huì)兒在main函數(shù)里就可以通過(guò)eax寄存器來(lái)獲取最終的計(jì)算結(jié)果。當(dāng)?shù)?5條指令leave執(zhí)行完后,??臻g的數(shù)據(jù)和布局如下:

    我們發(fā)現(xiàn),雖然棧頂從0xbffff5f8移動(dòng)到0xbffff60c了,但棧上的數(shù)據(jù)依然存在。也就是說(shuō),此時(shí)你通過(guò)esp-8依舊可以訪問(wèn)foo1函數(shù)里的局部變量x的值。當(dāng)然,這也是說(shuō)得通的,因?yàn)楹瘮?shù)此時(shí)還沒(méi)有返回。我們看棧布局可以知道當(dāng)前的棧頂0xbffff60c處存放的是下一條即將執(zhí)行的指令的地址,對(duì)照反匯編結(jié)果可以看到這正是main函數(shù)里的第18條指令(在整個(gè)匯編源文件test.s里的行號(hào)是39)movl  %eax, 28(%esp)”。leave指令其實(shí)完成了兩個(gè)任務(wù):
   1、將棧上為函數(shù)預(yù)留的空間“收回”;
   
2、恢復(fù)ebp;

   也就是說(shuō)leave指令等價(jià)于下面兩條指令,你將leave替換成它們編譯運(yùn)行,結(jié)果還是對(duì)的:


點(diǎn)擊(此處)折疊或打開

  1. movl %ebp,%esp
  2. popl %ebp



   前面我們也說(shuō)過(guò),ret指令會(huì)自動(dòng)到棧上去pop數(shù)據(jù),相當(dāng)于執(zhí)行了“popl %eip”,會(huì)使esp增大4字節(jié)。所以當(dāng)執(zhí)行完第16條指令ret后,esp從0xbffff60c增長(zhǎng)到0xbffff610處,??臻g結(jié)構(gòu)如下:

   現(xiàn)在已經(jīng)從foo1里返回了,但是由于還沒(méi)執(zhí)行任何push操作,棧頂“上部”的數(shù)據(jù)依舊還是可以訪問(wèn)到了,即esp-12的值就是foo1里的局部變量x的值、esp-4的值就是函數(shù)的返回地址,當(dāng)執(zhí)行第39條指令“movl %eax,28(%esp)”后棧布局變成下面的樣子:
   第39條指令就相當(dāng)于給main里的result變量賦值66,如上紅線標(biāo)注的地方。接下來(lái)main函數(shù)里要執(zhí)行printf("result=%d\n",result)語(yǔ)句了,而printf又是C庫(kù)的一個(gè)常用的輸出函數(shù),這里就又會(huì)像前面調(diào)用foo1那樣,初始化棧,然后用“call printf的地址”來(lái)調(diào)用C函數(shù)。當(dāng)40~43這4條指令執(zhí)行完后,棧里的數(shù)據(jù)如下:

點(diǎn)擊(此處)折疊或打開

  1. 40 movl $.LC0, %eax
  2. 41 movl 28(%esp), %edx
  3. 42 movl %edx, 4(%esp)
  4. 43 movl %eax, (%esp)
   上圖為了方便理解,將棧頂?shù)?x08048504替換了成字符串“result=%d\n”,但進(jìn)程實(shí)際運(yùn)行時(shí)此時(shí)棧頂esp的值是字符串所在的內(nèi)存地址。當(dāng)?shù)?4條指令執(zhí)行完后,棧布局如下:
   由于此時(shí)棧已經(jīng)用來(lái)調(diào)用printf了,所以棧頂0xbffff610“以上”部分的空間里就找不到foo1的任何影子了。最后在main函數(shù)里,當(dāng)?shù)?6、47條指令執(zhí)行完后棧的布局分別是:
    當(dāng)main函數(shù)里的ret執(zhí)行完,其實(shí)是返回到了C庫(kù)里繼續(xù)執(zhí)行剩下的清理工作。
   
所以,最后關(guān)于C的函數(shù)調(diào)用,我們可以總結(jié)一下:
   
1、函數(shù)輸入?yún)?shù)的入棧順序是函數(shù)原型中形參從右至左的原則;
   
2、匯編語(yǔ)言里調(diào)用函數(shù)通常情況下都用call指令來(lái)完成;
   
3、匯編語(yǔ)言里的函數(shù)大部分情況下都符合以下的函數(shù)模板:

點(diǎn)擊(此處)折疊或打開

  1. .globl fun_name
  2. .type fun_name, @function
  3. fun_name:
  4.         pushl %ebp
  5.         movl %esp, %ebp
  6.         <函數(shù)主體代碼>
  7.         leave
  8.         ret

   如果我們有個(gè)函數(shù)原型:int funtest(int x,int y int z char* ptr)
,在匯編層面,當(dāng)調(diào)用它時(shí)棧的布局結(jié)構(gòu)一般是下面這個(gè)樣子:

    而有些資料上將ebp指向函數(shù)返回地址的地方,這是不對(duì)的。正常情況下應(yīng)該是ebp指向old ebp才對(duì),這樣函數(shù)末尾的leave和ret指令才可以正常工作。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
ASM調(diào)試
匯編指令 mov和movl
函數(shù)調(diào)用堆棧變化情況
數(shù)據(jù)傳送指令詳解
也談C語(yǔ)言的內(nèi)聯(lián)函數(shù)
通過(guò)內(nèi)核源碼看函數(shù)調(diào)用之前世今生
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服