Linux下常用的函数调用栈规范
来源:互联网 发布:6寸windows平板 编辑:程序博客网 时间:2024/05/19 13:43
我们都应该知道,高级语言的函数调用过程中,有“栈”这么一个概念,被调用函数的局部变量是存放在栈中的,函数调用的参数也是通过栈传递的。那么,调用函数是怎么把各种数据压入栈中,被调用函数又是怎么对栈进行操作以获取必要的数据呢?函数调用发生完毕之后,谁又负责清理这个栈?这就用到了函数调用栈规范!
函数调用栈规范是指编译器的一中“约定”,他规定了调用者如何传递参数,被调用者如何获取参数,调用完成后怎么清理栈,怎么传递返回值等。编译器在编译程序的时候遵循这种规范,从而使程序正确的执行。对于不同的编译器,不同的高级语言,这种规范是不尽相同的。
在X86平台下的Linux内核中,常用的函数调用规范有:C,FastCall,Pascal等,下面简单介绍下这几种规范:
1、C规范
规定调用者将参数从右至左压栈,返回值通过EAX寄存器传递,如果返回值超过32位,则使用EDX:EAX传递,最后由调用者负责清理栈。这个规范被大多数C编译器遵守。
我们需要注意的是,由调用者负责清理堆栈,可以支持变参函数,比如我们所熟悉的printf函数。让我们看个例子:
- printf(“%d, %d, %d\n”, i, j, k);
printf("%d, %d, %d\n", i, j, k);
这个语句的目的是调用一个printf函数。被调用的函数在编译的时候并不知道自己将要被传递多少个函数,但是调用者知道。这个语句返汇编后大概的样子如下:
- pushl k </span><span class="comment">//伪汇编,将k入栈</span><span> </span></span></li><li class=""><span>pushl j
- pushl i </span></li><li class=""><span>push addr //addr为第一个参数的语句的地址
- call printf
- addl 0x10, %esp </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre code_snippet_id="533404" snippet_file_name="blog_20141126_2_4735559" name="code" class="cpp" style="display: none;">pushlk //伪汇编,将k入栈pushl
jpushl ipushaddr//addr为第一个参数的语句的地址callprintfaddl 0x10, %esp或者翻译成:
- sub 0x10, %esp </span><span class="comment">//先申请栈空间</span><span> </span></span></li><li class=""><span>mov addr, (%esp)
- mov i, 0x4(%esp) </span></li><li class=""><span>mov j, 0x8(%esp)
- mov k, 0xc(%esp) </span></li><li class=""><span>call printf </span></li><li class="alt"><span>addl 0x10, %esp
sub $0x10, %esp //先申请栈空间mov $addr, (%esp)mov $i, 0x4(%esp)mov $j, 0x8(%esp)mov $k, 0xc(%esp)call printfaddl $0x10, %esp
实际上,第二种翻译和第一种完成的功能基本一致,都是越右边的参数越靠近栈底,因为栈是从高地址往低地址增长的,所以地址也就越高。printf函数被调用后,先读栈顶的参数,也就是”%d, %d, %d\n”,然后根据这个参数确定其他参数的个数,并向高地址寻找即可。
从上面的汇编代码可以看出,
- addl 0x10, %esp </span></span></li></ol><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre code_snippet_id="533404" snippet_file_name="blog_20141126_4_9796279" name="code" class="cpp" style="display: none;">addl0x10, %esp
这条指令是负责清理栈的,他位于调用者种,他知道自己应该清理多大的栈。而在被调用者中,往往通过
- ret n </span></span></li></ol><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre code_snippet_id="533404" snippet_file_name="blog_20141126_5_7571432" name="code" class="cpp" style="display: none;">retn这条指令来清理栈的,因为printf在编译时并不知道自己会被传递多少个参数,因此这个n也就不能确定,所以没办法在被调用者中清理栈。
2、FastCall
顾名思义,fastcall就是快速调用的意思。因为压栈操作必须要访存,对于一些经常被调用的参数简单的小函数来说会有一定的开销,因此可以通过寄存器传参。以gcc为例,gcc使用fastcall的时候,默认从左到右的前两个参数通过ECX和EDX两个寄存器来传递,其他参数使用栈传递,但是可以通过__attribute__((regparm(n)))来控制可使用寄存器的数目,例如regparm(3)就表示前三个参数使用寄存器来传递,默认寄存器是EAX,ECX,EDX。返回值传递和栈清理同C规范相同。
这种规范应该是对C规范的一种补充和优化,在linux内核中经常用到,比如系统调用。
3、Pascal
参数从左到右入栈,被调用者负责清理栈,返回值通过EAX或EDX:EAX传递。
- ret n </span></span></li></ol><div class="save_code tracking-ad" data-mod="popu_249"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre code_snippet_id="533404" snippet_file_name="blog_20141126_5_7571432" name="code" class="cpp" style="display: none;">retn这条指令来清理栈的,因为printf在编译时并不知道自己会被传递多少个参数,因此这个n也就不能确定,所以没办法在被调用者中清理栈。
- Linux下常用的函数调用栈规范
- Linux下常用的函数调用栈规范
- Linux下常用的函数调用栈规范
- PHP调用Linux系统的常用函数
- PHP调用Linux系统的常用函数
- linux下函数调用栈Backtraces函数
- linux下socket调用的函数介绍
- linux 下的系统调用函数
- linux 下常用函数
- Linux下开发文件规范常用处理
- linux下的常用时间函数总结
- linux下常用的一些函数
- php Linux下常用的几个函数
- linux常用系统调用函数
- linux常用系统调用函数
- linux下常用系统调用
- 函数调用规范的一些知识
- linux高级编程常用的系统调用函数整理
- 基于hough变换和卡尔曼滤波的车道线检测算法
- Android Studio当中的编码问题
- 获取CListCtrl选中行行号的多种方法
- Eclipse创建Maven项目
- C语言函数调用栈剖析
- Linux下常用的函数调用栈规范
- 轻松管理你的NodeJs版本
- rosbag的使用
- Android Service
- Yii之数据安全
- MATLAB绘图参数汇总小记
- TCP 的那些事儿(下)
- 【leetcode 4】Median of Two Sorted Arrays
- Linux中使用 alias 来简化命令行输入