怎么看gcc源代码分析的函数实现

3134人阅读
GCC就像一个巨大的宝藏,只要你愿意花时间,总能淘到好东西。
在看一些大中型的软件的源代码时,你是不是非常希望有一个工具能够方便的生成各个函数之间的调用关系图呢?
为了实现这个目标,你可以通过对源代码进行静态扫描得到函数的调用关系,但是你无法通过这种方法获得更多的信息,
(如:对某个函数的调用次数,被调用的函数执行了多长时间等,这些信息对于软件的优化具有很好的参考价值)除了
静态扫描之外,还存在一些动态的方法,即在程序的运行过程中记录相关的信息,不过这些动态的方法通常都需要有编
译器的支持,通过编译器在编译的过程中插入相应的代码。下面简单的介绍一下,GCC的function instrumentation
简单的来说,gcc function instrumentation就是在每个函数调用之前调用一个名为__cyg_profile_func_enter的函数,
在函数调用介绍的时候调用__cyg_profile_func_exit。这两个函数在gcc看来只是两个普通的函数,是可以由用户自己进行
定制的。下面先看个例子:
#include &stdio.h&
#define DUMP(func, call) /
printf("%s: func = %p, called by = %p/n", __FUNCTION__, func, call)
void __attribute__((__no_instrument_function__))
__cyg_profile_func_enter(void *this_func, void *call_site)
DUMP(this_func, call_site);
void __attribute__((__no_instrument_function__))
__cyg_profile_func_exit(void *this_func, void *call_site)
DUMP(this_func, call_site);
puts("Hello World!");
编译与运行:
$ gcc -finstrument-functions hello.c -o hello
__cyg_profile_func_enter: func = 0x8048468, called by = 0xb7e36ebc
Hello World!
__cyg_profile_func_exit: func = 0x8048468, called by = 0xb7e36ebc
呵呵,是不是很神奇,只需要在软件中简单加入__cyg_profile_func_enter &__cyg_profile_func_exit这两个函数的定义,
然后再编译的时候加上-finstrument-functions选项,就可以在每个函数调用前后调用这两个函数获取你所感兴趣的信息。为了
对其具体的实现细节有所了解,可以通过&objdump -d hello&来看下程序反汇编之后的结果。
d 4c 24 04
0x4(%esp),%ecx
804848c: 83 e4 f0
$0xfffffff0,%esp
804848f: ff 71 fc
0xfffffffc(%ecx)
$0x10,%esp
804849a: 8b 45 04
0x4(%ebp),%eax
804849d: 89 44 24 04
%eax,0x4(%esp)
80484a1: c7 04 24 88 84 04 08
$0x8048488,(%esp)
80484a8: e8 87 ff ff ff
8048434 &__cyg_profile_func_enter&
80484ad: c7 04 24 03 86 04 08
$0x8048603,(%esp)
80484b4: e8 8f fe ff ff
8048348 &puts@plt&
80484b9: bb 00 00 00 00
80484be: 8b 45 04
0x4(%ebp),%eax
%eax,0x4(%esp)
80484c5: c7 04 24 88 84 04 08
$0x8048488,(%esp)
80484cc: e8 8d ff ff ff
804845e &__cyg_profile_func_exit&
$0x10,%esp
0xfffffffc(%ecx),%esp
80484dc: c3
从上面的汇编代码可以看出,gcc是在进入函数完成堆栈的初始化之后调用__cyg_profile_func_enter的,在函数返回清理堆栈
之前调用__cyg_profile_func_exit的。
经过上面的步骤,我们还只能获取到函数调用时的信息,还没有获取到函数之间的调用关系,但是明白了上述的原理之后,参看参看
参考资料[1]就能够很容易的实现目标了。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:72063次
排名:千里之外
原创:19篇
(2)(1)(1)(1)(2)(7)(2)(4)(1)开发工具(49)
对于涉及跨平台开发的项目,就可能会遇到数据大小端的问题,其实就是一个数字在内存中的字节序的问题,判断当前系统是大小端有现成的例子,自己实现转换代码也非常方便,网上有好多不用多说。
但我是个懒人,就算是这么简单的代码,有现成的就不想自己写。
今天要说的是gcc本身已经提供了大小端的判断和数据转换的函数,真的没必要自己写。
gcc预定义宏判断大小端(Endian)
先看看这个文章:
这个文章告诉你如何获取gcc默认的宏定义,在这些宏定义中就有大小端模式的预定义宏__BYTE_ORDER__
当__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__时是小端模式
当__BYTE_ORDER__==__ORDER_BIG_ENDIAN__时是小端模式
__BYTE_ORDER__,__ORDER_LITTLE_ENDIAN__,__ORDER_BIG_ENDIAN__都是gcc预定义的宏,在代码中可以直接使用。
gcc内置函数进行大小端转换
gcc提供了不少有用的内置函数(Built-in Functions),这些函数说明可以在gcc的网站上找到
这个页面最后面三个函数就是我们需要的:
— Built-in Function: uint16_t __builtin_bswap16 (uint16_t x)
Returns x with the order o for example, 0xaabb becomes 0xbbaa. Byte here always means exactly 8 bits.//返回x的反序字节,例如:0xaabb变成0xbbaa,下面类同。
— Built-in Function: uint32_t __builtin_bswap32 (uint32_t x)
Similar to __builtin_bswap16, except the argument and return types are 32 bit.
— Built-in Function: uint64_t __builtin_bswap64 (uint64_t x)
Similar to __builtin_bswap32, except the argument and return types are 64 bit.
_bswap16,_bswap32,_bswap64三个函数分别提供了16位,32位,64位数字的字节反转功能,正好可以用来实现16,32,64位数字的大小端转换。
如下写代码中用到了上面提到的函数,对32位的float和64位的double在大端模式下进行转换
#if __BYTE_ORDER__ ==__ORDER_BIG_ENDIAN__
inline face_code* _code_reverse(face_code* code) {
for(int i=0;i&CODE_FLOAT_NUM;i++)
code-&element[i]=(float)__builtin_bswap32((unsigned __int32)code-&element[i]);
code-&sum=(double)__builtin_bswap64((unsigned __int64)code-&sum);
#define FACE_CODE_CONVERT(code) _code_reverse(code)
#define FACE_CODE_CONVERT(code) code
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:56922次
积分:2382
积分:2382
排名:第12041名
原创:140篇
评论:43条
(2)(5)(11)(17)(12)(11)(5)(10)(14)(5)(20)(12)(9)(8)(7)(1)Linux(19)
学习Linux的,尤其是学习嵌入式Linux的,对GCC比较熟悉。当然我记录的都是自己的一些笔记,所以都是很基础的东西。
那我们来说说GCC的一些事情吧。
首先我们知道GCC就是一个编译器,我们写一个程序出来,这个程序我们称之为源代码。但是这个源代码,计算机是无法识别的。这就需要gcc编译成计算机可以执行的可执行文件。
对于这样一个的一个过程需要进行着四个关联的步骤:
机器只能执行二进制的代码,在形成二进制代码之前的那个过程,我们就称之为预处理。典型地,由预处理器(preprocessor) 对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。
这个过程中,对源代码文件中的文件包含(include),预编译语句(如宏定义define等)进行分析
编译:这个过程其实就是生成以.o为后缀的目标文件的过程。
汇编:汇编过程是针对的汇编语言的步骤,调用as进行工作,也是生成以.o为后缀的目标文件
链接:上述的两步都是形成了.o文件,这个部分就是很重要的链接阶段,就是将所有的目标文件安排在可执行程序中恰当的位置。同时该程序所调用到的库函数也从各自所在的库中连接到合适的地方来。
分割线------------------------------------------------------------------------GCC内部实现的机制-------------------------------------------------------------------------------------------------------
这个地方我们就来说说常用的GCC选项有哪些?
首先我们按照上述的四个步骤依次来实现一下:
上面简单的说了从源文件到可执行文件生成的整个过程。
还有哪些是我们常用的呢?
1)-I&gcc在链接库时,标准头文件路径在/usr/include下,对于自定义文件的使用,可以放进此目录下,也可以使用-I 指定其目录。
2)-Ldir 制定编译时,搜索库的路径
3)-static 静态链接
区分下静态链接与动态链接的区别:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:18431次
排名:千里之外
原创:80篇
(4)(21)(31)(8)(8)(7)(1)<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
您的访问请求被拒绝 403 Forbidden - ITeye技术社区
您的访问请求被拒绝
亲爱的会员,您的IP地址所在网段被ITeye拒绝服务,这可能是以下两种情况导致:
一、您所在的网段内有网络爬虫大量抓取ITeye网页,为保证其他人流畅的访问ITeye,该网段被ITeye拒绝
二、您通过某个代理服务器访问ITeye网站,该代理服务器被网络爬虫利用,大量抓取ITeye网页
请您点击按钮解除封锁&深入理解C语言的函数调用过程
查看: 247|
评论: 0|原作者: 伯乐在线|来自: 伯乐在线
摘要: 本文主要从进程栈空间的层面复习一下C语言中函数调用的具体过程,以加深对一些基础知识的理解。
本文主要从进程栈空间的层面复习一下C语言中函数调用的具体过程,以加深对一些基础知识的理解。先看一个最简单的程序:主函数main里定义了4个局部变量,然后调用同文件里的foo1()函数。4个局部变量毫无疑问都在进程的栈空间上,当进程运行起来后我们逐步了解一下main函数里是如何基于栈实现了对foo1()的调用过程,而foo1()又是怎么返回到main函数里的。为了便于观察的粒度更细致一些,我们对test.c生成的汇编代码进行调试。如下:&&&&&1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
.file "test.c"
&&&&&&&&.text
.globl foo1
&&&&&&&&.type foo1
&&&&&&&&pushl %ebp
&&&&&&&&movl %esp, %ebp
&&&&&&&&subl $16, %esp
&&&&&&&&movl 12(%ebp), %eax
&&&&&&&&movl 8(%ebp), %edx
&&&&&&&&leal (%edx,%eax), %eax
&&&&&&&&addl 16(%ebp), %eax
&&&&&&&&movl %eax, -4(%ebp)
&&&&&&&&movl -4(%ebp), %eax
&&&&&&&&leave
&&&&&&&&ret
&&&&&&&&.size foo1, .-foo1
&&&&&&&&.section .rodata
&&&&&&&&.string "result=%d\n"
&&&&&&&&.text
.globl main
&&&&&&&&.type main
&&&&&&&&pushl %ebp
&&&&&&&&movl %esp, %ebp
&&&&&&&&andl $-16, %esp
&&&&&&&&subl $32, %esp
&&&&&&&&movl $11, 16(%esp)
&&&&&&&&movl $22, 20(%esp)
&&&&&&&&movl $33, 24(%esp)
&&&&&&&&movl 24(%esp), %eax
&&&&&&&&movl %eax, 8(%esp)
&&&&&&&&movl 20(%esp), %eax
&&&&&&&&movl %eax, 4(%esp)
&&&&&&&&movl 16(%esp), %eax
&&&&&&&&movl %eax, (%esp)
&&&&&&&&call foo1
&&&&&&&&movl %eax, 28(%esp)
&&&&&&&&movl $.LC0, %eax
&&&&&&&&movl 28(%esp), %edx
&&&&&&&&movl %edx, 4(%esp)
&&&&&&&&movl %eax, (%esp)
&&&&&&&&call printf
&&&&&&&&movl $0, %eax
&&&&&&&&leave
&&&&&&&&ret
&&&&&&&&.size main, .-main
&&&&&&&&.ident "GCC: (GNU) 4.4.4
(Red Hat 4.4.4-13)"
&&&&&&&&.section .note.GNU-stack,"",@progbits
上面的汇编源代码和最终生成的可执行程序主体结构上已经非常类似了:&&&&&&12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
[root&&1]# gcc -g -o test test.s
[root&&1]# objdump -D test & testbin
[root&&1]# vi testbin
//… 省略部分不相关代码
80483c0:&&&&&& ff d0&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& call&& *%eax
80483c2:&&&&&& c9&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& leave
80483c3:&&&&&& c3&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ret
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
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
//… 省略部分不相关代码
用GDB调试可执行程序test:在main函数第一条指令执行前我们看一下进程test的栈空间布局。因为我们最终的可执行程序是通过glibc库启动的,在main的第一条指令运行前,其实还有很多故事的,这里就不展开了,以后有时间再细究,这里只要记住一点:main函数执行前,其进程空间的栈里已经有了相当多的数据。我的系统里此时栈顶指针esp的值是0xbffff63c,栈基址指针ebp的值0xbffff6b8,指令寄存器eip的值是0x80483de正好是下一条马上即将执行的指令,即main函数内的第一条指令“push %ebp”。那么此时,test进程的栈空间布局大致如下:然后执行如下三条指令:&&&&&123
pushl %ebp&&&&&&&& //将原来ebp的值0xbffff6b8如栈,esp自动增长4字节
movl %esp, %ebp&&&&//用ebp保存当前时刻esp的值
andl $-16, %esp&&&&//内存地址对其,可以忽略不计
行完上述三条指令后栈里的数据如上图所示,从0xbffff630到0xbffff638的8字节是为了实现地址对齐的填充数据。此时ebp的值0xbffff638,该地址处存放的是ebp原来的值0xbffff6b8。详细布局如下:第28条指令“subl $32, %esp”是在栈上为函数里的本地局部变量预留空间,这里我们看到main主函数有4个int型的变量,理论上说预留16字节空间就可以了,但这里却预留了32字节。GCC编译器在生成汇编代码时,已经考虑到函数调用时其输入参数在栈上的空间预留的问题,这一点我们后面会看到。当第28条指令执行完后栈空间里的数据和布局如下:然后main函数里的变量x,y,z的值放到栈上,就是接下来的三条指令:&&&&&123
movl $11, 16(%esp)
movl $22, 20(%esp)
movl $33, 24(%esp)
这是三条寄存器间接寻址指令,将立即数11,22,33分别放到esp寄存器所指向的地址0xbffff610向高位分别偏移16、20、24个字节处的内存单元里,最后结果如下:注意:这三条指令并没有改变esp寄存器的值。接下来main函数里就要为了调用foo1函数而做准备了。由于mov指令的两个操作数不能都是内存地址,所以要将x,y和z的值传递给foo1函数,则必须借助通用寄存器来完成,这里我们看到eax承担了这样的任务:&&&&&123456
movl 24(%esp), %eax
movl %eax, 8(%esp)
movl 20(%esp), %eax
movl %eax, 4(%esp)
movl 16(%esp), %eax
movl %eax, (%esp)
当foo1函数所需要的所有输入参数都已经按正确的顺序入栈后,紧接着就需要调用call指令来执行foo1函数的代码了。前面的博文说过,call指令执行时分两步:首先会将call指令的下一条指令(movl %eax, 28(%esp))的地址(0x0804841b)压入栈,然后跳转到函数foo1入口处开始执行。当第38条指令“call foo1”执行完后,栈空间布局如下:call指令自动将下一条要执行的指令的地址0x0804841b压入栈,栈顶指针esp自动向低地址处“增长”4字节。所以,我们以前在C语言里所说的函数返回地址,应该理解为:当被调用函数执行完之后要返回到它的调用函数里下一条马上要执行的代码的地址。为了便于观察,我们把foo1函数最后生成指令再列出来:&&&&&123456789101112131415
.globl foo1
&&&&&&&&&& .type foo1
&&&&&&&&&& pushl %ebp
&&&&&&&&&& movl %esp, %ebp
&&&&&&&&&& subl $16, %esp
&&&&&&&&&& movl 12(%ebp), %eax
&&&&&&&&&&movl 8(%ebp), %edx
&&&&&&&&&&leal (%edx,%eax), %eax
&&&&&&&&&&addl 16(%ebp), %eax
&&&&&&&& movl %eax, -4(%ebp)
&&&&&&&&&&movl -4(%ebp), %eax
&&&&&&&&&&leave
&&&&&&&&&&ret
&&&&&&&&&&.size foo1, .-foo1
进入到foo1函数里,开始执行该函数里的指令。当执行完第6、7、8条指令后,栈里的数据如下。这三条指令就是汇编层面函数的“序幕”,分别是保存ebp到栈,让ebp指向当前栈顶,然后为函数里的局部变量预留空间:接下来第9和第10条指令,也并没有改变栈上的任何数据,而是将函数输入参数列表中的的x和y的值分别转载到eax和edx寄存器,和main函数刚开始时做的事情一样。此时eax=22、edx=11。然后用了一条leaf指令完成x和y的加法运算,并将运算结果存在eax里。第12条指令“addl 16(%ebp), %eax”将第三个输入参数p的值,这里是实参z的值为33,同样用寄存器间接寻址模式累加到eax里。此时eax=11+22+33=66就是我们最终要得计算结果。因为我们foo1()函数的C代码中,最终计算结果是保存到foo1()里的局部变量x里,最后用return语句将x的值通过eax寄存器返回到mian函数里,所以我们看到接下来的第13、14条指令有些“多此一举”。这足以说明gcc人家还是相当严谨的,C源代码的函数里如果有给局部变量赋值的语句,生成汇编代码时确实会在栈上为本地变量预留的空间里的正确位置为其赋值。当然gcc还有不同级别的优化技术来提高程序的执行效率,这个不属于本文所讨论的东西。让我们继续,当第13、14条指令执行完后,栈布局如下:将ebp-4的地址处0xbffff604(其实就是foo1()里的第一个局部参数x的地址)的值设置为66,然后再将该值复制到eax寄存器里,等会儿在main函数里就可以通过eax寄存器来获取最终的计算结果。当第15条指令leave执行完后,栈空间的数据和布局如下:&我们发现,虽然栈顶从0xbffff5f8移动到0xbffff60c了,但栈上的数据依然存在。也就是说,此时你通过esp-8依旧可以访问foo1函数里的局部变量x的值。当然,这也是说得通的,因为函数此时还没有返回。我们看栈布局可以知道当前的栈顶0xbffff60c处存放的是下一条即将执行的指令的地址,对照反汇编结果可以看到这正是main函数里的第18条指令(在整个汇编源文件test.s里的行号是39)“movl %eax, 28(%esp)”leave指令其实完成了两个任务:1、将栈上为函数预留的空间“收回”;2、恢复ebp;也就是说leave指令等价于下面两条指令,你将leave替换成它们编译运行,结果还是对的:&&&&&12
movl %ebp,%esp
前面我们也说过,ret指令会自动到栈上去pop数据,相当于执行了“popl %eip”,会使esp增大4字节。所以当执行完第16条指令ret后,esp从0xbffff60c增长到0xbffff610处,栈空间结构如下:现在已经从foo1里返回了,但是由于还没执行任何push操作,栈顶“上部”的数据依旧还是可以访问到了,即esp-12的值就是foo1里的局部变量x的值、esp-4的值就是函数的返回地址,当执行第39条指令“movl %eax,28(%esp)”后栈布局变成下面的样子:第39条指令就相当于给main里的result变量赋值66,如上红线标注的地方。接下来main函数里要执行printf(“result=%d\n”,result)语句了,而printf又是C库的一个常用的输出函数,这里就又会像前面调用foo1那样,初始化栈,然后用“call printf的地址”来调用C函数。当40~43这4条指令执行完后,栈里的数据如下:&&&&&1234
movl $.LC0, %eax
movl 28(%esp), %edx
movl %edx, 4(%esp)
movl %eax, (%esp)
上图为了方便理解,将栈顶的0x替换了成字符串“result=%d\n”,但进程实际运行时此时栈顶esp的值是字符串所在的内存地址。当第44条指令执行完后,栈布局如下:由于此时栈已经用来调用printf了,所以栈顶0xbffff610“以上”部分的空间里就找不到foo1的任何影子了。最后在main函数里,当第46、47条指令执行完后栈的布局分别是:当main函数里的ret执行完,其实是返回到了C库里继续执行剩下的清理工作。所以,最后关于C的函数调用,我们可以总结一下:1、函数输入参数的入栈顺序是函数原型中形参从右至左的原则;2、汇编语言里调用函数通常情况下都用call指令来完成;3、汇编语言里的函数大部分情况下都符合以下的函数模板:&&&&&12345678
.globl fun_name
.type fun_name
&&&&&&&&pushl %ebp
&&&&&&&&movl %esp, %ebp
&&&&&&&&&函数主体代码&
&&&&&&&&leave
&&&&&&&&ret
如果我们有个函数原型:int funtest(int x,int y int z char* ptr),在汇编层面,当调用它时栈的布局结构一般是下面这个样子:而有些资料上将ebp指向函数返回地址的地方,这是不对的。正常情况下应该是ebp指向old ebp才对,这样函数末尾的leave和ret指令才可以正常工作。
刚表态过的朋友 ()
上一篇:下一篇:
快毕业了,没工作经验,
找份工作好难啊?
赶紧去人才芯片公司磨练吧!!

我要回帖

更多关于 gcc早期源代码 的文章

 

随机推荐