来自:http://blog.163.com/liuguang_123/blog/static/816701920105262543890/

------------------------------------------------------------------------------------------------

理解调用栈最重要的两点是:栈的结构,EBP寄存器的作用。

首先要认识到这样两个事实:

1、一个函数调用动作可分解为:零到多个PUSH指令(用于参数入栈),一个CALL指令。CALL指令内部其实还暗含了一个将返回地址(即CALL指令下一条指令的地址)压栈的动作。

2、几乎所有本地编译器都会在每个函数体之前插入类似如下指令:PUSH EBP; MOV EBP ESP;

即,在程序执行到一个函数的真正函数体时,已经有以下数据顺序入栈:参数,返回地址,EBP。
由此得到类似如下的栈结构(参数入栈顺序跟调用方式有关,这里以C语言默认的CDECL为例):

+| (栈底方向,高位地址) |
| .................... |
| .................... |
| 参数3                |
| 参数2                |
| 参数1                |
| 返回地址             |
-| 上一层[EBP]          | <-------- [EBP]

“PUSH EBP”“MOV EBP ESP”这两条指令实在大有深意:首先将EBP入栈,然后将栈顶指针ESP赋值给EBP。“MOV EBP ESP”这条指令表面上看是用ESP把EBP原来的值覆盖了,其实不然——因为给EBP赋值之前,原EBP值已经被压栈(位于栈顶),而新的EBP又恰恰指向栈顶。

此时EBP寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原EBP入栈后的栈顶),从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,而该地址处又存储着上一层函数调用时的EBP值!

一般而言,ss:[ebp+4]处为返回地址,ss:[ebp+8]处为第一个参数值(最后一个入栈的参数值,此处假设其占用4字节内存),ss:[ebp-4]处为第一个局部变量,ss:[ebp]处为上一层EBP值。

由于EBP中的地址处总是“上一层函数调用时的EBP值”,而在每一层函数调用中,都能通过当时的EBP值“向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值”。
如此形成递归,直至到达栈底。这就是函数调用栈。

编译器对EBP的使用实在太精妙了。

从当前EBP出发,逐层向上找到所有的EBP是非常容易的:

unsigned int _ebp;
__asm _ebp, ebp;
while (not stack bottom)
{
    //...
    _ebp = *(unsigned int*)_ebp;
}

基于上面的原理,我们可以实现:Copy code

function getIntHex(var a; len: integer): string;//整型转成HEX字符串
var
  d: pchar;
  i: Integer;
begin
  getmem(d, len * 2);
  binToHex(@a, d, len);
  result := '';
  for i := len - 1 downto 0 do
    result := result + d[i * 2] + d[i * 2 + 1];
  freemem(d);
end;

function PrintCallStack(): string;
var
  curEBP, nextEBP, val1, val3: Cardinal;
  p: ^Cardinal;
begin
  asm
       mov curEBP,ebp  ;//取得当前EBP
       mov eax,dword ptr ss:[ebp];
       mov  nextEBP,eax;//上一层的EBP
  end;
  val3 := 0;
  result := '';
  repeat
    p := Pointer(curEBP + 4);
    val1 := p^; //上一层的调用函数的断点(下一语句地址)
    val3 := val3 + 1;
    result := result + '================= No.' + IntToStr(val3) + ' ============='#13#10;
    result := result + '当前EBP:' + getIntHex(curEBP, SizeOf(curEBP)) + #13#10;
    result := result + '上一EBP:' + getIntHex(nextEBP, SizeOf(nextEBP)) + #13#10;
    result := result + '上一断点:' + getIntHex(val1, SizeOf(val1)) + #13#10;
    p := Pointer(curEBP);
    curEBP := p^;
    p := Pointer(curEBP);
    nextEBP := p^;
  until (nextEBP = 0) or (DWORD(p) >= $0012FFFC) ;//到栈顶了吗?
end;
有这样的方法,查找游戏的CALL的基址就不再是极难的事了

分析函数调用堆栈的原理和Delphi实现的更多相关文章

  1. C++反汇编代码分析--函数调用

    推荐阅读: C++反汇编代码分析–函数调用 C++反汇编代码分析–循环结构 C++反汇编代码分析–偷调函数 走进内存,走进汇编指令来看C/C++指针 代码如下: #include "stdl ...

  2. 深入浏览器工作原理和JS引擎(V8引擎为例)

    浏览器工作原理和JS引擎 1.浏览器工作原理 在浏览器中输入查找内容,浏览器是怎样将页面加载出来的?以及JavaScript代码在浏览器中是如何被执行的? 大概流程可观察以下图: 首先,用户在浏览器搜 ...

  3. 嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误

    嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误 2015-05-27 14:19 184人阅读 评论(0) 收藏 举报  分类: 嵌入式(928)  一般察看函数运行时堆栈的 ...

  4. linux下利用backtrace追踪函数调用堆栈以及定位段错误

    一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的. 在glibc ...

  5. Mybatis插件原理和PageHelper结合实战分页插件(七)

    今天和大家分享下mybatis的一个分页插件PageHelper,在讲解PageHelper之前我们需要先了解下mybatis的插件原理.PageHelper 的官方网站:https://github ...

  6. 用户态使用 glibc/backtrace 追踪函数调用堆栈定位段错误【转】

    转自:https://blog.csdn.net/gatieme/article/details/84189280 版权声明:本文为博主原创文章 && 转载请著名出处 @ http:/ ...

  7. Linux下函数调用堆栈帧的详细解释【转】

    转自:http://blog.chinaunix.net/uid-30339363-id-5116170.html 原文地址:Linux下函数调用堆栈帧的详细解释 作者:cssjtuer http:/ ...

  8. Linux下利用backtrace追踪函数调用堆栈以及定位段错误[转]

    来源:Linux社区  作者:astrotycoon 一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序 ...

  9. Lab_1:练习5——实现函数调用堆栈跟踪函数

    题目:实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址.如 ...

随机推荐

  1. js计算当前日期上一个月和下一个月

    /**         * 获取上一个月         *         * @date 格式为yyyy-mm-dd的日期,如:2014-01-25         */        funct ...

  2. php函数引用参数解惑《细说php》

  3. java enum naming rules & Pascal case, Camel case, Uppercase

    java enum naming rules Constant & all Capital Case https://stackoverflow.com/questions/3069743/c ...

  4. [剑指Offer] 20.包含min函数的栈

    题目描述 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数. [思路1]两个栈Stack和Min,Stack为当前栈中元素,Min为与Stack中元素一一对应的当前栈最小值. cl ...

  5. 【SVN】SVN服务器的本地搭建和使用

    Subversion是优秀的版本控制工具,其具体的的优点和详细介绍,这里就不再多说. 首先来下载和搭建SVN服务器. 现在Subversion已经迁移到apache网站上了,下载地址: http:// ...

  6. ai学习记录

    界面:多个预编辑区:制作图形,使用的图形放到工作区内,不使用在预编区.没有Ctrl/Alt+delete的概念,没有前后景颜色.新建:分辨率:矢量软件和分辨率无关: 新建时候不要勾选对齐到像素网格 存 ...

  7. 前端工程师必须要知道的SEO技巧(2):制作比设计还要漂亮的代码(内容和语义化代码)实现下

    提醒自己:上一篇文章属于纯理论的文章,我自己有的部分之从网上摘抄的,我自己也是不理解的.或许过一段日子我就能全明白了.我自己还是喜欢实战,做几个例子就明白了. 怎么做让自己网页的标签来实现语义化,我直 ...

  8. UDP收/发广播包原理及步骤

    原文链接地址:http://www.2cto.com/net/201311/254834.html UDP收/发广播包原理及步骤 如果网络中两个主机上的应用程序要相互通信,其一要知道彼此的IP,其二要 ...

  9. 理解[].forEach.call()

    例子: let cols = document.querySelectorAll('ul li') [].forEach.call(cols, function (col, index) { // T ...

  10. Spring源码解析-事件

    Spring事件的组件 主要是3个组件: 1.ApplicationEvent   事件 2.ApplicationListener 监听器,对事件进行监听 3.ApplicationEventMul ...