“让技术被看见 | OceanBase 布道师计划” 由OceanBase主办,墨天轮社区协办,面向广大开发者的年度征文活动。全年 4 轮,以季度为周期进行优秀文章评比,每年 1 届,以年为单位进行最佳布道师评选。目前,首轮技术征文获奖文章已评选出炉,本篇内容为「OceanBase 布道师计划」优秀文章之一,作者 胡呈清,爱可生 DBA 团队成员,擅长故障分析、性能优化,个人博客:[简书 | 轻松的鱼]。
活动仍在进行中,欢迎感兴趣的小伙伴「点击此处」进入活动官网,了解活动详情或进一步投稿。🥳
评委有话说:
封仲淹(OceanBase 开源生态总监、OceanBase 开源社区负责人):爱可生的这篇文章, 非常适合oceanbase 的中高级用户, 当oceanbase 发生问题后, 可以让用户快速定位改问题点, 从而从官方支持或知识库中找到解决办法, 让系统恢复稳定. 就像文章所说, 建议收藏. 是一个非常实用的技巧文章, 文章也详细介绍如何一步一步定位问题, 非常详实。
准备知识
OBServer 进程 Crash(崩溃退出)的常见的原因有:程序 bug、文件损坏、磁盘坏块、内存坏块,是一种比较难分析的故障。
集群部署时会自动配置 coredump 文件,因此发生 Crash 会自动生成 coredump 文件(捕获进程崩溃时内存的文件)。它包含程序在失败时的状态快照、所有线程的堆栈信息,可用于调试,是用来分析 Crash 问题的最佳工具。
但某些时候,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 的原因。希望本文对你有所帮助~
评论

