目录
- 程序添加打印日志
- GDB调试程序
- core文件
- 内核打印日志
- catchsegv
程序添加打印日志
走查代码,逐步添加printf打印,逐步定位,该方法最简单,且在开发调试过程中也较为快捷有效
GDB调试程序
编译时加入-g参数,使用gdb调试运行程序,出现错误时直接打印堆栈信息来定位
1 | [root@VM_0_4_centos example_breakInSo]# gdb main |
适用场景:开发调试过程中,方便重新编译和运行
core文件
开启core dump,ulimi -c unlimited
,程序崩溃时会生成core文件,使用gdb查看堆栈信息定位
适用场景:
- 可在开发调试中使用
- 也可用于现场无源代码时快速定位错误位置
- 无需重新运行程序
内核打印日志
其实段错误的core dump也是借助了内核的反异常代码生成了core文件,那么发生段错误的时候在/var/log/messages
中也有相关的提示信息,可使用dmesg
查看内核打印
一般的形式为进程名[pid]: segfault at 错误地址 ip 错误指令 sp 错误堆栈 error 错误原因 in 错误发生的可执行文件名称[加载基地址+不知道是啥东西]
例如main[18281]: segfault at 9527 ip 00007fdd5de94812 sp 00007fff486ea9d0 error 6 in libadd.so[7fdd5de94000+1000]
错误原因由3个bit组成,含义如下:
- bit2:值为1表示是用户态程序内存访问越界,值为0表示是内核态程序内存访问越界
- bit1: 值为1表示是写操作导致内存访问越界,值为0表示是读操作导致内存访问越界
- bit0: 值为1表示没有足够的权限访问非法地址的内容,值为0表示访问的非法地址根本没有对应的页面,也就是无效地址
我们可通过错误指令地址-动态库加载的基地址得到函数的相对地址,再配合nm
找到地址附近的函数,使用gdb
或者objdump
反汇编后定位到函数内的具体位置
适用场景:
- 不需要-g参数编译,不需要借助于core文件,不需要重新运行程序,但需要有一定的汇编语言基础
- core文件中堆栈信息全为????????,时可尝试使用此方法来定位
- 如果使用了gcc编译优化参数(-O1,-O2,-O3)的话,生成的汇编指令将会被优化,使得定位过程有些难度
实例如下:
肥肠简单的源代码,为了增大定位难度,简单的加了些无用代码
1 |
|
执行程序,
1 | [root@VM_0_4_centos example_breakInSo]# ./main |
出现了段错误,此时没有打开core文件,可查看内核打印信息
1 | [root@VM_0_4_centos example_breakInSo]# dmesg | tail -1 |
可见发生错误的指令地址为0x7fdd5de94812
,并且错误位置为libadd.so
,该动态库加载的基地址为0x7fdd5de94000
所以错误函数的相对地址为0x7fdd5de94812-0x7fdd5de94000 = 0x812
接下来使用nm
命令查看符号表,大概定位出现问题的函数
1 | [root@VM_0_4_centos example_breakInSo]# nm libadd.so -n -l -C |
可以发现大致就是在add这个函数中出现错误嗒,那么接下来使用objdump
命令反汇编来精确定位错误位置
1 | [root@VM_0_4_centos example_breakInSo]# objdump libadd.so -S -C --start-address=0x7b3 --stop-address=0x8b2 |
可以找到错误地址0x812
的具体位置啦,就是在这个memcpy处,可以看出确实是在向0x9527
地址写内容的时候报错啦,和我们构建的错误一致
使用GDB的方法如下
1 | [root@VM_0_4_centos example_breakInSo]# gdb libadd.so |
结束、、、、
catchsegv
catchsegv
专门用于捕获段错误
还是使用上一节的例子还定位问题
1 | [root@VM_0_4_centos example_breakInSo]# catchsegv ./main |
可见在Backtrace堆栈信息中,列出了出问题的地方,_Z3addii+0x5f
即代表问题出在add函数的5f位置,从上一个例子来看,add函数基地址7b3+5f=0x812
,相吻合