• 上一篇文章学习了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.h>int 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.out
  • gdb test.out
  • start
  • break convention.c:6
  • continue
  • info registers
  • continue

因为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    $0x4
  • func_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__调用约定)

【软件开发底层知识修炼】二十四 ABI之函数调用约定相关推荐

  1. 【软件开发底层知识修炼】十四 快速学习GDB调试一 入门使用

    前面几篇文章学习了链接器相关的内容.现在开始来学习GDB调试.我们的目的是通过这几篇文章将GDB调试完全学会. 文章目录 1 为什么需要GDB 2 GDB 的常规应用 3 GDB调试程序实例 4 总结 ...

  2. 【软件开发底层知识修炼】十五 快速学习GDB调试二 使用GDB进行断点调试

    上一篇文章我们学习了使用GDB的最基本方法:[软件开发底层知识修炼]十四 快速学习GDB调试一 入门使用 本篇文章将学习GDB的断点调试.断点调试是一种非常重要的调试方法. 文章目录 1 断点类型 2 ...

  3. 【软件开发底层知识修炼】十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录

    本文记录之前写过的5篇关于GDB快速学习的文章,从第一篇开始学习到最后一篇,保证可以从入门GDB调试到熟练掌握GDB调试的技巧. 学习交流加 个人qq: 1126137994 个人微信: liu112 ...

  4. 【软件开发底层知识修炼】十八 快速学习GDB调试五 使用GDB进行调试的一些小技巧

    上一篇文章学习了如何使用GDB进行函数调用栈的查看:[软件开发底层知识修炼]十六 快速学习GDB调试四 使用GDB进行函数调用栈的查看 本篇文章是GDB调试快速学习系列的最后一篇.将综合前几篇文章做一 ...

  5. 【软件开发底层知识修炼】十六 快速学习GDB调试三 使用GDB的数据断点监测变量是否改变

    上一篇文章我们学习了如何使用GDB进行软件断点调试和硬件断点调试:[软件开发底层知识修炼]十五 快速学习GDB调试二 使用GDB进行断点调试 本篇文章继续上一篇文章的学习,如何使用GDB的数据断点监测 ...

  6. 【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)

    上一篇文章学习了链接脚本的语法与相关概念:链接脚本的概念 在继续学习链接器的内容的同时,先学习一个新内容:内嵌汇编. GCC编译器一般支持C/C++内嵌汇编语言,这样可以实现语言本身无法实现的内容.我 ...

  7. 【软件开发底层知识修炼】十 链接器-main函数不是第一个被执行的函数

    上一篇文章,大概了解了链接器的工作内容就是:符号解析和重定位.点击上一篇文章查看:点击查看. 本片文章其实还是围绕链接器来学习.只不过不是很明显,当你学到下一篇文章时,就明白了. 本篇文章来弄明白一个 ...

  8. 【软件开发底层知识修炼】二十六 ABI-应用程序二进制接口 学习总结文章目录

    前面学习了ABI的知识,感觉受益良多.对底层与编译器有更加深刻的认识,为此这里将前面写过的关于ABI 的文章给列出来,方便学习与翻阅. [软件开发底层知识修炼]二十一 ABI-应用程序二进制接口一 [ ...

  9. 【软件开发底层知识修炼】二十五 ABI之函数调用约定二之函数返回值为结构体时的约定

    上一篇文章学习了几种函数调用约定的区别,点击链接查看上一篇文章:[软件开发底层知识修炼]二十四 ABI之函数调用约定 本篇文章继续学习函数调用约定中,关于函数返回值的问题.当函数返回值为结构体时,函数 ...

最新文章

  1. java list 常见的使用方法
  2. php 多选的 二进制,PHP二进制操作初体验
  3. Servlet--05--HttpServletRequest; HttpServletResponse
  4. 用第三方工具类,将JavaBean、List、MapString,Object转成JSON文本
  5. redis学习之数据结构与对象(一)
  6. 全网最全的git命令大全
  7. 力扣--36有效的数独
  8. Linux 录屏软件有哪些?
  9. 关于内存地址和内存空间的理解
  10. Linux中将两块新硬盘合并成一个,挂载到/data目录下
  11. C# Excel 操作
  12. Mapping Spiking Neural Networks的论文汇总以及思考
  13. [转载]英语语音断句规则
  14. 6G需要1000亿个基站;5G套餐资费年内或降至50至60元;国内首款L4级5G无人驾驶汽车量产...
  15. 比 Elasticsearch 更快,RediSearch + RedisJSON = 王炸
  16. Python负数除法取余操作
  17. esxi能直通的显卡型号_七彩虹RTX SUPER祝融(火神)版显卡上手体验
  18. 搜索光纤测试软件,了解光纤的常用工具及使用方法
  19. 判断二极管导通例题_通信电源 | 1个二极管是如何改变电流的?
  20. 毕业设计之 --- 在线考试系统

热门文章

  1. Linux 用C/C++创建新文件并写入内容
  2. android tabhost 多个activity,Android:TabHost中Activity的生命周期问题
  3. python两列数据生成邻接矩阵_用python实现邻接矩阵转换为邻接表,python语言实现...
  4. javascript网页自动填表_JavaScript脚本实现网页批量自动勾选及内容填写
  5. 记一次webpack4+react+antd项目优化打包文件体积的过程
  6. Chapter 5 Exercises Problems
  7. 各种推荐资料汇总。。。
  8. 【转】Android加密算法:AES、Base64加密算法
  9. idea和搜狗输入法快捷键冲突_ubuntu18.04安装搜狗输入法
  10. java 资深工程师必备技能