汇编语言中的函数调用

C语言从原则上来说,只能在函数内执行代码。
所以任何 text 段都对应有自己的帧栈。
本文主要谈一下 call leave ret 三条与函数调用紧密相关的指令。

call 指令

call 的不同形式

call Label 所谓直接跳转
call *operand 所谓间接跳转

080483f7 <caller>:
  804840c:   e8 dc ff ff ff          call   80483ed <callee>

上边代码段中 caller 中 call 80483ed <callee> 就是直接跳转

call 之前的准备

080483f7 <caller>:
  80483fa:   83 ec 08                sub    $0x8,%esp
  80483fd:   c7 44 24 04 1c a0 04    movl   $0x804a01c,0x4(%esp)
  8048404:   08
  8048405:   c7 04 24 01 00 00 00    movl   $0x1,(%esp)

gcc ABI约定被调函数的参数保存在调用者的栈帧(frame)上,所以 caller 需要将 callee 的参数放在自己的栈帧上。这个过程分两步完成。

  • 开栈。
    将栈指针向下(栈由高位向下扩展)移动 8 bytes。这是因为两个参数一个是指针类型,一个是整数类型,均需要 4 bytes 来存储。事实上由于对齐的要求,即使参数类型小于 4 bytes 编译器还是会为其分配 4 bytes 的栈空间,
  • 反向保存参数。
    gcc ABI规定,反向保存参数,故栈顶保存最后一个参数。如果参数类型大于 4 bytes,IA32 需要用两条 movl 指令来传递参数。
    值得注意的是,ABI只规定了参数在栈上存储的空间顺序,并没有规定参数压入栈中的时间顺序

call 干了什么

存储返回地址。
call 指令将 (%eip) 对应指令之后的那条指令的起始地址放在栈上,也就是把 %eip + n 放在 (%esp),其中 n 为 (%eip) 中指令的长度。然后跳转到 call 的操作数所指的地址。

call之后发生了什么

080483ed <callee>:
  80483ed:   55                      push   %ebp   // sub   $0x4,%esp
                                                   // mov   %ebp,(%esp)
  80483ee:   89 e5                   mov    %esp,%ebp

  80483f0:   83 ec 2c                sub    $0x2c,%esp

  8048405:   c7 45 e8 01 00 00 00    movl   $0x1,-0x18(%ebp)
  804840c:   c7 45 ec 02 00 00 00    movl   $0x2,-0x14(%ebp)
  8048413:   c7 45 f0 03 00 00 00    movl   $0x3,-0x10(%ebp)
  804841a:   c7 45 f4 04 00 00 00    movl   $0x4,-0xc(%ebp)
  8048421:   c7 45 f8 05 00 00 00    movl   $0x5,-0x8(%ebp)
  8048428:   c7 45 fc 06 00 00 00    movl   $0x6,-0x4(%ebp)
  • 切换栈帧。
    被调函数首先将旧的栈底指针 %ebp 压到自己的栈帧上,然后以其地址(而非内容)作为自己的栈底指针的内容,此时新的栈帧已经形成了,由于 %esp == %ebp,故新的栈帧暂时没有使用栈内存。
  • 开栈。
    当局部变量数量太大时,编译器会选择将局部变量放在栈帧上。gcc的ABI约定,函数栈帧的大小必须 16 bytes 对齐,所以sub指令所减去的16进制数以c结尾(栈帧上已经有上一帧 %ebp ) 。
  • 初始化局部变量。
    这里对局部变量的初始化是以栈底指针为基准的,此处值得注意的是 (%ebp) 中存储的是上一帧的 %ebp

leave 指令

8048411:   c9                      leave

leave 所做的工作是还原上一帧的栈底指针与栈顶指针,等效于

mov  %ebp,%esp // 把栈顶指针置为本帧的栈底(同时也是存储上一帧栈底指针内容的地址),
popl %ebp      // 还原上一帧的栈底指针,此时 %esp 指向返回地址

ret 指令

8048412:   c3                      ret

ret 所做的工作是弹出栈顶的返回地址,并跳转到此地址。此时 %esp 指向调用函数所存储的被调函数的最后一个参数。

杂记

一个完整的栈帧上会有什么?
从底到顶依次是:

1. 上一帧的 `%ebp`
1.  ABI 约定被调用者保存(如果有)的调用者的三个寄存器的内容 `%ebx` `%esi` `%edi`

2. 局部变量
2. 对齐空白

3. ABI 约定调用者保存(如果有)的自己的三个寄存器的内容 `%eax` `%edx` `%ecx`
3. 所调用的函数的参数
3. 返回地址(本帧的 %esp所指,下一帧的 0x4(%ebp))

原文地址:https://www.cnblogs.com/guochaoxxl/p/11217092.html

时间: 2024-12-15 08:35:50

汇编语言中的函数调用的相关文章

Java/Android中的函数调用&amp;回调函数&amp;自定义回调函数

在做Android自定义控件时遇到要自定义回调函数的问题,想想自己还暂时没有那么精深的技术,赶紧返过头回来再重新研究Java中回调函数的问题.然而不幸的是,网上太多杂乱的帖子和博客都是转来转去,而且都是那一篇"C中的回调函数.....指针.....java....",一点看不出来是自己的思路,估计都是哪哪哪抄来的!(呵呵,要么就是吐槽对了,要么就是我水平太烂读不懂还妄加评论)还有一些很不错的文章,我会在最后参考中加上链接,大家可以看看. 那么来开始我们的正题--什么是回调函数? 我们一

linux平台学x86汇编(十六):在汇编语言中调用C库函数

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 其实在汇编语言中也是可以使用C库函数的,这一节我们来看一下如何在汇编语言中调用C库函数以使得我们的程序看上去很方便地和用户交互. C库包括C程序通用的喝多函数,如printf和exit等,下面我们紧接着上一节的知识来实现一个两整数想加的计算并输出计算结果的程序. # libc.s .section .data output: .asciz "The result is %d.

javascript 与 jquery 中的函数调用的区别

标签:例如<input id="btn_show" type="button" onclick="show()" /> <script type="text/javascript"> $(function(){ function show(){ -- } }) function show(){ -- } </script> 注意, 此时button的点击事件不会调用jquery中的show(

在C语言中破坏函数调用堆栈

1 // 这段代码显示,在C语言修改函数的返回地址 2 int test1() 3 { 4 return 0; 5 } 6 7 int test2(int a) 8 { 9 *(&a-1) = (int)test1; // 将返回地址修改为test1 10 return a; 11 } 12 13 int main() 14 { 15 test2(10); 16 return 0; 17 } 在C语言中破坏函数调用堆栈,布布扣,bubuko.com

C++中构造函数调用构造函数

今天想做道矩阵的题目时,却卡在一些编程细节上了,找了好久才发现原来是在构造函数处出了问题,然后上网百度了下,发现这篇文章说得很好:从一道题谈C++中构造函数调用构造函数,很棒! 还补充一点: 看来自己C++的基本功还有待提高啊~~

c++中析构函数调用时机的研究

众所周知,c++中的每个类都会有一个析构函数,当这个类的对象被销毁的时候,对象会自动调用析构函数.那么什么情况下对象的析构函数会被自动调用呢?其实这个问题也可以换种方式问,什么情况下对象会被自动销毁. 我们跟据对象的声明方式分两种情况来讲: 1.动态声明的对象 这种声明方式下系统会自动销毁不再使用的对象,对应的对象的析构函数也会被调用.例如classname object:这样声明的对象,当程序运行到了对象作用域之外或者程序退出,对象都会被销毁,当然析构函数也会被调用. 2.静态声明的对象(ne

在chrome开发者工具中观察函数调用栈、作用域链与闭包

在chrome开发者工具中观察函数调用栈.作用域链与闭包 在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量对象,闭包,this等关键信息的变化.因此,断点调试对于快速定位代码错误,快速了解代码的执行过程有着非常重要的作用,这也是我们前端开发者必不可少的一个高级技能. 当然如果你对JavaScript的这些基础概念[执行上下文,变量对象,闭包,this等]了解还不够的话,想要透彻掌握断点调试可能会有一些困

在linux代码中打印函数调用的堆栈的方法

之前一直有这样的需求,当时问到,也没搜到方法,现在竟然既问到了,也搜到了,哎,世事真是不能强求啊! 在Linux内核调试中,经常用到的打印函数调用堆栈的方法非常简单,只需在需要查看堆栈的函数中加入: dump_stack();或 __backtrace();即可. dump_stack()在~/kernel/ lib/Dump_stack.c中定义 void dump_stack(void){ printk(KERN_NOTICE  "This architecture does not imp

Sql Server2008中自定义函数调用存储过程解决方案

1.开启sql server 2008远程连接 打开sql server配置管理器 配置SSCM,选中左侧的"SQL Server服务",确保右侧的"SQL Server"以及"SQL Server Browser"正在运行 11 在左则选择sql server网络配置节点下的sqlexpress的协议,在右侧的TCP/IP默认是"否",右键启用或者双击打开设置面板将其修改为"是" 选择"IP 地