gpt4 book ai didi

c++ - C 堆栈跟踪中缺少函数调用

转载 作者:太空狗 更新时间:2023-10-29 20:37:36 25 4
gpt4 key购买 nike

我正在我的代码中导入一个堆栈跟踪 C 代码(在 Stack Overflow 的某处找到)以跟踪内存块的分配位置:

struct layout
{
struct layout *ebp;
void *ret;
};

struct layout *fr;
__asm__("movl %%ebp, %[fp]" : /* output */ [fp] "=r" (fr));
for (int i=1 ; i<8 && (unsigned char*) fr > dsRAM; i++) {
x[i] = (size_t) fr->ret;
fr = fr->ebp;
}

一切运行良好,除了在某些调用中,代码在堆栈顶部附近缺少一些函数,例如GDB 将报告:

  1. main.cpp 中的 malloc()
  2. 来自 libstdc++.so.6 的运算符 new()
  3. BasicScript.cpp 中的 TestBasicScript()
  4. main() 在 main.cpp 中

虽然代码用 malloc、new 运算符和 main() 的地址填充 x[],但缺少 TestBasicScript。

代码由 g++ 4.5.1(用于自制控制台编程的旧开发工具包)编译,带有以下标志:

CFLAGS += -I libgeds/source/ -I wrappers -I $(DEVKITPRO)/include -DARM9 \
-include wrappers/nds/system.h -include wrappers/fake.h
CFLAGS += -m32 -Duint=uint32_t -g -Wall -Weffc++ -fno-omit-frame-pointer

我尝试改用 __builtin_return_address(),但我得到的结果几乎相同,但代码更长。

编辑: 我注意到我系统地缺少 operator new 的调用者,如果 _Znwj 的代码没有设置堆栈框架,这可以解释。所以问题列表变成了:

  • 如果 TestBasicScript() 函数调用不在堆栈帧列表中,GDB 如何找到它?

  • 如何配置链接步骤以便使用调试友好的 libstdc++ 变体(如果有)?

原始子问题“是否有编译时选项保证我可以 100% 跟踪对我的 malloc 克隆的调用?”因此@chqrlie 回答:-O0 是我所需要的。但只有应用于所有我的二进制文件,包括共享库,它才会有效。

最佳答案

某些帧可能被省略的原因有很多,例如内联和优化(尽管提供的 CFLAGS 不包含优化标志,默认情况下是 AFAIK 无优化)。

无论如何,对于 GCC,通过使用 backtrace() 内置了堆栈遍历支持。 , backtrace_symbols()并可能结合 abi::__cxa_demangle() ,您也可以尝试这些。

其他选项是使用 libunwind ,我也尝试过它并取得了很好的结果(在它的源代码中你可以看到一些有用的应用程序内堆栈遍历技术)。

以上所有优化(发布)可执行文件通常不能很好地工作,特别是如果它们不包含调试信息(尽管它可能已经生成并存储在一边)打印的堆栈将无用(除了跳过框架因为优化)。

即使对优化代码也适用的终极技术是生成核心转储。那里有关于堆栈的所有信息(二进制文件本身不需要包含调试信息,它可以放在一边,只用于离线检查核心),作为堆栈上所有变量的奖励值,信息关于当前正在运行的所有线程等对于跟踪内存分配,它可能有点矫枉过正(它也很慢),但有时它非常有用。在我的一个项目中,我创建了此类核心转储程序的有效实现,它仍然存在于生产代码中。

请注意,您实际上可以在不终止应用程序的情况下生成应用程序的核心转储 - 我创建的实现基本上按如下方式工作:

  • fork()应生成核心转储的过程
  • 子进程调用abort()生成核心转储( fork 进程的调用堆栈与原始进程相同),即只有 fork 进程被abort()终止。
  • 原始父进程使用waitpid()等到子进程生成核心转储并终止(使用保护计数器不会永远等待)
  • 然后原始进程继续运行(并将已生成诊断核心以及用于生成核心的 fork 进程的 PID 写入日志)

事实证明,在发布生产应用程序需要诊断堆栈跟踪的某些情况下,这非常有效。

编辑:我也尝试过的另一个选项是使用 ptrace() (如果我没记错的话,这也是上面提到的 libunwind 使用的技术之一,实际上 GDB 也使用过)。其工作方式类似 - 通过 fork() 生成子进程然后调用ptrace(PTRACE_TRACEME)在那里;然后父进程可以发出各种 ptrace()调用以检查子堆栈(恰好与 fork() 点的父堆栈相同)。我认为 libunwind 源代码包含它的用途,因此您可以在那里检查它。

关于c++ - C 堆栈跟踪中缺少函数调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34298882/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com