OB 运维 | 进程崩溃定位难?日志分析五步安!

科技   科技   2024-10-22 16:30   上海  

作者:胡呈清,爱可生 DBA 团队成员,擅长故障分析、性能优化,个人博客:[简书 | 轻松的鱼],欢迎讨论。

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

本文约 1400 字,预计阅读需要 5 分钟。


1准备知识

OBServer 进程 Crash(崩溃退出)的常见的原因有:程序 bug、文件损坏、磁盘坏块、内存坏块,是一种比较难分析的故障。

集群部署时会自动配置 coredump 文件,因此发生 Crash 会自动生成 coredump 文件(捕获进程崩溃时内存的文件)。它包含程序在失败时的状态快照、所有线程的堆栈信息,可用于调试,是用来分析 Crash 问题的最佳工具。

但某些时候,coredump 文件 不一定能顺利生成,这个时候就需要从 observer.log 中获取崩溃时的堆栈,分析崩溃位置的代码,以此来寻找原因。本文介绍的就是这个方法。

本文发布时,该方法适用于 OceanBase 的所有版本。

2分析步骤

1. 找到 Crash 日志

OBServer 在 Crash 后会在 observer.log 里打这么一段类似的日志,基本上你只要去搜索 “CRASH ERROR” 关键字就行:

CRASH ERROR!!! sig=11, sig_code=2, \
sig_addr=7f3edd31dffb, timestamp=1725496052323606, \
tid=57605, tname=TNT_L0_1002, \
trace_id=20970917872454-1707004480400037, \
extra_info=((null)), lbt=0x9baead8 \
0x9b9f358 0x7f43d58e562f \
0x7f43d52525fc 0x95eeda9 \
0x95ec568 0x95e6c0c \
0x95e4c33 0x9cbf4c7 \
0x93be9ee 0x939e320 \
0x93bd64e 0x939c105 \
0x939c6e6 0x2cff1c1 \
0x9918a74 0x9917461 0x9913f1e

这里手动增加换行,提高阅读性

2. 得到崩溃的线程堆栈

堆栈信息可以通过解析内存地址得到(每个内存地址对应一个栈桢):

addr2line -pCfe /home/admin/oceanbase/bin/observer \
0x9baead8 0x9b9f358 0x7f43d58e562f 0x7f43d52525fc \
0x95eeda9 0x95ec568 0x95e6c0c 0x95e4c33 0x9cbf4c7 \
0x93be9ee 0x939e320 0x93bd64e 0x939c105 0x939c6e6 \
0x2cff1c1 0x9918a74 0x9917461 0x9913f1e

这里手动增加换行,提高阅读性

输出如下:

  • 堆栈信息是从下往上看,最上面 4 行是处理 Crash 的固定栈(不用管)
  • 崩溃的位置在第 5 行 ObMPStmtExecute::copy_or_convert_str 这个函数
safe_backtrace at ??:?
oceanbase::common::coredump_cb(int, siginfo_t*) at ??:?
?? ??:0
?? ??:0
oceanbase::observer::ObMPStmtExecute::copy_or_convert_str(oceanbase::common::ObIAllocator&, oceanbase::common::ObCollationType, oceanbase::common::ObCollationType, oceanbase::common::ObString const&, oceanbase::common::ObString&, long) at ??:?
oceanbase::observer::ObMPStmtExecute::parse_basic_param_value(oceanbase::common::ObIAllocator&, unsigned int, oceanbase::common::ObCharsetType, oceanbase::common::ObCollationType, oceanbase::common::ObCollationType, char const*&, oceanbase::common::ObTimeZoneInfo const*, oceanbase::common::ObObj&) at ??:?
oceanbase::observer::ObMPStmtExecute::parse_param_value(oceanbase::common::ObIAllocator&, unsigned int, oceanbase::common::ObCharsetType, oceanbase::common::ObCollationType, oceanbase::common::ObCollationType, char const*&, oceanbase::common::ObTimeZoneInfo const*, oceanbase::sql::TypeInfo*, oceanbase::sql::TypeInfo*, oceanbase::common::ObObjParam&, short) at ??:?
oceanbase::observer::ObMPStmtExecute::before_process() at ??:?
oceanbase::rpc::frame::ObReqProcessor::run() at ??:?
oceanbase::omt::ObWorkerProcessor::process_one(oceanbase::rpc::ObRequest&, int&) at ??:?
oceanbase::omt::ObWorkerProcessor::process(oceanbase::rpc::ObRequest&) at ??:?
oceanbase::omt::ObThWorker::process_request(oceanbase::rpc::ObRequest&) at ??:?
oceanbase::omt::ObThWorker::worker(long&, long&, int&) at ??:?
non-virtual thunk to oceanbase::omt::ObThWorker::run(long) at ??:?
oceanbase::lib::CoKThreadTemp<oceanbase::lib::CoUserThreadTemp<oceanbase::lib::CoSetSched> >::start()::{lambda()#1}::operator()() const at ??:?
oceanbase::lib::CoSetSched::Worker::run() at ??:?
oceanbase::lib::CoRoutine::__start(boost::context::detail::transfer_t) at ??:?
trampoline at safe_snprintf.c:?

3. 获取崩溃点具体的代码位置

如何确认具体执行到 ObMPStmtExecute::copy_or_convert_str 函数的哪一行?需要在 debug 版本上使用 gdb (要求不低于 9.0 版本)来解析内存地址:

##下载对应版本的 debug 安装包(企业版需要问官方获取)
https://mirrors.aliyun.com/oceanbase/community/stable/el/7/x86_64/

##安装debug包
rpm2cpio oceanbase-ce-debuginfo-3.1.5-100010012023060910.el7.x86_64.rpm |cpio -div

##然后用gdb打开二进制文件
gdb ./usr/lib/debug/home/admin/oceanbase/bin/observer.debug

##解析内存地址
(gdb) list *0x95eeda9
0x95eeda9 is in oceanbase::observer::ObMPStmtExecute::copy_or_convert_str(oceanbase::common::ObIAllocator&, oceanbase::common::ObCollationType, oceanbase::common::ObCollationType, oceanbase::common::ObString const&, oceanbase::common::ObString&, long) (./src/observer/mysql/obmp_stmt_execute.cpp:1428).
(gdb) list *0x95ec568
0x95ec568 is in oceanbase::observer::ObMPStmtExecute::parse_basic_param_value(oceanbase::common::ObIAllocator&, unsigned int, oceanbase::common::ObCharsetType, oceanbase::common::ObCollationType, oceanbase::common::ObCollationType, char const*&, oceanbase::common::ObTimeZoneInfo const*, oceanbase::common::ObObj&) (./src/observer/mysql/obmp_stmt_execute.cpp:1237).
(gdb) list *0x95e6c0c
0x95e6c0c is in oceanbase::observer::ObMPStmtExecute::parse_param_value(oceanbase::common::ObIAllocator&, unsigned int, oceanbase::common::ObCharsetType, oceanbase::common::ObCollationType, oceanbase::common::ObCollationType, char const*&, oceanbase::common::ObTimeZoneInfo const*, oceanbase::sql::TypeInfo*, oceanbase::sql::TypeInfo*, oceanbase::common::ObObjParam&, short) (./src/observer/mysql/obmp_stmt_execute.cpp:1372).
(gdb) list *0x95e4c33
0x95e4c33 is in oceanbase::observer::ObMPStmtExecute::before_process() (./src/observer/mysql/obmp_stmt_execute.cpp:512).
507 in ./src/observer/mysql/obmp_stmt_execute.cpp

拓展阅读:《数据库源码学习调试利器之 CGDB

本案例的调用栈为:

...
->ObMPStmtExecute::before_process() 
-->ObMPStmtExecute::parse_param_value(oceanbase::common::ObIAllocator&, unsigned int, oceanbase::common::ObCharsetType, oceanbase::common::ObCollationType, oceanbase::common::ObCollationType, char const*&, oceanbase::common::ObTimeZoneInfo const*, oceanbase::sql::TypeInfo*, oceanbase::sql::TypeInfo*, oceanbase::common::ObObjParam&, short)
--->ObMPStmtExecute::parse_basic_param_value(oceanbase::common::ObIAllocator&, unsigned int, oceanbase::common::ObCharsetType, oceanbase::common::ObCollationType, oceanbase::common::ObCollationType, char const*&, oceanbase::common::ObTimeZoneInfo const*, oceanbase::common::ObObj&)
---->ObMPStmtExecute::copy_or_convert_str(oceanbase::common::ObIAllocator&, oceanbase::common::ObCollationType, oceanbase::common::ObCollationType, oceanbase::common::ObString const&, oceanbase::common::ObString&, long

4. 代码分析

最后崩溃在 ObMPStmtExecute::copy_or_convert_str 函数,代码位置 obmp_stmt_execute.cpp:1428

在该文件的第 1428 行

函数的作用

ObMPStmtExecute::copy_or_convert_str 函数的主要功能是根据给定的字符集类型,将输入的字符串 src (statement协议里的请求参数)进行复制或字符集转换,并将结果保存在 out 中。崩溃信息中 sig=11,也就是 signal 11,表示程序访问了无效的内存地址,通常是由于空指针引用或者访问了已经释放的内存。

崩溃位置代码 MEMCPY(buf + extra_buf_len, src.ptr(), src.length()); 意思是:通过 MEMCPY 函数将源字符串的内容复制到分配的内存区域中:

  • buf + extra_buf_len :复制时目标地址是缓冲区指针 buf 的偏移地址(加上 extra_buf_len)。
  • src.ptr() :源字符串的指针。
  • src.length() :源字符串的长度,表示要复制的字节数。

所以这里基本可以确认 src.ptr() 是个空指针,如果有 coredump 文件,只需要用 gdb 打印一下这个指针变量就知道了。

5. 搜索知识库

然后带着 copy_or_convert_str 关键字(也就是崩溃的那个函数)搜一下官方的知识库,找到对应了的 bug[1]

崩溃位置的这段代码的逻辑与 bug 描述吻合:在处理 execute 协议时,send long data 协议还没有把 param_data 信息处理完,因此 execute 协议在转换 param_data时读到了空指针,引发了 crash。

3小结

通常,按照以上五个步骤对日志进行分析,可以快速定位导致 OBServer 进程 Crash 的原因。希望本文对你有所帮助~

参考资料

[1]

示例 bug: https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000000430545?back=kb

本文关键字:#OceanBase# #OBServer# #日志# #Crash#



一文讲透 OceanBase 单机版【建议收藏】
如何有效使用 outline 功能?
OceanBase 4.X-2F1A 仲裁高可用方案初探
Oracle 中部分不兼容对象迁移到 OceanBase 的处理方式
一个关于 NOT IN 子查询的 SQL 优化案例
Join 估行不准选错执行计划该如何优化?
一个提升本地索引性能的 SQL 优化案例
一则 Oracle 迁移到 OB 后存储过程语法报错问题诊断案例
优化器走对,执行计划导致查询快 1000 倍
如何通过日志观测冻结转储流程?


✨ Github:https://github.com/actiontech/sqle

📚 文档:https://actiontech.github.io/sqle-docs/

💻 官网:https://opensource.actionsky.com/sqle/

👥 微信群:请添加小助手加入 ActionOpenSource

🔗 商业支持:https://www.actionsky.com/sqle


爱可生开源社区
爱可生开源社区,提供稳定的MySQL企业级开源工具及服务,每年1024开源一款优良组件,并持续运营维护。
 最新文章