<acronym id="xonnx"></acronym>
      <td id="xonnx"></td>
    1. <pre id="xonnx"></pre>

      1. 專注電子技術學習與研究
        當前位置:單片機教程網 >> MCU設計實例 >> 瀏覽文章

        C語言函數調用分析

        作者:佚名   來源:本站原創   點擊數:  更新時間:2014年03月13日   【字體:
        我的測試環境:Fedora14
        Gcc版本:gcc-4.5.1
        內核版本:2.6.38.1
         
        C語言是一個強大的語言,特別是對于嵌入式開發過程中有時需要反匯編分析代碼中存在的問題,函數是C語言中的難點,關于函數的調用也是很多人不能理解的,很多知道的也是一知半解。對C語言的調用有了一個比較清晰的認識就能夠更清晰的分析代碼中存在的問題。我也是看了很多的資料,然后自己寫了一一段小代碼作為分析的測試代碼。首先記住在X86體系里很多的寄存器都有特殊的用途,其中ESP表示當前函數堆棧的棧頂指針,而EBP則表示當前函數堆棧的基地址。EBP是;返闹羔,永遠指向棧底(高地址),ESP是棧指針,永遠指向棧頂(低地址)。 
        我的代碼如下:
        1. #include<stdio.h>

        2.  
        3. int pluss_a_and_b(int a,int b)
           
        4. {
           
        5.         int c = -2;
           
        6.         return (a + b - c);
           
        7. }
           
        8. int call_plus(int *a,int *b)
           
        9. {
           
        10.         int c = *a;
           
        11.         int d = *b;
           

        12.  
        13.         *a = d;
           
        14.         *b = c;
           
        15.         return pluss_a_and_b(c,d);
           
        16. }
           
        17. int main()
           
        18. {
           
        19.         int c = 10;
           
        20.         int d = 20;
           
        21.         int g = call_plus(&c,&d);
           
        22.         return 0;
           
        23. }
        對上面的代碼進行編譯和反匯編:
        [gong@Gong-Computer deeplearn]$ gcc -g testcall.c -o testcall
        [gong@Gong-Computer deeplearn]$ objdump -S -d testcall > testcall_s 
        然后對反匯編的代碼進行分析:
        1. ...
        2.  
        3. 8048393: c3 ret
        4.  
        5.  
        6.  
        7. 08048394 <pluss_a_and_b>:
        8.  
        9. #include<stdio.h>
        10.  
        11.  
        12.  
        13. int pluss_a_and_b(int a,int b)
        14.  
        15. {
        16.  
        17. 8048394: 55 push %ebp
        18.  
        19. 8048395: 89 e5 mov %esp,%ebp
        20.  
        21. 8048397: 83 ec 10 sub $0x10,%esp
        22.  
        23. int c = -2;
        24.  
        25. 804839a: c7 45 fc fe ff ff ff movl $0xfffffffe,-0x4(%ebp)
        26.  
        27. return (a + b - c);
        28.  
        29. 80483a1: 8b 45 0c mov 0xc(%ebp),%eax
        30.  
        31. 80483a4: 8b 55 08 mov 0x8(%ebp),%edx
        32.  
        33. 80483a7: 8d 04 02 lea (%edx,%eax,1),%eax
        34.  
        35. 80483aa: 2b 45 fc sub -0x4(%ebp),%eax
        36.  
        37. }
        38.  
        39. 80483ad: c9 leave
        40.  
        41. 80483ae: c3 ret
        42.  
        43.  
        44.  
        45. 080483af <call_plus>:
        46.  
        47.  
        48.  
        49. int call_plus(int *a,int *b)
        50.  
        51. {
        52.  
        53. 80483af: 55 push %ebp
        54.  
        55. 80483b0: 89 e5 mov %esp,%ebp
        56.  
        57. 80483b2: 83 ec 18 sub $0x18,%esp
        58.  
        59. int c = *a;
        60.  
        61. 80483b5: 8b 45 08 mov 0x8(%ebp),%eax
        62.  
        63. 80483b8: 8b 00 mov (%eax),%eax
        64.  
        65. 80483ba: 89 45 fc mov %eax,-0x4(%ebp)
        66.  
        67. int d = *b;
        68.  
        69. 80483bd: 8b 45 0c mov 0xc(%ebp),%eax
        70.  
        71. 80483c0: 8b 00 mov (%eax),%eax
        72.  
        73. 80483c2: 89 45 f8 mov %eax,-0x8(%ebp)
        74.  
        75.  
        76.  
        77. *a = d;
        78.  
        79. 80483c5: 8b 45 08 mov 0x8(%ebp),%eax
        80.  
        81. 80483c8: 8b 55 f8 mov -0x8(%ebp),%edx
        82.  
        83. 80483cb: 89 10 mov %edx,(%eax)
        84.  
        85. *b = c;
        86.  
        87. 80483cd: 8b 45 0c mov 0xc(%ebp),%eax
        88.  
        89. 80483d0: 8b 55 fc mov -0x4(%ebp),%edx
        90.  
        91. 80483d3: 89 10 mov %edx,(%eax)
        92.  
        93.  
        94.  
        95. return pluss_a_and_b(c,d);
        96.  
        97. 80483d5: 8b 45 f8 mov -0x8(%ebp),%eax
        98.  
        99. 80483d8: 89 44 24 04 mov %eax,0x4(%esp)
        100.  
        101. 80483dc: 8b 45 fc mov -0x4(%ebp),%eax
        102.  
        103. 80483df: 89 04 24 mov %eax,(%esp)
        104.  
        105. 80483e2: e8 ad ff ff ff call 8048394 <pluss_a_and_b>
        106.  
        107. }
        108.  
        109. 80483e7: c9 leave
        110.  
        111. 80483e8: c3 ret
        112.  
        113.  
        114.  
        115. 080483e9 <main>:
        116.  
        117.  
        118.  
        119. int main()
        120.  
        121. {
        122.  
        123. 80483e9: 55 push %ebp
        124.  
        125. 80483ea: 89 e5 mov %esp,%ebp
        126.  
        127. 80483ec: 83 ec 18 sub $0x18,%esp
        128.  
        129. int c = 10;
        130.  
        131. 80483ef: c7 45 f8 0a 00 00 00 movl $0xa,-0x8(%ebp)
        132.  
        133.  
        134.  
        135. int d = 20;
        136.  
        137. 80483f6: c7 45 f4 14 00 00 00 movl $0x14,-0xc(%ebp)
        138.  
        139.  
        140.  
        141. int g = call_plus(&c,&d);
        142.  
        143. 80483fd: 8d 45 f4 lea -0xc(%ebp),%eax
        144.  
        145. 8048400: 89 44 24 04 mov %eax,0x4(%esp)
        146.  
        147. 8048404: 8d 45 f8 lea -0x8(%ebp),%eax
        148.  
        149. 8048407: 89 04 24 mov %eax,(%esp)
        150.  
        151. 804840a: e8 a0 ff ff ff call 80483af <call_plus>
        152.  
        153. 804840f: 89 45 fc mov %eax,-0x4(%ebp)
        154.  
        155.  
        156.  
        157. return 0;
        158.  
        159. 8048412: b8 00 00 00 00 mov $0x0,%eax
        160.  
        161. }
        162.  
        163. 8048417: c9 leave
        164.  
        165. 8048418: c3 ret
        166.  
        167. 8048419: 90 nop
        168.  
        169. 804841a: 90 nop
        170.  
        171. ...
        首先,C語言的入口都是從main函數開始的,但是從反匯編代碼中可以發現并不是只有自己設計的代碼,還存在很多關于初始化等操作。這主要是因為C語言的運行需要一些基本的環境和C-RunTime的一些基本函數。因此main 函數只是我們C語言的入口,但并不是一個程序的開始。因此main函數也需要堆棧的控制,也需要壓棧出棧等操作。
        需要注意的是:
        指令call用來調用一個函數或過程,這時下一條指令地址被壓入堆棧中,以備返回時能恢復執行下條指令。sp=sp-1。通過下面的匯編代碼就可知道函數的返回地址。
        80483e2: e8 ad ff ff ff call 8048394 <pluss_a_and_b>
        }
        80483e7: c9 leave
        可以知道指令call后的返回地址就是80483e7。而8048394則說明被調用函數的起始地址,這些數字可能在不同的系統中存在差別。
        RET指令用來從一個函數或過程返回,之前CALL保存的下條指令地址會從棧內彈出到EIP寄存器中,程序轉到CALL之前下條指令處執行。
         
        下面簡單的介紹幾個代碼:
        80483e9: 55 push %ebp
        80483ea: 89 e5 mov %esp,%ebp
        80483ec: 83 ec 18 sub $0x18,%esp
        首先push %ebp,是將調用函數的棧幀基地址壓入棧中,也就是保存調用函數的棧幀EBP。將其指向的地址壓入堆棧中。mov %esp,%ebp則是將ESP和EBP指向同一個地址,作為被調用函數的棧幀基地址。sub $0x18,%esp則是修改ESP的值,與EBP構成當前被調用函數的棧幀空間。
         
         
        從圖中可以每個函數的?臻g都是相互獨立的,但是每一個?臻g的基本結構都是相同的。都是該函數的EBP指針,然后是局部變量空間,然后是往下一個函數的傳遞參數空間,返回的EBP地址。這樣就能實現不同函數的調用,然后傳遞參數是采用基于EBP指針的相對位置實現的,并沒有絕對地址。
         
        由此可以知道?臻g的分布是根據調用情況分析的,當調用過多時就會導致溢出錯誤,因此并不是一味的迭代和遞歸。
         
        關于函數調用的返回都是采用EAX寄存器實現的,但是當返回的是結構體以及聯合體時返回就不能采用EAX實現了,基本的實現方法也是基于堆棧的。
        1. #include<stdio.h>

        2.  
        3. typedef struct {
           
        4.         double d;
           
        5.         float f;
           
        6.         int i;
           
        7.         char c;
           
        8. }return_value;
           

        9.  

        10.  
        11. return_value my_test_of_return()
           
        12. {
           
        13.         return_value rv;
           
        14.     
           
        15.         rv.d = 12.56;
           
        16.         rv.f = 3.1;
           
        17.         rv.i = 10;
           
        18.         rv.c = 'a';
           

        19.  
        20.         return rv;
           
        21. }
           

        22.  
        23. int main()
           
        24. {
           
        25.         return_value local = my_test_of_return();

        26.  
        27.         return 0;
           
        28. }
        編譯以及反匯編以后得到如下的結果:
        [gong@Gong-Computer deeplearn]$ gcc -g structpass.c -o structpass
        [gong@Gong-Computer deeplearn]$ objdump -S -d structpass > structpass_s
         
        1. ...
        2.  
        3. 08048394 <my_test_of_return>:
        4.  
        5. char c;
        6.  
        7. }return_value;
        8.  
        9.  
        10.   return_value my_test_of_return()
        11.  
        12. {
        13.  
        14. 8048394: 55 push %ebp
        15.  
        16. 8048395: 89 e5 mov %esp,%ebp
        17.  
        18. 8048397: 83 ec 20 sub $0x20,%esp
        19.  
        20. 804839a: 8b 45 08 mov 0x8(%ebp),%eax
        21.  
        22. return_value rv;
        23.  
        24.  
        25.  
        26. rv.d = 12.56;
        27.  
        28. 804839d: dd 05 d8 84 04 08 fldl 0x80484d8
        29.  
        30. 80483a3: dd 5d e8 fstpl -0x18(%ebp)
        31.  
        32. rv.f = 3.1;
        33.  
        34. 80483a6: ba 66 66 46 40 mov $0x40466666,%edx
        35.  
        36. 80483ab: 89 55 f0 mov %edx,-0x10(%ebp)
        37.  
        38. rv.i = 10;
        39.  
        40. 80483ae: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%ebp)
        41.  
        42. rv.c = 'a';
        43.  
        44. 80483b5: c6 45 f8 61 movb $0x61,-0x8(%ebp)
        45.  
        46.  
        47.  
        48. return rv;
        49.  
        50. 80483b9: 8b 55 e8 mov -0x18(%ebp),%edx
        51.  
        52. 80483bc: 89 10 mov %edx,(%eax)
        53.  
        54. 80483be: 8b 55 ec mov -0x14(%ebp),%edx
        55.  
        56. 80483c1: 89 50 04 mov %edx,0x4(%eax)
        57.  
        58. 80483c4: 8b 55 f0 mov -0x10(%ebp),%edx
        59.  
        60. 80483c7: 89 50 08 mov %edx,0x8(%eax)
        61.  
        62. 80483ca: 8b 55 f4 mov -0xc(%ebp),%edx
        63.  
        64. 80483cd: 89 50 0c mov %edx,0xc(%eax)
        65.  
        66. 80483d0: 8b 55 f8 mov -0x8(%ebp),%edx
        67.  
        68. 80483d3: 89 50 10 mov %edx,0x10(%eax)
        69.  
        70. }
        71.  
        72. 80483d6: c9 leave
        73.  
        74. 80483d7: c2 04 00 ret $0x4
        75.  
        76.  
        77.  
        78. 080483da <main>:
        79.  
        80.  
        81.  
        82. int main()
        83.  
        84. {
        85.  
        86. 80483da: 8d 4c 24 04 lea 0x4(%esp),%ecx
        87.  
        88. 80483de: 83 e4 f8 and $0xfffffff8,%esp
        89.  
        90. 80483e1: ff 71 fc pushl -0x4(%ecx)
        91.  
        92. 80483e4: 55 push %ebp
        93.  
        94. 80483e5: 89 e5 mov %esp,%ebp
        95.  
        96. 80483e7: 51 push %ecx
        97.  
        98. 80483e8: 83 ec 2c sub $0x2c,%esp
        99.  
        100. return_value local = my_test_of_return();
        101.  
        102. 80483eb: 8d 45 e0 lea -0x20(%ebp),%eax
        103.  
        104. 80483ee: 89 04 24 mov %eax,(%esp)
        105.  
        106. 80483f1: e8 9e ff ff ff call 8048394 <my_test_of_return>
        107.  
        108. 80483f6: 83 ec 04 sub $0x4,%esp
        109.  
        110.  
        111.  
        112. return 0;
        113.  
        114. 80483f9: b8 00 00 00 00 mov $0x0,%eax
        115.  
        116. }
        117.  
        118. 80483fe: 8b 4d fc mov -0x4(%ebp),%ecx
        119.  
        120. 8048401: c9 leave
        121.  
        122. 8048402: 8d 61 fc lea -0x4(%ecx),%esp
        123.  
        124. ...
        從上面的結果可以知道可以知道,返回的過程并不是一次通過EAX返回的,而是通過堆棧一個一個的傳遞出來,實現結果的返回。因此這也是我們需要注意的地方。
         
        同樣對于結構體的傳遞方式也是采用堆棧的方式進行傳遞,基本的參看下面的分析。參數也是依據堆棧中的位置進行控制的。
        代碼:
        1. #include<stdio.h>

        2.  
        3. typedef struct {
           
        4.         double d;
           
        5.         float f;
           
        6.         int i;
           
        7.         char c;
           
        8. }return_value;
           

        9.  
        10. return_value my_test_pass(return_value pass)
           
        11. {
           
        12.         return_value rv;
           
        13.         rv.d = pass.d;
           
        14.         rv.f = pass.f;
           
        15.         rv.i = pass.i;
           
        16.         rv.c = pass.c;
           

        17.  
        18.         return rv;
           
        19. }
           
        20. return_value my_test_of_return()
           
        21. {
           
        22.         return_value rv;
           
        23.     
           
        24.         rv.d = 12.56;
           
        25.         rv.f = 3.1;
           
        26.         rv.i = 10;
           
        27.         rv.c = 'a';
           

        28.  
        29.         return rv;
           
        30. }
           

        31.  
        32. int main()
           
        33. {
           
        34.         return_value local = my_test_of_return();
           
        35.         return_value local1 = my_test_pass(local);

        36.  
        37.         return 0;
           
        38. }
        編譯和反匯編過程:
        [gong@Gong-Computer deeplearn]$ gcc -g structpass.c -o structpass
        [gong@Gong-Computer deeplearn]$ objdump -S -d structpass > structpass_s
         
        1. ...
        2. int main()
        3.  
        4. {
        5.  
        6. 804841d: 8d 4c 24 04 lea 0x4(%esp),%ecx
        7.  
        8. 8048421: 83 e4 f8 and $0xfffffff8,%esp
        9.  
        10. 8048424: ff 71 fc pushl -0x4(%ecx)
        11.  
        12. 8048427: 55 push %ebp
        13.  
        14. 8048428: 89 e5 mov %esp,%ebp
        15.  
        16. 804842a: 51 push %ecx
        17.  
        18. 804842b: 83 ec 4c sub $0x4c,%esp
        19.  
        20. return_value local = my_test_of_return();
        21.  
        22. 804842e: 8d 45 e0 lea -0x20(%ebp),%eax
        23.  
        24. 8048431: 89 04 24 mov %eax,(%esp)
        25.  
        26. 8048434: e8 9e ff ff ff call 80483d7 <my_test_of_return>
        27.  
        28. 8048439: 83 ec 04 sub $0x4,%esp
        29.  
        30.  
        31.  
        32. return_value local1 = my_test_pass(local);
        33.  
        34. 804843c: 8d 45 c8 lea -0x38(%ebp),%eax
        35.  
        36. 804843f: 8b 55 e0 mov -0x20(%ebp),%edx
        37.  
        38. 8048442: 89 54 24 04 mov %edx,0x4(%esp)
        39.  
        40. 8048446: 8b 55 e4 mov -0x1c(%ebp),%edx
        41.  
        42. 8048449: 89 54 24 08 mov %edx,0x8(%esp)
        43.  
        44. 804844d: 8b 55 e8 mov -0x18(%ebp),%edx
        45.  
        46. 8048450: 89 54 24 0c mov %edx,0xc(%esp)
        47.  
        48. 8048454: 8b 55 ec mov -0x14(%ebp),%edx
        49.  
        50. 8048457: 89 54 24 10 mov %edx,0x10(%esp)
        51.  
        52. 804845b: 8b 55 f0 mov -0x10(%ebp),%edx
        53.  
        54. 804845e: 89 54 24 14 mov %edx,0x14(%esp)
        55.  
        56. 8048462: 89 04 24 mov %eax,(%esp)
        57.  
        58. 8048465: e8 2a ff ff ff call 8048394 <my_test_pass>
        59.  
        60. 804846a: 83 ec 04 sub $0x4,%esp
        61.  
        62.  
        63.  
        64. return 0;
        65.  
        66. 804846d: b8 00 00 00 00 mov $0x0,%eax
        67.  
        68. }
        ...
        由上面的反匯編代碼可以知道結構體的傳遞參數是依據堆棧實現的。這也說明了多參數的傳遞過程并不是按著固定的模式實現的,這也是我們需要注意的問題。參數的傳遞需要根據實際情況分析。
         
        總結:
        函數的調用是有一定的方式的,各個函數都有一定的堆?臻g,而且每一個堆?臻g的分布情況也是類似的,但是大小要根據實際的情況分析。一般一個函數的堆?臻g中包含下面幾個部分:1、棧幀(用來表示該堆?臻g的棧底,也就是指開始的地址EBP),局部變量的空間,下一個被調用函數的參數傳遞,最后是返回地址(實質上也是一個EBP)。就是依據EBP和相對位置就能知道每一個函數的基本分布,而ESP就能知道堆?臻g的大小。
         
        被調用參數的獲取主要是依據EBP指針的相對位置獲得,因為被調用函數的堆?臻g上一個堆?臻g就是調用函數的堆?臻g。根據函數的棧幀指針(EBP)和相對位置(-4,-8等)找到對應的參數,但是相對位置也是不固定的,這需要考慮結構體的對齊等方式,具體的要在實際中計算。
         
        返回值一般都是采用EAX返回的,但是對于結構體等則是采用堆棧的方式一個元算一個元素的返回的,但是還是運用了EAX的特性。
         
        函數調用的分布打開如下:

        從上面的分析我們可以發現匯編代碼是非常有用的,建議多參看匯編代碼分析具體的問題。
        關閉窗口

        相關文章

        欧美性色欧美精品视频,99热这里只有精品mp4,日韩高清亚洲日韩精品一区二区,2020国自产拍精品高潮