gdb如何重建Ctract stacktrace?

我把整个问题分成了小问题:

  • GDB能够使用什么样的不同算法重建堆栈跟踪?
  • 每个堆栈跟踪重建算法如何在高层次上工作? 的优点和缺点?
  • 什么样的元信息编译器需要在程序中为每个栈跟踪重构算法提供工作?
  • 还有相应的g ++编译器开关,用于启用/禁用特定算法?

  • 说起伪代码,你可以将堆栈称为“一个堆栈栈帧数组”,其中每个栈帧都是一个可变大小的数据结构,你可以这样表示:

    template struct stackframe<N> {
        uintptr_t contents[N];
    #ifndef OMIT_FRAME_POINTER
        struct stackframe<> *nextfp;
    #endif
        void *retaddr;
    };
    

    问题是每个功能都有不同的<N> - 帧大小不一。

    编译器知道帧大小,并且如果创建调试信息通常会将这些信息作为其中的一部分发出。 所有调试器然后需要做的是定位最后一个程序计数器,在符号表中查找函数,然后使用该名称查找调试信息中的帧大小。 将其添加到堆栈指针,然后到达下一帧的开始处。

    如果使用这种方法,则不需要帧链接,即使使用-fomit-frame-pointer ,回溯也可以正常工作。 另一方面,如果你有帧链接,那么迭代堆栈就是在链表之后 - 因为新堆栈中的每个帧指针都由函数序言代码初始化,指向前一个。

    如果您既没有帧大小信息也没有帧指向器,但仍然是符号表,那么您也可以通过一些逆向工程来执行反向追踪以计算实际二进制帧的帧大小。 从程序计数器开始,在符号表中查找它所属的函数,然后从头开始分解函数。 隔离函数的开头和实际修改堆栈指针的程序计数器之间的所有操作(将任何内容写入堆栈和/或分配堆栈空间)。 它计算当前函数的帧大小,因此将其从堆栈指针中减去,并且(在大多数体系结构中)应该在函数输入前找到写入堆栈的最后一个字 - 这通常是调用者的返回地址。 根据需要重新进行重复。

    最后,您可以对堆栈内容进行启发式分析 - 隔离堆栈中位于进程地址空间的可执行映射段内的所有字(因此可能是函数偏移或返回地址)如果游戏查找内存,在那里分解指令并查看它是否实际上是一种排序的调用指令,如果是,那么是否真的称为“下一个”,以及是否可以从中构建不间断的调用序列。 即使二进制文件被完全删除(尽管在这种情况下你可以得到的是一个返回地址列表),这在某种程度上是有效的。 我不认为GDB使用这种技术,但是一些嵌入式低级调试器可以。 在x86上,由于指令长度不同,这是非常困难的,因为您不容易通过指令流“退后”,但在指令长度固定的RISC上,例如在ARM上,这更简单。

    有些漏洞使得这些算法的简单甚至复杂/穷举的实现有时会出现,如尾递归函数,内联代码等等。 gdb的源代码可能会给你更多的想法:

    http://sourceware.org/cgi-bin/cvsweb.cgi/src/gdb/frame.c?rev=1.287&content-type=text/x-cvsweb-markup&cvsroot=src

    GDB采用了多种这样的技术。

    链接地址: http://www.djcxy.com/p/86029.html

    上一篇: How gdb reconstructs stacktrace for C++?

    下一篇: How to make gdb get stacktrace repeatably?