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将报告:
- main.cpp上的malloc()
- libstdc++.so.6中的运算符new()
- 在BasicScript.cpp上测试BasicScript()
- main.cpp上的main()
当代码用malloc、new运算符和main()的地址填充x[]
时,缺少TestBasicScript。
该代码由g++4.5.1(用于自制控制台编程的旧devkit)编译,带有以下标志:
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()
结合使用,您也可以尝试这些功能。
另一种选择是使用libunfold,我也尝试过,结果很好(在它的源代码中,你可以看到一些有用的应用内堆栈遍历技术)。
以上所有内容通常都不能很好地与优化(发布)的可执行文件配合使用,特别是如果它们不包含调试信息(尽管它可能已经生成并存储在一旁),则打印的堆栈将毫无用处(除了由于优化而跳过的帧之外)。
一种甚至适用于优化代码的终极技术是生成核心转储。在那里,你有关于堆栈的所有信息(二进制文件本身不需要包含debuginfo,它可以放在一边,只用于离线检查核心),以及堆栈上所有变量的额外值,关于当前运行的所有线程的信息等。对于跟踪内存分配来说,这可能有些过头了(它也很慢),但有时它可能非常有用。在我的一个项目中,我创建了这样一个核心dumper的工作实现,它仍然存在于生产代码中。
请注意,您实际上可以在不终止应用程序的情况下生成应用程序的核心转储——我创建的实现基本上如下所示:
fork()
应生成堆芯转储点的进程- 子进程调用
abort()
生成核心转储(分叉进程的调用堆栈与原始进程相同),即只有分叉进程被abort()
终止 - 原始父进程使用
waitpid()
等待,直到子进程生成核心转储并终止(使用保护计数器以避免永远等待) - 然后原始进程继续运行(并在日志中写入已生成诊断核心以及用于生成核心的分叉进程的PID)
在发布生产应用程序需要诊断堆栈跟踪的某些情况下,这种方法效果非常好。
EDIT:我也尝试过的另一个选项是使用ptrace()
(如果我记得很清楚的话,这也是上面提到的libunfold使用的技术之一,实际上也是GDB使用的技术)。其工作方式类似——通过fork()
生成一个子进程,然后在其中调用ptrace(PTRACE_TRACEME)
;父进程然后可以发出各种CCD_ 15调用来检查子进程的堆栈(其恰好与父进程在CCD_。我认为libunfind源代码包含它的用途,所以您可以在那里检查它。
编译器可能并不总是生成%ebp
指向前一帧的堆栈帧。对于某些函数,它可能会生成使用基于%esp
的寻址来检索参数的代码,对于其他函数,它可以使用跳转而不是调用/ret序列来生成尾部递归。当您尝试扫描堆栈跟踪时,它可能不完整。
尝试在禁用优化的情况下编译整个项目(-O0
)。
- C++析构函数调用两次,堆栈分配的复合对象
- 以下代码如何工作以每次为唯一调用堆栈唯一实例化模板函数?
- 堆栈展开如何与析构函数调用有关?
- 在堆栈已满之前,在C/C 中以最大递归函数调用并给出分段故障
- 函数调用时局部变量在堆栈中的组织方式
- 从调用堆栈获取函数指针
- 汇编函数调用是否会导致所有寄存器被推送到堆栈中
- 线程的调用堆栈中充满了相同的函数调用--curl_inet_ntop()
- 如何跟踪递归函数的调用堆栈使用情况
- 何时构造函数调用中的堆栈对象
- 从调用堆栈地址获取函数参数
- 析构函数在(堆栈)变量之间的赋值时调用
- C堆栈跟踪中缺少函数调用
- 如何使堆栈在函数调用中工作
- 如何使用客户端调用堆栈中的地址从 PDB 检索文件/函数/行号?
- DebugDiag调用堆栈不显示调用堆栈中函数的行号
- c++ 函数调用的参数推送顺序不反映堆栈中参数的地址
- 在函数调用时获得堆栈溢出
- 构造函数/析构函数在堆栈上的调用顺序
- 理解C/ c++中函数调用的堆栈框架