关于汇编指令call和ret的具体细节操作!

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于汇编指令call和ret的具体细节操作!相关的知识,希望对你有一定的参考价值。

小弟主要疑问在于ret,据我所知,函数内部的局部变量是分配在栈空间里的,也就是在call指令将函数的返回地址压入之后,如若函数内部定义了若干局部变量,那ret指令是怎样找到返回地址的地址啊?小弟猜测就是在编译的时候,编译器在每次函数调用时,都记录了函数的返回地址的地址,然后在函数执行完成之后,ret指令有一部分就是
jmp 返回地址的栈地址 这一条指令,从而实现函数返回,这仅是小弟的猜想,望大虾细心讲解一下,感激不尽!

堆栈的压入顺序是从高位地址向低位地址延伸,局部变量的分配方向也是这样,这个是关键。
每个函数开始一般有push bp、mov bp, sp两条指令,局部变量的分配地址都在bp之下,访问也是通过bp-n,而bp+2(32位汇编中是esp+4)才是栈顶,所以局部变量不会破坏栈顶数据,ret之前有pop bp,则栈顶数据就是call的时候压入堆栈的返回地址,没有什么“寻找返回地址”的活儿要干。追问

我对于你的局部数据的理解很有疑问啊,我看过相关的书,局部变量在分配时,是会影响到esp的,但是貌似每次edp都会指向返回地址,但edp是可以使用的,如果改变了值,esp又如何找回到返回地址啊?

追答

在16位汇编中,sp不能用于间接寻址,局部变量的访问需要通过能够间接寻址的bp进行,在32位汇编中,各个寄存器都能用于间接寻址,故局部变量也可直接以[esp-n]来访问。

call调用进入一个函数后,栈顶就是返回地址,而正确的函数实现必须保证自身的堆栈push和pop的平衡,在ret之际,esp必须还原到刚进入此函数时的值;高级语言的函数若有局部变量,编译器在编译阶段将计算它们的总占用空间,在链接阶段于函数目标代码的头部加入add esp,-n或sub esp,n的指令,在ret之前加入add esp,n的指令,这个n是由编译器分析源程序后计算得到,对于高级语言的程序员来说是透明的,换言之,关于局部变量分配与释放的堆栈平衡,由编译器保证。两个方面均能保证堆栈指针(esp)的平衡,就ret无误。

暂存和还原esp的办法有很多,不仅仅是add esp,-n与add esp,n的呼应,也可以先存入到某全局变量(编译器自行构造,非高级语言程序声明的),后取出。

一楼回答的不是局部变量的问题,而是“通过堆栈传递参数造成的堆栈资源占用之释放”问题,有两种风格,可以说是C风格和pascal风格,前者由调用者在call返回后执行add esp,n的操作,后者由被调用者以ret n的指令返回,手写汇编则可根据喜好自行采用。

参考技术A call指令返回地址压栈,此时返回地址存储在栈顶而此时esp正好指向栈顶,而分配局部变量空间会改变esp,所以在ret执行之前esp必须指向返回地址所存储的地方,不然就会出错
编译器不会记录函数的返回地址,函数的返回地址call指令的下一条指令的地址,由cpu自动压入栈追问

所以在ret执行之前esp必须指向返回地址所存储的地方,
就是你这句是我想知道的,它是怎么指向返回地址啊?都已经改变了,如果不记录,怎么指回去?

追答

你可能对栈的操作还不是很理解,
push和pop是配对的,有一个push也要有一个pop
在分配局部变量时用sub esp, xxx
在ret之前也要有add esp, xxx与之配对的
所以在执行ret前,esp总是指向正确的地方

参考技术B call xxx 就是 push eip + jmp xxx
ret 就是 pop eip

函数一般遵循调用原则,比如标准调用(stdcall),规定被调函数自己清理堆栈。这样的话,call进这个函数的时候,他可以使用堆栈,但是在使用完之后,就需要恢复堆栈成之前的样子,ret才不会出错追问

我现在就是不怎么理解它清理局部变量,恢复堆栈的操作,怎么实现啊?改都改了,不记录,怎么恢复!

以上是关于关于汇编指令call和ret的具体细节操作!的主要内容,如果未能解决你的问题,请参考以下文章

汇编语言——call 和 ret 指令

[汇编]《汇编语言》第10章 CALL和RET指令

汇编语言中的call是啥意思

汇编-10.0-CALL和RET指令

汇编10:CALL和RET指令

汇编入门学习笔记 —— call和ret