C语言函数调用分析
Gcc版本:gcc-4.5.1
内核版本:2.6.38.1
C语言是一个强大的语言,特别是对于嵌入式开发过程中有时需要反汇编分析代码中存在的问题,函数是C语言中的难点,关于函数的调用也是很多人不能理解的,很多知道的也是一知半解。对C语言的调用有了一个比较清晰的认识就能够更清晰的分析代码中存在的问题。我也是看了很多的资料,然后自己写了一一段小代码作为分析的测试代码。首先记住在X86体系里很多的寄存器都有特殊的用途,其中ESP表示当前函数堆栈的栈顶指针,而EBP则表示当前函数堆栈的基地址。EBP是栈基址的指针,永远指向栈底(高地址),ESP是栈指针,永远指向栈顶(低地址)。
我的代码如下:
- #include
- intpluss_a_and_b(inta,intb)
- {
- intc=-2;
- return(a+b-c);
- }
- intcall_plus(int*a,int*b)
- {
- intc=*a;
- intd=*b;
- *a=d;
- *b=c;
- return pluss_a_and_b(c,d);
- }
- intmain()
- {
- intc=10;
- intd=20;
- intg=call_plus(&c,&d);
- return 0;
- }
对上面的代码进行编译和反汇编:
[gong@Gong-Computer deeplearn]$ gcc -g testcall.c -o testcall
[gong@Gong-Computer deeplearn]$ objdump -S -d testcall > testcall_s
然后对反汇编的代码进行分析:
- ...
- 8048393: c3 ret
- 08048394
: - #include
- int pluss_a_and_b(int a,int b)
- {
- 8048394: 55 push %ebp
- 8048395: 89 e5 mov %esp,%ebp
- 8048397: 83 ec 10 sub $0x10,%esp
- int c = -2;
- 804839a: c7 45 fc fe ff ff ff movl $0xfffffffe,-0x4(%ebp)
- return (a + b - c);
- 80483a1: 8b 45 0c mov 0xc(%ebp),%eax
- 80483a4: 8b 55 08 mov 0x8(%ebp),%edx
- 80483a7: 8d 04 02 lea (%edx,%eax,1),%eax
- 80483aa: 2b 45 fc sub -0x4(%ebp),%eax
- }
- 80483ad: c9 leave
- 80483ae: c3 ret
- 080483af
: - intcall_plus(int *a,int *b)
- {
- 80483af: 55 push %ebp
- 80483b0: 89 e5 mov %esp,%ebp
- 80483b2: 83 ec 18 sub $0x18,%esp
- int c = *a;
- 80483b5: 8b 45 08 mov 0x8(%ebp),%eax
- 80483b8: 8b 00 mov (%eax),%eax
- 80483ba: 89 45 fc mov %eax,-0x4(%ebp)
- int d = *b;
- 80483bd: 8b 45 0c mov 0xc(%ebp),%eax
- 80483c0: 8b 00 mov (%eax),%eax
- 80483c2: 89 45 f8 mov %eax,-0x8(%ebp)
- *a = d;
- 80483c5: 8b 45 08 mov 0x8(%ebp),%eax
- 80483c8: 8b 55 f8 mov -0x8(%ebp),%edx
- 80483cb: 89 10 mov %edx,(%eax)
- *b = c;
- 80483cd: 8b 45 0c mov 0xc(%ebp),%eax
- 80483d0: 8b 55 fc mov -0x4(%ebp),%edx
- 80483d3: 89 10 mov %edx,(%eax)
- return pluss_a_and_b(c,d);
- 80483d5: 8b 45 f8 mov -0x8(%ebp),%eax
- 80483d8: 89 44 24 04 mov %eax,0x4(%esp)
- 80483dc: 8b 45 fc mov -0x4(%ebp),%eax
- 80483df: 89 04 24 mov %eax,(%esp)
- 80483e2: e8 ad ff ff ff call 8048394
- }
- 80483e7: c9 leave
- 80483e8: c3 ret
- 080483e9
: - int main()
- {
- 80483e9: 55 push %ebp
- 80483ea: 89 e5 mov %esp,%ebp
- 80483ec: 83 ec 18 sub $0x18,%esp
- int c = 10;
- 80483ef: c7 45 f8 0a 00 00 00 movl $0xa,-0x8(%ebp)
- int d = 20;
- 80483f6: c7 45 f4 14 00 00 00 movl $0x14,-0xc(%ebp)
- int g =call_plus(&c,&d);
- 80483fd: 8d 45 f4 lea -0xc(%ebp),%eax
- 8048400: 89 44 24 04 mov %eax,0x4(%esp)
- 8048404: 8d 45 f8 lea -0x8(%ebp),%eax
- 8048407: 89 04 24 mov %eax,(%esp)
- 804840a: e8 a0 ff ff ff call 80483af
- 804840f: 89 45 fc mov %eax,-0x4(%ebp)
- return 0;
- 8048412: b8 00 00 00 00 mov $0x0,%eax
- }
- 8048417: c9 leave
- 8048418: c3 ret
- 8048419: 90 nop
- 804841a: 90nop
- ...
首先,C语言的入口都是从main函数开始的,但是从反汇编代码中可以发现并不是只有自己设计的代码,还存在很多关于初始化等操作。这主要是因为C语言的运行需要一些基本的环境和C-RunTime的一些基本函数。因此main 函数只是我们C语言的入口,但并不是一个程序的开始。因此main函数也需要堆栈的控制,也需要压栈出栈等操作。
需要注意的是:
指令call用来调用一个函数或过程,这时下一条指令地址被压入堆栈中,以备返回时能恢复执行下条指令。sp=sp-1。通过下面的汇编代码就可知道函数的返回地址。
80483e2: e8 ad ff ff ff call 8048394 可以知道指令call后的返回地址就是80483e7。而8048394则说明被调用函数的起始地址,这些数字可能在不同的系统中存在差别。 RET指令用来从一个函数或过程返回,之前CALL保存的下条指令地址会从栈内弹出到EIP寄存器中,程序转到CALL之前下条指令处执行。 下面简单的介绍几个代码: 80483e9: 55 push %ebp
}
80483e7: c9 leave
80483ea: 89 e5 mov %esp,%ebp
80483ec: 83 ec 18 sub $0x18,%esp
C语言函数调 相关文章:
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)