我希望我的异常处理程序和调试函数能够打印调用堆栈回溯,基本上就像 glibc 中的 backtrace() 库函数一样。不幸的是,我的 C 库(Newlib)没有提供这样的调用。
我有这样的事情:
#include <unwind.h> // GCC's internal unwinder, part of libgcc
_Unwind_Reason_Code trace_fcn(_Unwind_Context *ctx, void *d)
{
int *depth = (int*)d;
printf("\t#%d: program counter at %08x\n", *depth, _Unwind_GetIP(ctx));
(*depth)++;
return _URC_NO_REASON;
}
void print_backtrace_here()
{
int depth = 0;
_Unwind_Backtrace(&trace_fcn, &depth);
}
这基本上可以工作,但生成的跟踪并不总是完整的。例如,如果我这样做
int func3() { print_backtrace_here(); return 0; }
int func2() { return func3(); }
int func1() { return func2(); }
int main() { return func1(); }
回溯只显示 func3() 和 main()。(这是一个玩具示例,但我检查了反汇编并确认这些功能都在这里完整,没有优化或内联。)
更新:我在旧的 ARM7 系统上尝试了这个回溯代码,但使用相同(或至少尽可能等效)的编译器选项和链接器脚本,它打印出正确的完整回溯(即 func1 和 func2 没有丢失)和事实上,它甚至可以追溯到过去的 main 引导初始化代码。所以大概问题不在于链接描述文件或编译器选项。(另外,通过反汇编确认在此 ARM7 测试中也没有使用帧指针)。
代码是用 -fomit-frame-pointer 编译的,但我的平台(裸机 ARM Cortex M3)定义了一个无论如何都不使用帧指针的 ABI。(该系统的早期版本使用 ARM7 上的旧 APCS ABI,具有强制堆栈帧和帧指针,以及类似此处的回溯,效果很好)。
整个系统使用 -fexception 编译,确保 _Unwind 使用的必要元数据包含在 ELF 文件中。(我认为_Unwind 是为异常处理而设计的)。
所以,我的问题是: 在使用 GCC 的嵌入式系统中,是否有一种“标准”、可接受的方式来获得可靠的回溯?
如有必要,我不介意乱用链接器脚本和 crt0 代码,但不想让工具链本身有任何机会。
谢谢!