c++程序异常定位方法
文章目录
- (一)、core.dump
- (二)、dmesg
- (三)、pstack
- (四)、strace
- (五)、valgrind
对于c++程序来说,以segment fault为代表的程序异常行为前奇百怪,没有一套比较丰富的工具集去对付他们,在处理实际问题时就会显得捉襟见肘。本文列举几种程序异常的定位方法。
(一)、core.dump
最有效的一种方法,可以在程序挂断之后通过core文件定位程序错误的地方,而且不影响程序运行。需要以下三个步骤。
- 在编译可执行的时候需要给g++加参数-g,否则不能够定位到具体的行数。
如何判断一个可执行是否加了参数-g编译出来的?
readelf -S sonodebug |grep debug,若有输出则是加了-g编译的
- 允许系统生成core file
开启方法为在 /etc/profile 文件中加入ulimit -c unlimited,查看系统是否允许生成core文件可以用命令ulimit -c查看,或者用ulimit -a查看其中的core file size 行。有一点需要注意的是,进程到底是否会在down掉时生成core文件不取决于当前系统是否允许生成core文件,还需要在启动程序时就开启core文件。查看要分析的进程是否会产生core文件的方法:
ps -ef|grep a.out#查询到进程号
cat /proc/pid/limits|grep "Max core file size"#为0则不运行
Max core file size 字段的Soft Limit和Hard Limit值,才是标明了该进程是否真的能生成core文件。
- gdb带core文件运行可执行文件
gdb ./aout core.file
gdb调试core文件的一些常用命令:
- 查看案发现场情况,函数调用关系,一层一层顺藤摸瓜。
(gdb)bt //这个命令会列出程序崩溃时的堆栈信息,一层一层会有标号 #0 #1 #2 ....
- 想要局部放大,具体查看某一层具体的堆栈变量等信息
(gdb)f N //其中N是项查看的层数,在bt命令里会有打印
- 来到某一层之后,想查看具体信息用以下命令
(gdb)info args//打印当前函数的参数以及其值
(gdb)info locals//打印所有的局部变量
(gdb)info catch//打印当前函数的异常处理信息
(gdb)p var//打印具体的某一个变量值
gdb+core文件的方法,可以定位到常见的绝大数错误。但也存在一些不好解决的问题:
- 由于某些原因,例如程序退出时破坏了堆栈等,在用gdb的bt命令打印堆栈信息时,会出现不能定位的情况,出现全是 ?? 的情况,让人不知所以然。以下这个例子就是这种情况,当时用gdb+core不管用,gdb前台运行结果一样,最终使用valgrind定位到了问题。
gdb ./a.out core.out……………… ………………Program terminalted with signal 11, segmentation fault.#0 __memmove_ssse3_back () at ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S:1658 1658 movdqu -0x40(%rsi),%xmm4 (gdb) bt#0 __memmove_ssse3_back () at ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S:1658#1 0x0000000000000000 in ?? () (gdb) quit
- 由于运行环境和调试环境不同,出现定位不准确的问题。
(二)、dmesg
/var/log/message 中记录了程序的异常退出,dmesg也可以列出程序的段错误。dmesg + grep a.out 可以查出程序是否发生了segment fault。
dmesg中有哪些有用信息呢?它可以告诉我们程序的内存错误类型和错误位置。以下是可执行文件sowithdebug段错误之后,用dmesg |grep sowithdebug获得的错误信息。
sowithdebug[6847]: segfault at 28 ip 00000000004008cc sp 00007fff016d9400 error 4 in sowithdebug[400000+2000]
其中的ip后面的数字是错误的行数(4008cc), in 后面的是错误的文件(sowithdebug)。然后用objdump可以查错误的汇编代码,用addr2line可以定位到具体的行数。
dmesg |grep sowithdebug #查询错误文件及行数
objdump -d sowithdebug |grep 4008cc #查询汇编指令
addr2line -e sowithdebug 4008c #定位行数
error 4是错误的类型,表示用户态程序读操作访问越界。
error后边数字的含义:
bit2: 值为1表示是用户态程序内存访问越界,值为0表示是内核态程序内存访问越界
bit1: 值为1表示是写操作导致内存访问越界,值为0表示是读操作导致内存访问越界
bit0: 值为1表示没有足够的权限访问非法地址的内容,值为0表示访问的非法地址根本没有对应的页面,也就是无效地址
dmesg的不足之处:
- 在可执行没有加 -g 编译时依然不能定位行数
- 如果dmesg显示的出错文件是动态库so文件,问题位置依然比较难以定位
这篇博文非常非常精彩的介绍了dmesge定位出错行数的一些方法,包括处理静态库和动态库的情况:《在Linux中如何利用backtrace信息解决问题》
(三)、pstack
要想清楚进程此刻正在忙什么,pstack是一个很好的工具。它可以告诉我们各线程的函数调用情况。
pstack PID #PID既可以是进程号也可以是线程号
当pstack加进程号的时候,它会打印出所有的线程运行情况。如果加线程号,就打印此线程的函数调用情况。
其中的LWP就是线程号。
pstack的弊端:
只是进程的一个静态照片,不能把握程序的整体运行流程。多次执行pstack,或者结合watch -n 0.1,通过“高频率的快照近似看视频”的方式,掌握程序的运行情况。但watch的最高频率是0.1s。
(四)、strace
如果说pstack是给各线程照了一张照片,那么strace就是给各线程录视频。
strace可以实时动态的查看程序的运行情况,使用起来也很方便。
ps -ef|grep a.out #查出进程的PID
strace -p PID #更踪此进程的运行(既可以跟踪进程PID,也可以是线程PID)
strace 也可以直接运行程序:
strace -f -F -o out.log a.out #a.out是可执行文件,out.log是运行结果
其中的 -f , -F 告诉strace同时跟踪fork和vfork出来的进程,否则strace只会跟踪主线程。
strace的弊端:
它打印的都是一些系统调用,需要对这些系统调用有比较熟悉的掌握,才能清楚strace告诉我们的到底是什么。
这篇博文很精彩的介绍了strace的一些使用方法,很巧妙的解决一些问题:《strace命令详解》
(五)、valgrind
valgriind是一个代码动态检测工具,它是一个大的工具集,有几个小组件。其中比较常用的是memcheck和helgrind。
- memcheck检测内存问题
valgrind --leak-check=full --show-reachable=yes --trace-children=yes --log-file=./valgrind.log ./a.out
–leak-check=full指的是完全检查内存泄漏,–show-reachable=yes是显示内存泄漏的地点,–trace-children=yes是跟入子进程。
以上介绍的我遇到的gdb+core出现的不能定位问题,gdb只仍给我一行又一行的问号问题,就是用valgrind的找到原因的。用以上参数运行程序,最后的日志会非常庞大而杂乱,可以重点关注其中的内存写(write)错误,程序断掉一般都是这些错误导致的。
- helgrind检测线程问题
用工具helgrind检测线程问题:
valgrind --tool=helgrind --trace-children=yes ./a.out
它可以检测到大部分的共享资源竞争。错误类型也分的比较细致。
- 检测内存泄漏
运行命令同 1. memcheck检测内存问题 ,但是需要注意的是要让valgrind运行结束,他才会给出内存报告。所以,如果我们的程序是一个可以自动推出的程序,就不会存在问题,等程序运行完毕,valgrind也就会推出,内存报告就给出了。但是如果我们的程序是一个不会自己退出的程序,就需要用killall 命令杀死valgrind,以让其生成内存报告。然后在valgrind的输出中grep “definitely lost”,是内存泄漏最严重的地方,是必须修改的地方。如果valgrind报告因为错误太多超过1千,不再显示新的错误,就需要修改参数改为不限制错误数量。
killall memcheck- #先用top看一下valgrind实际运行的进程名称,killall杀死
killall 默认发送SIGTERM(15),如果用kill -9 直接杀死 memcheck是不能生成内存报告的
用valgrind定位程序问题的弊端:
- valgrind是动态工具,用valgring带程序跑,必须要等到问题复现,valgrind才能报告给我们有用的信息。
- valgrind带程序跑之后,会大大降低程序的运行速度。如果在生产环境上调试,这有可能是一个必须忍受的问题。
c++程序的异常定位,是一个非常复杂的问题,写出没有bug的c++代码,也是一项几乎不可能完成的任务。掌握排错工具和方法,就是一个非常重要的技能。后续实践中又好的排错工具和方法,会继续更新本文。
c++程序异常定位方法相关推荐
- stm32进入HardFault的异常定位方法
在用Keil对STM32的程序进行仿真时程序有时会跑飞,停止仿真程序会停在HardFault_Handler函数里的死循环while(1)中.这说明STM32出现了硬件错误. STM32出现硬件错误可 ...
- ARM 之十二 Cortex-M 内核异常处理、异常定位方法、在线调试、Keil MDK-ARM 的使用
Cortex-M 内核本身提供了非常强大的异常处理机制.它可以非常有效的捕捉非法的内存访问以及其他一些异常.而我们常用的开发工具的异常处理就是使用了 Cortex-M 核的异常处理机制. 在 ...
- vmlinux 反汇编_ARM Linux内核驱动异常定位方法分析--反汇编方式
通常认为,产生异常的地址是lr寄存器的值,从上面的异常信息可以看到[lr]的值是c01a4e30. 接下来,我们可以通过内核镜像文件反汇编来找到这个地址.内核编译完成后,会在内核代码根目录下生成vml ...
- 微服务系统异常检测和根因定位 方法综述
CSUR22 - Anomaly Detection and Failure Root Cause Analysis in (Micro) Service-Based Cloud Applicatio ...
- Android Native程序crash的一些定位方法简介
Android Native程序crash的一些定位方法简介 经常,避免不了,我们的代码会崩溃.如果crash在native代码上,Android会和其他Linux一样,生成一份core dump,将 ...
- windows 程序异常崩溃等错误定位
MAP/映射文件 1. MAP 映射文件的作用:MAP文件可以查找崩溃或者程序异常地址,然后就可以精确地定位到源代码中出错的代码行. 2.VS中生成MAP文件的方法,项目属性中选择生成映 ...
- 通过gdb core dump方法查看程序异常时的堆栈信息
在Linux下可通过core文件来获取当程序异常退出(如异常信号SIGSEGV, SIGABRT等)时的堆栈信息.core dump叫做核心转储,当程序运行过程中发生异常的那一刻的一个内存快照,操作系 ...
- 解决“安装程序无法定位现有系统分区,也无法创建新的系统分区”的方法
使用老毛桃PE格式化C盘后安装Win7出现"安装程序无法定位现有系统分区,也无法创建新的系统分区"的错误.本文给出了我遇到该情况的解决办法,亲身经历,绝非抄袭. 在网上看了好多办法 ...
- linux下怎么查看程序异常,如何检测、定位linux程序异常
作为linux开发,在工作中或者面试的时候经常会遇到怎么检测程序异常的问题,下面对此作一个总结. 0. 查看程序日志.项目更新日志,发现可疑的地方 使用linux查看内存cpu指令,top.ps初步看 ...
最新文章
- 120000字,你们要的Java 并发编程图文小册整理出来了,免费送给大家!
- jquery通过ajax提交form
- 关于登陆到域的用户,不需要显示登陆界面的问题(aspx)
- 阿里云的很多域名没有办法进行实名认证了吗
- .toString(c) 将数字值 渲染成 货币形式
- Lab1--关于安装JUnit的简要描述
- 【Elasticsearch】如何构建一个好的电商搜索引擎?
- 电子科大计算机2014级,电子科大-计算机-操作系统实验报告-2014级.docx
- php处理管道文件流
- JAVA菜鸟教程(一)
- 海康威视Java SDK实战
- DNSPod十问党霏霏:充电桩是披着高科技外皮的传统基建?
- Mac实现ts文件转为mp4文件
- Pytorch基础知识(7)单目标检测
- 如何调用Sphinx
- 3.3.9nbsp;艾利·高德拉特——TOC制…
- 服务器怎么导入皮肤文件,Malody皮肤导入的详细技巧
- 用VC++进行MapX二次开发::之三------使用MapX工具
- Vue3通过axios来读取本地json文件
- 干掉 强硬的 css !important