北京商城网站建设费用,php网站开发机试题目,十大求职招聘app排行,番禺 网站建设上一篇文章学习了Linux环境下的函数栈帧的形成与摧毁。点击链接查看相关文章#xff1a;软件开发底层知识修炼】二十三 ABI-应用程序二进制接口三之深入理解函数栈帧的形成与摧毁本篇文章继续学习ABI接口相关的内容。函数调用约定 文章目录1 函数参数如何入栈#xff0c;返回… 上一篇文章学习了Linux环境下的函数栈帧的形成与摧毁。点击链接查看相关文章软件开发底层知识修炼】二十三 ABI-应用程序二进制接口三之深入理解函数栈帧的形成与摧毁本篇文章继续学习ABI接口相关的内容。函数调用约定 文章目录1 函数参数如何入栈返回值在哪里2 函数调用约定的编程实验2.1 使用gdb调试代码证明eax存的值是函数返回值2 .2 查看程序的反汇编文件来说明调用约定的不同3 总结 1 函数参数如何入栈返回值在哪里
前面学过的文章中已经深入的了解了函数调用过程中函数的栈帧的形成与摧毁。在发生函数调用时首先入栈的是函数的参数。但是我们知道一般函数的参数都是会比较多。在参数比较多的时候函数的参数是以什么样的顺序入栈的呢函数返回时是谁来将参数弹出栈呢
并且在函数执行完之后返回的时候返回值在哪里通过什么方式将返回值传递给调用者
首先给出在C语言中默认的调用约定cdecl 调用函数时参数从右往左入栈函数返回时调用者负责将参数弹出栈。这里说是调用者实际上是编译器在编译的时候为调用者添加了相应的指令函数返回值保存在eax寄存器中。前提是函数返回值是基础数据类型如果是结构体这种的类型就另说。 上面的调用约定是C语言默认的函数调用约定。我们平常所熟知的也就是上面的默认的调用约定。当然或许大多数人是不知道的吧
下面的表格给出了其他各种调用约定 注意以上三个调用约定只需要注意__thiscall__约定。它一般是C中的成员函数的约定C成员函数又由隐藏的this指针。如果函数的参数是确定个数的则this存放于ECX寄存器函数自身清理栈中的参数。如果成员函数是可变参数那么久相当于前面的__cdecl__调用约定 在上面的函数调用约定中还有几点需要注意
只有使用的__cdecl__约定的函数才支持可变参数。使用其他调用约定的不支持可变参数在C中当类的成员函数为可变参数时调用约定自动变为__cedcl__调用约定定义义了函数被编译后对应的在符号表中的最终的符号名称的样子
2 函数调用约定的编程实验
2.1 使用gdb调试代码证明eax存的值是函数返回值
convention.c
#include stdio.hint test(int a, int b, int c) //默认的__cdecl__调用约定
{return a b c;
}
//__cdecl__调用约定
void __attribute__((__cdecl__)) func_1(int i)
{
}
//__stdcall__调用约定
void __attribute__((__stdcall__)) func_2(int i)
{
}
//__fastcall__调用约定
void __attribute__((__fastcall__)) func_3(int i)
{
}int main()
{int r test(1, 2, 3);printf(r %d\n, r);return 0;
}上述代码很简单分别将几个函数强制设定为相应的调用约定并可以通过查看变量r的内容与寄存器eax的内容来证明函数返回值是存储在eax寄存器中的。下面就来通过运行该程序并使用GDB进行查看相应的内存的值。
编译运行程序并且记性gdb调试
gcc -g convention.c -o test.outgdb test.outstartbreak convention.c:6continueinfo registerscontinue 因为gdb在前面的文章中已经使用过很多次并且使用了各种动态图展示gdb的使用。这里就直接给出相应的命令了。 上述第一个contiue后运行到convention.c的第6行就停止了。此时再info registers。可以看到如下信息
可以看到在函数test即将返回时。寄存器eax存的值为6 。 这正好等于test函数的返回值这不是巧合。因为eax寄存器就是用来保存函数的返回值的。当然后面我们还可以通过查看反汇编文件来分析。 最后的执行continue命令导致整个程序的运行结束 2 .2 查看程序的反汇编文件来说明调用约定的不同
上面的gdb调试方法没有证明出函数调用约定的不同下面我么使用查看可执行代码的反汇编文件进行说明。
使用命令
objdump -S test.c test.s
生成可执行文件的反汇编代码test.s。
在该文件中可以找到
func_1函数的反汇编代码
void __attribute__((__cdecl__)) func_1(int i)
{80483d5: 55 push %ebp80483d6: 89 e5 mov %esp,%ebp
}80483d8: 5d pop %ebp80483d9: c3 ret func_2的反汇编代码
080483da func_2:void __attribute__((__stdcall__)) func_2(int i)
{80483da: 55 push %ebp80483db: 89 e5 mov %esp,%ebp
}80483dd: 5d pop %ebp80483de: c2 04 00 ret $0x4func_3的反汇编代码
080483e1 func_3:void __attribute__((__fastcall__)) func_3(int i)
{80483e1: 55 push %ebp80483e2: 89 e5 mov %esp,%ebp80483e4: 83 ec 04 sub $0x4,%esp80483e7: 89 4d fc mov %ecx,-0x4(%ebp)
}80483ea: c9 leave 80483eb: c3 ret 从以上三个函数的反汇编代码可以看出它们的前言和后序是由区别的这是因为它们分别使用了不同的函数调用约定。使用了不同的 函数调用预定汇编层面肯定是不一样的。 3 总结
学会了以下内容
函数返回值如果是整形的通过eax寄存器传递返回值给调用者下一篇文章讲解返回值是结构体的类型个时是如何传递给调用者的学会三种不同的调用约定对应的区别__cdecl__调用约定__stdcall__调用约定__fastcall__调用约定