博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅析操作系统函数调用原理-附实例
阅读量:6195 次
发布时间:2019-06-21

本文共 2699 字,大约阅读时间需要 8 分钟。

最近在研究二进制,研究到函数调用部分,将自己理解的原理做个记录。

首先需要了解系统栈的工作原理,栈可以理解成一种先进后出的数据结构,这就不用多说了。

在操作系统中,系统栈也起到用来维护函数调用、参数传递等关系的一个作用。嗯,这是我的理解。
在高级语言编程中,函数调用的底层原理是对用户屏蔽的,所以不用过多的纠结于底层的实现。而对于
汇编研究者来说,了解这个原理就很重要了。
首先可以想象一下,汇编语言在内存中是以指令的形式存在的,这些指令是按照顺序存储和执行的,高级语言中
编写的循环、调用,到了底层都会变成一些最基本的判断和跳转,如何在线性的结构上完成“非线性”的过程调度,
理解了这些,就理解了汇编。

这里先抛出高级语言的一个例子:

/*20160701*/    #include 
int funcA( int arg ){ arg += 1; return arg; } int main(){ int a; a = funcA(1); printf("%d", a); }

在这个程序中,main函数调用了函数funcA,funcA对传入的数据进行+1然后返回。

这个程序在编译之后,main函数变成这样:

(gdb) disassemble main    Dump of assembler code for function main:       0x0000000000400516 <+0>: push   rbp       0x0000000000400517 <+1>: mov    rbp,rsp       0x000000000040051a <+4>: sub    rsp,0x10       0x000000000040051e <+8>: mov    edi,0x1       0x0000000000400523 <+13>:call   0x400506 
0x0000000000400528 <+18>:mov DWORD PTR [rbp-0x4],eax 0x000000000040052b <+21>:mov eax,DWORD PTR [rbp-0x4] 0x000000000040052e <+24>:mov esi,eax 0x0000000000400530 <+26>:mov edi,0x4005d4 0x0000000000400535 <+31>:mov eax,0x0 0x000000000040053a <+36>:call 0x4003e0
0x000000000040053f <+41>:leave 0x0000000000400540 <+42>:ret End of assembler dump.

其中rbp是调用main函数的函数的栈桢的底部,这么说有点绕,简单的来说,main函数调用了funcA,那funcA中首先要做的一件事情就是把调用它的main函数栈桢的底部保存,所以在main函数被操作系统装载执行之后,main要做的首先是把调用它的函数的栈桢的底部保存,不然怎么返回呢?

第二个步骤把rsp的值传递给rbp,这是替换当前栈桢的底部,因为调用了funcA,所以要为funcA创建独立的栈桢,于是抬高栈底,怎么抬高呢,把栈顶传给指向栈桢底部的指针就可以了。
下一步是抬高栈顶,这是为funcA创建栈桢空间。
接着将参数传递给edi,因为这里只有一个参数,所以不涉及到参数顺序的问题,关于这个问题,可以去了解一下
调用了funcA,再来观察一下funcA的内部机制:

(gdb) disassemble funcA    Dump of assembler code for function funcA:       0x0000000000400506 <+0>: push   rbp       0x0000000000400507 <+1>: mov    rbp,rsp       0x000000000040050a <+4>: mov    DWORD PTR [rbp-0x4],edi       0x000000000040050d <+7>: add    DWORD PTR [rbp-0x4],0x1       0x0000000000400511 <+11>:    mov    eax,DWORD PTR [rbp-0x4]       0x0000000000400514 <+14>:    pop    rbp       0x0000000000400515 <+15>:    ret        End of assembler dump.

同样的,在funcA中,首先保存上一个函数,即main函数栈桢的栈底,然后将rsp的值赋给rbp,抬高栈桢底部。

接着从edi中取得参数,并放入位于自身栈桢空间中,rbp之后的双字单元内。
然后执行操作,将其自增。
执行完成之后,将返回值保存在eax中,等待返回。
弹出上一个函数的栈桢的底部,重新回到main函数的空间。

PS:

直到目前为止,这个程序反编译出来的结果和书上说的原理还是有一些出入的,还有下面几个问题:
0x01 书上说的是,传递参数,会将参数按照一定顺序压栈,而不是像本程序中这样使用edi
0x02 在main函数调用funcA函数之后,将栈顶指针esp抬高了,但是在funcA函数执行完成需要返回到main函数的时候,只恢复了ebp指针,并没有恢复esp指针,这是为什么?

希望接下来可以搞懂上面的两个问题。

本文中用到的相关代码:

/*20160701*/#include 
int funcA( int arg ){ arg += 1; return arg;}int main(){ int a; a = funcA(1); printf("%d", a);}

转载于:https://www.cnblogs.com/Chorder/p/9114473.html

你可能感兴趣的文章
常用SQL(三)
查看>>
ubuntu1.6安装nfs
查看>>
vRealize Operations Manager 6.2部署与配置
查看>>
利用POI 技术动态替换word模板内容
查看>>
Java-学习笔记-8-接口
查看>>
机器废渣!AI离占领华尔街还早着呢
查看>>
DHTML快速入门
查看>>
linux shell基础
查看>>
logstash之input
查看>>
科来抓包验证OSPF选择DR
查看>>
Android多功能日期选择控件
查看>>
Netty实践(四):心跳检测实现
查看>>
1-消息队列简介
查看>>
SQLServer图数据库一些优点
查看>>
selenium-----简单的页面元素查找方法
查看>>
自动化运维之批量修改主机名
查看>>
二分查找法
查看>>
Redistribute配置实例
查看>>
mysql性能测试工具之mysqlslap
查看>>
Java Http断点下载文件
查看>>