热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何分析crash的backtrace

MySQL异常退出往往会会在error.log中打印backtrace信息,我们从这个backtrace中可以得到一些异常的原因,例如断言错误,空指针内容的访问等。顺着这些信息排查

MySQL异常退出往往会会在error.log中打印backtrace信息,我们从这个backtrace中可以得到一些异常的原因,例如断言错误,空指针内容的访问等。顺着这些信息排查,我们一般再结合代码逻辑来做推断,写测试用例重现,再打补丁,再验证等过程。 但是,线上早期部

MySQL异常退出往往会会在error.log中打印backtrace信息,我们从这个backtrace中可以得到一些异常的原因,例如断言错误,空指针内容的访问等。顺着这些信息排查,我们一般再结合代码逻辑来做推断,写测试用例重现,再打补丁,再验证等过程。

但是,线上早期部署的MySQL编译参数不太规范,导致一些MySQL crash的backtrace看起来不是那么透明,非常难懂,甚至一点意义也没有。这给我们排查问题带来非常大的不便。当然,这个问题已经解决,我们采用google-breakpad来获取MySQL crash时的mini-dump,但这是后话,在此不展开。

那么,这种backtrace就真的没有什么信息可以挖掘吗?不一定。下面我们就以这周发线上的一个故障来分析。

线上一台5.5版本的备库跑了3月之久突然就crash,crash的backtrace为:

/u01/mysql/bin/mysqld(my_print_stacktrace+0x39)[0x7b1b69]
/u01/mysql/bin/mysqld(handle_segfault+0x43c)[0x4fa39c]
/lib64/libpthread.so.0[0x344dc0f520]
/u01/mysql/bin/mysqld[0x7fb4c1]
/lib64/libpthread.so.0[0x344dc077e1]
/lib64/libc.so.6(clone+0x6d)[0x344d8e68ed

这个backtrace就是典型早期编译部署的MySQL,backtrace信息很难看懂。但是,凭经验和一些常识,这一个调用关系非常简单的线程,因为backtrace中创建的pthread只调用了一次mysqld的函数。那么,我猜测这个线程可能是一个后台线程。mysqld层显示的地址0x7fb4c1到底是哪行代码,对我们分析问题非常关键。

猜测后就该验证了,GDB出马!用GDB在出问题的那台机器上来玩转下反汇编。
注意:
1. 不要在业务高峰期执行GDB相关操作!
2. 一定要对所执行的GDB动作非常熟悉!
3. 最好通知对应的DBA!

那末,搞起!在线disass下地址0x7fb4c1先看看是哪个函数:
gdb -p 31639 -ex “disassemble?0x7fb4c1?” –ex “quit” –batch > /tmp/g1.log

Dump of assembler code from 0x7fb4c1 to 0x7fb4f1:
0x00000000007fb4c1 : cmp %esi,(%rax)
0x00000000007fb4c3 : jne 0x7fb4b0
0x00000000007fb4c5 : mov 0xc(%r8),%edi
0x00000000007fb4c9 : cmp 0x4(%rax),%edi
0x00000000007fb4cc : jne 0x7fb4b0
0x00000000007fb4ce : mov 0x60(%rax),%rdi
0x00000000007fb4d2 : cmp %rdi,0x10(%r11)
0x00000000007fb4d6 : jne 0x7fb4b0
0x00000000007fb4d8 : nopl 0x0(%rax,%rax,1)
0x00000000007fb4e0 : sub %r13,%rdx
0x00000000007fb4e3 : add (%r11),%rdx
0x00000000007fb4e6 : mov $0x0,%eax
0x00000000007fb4eb : mov %r13,(%r11)
0x00000000007fb4ee : mov (%r12),%esi
End of assembler dump.

有两点我们可以确认:
1. 这是个srv_master_thread线程.
2. 在读取异常指针作比较时,导致segfault.

那这段汇编代码上下文是什么很难看出到底是哪行代码出问题,这时我们可以把srv_master_thread函数disass下:
gdb -p 31639 -ex “disassemble srv_master_thread” –ex “quit” –batch > /tmp/g2.log


0x00000000007fb478 : mov 0x3a8(%r10),%r12
0x00000000007fb46d : mov %r12,-0x740(%rbp)
0x00000000007fb474 : nopl 0x0(%rax)
0x00000000007fb486 : test %r12,%r12
0x00000000007fb489 : je 0x7fb908
0x00000000007fb48f : lea (%r9,%r9,2),%rdi
0x00000000007fb493 : mov %r12,%rax
0x00000000007fb496 : xor %edx,%edx
0x00000000007fb498 : shl $0x3,%rdi
0x00000000007fb49c : mov -0x6c8(%rbp,%rdi,1),%esi
0x00000000007fb4b4 : test %rax,%rax
0x00000000007fb4b7 : je 0x7fb900
0x00000000007fb4c1 : cmp %esi,(%rax)
0x00000000007fb4c3 : jne 0x7fb4b0
0x00000000007fb4c5 : mov 0xc(%r8),%edi
0x00000000007fb4c9 : cmp 0x4(%rax),%edi
0x00000000007fb4cc : jne 0x7fb4b0
0x00000000007fb4ce : mov 0x60(%rax),%rdi
0x00000000007fb4d2 : cmp %rdi,0x10(%r11)
0x00000000007fb4d6 : jne 0x7fb4b0
0x00000000007fb4d8 : nopl 0x0(%rax,%rax,1)

如果你汇编能力非常强,但是可以慢慢读起。但是,这往往非常累,因为这个函数太长了,并且由于编译优化,汇编代码不是和逻辑代码行级别的顺序完全对应。

对于我们这样的懒人,一般不太愿意,触发是实在没有办法。

那么,还有没有其它办法?有
答案是有的,用 disass /m 可以将汇编和代码对应起来!

gdb -p 31639 -ex “disassemble /m srv_master_thread” –ex “quit” –batch > /tmp/g3.log

3447 in /home/jiyuan/rpmbuild/BUILD/tb-mysql-5.5.18/storage/innobase/srv/srv0srv.c
0x00000000007fb478 : mov 0x3a8(%r10),%r12

3448 in /home/jiyuan/rpmbuild/BUILD/tb-mysql-5.5.18来&[email protected]!ma.com搞$代^码%网/storage/innobase/srv/srv0srv.c
3449 in /home/jiyuan/rpmbuild/BUILD/tb-mysql-5.5.18/storage/innobase/srv/srv0srv.c
3450 in /home/jiyuan/rpmbuild/BUILD/tb-mysql-5.5.18/storage/innobase/srv/srv0srv.c
3451 in /home/jiyuan/rpmbuild/BUILD/tb-mysql-5.5.18/storage/innobase/srv/srv0srv.c
0x00000000007fb46d : mov %r12,-0x740(%rbp)
0x00000000007fb474 : nopl 0x0(%rax)
0x00000000007fb486 : test %r12,%r12
0x00000000007fb489 : je 0x7fb908
0x00000000007fb48f : lea (%r9,%r9,2),%rdi
0x00000000007fb493 : mov %r12,%rax
0x00000000007fb496 : xor %edx,%edx
0x00000000007fb498 : shl $0x3,%rdi
0x00000000007fb49c : mov -0x6c8(%rbp,%rdi,1),%esi
0x00000000007fb4b4 : test %rax,%rax
0x00000000007fb4b7 : je 0x7fb900

3452 in /home/jiyuan/rpmbuild/BUILD/tb-mysql-5.5.18/storage/innobase/srv/srv0srv.c
0x00000000007fb4c1 : cmp %esi,(%rax)
0x00000000007fb4c3 : jne 0x7fb4b0
0x00000000007fb4c5 : mov 0xc(%r8),%edi
0x00000000007fb4c9 : cmp 0x4(%rax),%edi
0x00000000007fb4cc : jne 0x7fb4b0
0x00000000007fb4ce : mov 0x60(%rax),%rdi
0x00000000007fb4d2 : cmp %rdi,0x10(%r11)
0x00000000007fb4d6 : jne 0x7fb4b0
0x00000000007fb4d8 : nopl 0x0(%rax,%rax,1)

是不是看起来有点感觉了?终于有对应的代码了!
但是,高兴太早了,只是显示了代码文件名,没有说明是那行代码!
这个问题的原因是mysqld是在显示的那个目录下编译的,如何rpm打包,分发到线上机器部署。而安装的时候,我们是不安装对应的编译的代码文件。

那么,接下来怎么办?
好办,将对应的源文件拷贝一份到任何一个目录下,利用GDB的substitute-path来将编译时的路径和我们拷贝的代码路径对应起来就行了。

拷贝一份对应版本的源码到/tmp下,再次disass下:
注:0x00000000007fb46d 这个地址是我随便选择的,在srv_master_thread函数中某个有些地址,在0x7fb4c1之前,没有太多实际意义。

gdb -p 31639 -ex “set substitute-path? /home/jiyuan/rpmbuild/BUILD/tb-mysql-5.5.18 /tmp/alimysql-5.5.18”?-ex “disassemble /m??0x00000000007fb46d??” –ex “quit” –batch > /tmp/g4.log

3446 for (j = 0; j 0x00000000007fb47f : mov 0x3a0(%r10),%r13

3447 lint blocks_num, new_blocks_num, flushed_blocks_num;
0x00000000007fb478 : mov 0x3a8(%r10),%r12

3448 ibool found;
3449
3450 buf_pool = buf_pool_from_array(j);
3451
0x00000000007fb46d : mov %r12,-0x740(%rbp)
0x00000000007fb474 : nopl 0x0(%rax)
0x00000000007fb486 : test %r12,%r12
0x00000000007fb489 : je 0x7fb908
0x00000000007fb48f : lea (%r9,%r9,2),%rdi
0x00000000007fb493 : mov %r12,%rax
0x00000000007fb496 : xor %edx,%edx
0x00000000007fb498 : shl $0x3,%rdi
0x00000000007fb49c : mov -0x6c8(%rbp,%rdi,1),%esi
0x00000000007fb4b4 : test %rax,%rax
0x00000000007fb4b7 : je 0x7fb900

3452 blocks_num = UT_LIST_GET_LEN(buf_pool->flush_list);
0x00000000007fb4c1 : cmp %esi,(%rax)
0x00000000007fb4c3 : jne 0x7fb4b0
0x00000000007fb4c5 : mov 0xc(%r8),%edi
0x00000000007fb4c9 : cmp 0x4(%rax),%edi
0x00000000007fb4cc : jne 0x7fb4b0
0x00000000007fb4ce : mov 0x60(%rax),%rdi
0x00000000007fb4d2 : cmp %rdi,0x10(%r11)
0x00000000007fb4d6 : jne 0x7fb4b0
0x00000000007fb4d8 : nopl 0x0(%rax,%rax,1)

3453 bpage = UT_LIST_GET_FIRST(buf_pool->flush_list);
3454 new_blocks_num = 0;
0x00000000007fb4a3 : lea -0x6d0(%rbp,%rdi,1),%r8
0x00000000007fb4ab : jmp 0x7fb4c1
0x00000000007fb4ad : nopl (%rax)

3455
3456 found = FALSE;
3457 while (bpage != NULL) {
3458 if (prev_flush_info[j].space == bpage->space
3459 && prev_flush_info[j].offset == bpage->offset
0x00000000007fb4b0 : mov 0x40(%rax),%rax

3460 && prev_flush_info[j].oldest_modification
0x00000000007fb4bd : add $0x1,%rdx

终于看到熟悉的代码了!
可以定位到问题本质,第3452行异常导致,即buf_pool->flush_list.count访问为非法内存地址。

这段代码是srv_master_thread频繁访问的内存变量,出现这种问题,我只能说是其它地方有异常导致此处内存被污染。至于原因,没有啥思路,等下会遇到再分析。

到此,我们就能力范围内对mysql的backtrace进行了深度挖掘,揭开backtrace中地址的神秘面纱。但这个问题还是没有解决,backtrace中还有其它可以挖掘的信息,后面有高人指定也许会豁然开朗。



推荐阅读
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • mysql-cluster集群sql节点高可用keepalived的故障处理过程
    本文描述了mysql-cluster集群sql节点高可用keepalived的故障处理过程,包括故障发生时间、故障描述、故障分析等内容。根据keepalived的日志分析,发现bogus VRRP packet received on eth0 !!!等错误信息,进而导致vip地址失效,使得mysql-cluster的api无法访问。针对这个问题,本文提供了相应的解决方案。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • 如何搭建Java开发环境并开发WinCE项目
    本文介绍了如何搭建Java开发环境并开发WinCE项目,包括搭建开发环境的步骤和获取SDK的几种方式。同时还解答了一些关于WinCE开发的常见问题。通过阅读本文,您将了解如何使用Java进行嵌入式开发,并能够顺利开发WinCE应用程序。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 本文介绍了解决MySQL数据库Error Code: 1030错误的方法和原因。通过检查文件权限和磁盘空间,注释掉innodb_force_recovery参数等步骤,可以解决无法插入数据和修改文件的问题。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • 如何利用 Myflash 解析 binlog ?
    本文主要介绍了对Myflash的测试,从准备测试环境到利用Myflash解析binl ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
author-avatar
哓尐_271
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有