所以,假设我可以访问程序的寄存器。我可以访问esp,ebp和eip。 eip指向需要执行的下一条指令,ebp指向另一个帧指针,esp指向堆栈顶部。我理解这一点,但我不了解堆栈的其余部分或如何解析它。
例如,如果我想获取一个帧的局部变量,我应该减去ebp - esp
(知道ebp
比esp
更大的地址)然后去通过这些地址并取消引用它们?这是从特定帧中获取局部变量的正确方法吗?
另一个问题是,找出哪个函数与每个帧相关的最佳方法是什么?如果我将1减去ebp
地址然后取消引用该值,我应该得到返回地址“0x804 ...”吗?这个地址和功能之间有什么关系?例如,如果Foo()的pc地址高0x8045555
且pc地址低0x8045550
,那么我将要在这些地址之间返回地址吗?
非常感谢先进,如果我不够清楚,请告诉我。
注意:如果有人有更好的标题建议,我找不到更好的。
答案 0 :(得分:1)
具体细节取决于您的CPU指令集架构(您显然使用的是32位x86)和编译器工具链(我无法猜测)。通常,您不希望重新编写代码来自行遍历堆栈帧,因为它复杂且易碎,并且取决于编译器的优化和调试设置。
如果您正在尝试调试程序,首先应该让平台的调试器尝试整理您的堆栈。例如,使用gdb
,您可以运行bt
以获得“回溯”。
如果您尝试从相关程序内部执行此操作,并且您正在使用GNU C库,则可以使用backtrace(3)
函数。
如果您只想了解事情的确如何运作,请参阅以下文章:http://eli.thegreenplace.net/2011/02/04/where-the-top-of-the-stack-is-on-x86/
如需更深入的了解,请尝试维基百科的x86 Calling Conventions文章。更深入一点,如果您使用的是基于ELF的Linux体系结构,请参阅ELF ABI specifications ..
答案 1 :(得分:1)
寄存器和堆栈中的数据只是一系列字节。数据由将字节放入系列的各种指令和应用程序构成,但是有关字节结构的信息不是字节序列的一部分,而是可能有或没有的附加信息。
例如,当编译源代码并且编译器生成二进制代码时,二进制代码将包含其他描述性信息。额外描述性信息的数量取决于所选的编译器选项以及所用工具链中各种其他工具的功能。
例如,如果选项是使用附加信息(如函数名称)创建调试版本,并且工具链支持使用附加信息显示二进制代码,那么您可以非常好地查看变量,函数名称,并在源代码级别单步执行代码。
另一方面,如果选项是创建没有附加信息的优化构建,那么即使工具链可能支持使用附加信息显示二进制代码以在更人性化的视图中呈现二进制代码,因为信息不存在,工具链无法在源代码级别显示二进制代码,显示源代码行,变量,函数名称等。
因此,为了完成您想要做的事情,您需要获得其他信息,以便将二进制代码与其他信息结合起来。没有附加信息,你所拥有的只是内存中的一堆字节。您可以通过各种方式显示这些字节,例如将它们解释为汇编代码或将它们解释为文本字符串。但是,如果没有额外的信息,你只是猜测,无论如何受过教育。
不同平台和操作系统上的不同编译器和工具链将生成不同类型的附加信息。因此,您需要获得有关工具链提供的特定信息的信息。