繁体   English   中英

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

[英]Function call missing from C stack trace

我正在我的代码中导入堆栈跟踪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的operator new()
  3. BasicScript.cpp中的TestBasicScript()
  4. main.cpp上的main()

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

该代码由g ++ 4.5.1(旧的devkit for homebrew console programming)编译,带有以下标志:

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的调用operator new ,如果_Znwj的代码没有设置堆栈帧,可以解释。 所以问题清单变成了:

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

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

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

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

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

其他选择是使用libunwind ,我也尝试了相当不错的结果(在其源代码中,您可以看到一些有用的技术,用于应用程序内堆栈行走)。

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

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

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

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

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

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

编译器可能并不总是生成一个堆栈帧,其中%ebp指向前一帧。 对于某些函数,它可以生成使用基于%esp的寻址来检索参数的代码,对于其他函数,它可以使用跳转而不是调用/返回序列来生成尾递归。 您尝试扫描它时的堆栈跟踪可能不完整。

尝试在禁用优化的情况下编译整个项目( -O0 )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM