`
zachary.guo
  • 浏览: 482458 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

函数调用栈

 
阅读更多
        参考文章:http://liyiwen.iteye.com/blog/345525

        1. 一个函数调用动作可分解为:零到多个 PUSH 指令(用于参数入栈),一个 CALL 指令。CALL 指令内部其实还暗含了一个将返回地址(即 CALL 指令下一条指令的地址)压栈的动作。
        2. 几乎任何本地编译器都会在每个函数体之前插入类似如下指令:PUSH EBP;  MOV EBP ESP; 即,在程序执行到一个函数的真正函数体时,已有以下数据顺序入栈:参数,返回地址,EBP。

        函数调用的返回地址,正是调用指令 Call 的下一个指令的地址。那么,有了返回地址,就可以得到 Call 指令的位置了。有 Call 指令的位置又能干什么呢?Call 指令就是一个跳转指令,它可以让 IP(instruction point) 指向要跳转的指令的地址,从那里开始执行。对于函数调用来说,就是让 IP 指向被调用的函数的地址。Call 指令的操作数其实和被调用函数的地址有非常重要的关系。有了 Call 指令的操作数,就可以计算出被调用函数的地址。

        但仅仅有这个还不够。比如,A 调用了 B,那么在 A 函数中肯定有一个 Call 指令,但这个 Call 指令中的操作数是和 B 函数地址相关的,与 A 的函数地址直接关系不大(至少在没有其它信息的情况下,不能计算出 A 的地址)。而我们要得到的却是 A 函数的地址。所以,得向上再找一层,找到调用 A 函数的地方,那个地方的 Call 指令里的操作数才和 A 函数地址有关。也就是说,Z 函数调用了 A 函数,A 函数调用了 B 函数。现在要得到 A 函数的地址,我们得在 Z 函数里找 Call 指令的操作数。这时候 EBP 就派上用场了。本地编译器在每个函数体之前插入的指令(PUSH EBP; MOV EBP ESP)构造了一个巧妙的结构,使得我们可以顺着函数调用栈一层一层向上,找到所有调用关系。

        如何向上查找呢?我们看看函数调用时栈、EBP 的值的情况就知道了。假设现在函数在正 Z 函数内执行,那么此时栈和 EBP 的值可能是像下图这样的:
                                             
        我们先不管现在 EBP 指向的内存(0x000F)中的内容 XXX 是什么(要不然会是鸡生蛋生鸡的问题),总之目前在栈中的着色块中的内容是属于函数 Z 的参数,Z 执行结束后应该返回的地址以及 Z 函数的局部变量值。

        现在 Z 函数调用 A 函数,会先将传给 A 的参数压栈,然后将现在这个指令(就是"Call A")的下一个指令的地址压入栈中,以便 A 函数完后返回到 Z 中继续执行。然后进入 A 函数的内存空间,首先就是调用 PUSH EBP,也就是将 Z 的 EPB 的内容(地址 0x000F)压入栈中,然后再 MOV EBP ESP,让 EBP 有一个新的栈顶(此时栈顶中的内容不就是 Z 函数时 EBP 的内容么?),然后再将 A 函数的局部变量压入栈中,开始执行 A 函数的代码。这时,栈和 EBP 的情况就像如图所示了:
                                         
        这样就很清楚了,原来现在的 EBP 中的内容,正是上一级函数的 EBP 中的内容。而每一个函数的 EBP 指向的位置,向栈顶可以得到该函数的局部变量,向栈底可以得到函数的返回地址和参数。于是我们就可以根据这个结构层层向上,找到任何一层我们想找的函数 EBP,从而也就能得到相应的返回地址了。
  • 大小: 21.4 KB
  • 大小: 30.5 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics