崩溃点一定在bt输出的最顶层栈帧(#0)中,它代表cpu停止执行时的指令地址;若#0为系统库函数,则需查看#1定位业务代码问题源头。

崩溃点一定在 bt 输出的最顶层栈帧里
不是“哪个函数调用最多”,而是看 bt 命令输出的第一行(#0),它代表 CPU 停止执行时正在运行的指令地址——也就是真正出错的位置。这个函数未必是你写的业务逻辑,很可能是 memcpy、strlen、std::string::assign 这类底层操作,但错误根源往往在它的调用者传入了非法参数。
- 执行
gdb ./myapp core后,第一件事就是输bt,别跳过 - 如果
#0显示的是系统库(如__memcpy_ssse3或__GI_raise),说明程序已因信号终止,要往#1看——那里才是你代码里触发问题的地方 -
bt full比bt多显示局部变量值,对判断空指针或越界索引更直接 - 若
#0是??(问号),大概率是没带-g编译,或符号被 strip 过;此时info registers和x/10i $rip可辅助看崩溃时的汇编上下文
frame 0 切进去后,info locals 和 print 才开始起作用
光有栈帧编号没用,得切进去才能查现场变量。很多同学输完 bt 就停了,其实关键信息全在 frame 0 的上下文中。
- 先执行
frame 0(确保你在最顶帧),再输info locals——能立刻看到崩溃函数内所有局部变量的值,比如ptr = 0x0或len = -1 - 用
print *(char**)ptr类命令验证指针是否有效,避免只看变量名就下结论 - 如果崩溃发生在内联函数里,
bt可能不显示真实行号,这时用list查看当前源码上下文,或disassemble看汇编指令和寄存器值 - 注意:优化编译(如
-O2)可能导致info locals显示<optimized out></optimized>,调试前务必确认用了-O0 -g
多线程环境下,thread apply all bt 比单看主线程更可靠
段错误不一定发生在主线程。子线程访问已释放内存、锁竞争、或主线程退出后子线程继续运行,都可能让 bt 显示的“最顶层”不是真正的问题源头。
- 先用
info threads看有多少线程活着,注意状态是不是running或stopped - 执行
thread apply all bt,逐个检查每个线程的栈,特别关注那些卡在malloc、pthread_mutex_lock或你自己业务函数里的线程 - 某个线程的
#0如果是__lll_lock_wait,说明可能死锁;如果是free附近崩溃,大概率是 use-after-free - 不要默认认为 crash 信号来自主线程——
info signals能查哪些信号被阻塞或忽略,有时 SIGSEGV 被某个线程捕获后没处理好,也会误导定位
core 文件没生成?ulimit -c 和 /proc/sys/kernel/core_pattern 必须一起查
找不到 core,后面所有 GDB 操作都是空谈。生产环境里最常踩的坑不是不会用 bt,而是根本没拿到现场快照。
- 崩溃后立刻在相同用户下运行
ulimit -c,输出为0就说明限制没开;临时生效用ulimit -c unlimited,但仅限当前 shell - 查
cat /proc/sys/kernel/core_pattern,如果输出是|/usr/lib/systemd/systemd-coredump,说明走的是 systemd-coredump 机制,日志在coredumpctl list里找,不是直接写文件 - 容器中常见问题:挂载目录权限不对、
/tmp不可写、或设置了securityContext.runAsNonRoot: true但 core_pattern 指向 root-only 目录 - SUID 程序(如某些网络工具)默认禁止生成 core,需设
echo 2 > /proc/sys/fs/suid_dumpable
真正难的不是看懂 bt 输出,而是确认你看到的 #0 是原始错误点,而不是错误传播后的表象。比如空指针解引用会先触发 SIGSEGV,但如果你在 signal handler 里又做了非法操作,GDB 会停在 handler 里——这时候得结合 info signals 和 where 判断信号来源。











