程序调试六部曲
0.对于简单程序的简单错误,code review复查程序
1.对于简单程序的一般错误,可以用gdb单步跟踪
2.对于应用程序的异常退出,检查是否有应用程序的core,利用core进行调试
3.对于内核程序的异常退出,根据dmesg/kernel panic、内核crash log: /var/crash 进行调试
4.对于没有任何core、vmcore产生的异常,只有出错指令IP信息的,可以利用objdump反汇编二进制,然后结合IP定位出错的汇编语句和函数:
5.对于没有任何线索的异常退出,仔细检查相关的所有日志
为此需要:
0.系统编程实现的时候尽量保证代码的可读性、可维护性;
1.对于重要的代码,尽量维护release/debug两个版本,保证后者编译生成的时候带有-g或者-ggdb选项;
2.保证系统异常时能够生成core,并且自动保存core文件到非易失性存储上去;同时在应用程序的所有异常出口,能利用类似assert(0)的机制触发core的产生;
为了配置系统,让应用程序能够产生core,可以参考下面的设置:
[root@localhost mvt]# cat /etc/sysctl.conf
kernel.core_pattern=/var/core/app/core-%e-%p-%t
kernel.core_uses_pid=1
[root@localhost mvt]# man sysctl
[root@localhost mvt]# cat /etc/systemd/system.conf
[root@localhost mvt]# sysctl -p
kernel.core_pattern = /var/core/app/core-%e-%p-%t
kernel.core_uses_pid = 1
3.设置内核让它能产生vmcore
现在较新的Linux系统都自带了vmcore的自动生成和转储功能,比如在笔者的CentOS7上已经能够在/var/crash/下自动产生vmcore、vmcore-dmesg.txt。如果系统默认不支持,可以检查/boot/grub/grub.conf文件中kernel一行最后是否有crashkernel=auto如果没有,添加上去,重启来让内核支持vmcore自动生成功能。系统起来之后,可以通过下面的命令测试一下:
echo c >/proc/sysrq-trigger 会在/var/crash 下产生一个vmcore 文件
4. 生成的二进制能够通过反汇编定位到所在的函数。
如果没有产生Core,程序里只是提示ip寄存器出错的位置,可以把产生错误的二进制利用objdump进行反汇编,然后依据IP定位出现问题的语句和函数。
5. 如果程序没有任何出错提示就异常退出了,需要仔细分析应用程序日志和系统日志。
笔者在双控系统上曾经碰到两个奇怪的问题:
应用程序中的RPC服务偶尔无法注册成功;
双控的IP心跳偶尔会丢失;
针对第一个问题,后来是通过检查系统日志,发现是因为用systemctl启动的程序中RPC函数依赖的RPC服务并没有在/etc/rc.local里面先执行完,根本原因是:现在的centOS系统启动脚本的执行流程都是基于systemd的方式了,因此传统基于init的/etc/rc.local里的脚本就没办法保证在每个服务启动之前先执行完了。
针对第二个问题,调试过程中发现系统偶尔出现心跳丢失的情况,导致出现failover切机。但是检查正常触发failover的情况,并没有发现程序主动退出的情况。正在百思不得其解的时候,检查系统日志,发现心跳网卡被其他系统服务进程服务接管,并且出现网卡停下的情况,而这刚好导致双控心跳丢失,导致切机。
根据上面的经验,当应用程序作为整个系统服务的一部分启动,出现莫名其妙退出的情况时,需要结合应用程序日志、系统日志进行分析。常用的日志有:/var/log/kernel.log; /var/log/messages;dmesg;/var/log/boot.log。当然这些只是显示内核和启动相关的日志,如果需要拿到系统启动后所有的日志,在centOS上可以通过journalctl >> all.log 抓到所有的日志输出,然后进行分析。