微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > 嵌入式设计 > C语言函数调用分析

C语言函数调用分析

时间:12-01 来源:互联网 点击:
我的测试环境:Fedora14

Gcc版本:gcc-4.5.1

内核版本:2.6.38.1

C语言是一个强大的语言,特别是对于嵌入式开发过程中有时需要反汇编分析代码中存在的问题,函数是C语言中的难点,关于函数的调用也是很多人不能理解的,很多知道的也是一知半解。对C语言的调用有了一个比较清晰的认识就能够更清晰的分析代码中存在的问题。我也是看了很多的资料,然后自己写了一一段小代码作为分析的测试代码。首先记住在X86体系里很多的寄存器都有特殊的用途,其中ESP表示当前函数堆栈的栈顶指针,而EBP则表示当前函数堆栈的基地址。EBP是栈基址的指针,永远指向栈底(高地址),ESP是栈指针,永远指向栈顶(低地址)。
我的代码如下:

  1. #include

  2. intpluss_a_and_b(inta,intb)
  3. {
  4. intc=-2;
  5. return(a+b-c);
  6. }
  7. intcall_plus(int*a,int*b)
  8. {
  9. intc=*a;
  10. intd=*b;

  11. *a=d;
  12. *b=c;
  13. return pluss_a_and_b(c,d);
  14. }
  15. intmain()
  16. {
  17. intc=10;
  18. intd=20;
  19. intg=call_plus(&c,&d);
  20. return 0;
  21. }

对上面的代码进行编译和反汇编:

[gong@Gong-Computer deeplearn]$ gcc -g testcall.c -o testcall

[gong@Gong-Computer deeplearn]$ objdump -S -d testcall > testcall_s

然后对反汇编的代码进行分析:

  1. ...
  2. 8048393: c3 ret
  3. 08048394 :
  4. #include
  5. int pluss_a_and_b(int a,int b)
  6. {
  7. 8048394: 55 push %ebp
  8. 8048395: 89 e5 mov %esp,%ebp
  9. 8048397: 83 ec 10 sub $0x10,%esp
  10. int c = -2;
  11. 804839a: c7 45 fc fe ff ff ff movl $0xfffffffe,-0x4(%ebp)
  12. return (a + b - c);
  13. 80483a1: 8b 45 0c mov 0xc(%ebp),%eax
  14. 80483a4: 8b 55 08 mov 0x8(%ebp),%edx
  15. 80483a7: 8d 04 02 lea (%edx,%eax,1),%eax
  16. 80483aa: 2b 45 fc sub -0x4(%ebp),%eax
  17. }
  18. 80483ad: c9 leave
  19. 80483ae: c3 ret
  20. 080483af :
  21. intcall_plus(int *a,int *b)
  22. {
  23. 80483af: 55 push %ebp
  24. 80483b0: 89 e5 mov %esp,%ebp
  25. 80483b2: 83 ec 18 sub $0x18,%esp
  26. int c = *a;
  27. 80483b5: 8b 45 08 mov 0x8(%ebp),%eax
  28. 80483b8: 8b 00 mov (%eax),%eax
  29. 80483ba: 89 45 fc mov %eax,-0x4(%ebp)
  30. int d = *b;
  31. 80483bd: 8b 45 0c mov 0xc(%ebp),%eax
  32. 80483c0: 8b 00 mov (%eax),%eax
  33. 80483c2: 89 45 f8 mov %eax,-0x8(%ebp)
  34. *a = d;
  35. 80483c5: 8b 45 08 mov 0x8(%ebp),%eax
  36. 80483c8: 8b 55 f8 mov -0x8(%ebp),%edx
  37. 80483cb: 89 10 mov %edx,(%eax)
  38. *b = c;
  39. 80483cd: 8b 45 0c mov 0xc(%ebp),%eax
  40. 80483d0: 8b 55 fc mov -0x4(%ebp),%edx
  41. 80483d3: 89 10 mov %edx,(%eax)
  42. return pluss_a_and_b(c,d);
  43. 80483d5: 8b 45 f8 mov -0x8(%ebp),%eax
  44. 80483d8: 89 44 24 04 mov %eax,0x4(%esp)
  45. 80483dc: 8b 45 fc mov -0x4(%ebp),%eax
  46. 80483df: 89 04 24 mov %eax,(%esp)
  47. 80483e2: e8 ad ff ff ff call 8048394
  48. }
  49. 80483e7: c9 leave
  50. 80483e8: c3 ret
  51. 080483e9
    :
  52. int main()
  53. {
  54. 80483e9: 55 push %ebp
  55. 80483ea: 89 e5 mov %esp,%ebp
  56. 80483ec: 83 ec 18 sub $0x18,%esp
  57. int c = 10;
  58. 80483ef: c7 45 f8 0a 00 00 00 movl $0xa,-0x8(%ebp)
  59. int d = 20;
  60. 80483f6: c7 45 f4 14 00 00 00 movl $0x14,-0xc(%ebp)
  61. int g =call_plus(&c,&d);
  62. 80483fd: 8d 45 f4 lea -0xc(%ebp),%eax
  63. 8048400: 89 44 24 04 mov %eax,0x4(%esp)
  64. 8048404: 8d 45 f8 lea -0x8(%ebp),%eax
  65. 8048407: 89 04 24 mov %eax,(%esp)
  66. 804840a: e8 a0 ff ff ff call 80483af
  67. 804840f: 89 45 fc mov %eax,-0x4(%ebp)
  68. return 0;
  69. 8048412: b8 00 00 00 00 mov $0x0,%eax
  70. }
  71. 8048417: c9 leave
  72. 8048418: c3 ret
  73. 8048419: 90 nop
  74. 804841a: 90nop
  75. ...

首先,C语言的入口都是从main函数开始的,但是从反汇编代码中可以发现并不是只有自己设计的代码,还存在很多关于初始化等操作。这主要是因为C语言的运行需要一些基本的环境和C-RunTime的一些基本函数。因此main 函数只是我们C语言的入口,但并不是一个程序的开始。因此main函数也需要堆栈的控制,也需要压栈出栈等操作。

需要注意的是:

指令call用来调用一个函数或过程,这时下一条指令地址被压入堆栈中,以备返回时能恢复执行下条指令。sp=sp-1。通过下面的汇编代码就可知道函数的返回地址。

80483e2: e8 ad ff ff ff call 8048394
}
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

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top