作者:胡呈清,爱可生 DBA 团队成员,擅长故障分析、性能优化。
背景
Server 进程崩溃的常见原因包括程序bug、文件损坏、磁盘坏块以及内存坏块,这类故障通常较难分析和定位。
在OceanBase 集群部署时,系统会自动配置coredump文件。一旦 OBServer进程崩溃,就会自动生成coredump文件,该文件能够捕获进程崩溃时的内存状态。它包含了程序失败时的状态快照以及所有线程的堆栈信息,适合用于调试,也是分析崩溃问题的有力工具。
但某些时候,coredump 文件 不一定能顺利生成,这个时候就需要从 observer.log 中获取崩溃时的堆栈,分析崩溃位置的代码,以此来寻找原因。本文介绍的就是这个方法。
本文的方法适用于 OceanBase 的所有版本。
分析步骤
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
拓展阅读:
本案例的调用栈为:
...
->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。
函数的作用
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。
崩溃位置的这段代码的逻辑与 bug 描述吻合:在处理 execute 协议时,send long data 协议还没有把 param_data 信息处理完,因此 execute 协议在转换 param_data时读到了空指针,引发了 crash。
小结
通常,按照以上五个步骤对日志进行分析,可以快速定位导致 OBServer 进程 Crash 的原因。希望本文对你有所帮助~