我想通过查看堆栈上的原始数据将堆栈划分为堆栈帧。我想通过找到已保存的EBP指针的“链表”来实现这一目的。
我可以假设一个(标准的和常用的)C编译器(例如gcc)总会在函数序言中的函数调用中更新并保存EBP吗?
pushl%ebp
movl%esp,%ebp
或者是否有些编译器可能会跳过那部分没有获取任何参数且没有局部变量的函数?
x86 calling conventions上的function prologue和Wiki文章对此没什么帮助。
有没有更好的方法可以通过查看原始数据来将堆栈划分为堆栈帧?
谢谢!
答案 0 :(得分:3)
某些版本的gcc具有-fomit-frame-pointer
优化选项。如果内存服务,它甚至可以用于参数/局部变量(它们直接从ESP索引而不是使用EBP)。除非我的错误,MS VC ++可以大致相同。
另一方面,我不确定哪种方式接近普遍适用。如果你有调试信息的代码,通常很容易 - 否则......
答案 1 :(得分:2)
即使优化了framepointer,堆栈帧通常也可以通过查看堆栈内存来查找保存的返回地址。请记住,x86中的函数调用序列始终包含:
call someFunc ; pushes return address (instr. following `call`)
...
someFunc:
push EBP ; if framepointer is used
mov EBP, ESP ; if framepointer is used
push <nonvolatile regs>
...
所以你的堆栈将始终 - 即使缺少画框指针 - 在那里有返回地址。
您如何识别退货地址?
call
指令具有特定的操作码格式;在返回地址之前读取几个字节并检查是否在那里找到call
操作码(99%的大部分时间,直接调用返回5个字节,返回3个字节)通过注册表打电话)。如果是这样,你找到了一个回邮地址
这也是一种区分C ++ vtable和返回地址的方法 - 您可以在堆栈中找到vtable入口点,但是从那些找不到call
指令的地址“回来”。使用该方法,即使没有符号,框架化调试信息或其他任何内容,您也可以从堆栈中获取调用序列的候选项。
如何将实际调用序列从这些候选者中分离出来的细节不那么简单,但是,您需要一个反汇编程序和一些启发式方法来跟踪从最低找到的返回地址一直到最后一个已知程序的潜在调用流程地点。也许有一天我会写博客;-)虽然此时我宁愿说stackoverflow发布的余量太小而不能包含这个......