Han*_*olo 7 gdb g++ stack-trace
我把整个问题分成了小问题:
Fra*_*kH. 10
说到伪代码,您可以将堆栈称为"堆栈堆栈的数组",其中每个堆栈帧都是可变大小的数据结构,您可以表达如下:
template struct stackframe<N> {
uintptr_t contents[N];
#ifndef OMIT_FRAME_POINTER
struct stackframe<> *nextfp;
#endif
void *retaddr;
};
Run Code Online (Sandbox Code Playgroud)
问题是每个功能都有不同<N>- 帧大小不一.
编译器知道帧大小,如果创建调试信息通常会发出这些作为其中的一部分.然后,所有调试器需要做的是找到最后一个程序计数器,在符号表中查找该函数,然后使用该名称在调试信息中查找framesize.将其添加到堆栈指针,然后到达下一帧的开头.
如果使用此方法,则不需要框架链接,即使使用,回溯也能正常工作-fomit-frame-pointer.另一方面,如果你有帧链接,那么迭代堆栈只是跟随链表 - 因为新的堆栈帧中的每个帧指针都由函数序言代码初始化为指向前一个.
如果既没有帧大小信息也没有framepointers,但仍然是符号表,那么您还可以通过一些逆向工程来执行回溯以从实际二进制文件计算帧大小.从程序计数器开始,在符号表中查找它所属的函数,然后从头开始反汇编该函数.隔离函数开头和实际修改堆栈指针的程序计数器之间的所有操作(将任何内容写入堆栈和/或分配堆栈空间).这会计算当前函数的帧大小,所以从stackpointer中减去它,你应该(在大多数架构上)找到在输入函数之前写入堆栈的最后一个字 - 这通常是调用者的返回地址.根据需要重新进行迭代.
最后,您可以对堆栈的内容执行启发式分析 - 隔离堆栈中的所有单词,这些单词位于进程地址空间的可执行映射的段内(因此可以是函数偏移,也就是返回地址),并播放一个 - 如果游戏查找内存,在那里反汇编指令,看看它是否实际上是一个排序的调用指令,如果是,那么是否真的称为'下一个',如果你可以构建一个不间断的调用序列.即使二进制文件被完全剥离,这在某种程度上也是有效的(尽管在这种情况下你可以得到的只是一个返回地址列表).我不认为GDB采用这种技术,但是一些嵌入式低级调试器可以.在x86上,由于指令长度的变化,这非常困难,因为你不能轻易"退后一步"
有些漏洞使得这些算法的简单甚至复杂/穷举实现有时会失败,例如尾递归函数,内联代码等等.gdb源代码可能会给你一些更多的想法:
GDB采用了各种这样的技术.