backtrace帧编号从0开始,因为#0表示当前正在执行的函数栈帧,外层调用者依次为#1、#2……符合“最内层最先崩溃”的调试直觉,便于快速定位问题源头。

backtrace 输出的帧编号为什么从 0 开始?
因为 backtrace(或简写 bt)把当前正在执行的函数栈帧标为 #0,外层调用者是 #1,再外层是 #2……这和调用顺序相反,但符合“最内层最先崩溃/停住”的调试直觉。你看到 #0 函数出错,真正问题可能藏在 #2 传进来的非法指针里。
常见错误现象:bt 显示一堆 ?? 或地址而非函数名,说明没用 -g 编译,或用了 -fomit-frame-pointer 优化(尤其在 Release 构建中)。此时 bt full 也救不了——帧信息本身已丢失。
- 必须用
gcc -g -O0或g++ -g -O0重新编译,-O0比-g还关键:优化会内联、重排、删变量,让栈帧语义错乱 - Android NDK 调试时,确保
APP_STRIP_MODE := none且APP_DEBUGGABLE := true,否则libxxx.so的符号被 strip 掉,bt只剩地址
frame 和 info frame 的区别在哪?
frame 只打印当前栈帧的函数名、源码位置、参数值(如果可读),适合快速定位“我在哪”;info frame 打印该帧的完整元数据:保存的 rbp 值、rip(返回地址)、栈范围(sp 到 bp)、是否使用了 red zone,甚至该帧在内存中的起始地址。
使用场景:当你怀疑某个参数被上层函数污染,但 print arg 显示正常时,用 info frame 看该帧的 rip 是否指向预期指令,再配合 x/2i $rip 查看汇编上下文。
-
frame 2切到第 3 层调用帧后,info args和info locals才能正确读取那层的变量——不切换帧,print默认只查当前帧 - 若
info frame显示saved rip = 0x0或地址异常低(如0x7fffff...),大概率是栈溢出或野指针覆写了返回地址
up/down 切帧时为什么局部变量突然“不见了”?
不是变量真丢了,是 GDB 在不同帧里查找变量的作用域不同。down 1 进入被调函数后,上层函数的局部变量(比如 main 里的 int a = 5)按 C/C++ 语义已不可见——它们存在栈上,但调试信息没标记为“当前作用域可用”。
性能影响:频繁 up/down 不耗性能,但每次切换后执行 info locals 会触发符号表查询,若二进制无调试信息,会明显卡顿。
- 想跨帧看变量?用
print ((struct my_s *)$rbp+1)->field手动算偏移——但这要求你清楚栈帧布局,且 ABI 未变(x86_64 下rbp+16常是第一个局部变量) - 更稳的方式:在关键帧设断点,用
display命令把想盯的变量加入自动监视列表,切帧时它会实时刷新
bt full 为什么有时卡住或输出极长?
bt full 不仅打印每帧函数名和参数,还会对每个局部变量调用 print——如果某帧里有个大数组、std::vector 或递归结构体,GDB 会试图展开全部元素,导致卡死或输出几万行。
容易踩的坑:在多线程程序里执行 bt full,GDB 可能因锁竞争卡在某个线程的栈扫描上;或者遇到 std::string 内部指针损坏,print 尝试解引用时触发二次崩溃。
- 先用
bt 5看最内层 5 帧,确认问题范围;再对可疑帧单独执行frame 3→info locals→ 逐个print关键变量 - 临时禁用自动展开:
set print elements 50限制数组打印长度,set print pretty off关闭结构体缩进,减少干扰
bt 输出,在 stepi 单步一条汇编后就可能变化——尤其是涉及 push/pop、call、ret 的指令。别只信第一眼看到的帧编号,结合 info registers 看 rsp 和 rbp 的实际值,才能确认栈是否真的连续。


















