定位core程序崩溃问题

1、什么是core文件
core文件是程序崩溃时,操作系统记录的内存快照,里面包含信息:
程序崩溃时调用的堆栈信息

信号量
变量
符号
内存
各个线程状态
CPU 寄存器信息

2、遇到程序崩溃怎么定位?
1)先看是哪个业务崩溃,找到崩溃业务的微服务;
2)用gdb工具查看core文件,core文件的信号量和调用栈,在哪个函数内崩溃;
3)查看微服务日志,服务重启前后,或者崩溃前后,打印的日志:

对比服务重启前后是否有异常请求、错误码或异常堆栈信息。

4)定位到具体函数内,查看崩溃的那一行,发生了什么,
一般core有可能是数组越界,空指针,非法访问内存 ,子线程未结束主线程提前退出,多个线程访问同一资源未加锁,变量未初始化,使用未赋值的变量等问题,打印日志,分析函数;

可增加:
0)前置准备:确保 core dump 能生成

确保部署环境开启了 core dump( ulimit -c unlimited)

1.5)是否能复现?尝试复现场景

如果是偶发 core,可以尝试:

用相同请求参数重放

在压力测试下复现

用相同环境和配置部署副本再测

2.5)分析 core 时,获取更多上下文信息

使用 info locals、info args、info threads、thread apply all bt 等命令

3、core常见命令

  1. info locals
查看当前函数的 局部变量 及其值。
📌 用法:
(gdb) info locals

✅ 举例:
void foo() {
    int a = 10;
    std::string s = "hello";
    // 崩溃点
}


调试 core 时定位到 foo() 函数,可以输入:

(gdb) frame 0
(gdb) info locals


输出类似:

a = 10
s = "hello"

作用:快速查看当前帧的所有局部变量,是否有异常值(如空指针、意外 0 值等)

🧠 2. info args

查看当前函数的 参数值

📌 用法:
(gdb) info args

✅ 举例:
void process(int id, Node* ptr) {
    // 崩溃点
}


调试 core 时,切到崩溃帧:

(gdb) frame 0
(gdb) info args


输出类似:

id = 42
ptr = (Node *) 0x0   ← 空指针


✔️ 作用:快速判断函数参数传入是否异常,是不是传了 null 等

🧠 3. info threads

查看程序中的 所有线程列表

📌 用法:
(gdb) info threads

✅ 输出示例:
  Id   Target Id         Frame
* 1    Thread 0x7ffff7bc4740 (LWP 1234)  main at main.cpp:25
  2    Thread 0x7ffff6ab3700 (LWP 1235)  worker at worker.cpp:42
  3    Thread 0x7ffff60b2700 (LWP 1236)  sleeping at <...>


✔️ 作用:

多线程程序崩溃时,能看到 哪个线程崩溃了

可手动切换线程查看其他线程状态

🧠 4. thread apply all bt

对所有线程执行 bt(backtrace),查看每个线程的调用栈

📌 用法:
(gdb) thread apply all bt

✅ 输出示例(多线程栈回溯):
Thread 1 (Thread 0x7f...):
#0  crash_func() at crash.cpp:15
#1  main() at main.cpp:30

Thread 2 (Thread 0x7f...):
#0  __libc_sleep
#1  worker_func() at worker.cpp:42

Thread 3 (Thread 0x7f...):
#0  waiting_func() at wait.cpp:18


✔️ 作用:

一次性查看所有线程栈信息

看看是否是子线程崩溃、死锁、卡住等

非常适合排查多线程场景下的崩溃或卡死问题
命令	作用说明	常用场景
info locals	查看当前函数的局部变量	判断崩溃时变量是否异常
info args	查看当前函数的参数	参数是否为空、非法
info threads	查看程序中所有线程	是否是主线程崩溃?有多少线程
thread apply all bt	查看所有线程的调用栈	多线程崩溃、死锁、竞争分析

4、但如果是发布版本,缺少调试符号信息symbol table,无法使用上述命令进行调试

下面命令帮助定位:

即使没有符号信息,你仍然可以:

工具/命令	用法说明
bt	看栈帧,知道崩溃函数
disas	反汇编当前函数,有时能看出空指针、越界
info registers	看寄存器值,特别是 rip, rsp, rbp, rdi 等
x	查看某个地址的内存内容,比如 x/4x $rsp
thread apply all bt	多线程崩溃定位

1)反汇编:disas

Dump of assembler code for function _ZThn8_NK3Frm4Impl23KernelModuleContextImpl6adm_idEv:
   .........
   0x00007f614c27d36d <+93>:	mov    (%rbx),%rax
   0x00007f614c27d370 <+96>:	mov    %rbx,%rdi
   0x00007f614c27d373 <+99>:	call   *0x10(%rax)
   0x00007f614c27d376 <+102>:	mov    0x38(%rsp),%rax
   0x00007f614c27d37b <+107>:	sub    %fs:0x28,%rax
   0x00007f614c27d384 <+116>:	jne    0x7f614c27d3c6 <_ZThn8_NK3Frm4Impl23KernelModuleContextImpl6adm_idEv+182>
   0x00007f614c27d386 <+118>:	mov    0x0(%rbp),%rax
   0x00007f614c27d38a <+122>:	mov    %rbp,%rdi
=> 0x00007f614c27d38d <+125>:	mov    0xa0(%rax),%rax   崩溃在这一行
   0x00007f614c27d394 <+132>:	add    $0x48,%rsp
   0x00007f614c27d398 <+136>:	pop    %rbx
   0x00007f614c27d399 <+137>:	pop    %rbp
   0x00007f614c27d39a <+138>:	jmp    *%rax
   0x00007f614c27d39c <+140>:	nopl   0x0(%rax)
   0x00007f614c27d3a0 <+144>:	movq   $0x0,0x8(%rsp)
   0x00007f614c27d3a9 <+153>:	mov    %rbx,%rdi

反汇编(disassemble)

把机器码(CPU 能执行的二进制指令)翻译回更易读的汇编语言代码。汇编语言是机器码的“助记符”形式,比如:

机器码可能是:0x48 0x89 0xe5

反汇编后是:mov %rsp, %rbp

为什么“反汇编当前函数”有用?

查看底层执行细节
当源码不可用,或者调试符号缺失时,可以通过反汇编看程序在做什么。

排查异常
通过观察汇编指令,比如访问的内存地址,可以推断是不是空指针访问,或者越界访问。

理解代码行为
有些复杂bug,源码看不清楚,反汇编能帮助理解程序具体执行的指令。
举个例子

假设你在调试一个函数,遇到段错误(Segmentation Fault)。
用 disas 查看当前函数的汇编代码,可能看到某条指令访问了一个无效地址,比如 mov (%rax), %rbx,而 %rax 是 0x0,说明空指针访问。

例:
=> 0x00007f614c27d38d <+125>: mov 0xa0(%rax),%rax 这条说明什么

拆开来看:
0x00007f614c27d38d:这是指令在内存中的地址。
<+125>:这是当前函数的偏移量,表示这条指令距离函数起始地址的偏移125字节。
mov 0xa0(%rax), %rax:这是汇编指令,意思是:
从寄存器 rax 指向的内存地址开始偏移 0xa0(即 160 字节)的位置读取数据,然后把读取到的数据放回寄存器 rax。

这条指令说明了什么?
寄存器 %rax 中存的是某个内存地址。
程序要从这个地址向后偏移160字节的位置取值。
取到的值又写回 %rax。

查看rax寄存器的值

info registers rax
rax = 0x7f61490d4758   (十进制:140055814162264)
rbp = 0x562c0e671308

0x7f61490d4758 是rax寄存器存储的地址,这条指令是取rax存储的地址偏移0xa0位,放到rax内

2)info registers 查看寄存器存储的地址,能看出来谁是空指针,

(gdb) info registers
rax            0x7f61490d4758      140055814162264
rbx            0x562c0eedd740      94747229017920
rcx            0x562c0e0e2140      94747214356800
rdx            0x3                 3
rsi            0x562c0da3d560      94747207390560
rdi            0x562c0e671308      94747220185864
rbp            0x562c0e671308      0x562c0e671308
rsp            0x7f6141623e00      0x7f6141623e00
r8             0x562c0da3d500      94747207390464
r9             0x20                32
r10            0xc                 12
r11            0x7ffe731de080      140730829758592
r12            0x562c0e56a530      94747219109168
r13            0x562c0e56a4d0      94747219109072
r14            0x562c0ee16980      94747228203392
r15            0x7f6141624320      140055685514016
rip            0x7f614c27d38d      0x7f614c27d38d <non-virtual thunk to Frm::Impl::KernelModuleContextImpl::adm_id() const+125>
eflags         0x10246             [ PF ZF IF RF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0

看起来rax存储的寄存器的指针不空,可能是个指针

3、查看这个地址对应的内容:

x/16gx 0x7f61490d4758

(gdb) x/gx 0x7f61490d4758 + 0xa0
0x7f61490d47f8 <_ZTVN5Table4CmDc6SONLog25DftTableSchemaFactoryImplE+240>:	0x0000000000000000
这行输出的含义:

0x7f61490d47f8 是内存地址,等于 0x7f61490d4758 + 0xa0

<_ZTVN5Table4CmDc6SONLog25DftTableSchemaFactoryImplE+240> 这个是 GDB 根据符号表识别出来的符号(名称):

这是一个 C++的虚表(vtable)地址,符号名被C++名字修饰(mangled),大致意思是类 Table::CmDc::SONLog::DftTableSchemaFactoryImpl 的虚函数表中的某个位置,偏移 240 字节。

0x0000000000000000 是这个地址处存放的内容,也就是64位的一个值,是 0。

✅ 正确解释:
项目	说明
0x7f61490d47f8	是一个虚函数表(vtable)中某个槽的地址,即 某个虚函数指针存放的位置
0x0000000000000000	是存储在该地址中的值,也就是这个虚函数指针的指向地址(目标地址)(虚函数表的地址)

结论:

为什么会发生 SIGBUS?

重点: 你访问 0xa0(%rax),想读出虚函数表中对应的函数指针(应该是非0地址),但是你发现它是 0。

而虚函数表中的函数指针如果是 0,后面你再间接调用这个地址(即调用空指针函数)时就会发生异常。

但是你报告的信号是 SIGBUS(总线错误),SIGBUS 代表“非法的内存访问”,通常发生于:

访问未映射的内存页

访问映射的内存页,但是不对齐或大小错误(在某些架构)

访问被内核禁止的内存区域(比如文件映射结尾处)
总结

你当前访问的内存是一个虚函数表的位置,且值为 0,不是合法函数地址。

可能的原因:

你的对象已经被销毁(析构),虚函数表被覆盖了

对象未初始化,导致虚表指针为空

内存被误改

直接跳转到 0x0(空指针)会导致崩溃,可能这里是你崩溃的根源。

4、符号

符号名前缀	含义
_ZTV	虚函数表(vtable)
_ZTI	类型信息(typeinfo)
_ZN	命名空间或类名

5、信号
✅ 信号(signal):操作系统用来通知进程发生了某种事件的机制。比如 SIGSEGV, SIGABRT, SIGFPE 等。

💡 常见的 signal 类型(core 文件中可能出现):
信号编号	名称	含义
6	SIGABRT	异常终止(如 abort())
11	SIGSEGV	段错误(访问非法内存)
8	SIGFPE	浮点异常(除零等)
4	SIGILL	非法指令
7	SIGBUS	总线错误

6、程序崩溃
操作系统检测到程序运行时出现了非法或危险的行为,然后发出一个信号(signal),终止该程序的运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值