函数调用的过程理解_汇编角度

1、调用函数流程(main函数调用print函数):

Step1. 保存main函数现场地址等信息

Step2. 跳转到print函数的位置

Step3. 执行print函数的指令

Step4. 返回main函数,执行下一条指令

Step1 保存main函数现场地址等信息

要存储的内容,主要有3个:

(1)传入print函数的形参【父函数中实现】

(2)原eip(main函数call指令的下一条指令的地址)【父函数中实现】

(3)原ebp(main函数的ebp地址,因为是main函数直接调用print函数)【子函数中实现】

实现方式:

main函数中,依次执行以下指令:

push eax; # 传入函数的形参arg
call print; #作用是保存eip。当然,也有跳转的作用(见Step2)

print函数开头,依次执行以下指令:

push ebp; # 保存父函数main函数的栈底地址,方便调用函数结束后pop回去
mov ebp esp; # 指定print函数的栈帧基地址(print函数对应的栈底地址)

Step2 跳转到print函数的位置

核心是修改eip寄存器,把eip寄存器的值,修改为print函数的入口地址。

实现方式:main函数中调用call指令

call print.77c70888; # 相当于esp = esp - 4; push eip(此时的eip值,就是下面"main函数中的下一条指令"的地址); mov eip print函数的地址77c70888(跳转到新函数)
main函数中的下一条指令;

Step3 执行print函数的指令

子函数最开始,一般要执行以下的命令:

push ebp; # 保存原ebp
mov ebp esp; # ebp指向当前函数的栈底
...
sub esp, num; # 为子程序分配栈空间
...
pop ebp; # 让ebp指向父函数的栈帧基地址
ret n; # 让eip指向“main函数call指令的下一条指令的地址”;n是指平衡堆栈,即让最先push进去的形参args清空

Step4 返回main函数,执行下一条指令

核心是修改eip寄存器的值,把eip寄存器的值,修改为main函数中下一条指令的地址。

实现方式:在print函数中,调用ret命令

ret ; #相当于pop eip; esp = esp + 4

注意,如果原先栈中还有其他数据,esp 没有归位会导致主函数引用栈中数据出错。在这种背景下,出现了堆栈平衡的概念。即,还需对esp 进行单独操作,才能将 esp 指向原函数栈顶。以常见的 c 语言,函数有好几种调用规则。比如 cdecl 方式和 stdcall 方式。

cdecl 方式中,由主程序执行 add esp, n 指令调整 esp,达到堆栈平衡。在 stdcall 方式中,由子程序在返回时,执行 ret n 平衡堆栈。n 其实就是函数的参数所占的空间大小。

流程连续性总结

重要参考:《从汇编角度理解 ebp&esp 寄存器、函数调用过程、函数参数传递以及堆栈平衡》

函数调用

在一个函数中,调用另外一个函数,往往有以下几个步骤:

汇编指令指令归属函数SP 变化作用
push arg2主函数sp-4
push arg1主函数sp-4
call function主函数sp-4开始调用子程序,同时保存返回地址
push ebp子函数sp-4
mov ebp, esp子函数sp-4将当前esp 存入 ebp,目的是定位函数参数
sub sp, #num子函数sp-num为子程序分配栈空间
子函数函数的具体实现逻辑
pop ebp子函数sp+4
ret子函数sp+4

说明:

  • push arg 在调用一个函数之前,需要把传递的参数压入栈,因此需要有。每次 push 之后,栈多了一个字长(32 位系统 --> 4 字节),因此栈顶需要往上移动 4 字节,该指令暗含 sub sp, #4
  • call call 指令用来调用某个函数,该指令有3个操作(1)sp = sp - 4(2)将返回地址压入栈; (3)修改eip
  • push ebpmov ebp, esp 这样的操作,你会在各个函数的开头见到,保存上一个函数栈的基址,并更新本函数的基址
  • ret,即 return,此时 sp 应该指向 call 指令刚刚压入的返回地址;执行 ret 其实就是将此时栈中的数据弹出,存至 eip 寄存器。eip 存放的是下一条即将执行的指令的地址。 同时 sp = sp + 4
  • ret 指令相当于 pop eip(esp = esp + 4)
  • call 指令相当于 push eip(esp = esp - 4); mov eip 新函数的地址

下图左边是主函数调用子函数,右边是子函数返回主函数:

在这里插入图片描述

2、其他知识总结

1、esp寄存器,永远指向整个栈帧的栈顶(esp寄存器保存的值是堆栈的地址值);栈顶中的内容,可能是一个地址值,也可能是一个立即数等等。esp寄存器指向的堆栈地址始终有值!所以push是先移esp后压栈,pop是先弹栈后移esp。

2、ebp寄存器,永远指向当前函数所在栈帧的栈底(ebp寄存器保存的值是堆栈的地址值);栈底中的内容,永远保存的是上一个函数(父函数)的ebp地址!(方便函数执行完后pop回去,即修改ebp)

  • ebp 的作用之一就是找到函数的形参(通过ebp + 偏移量),当然栈中的局部变量也是可以通过 ebp 来定位的(ebp - 偏移量)

3、父函数调用子函数,子函数的栈帧(低地址)是紧挨着父函数的栈帧(高地址)。

  • 所以父函数调用子函数,一定是①args先压榨 ② 然后 原eip压栈(by call)③ 再 ebp压栈 ④ 最后函数的局部变量压榨

  • 父函数栈帧 和 子函数栈帧,隔着的东西就是:args 和 eip

    在这里插入图片描述

4、栈的增长方向,永远向着低地址的方向增长。

5、call 指令相当于:esp = esp - 4; push eip; mov eip 函数的地址.77c70888 (注意,push中已包含esp移动,这里只是表示先后)

6、ret指令相当于:pop eip; esp = esp + 4 (注意,pop中已包含esp移动,这里只是表示先后)

7、push eax指令,只会修改栈和esp:① esp = esp - 4 ② mov [esp], eax 这个[esp]表示esp指向的栈值(内存值)被赋值。

8、pop eax指令,不仅会修改栈和esp,而且还会修改eax(寄存器被赋值):① mov eax, [esp] ②esp = esp + 4

9、寄存器的地址值是不会变的,变的只是寄存器中装的堆栈地址值,以及这个堆栈地址中的内存内容值。这个类似并区别于C语言的指针变量。C语言指针变量有2个地址值,一个是指针变量装的堆栈地址值,另一个是指针本身的地址值(也位于堆栈)。指针变量声明后,它存在于内存中(堆栈中),指针释放了,这个指针的地址值也就无了。区别点在于 指针有本身的地址值(可以通过&取出来),而寄存器没有本身的地址值的说法(取不出来,或者有也是固定的但不常用)。指针变量的常操作数据有3个:①p ②*p ③&p;esp寄存器常操作的数据有2个:①esp ②[esp]

  • esp:esp寄存器的内容值,即栈/内存的某个地址
  • [esp]:栈中的值 / 内存中的值
  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
函数调用过程是程序中常见的一种操作,它通常涉及到参数传递、栈帧的建立与销毁、返回值的传递等多个方面。从汇编角度来看,函数调用过程可以分为以下几个步骤: 1. 将函数的参数压入栈中。在调用函数时,需要将函数所需的参数传递给它。这些参数通常以一定的顺序压入栈中,以便在函数内部使用。在 x86 架构中,参数的传递是通过将参数压入栈顶实现的。 2. 调用函数。函数调用的指令通常是 CALL 指令。在调用函数前,需要将函数的入口地址压入栈中,以便在函数执行完毕后返回到调用位置。CALL 指令会将当前的程序计数器(PC)压入栈中,并将函数的入口地址作为新的 PC。 3. 建立栈帧。在函数被调用时,需要为函数建立一个独立的栈帧,以便在函数内部使用局部变量和临时变量。栈帧通常包括以下几个部分:返回地址、旧的基址指针、局部变量和临时变量。在 x86 架构中,栈帧的建立是通过将 ESP 寄存器减去一个固定的值实现的。 4. 执行函数。在函数被调用后,CPU 会跳转到函数的入口地址并开始执行函数。函数内部可以通过栈中的参数和局部变量完成相应的计算和操作。 5. 返回值传递。在函数执行完毕后,需要将函数的返回值传递给调用者。在 x86 架构中,函数的返回值通常通过 EAX 寄存器传递。 6. 销毁栈帧。在函数执行完毕后,需要将栈帧销毁,以便释放栈空间。栈帧的销毁通常是通过将 ESP 寄存器还原到旧的基址指针处实现的。 7. 返回到调用位置。在函数执行完毕后,需要返回到函数被调用的位置。在 x86 架构中,返回指令通常是 RET 指令。RET 指令会将栈顶的返回地址弹出,并将其作为新的 PC。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值