linux c 段错误如何定位
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux c 段错误如何定位相关的知识,希望对你有一定的参考价值。
参考技术A 1. 段错误是什么一句话来说,段错误是指访问的内存超出了系统给这个程序所设bai定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址等等情况。这里贴一个对于“段错误”的准确定义(参考Answers.com):
A segmentation fault (often shortened to segfault) is a particular error condition that can occur during the operation of computer software. In short, a segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (e.g., attempts to write to a read-only location, or to overwrite part of the operating system). Systems based on processors like the Motorola 68000 tend to refer to these events as Address or Bus errors.
Segmentation is one approach to memory management and protection in the operating system. It has been superseded by paging for most purposes, but much of the terminology of segmentation is still used, "segmentation fault" being an example. Some operating systems still have segmentation at some logical level although paging is used as the main memory management policy.
On Unix-like operating systems, a process that accesses invalid memory receives the SIGSEGV signal. On Microsoft Windows, a process that accesses invalid memory receives the STATUS_ACCESS_VIOLATION exception.
2. 段错误产生的原因
2.1 访问不存在的内存地址
#include<stdio.h>
#include<stdlib.h>
void main()
int *ptr = NULL;
*ptr = 0;
2.2 访问系统保护的内存地址
#include<stdio.h>
#include<stdlib.h>
void main()
int *ptr = (int *)0;
*ptr = 100;
2.3 访问只读的内存地址
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void main()
char *ptr = "test";
strcpy(ptr, "TEST");
2.4 栈溢出
#include<stdio.h>
#include<stdlib.h>
void main()
main();
等等其他原因。
3. 段错误信息的获取
程序发生段错误时,提示信息很少,下面有几种查看段错误的发生信息的途径。
3.1 dmesg
dmesg可以在应用程序crash掉时,显示内核中保存的相关信息。如下所示,通过dmesg命令可以查看发生段错误的程序名称、引起段错误发生的内存地址、指令指针地址、堆栈指针地址、错误代码、错误原因等。以程序2.3为例:
panfeng@ubuntu:~/segfault$ dmesg
[ 2329.479037] segfault3[2700]: segfault at 80484e0 ip 00d2906a sp bfbbec3c error 7 in libc-2.10.1.so[cb4000+13e000]
3.2 -g
使用gcc编译程序的源码时,加上-g参数,这样可以使得生成的二进制文件中加入可以用于gdb调试的有用信息。以程序2.3为例:
panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c
3.3 nm
使用nm命令列出二进制文件中的符号表,包括符号地址、符号类型、符号名等,这样可以帮助定位在哪里发生了段错误。以程序2.3为例:
panfeng@ubuntu:~/segfault$ nm segfault3
08049f20 d _DYNAMIC
08049ff4 d _GLOBAL_OFFSET_TABLE_
080484dc R _IO_stdin_used
w _Jv_RegisterClasses
08049f10 d __CTOR_END__
08049f0c d __CTOR_LIST__
08049f18 D __DTOR_END__
08049f14 d __DTOR_LIST__
080484ec r __FRAME_END__
08049f1c d __JCR_END__
08049f1c d __JCR_LIST__
0804a014 A __bss_start
0804a00c D __data_start
08048490 t __do_global_ctors_aux
08048360 t __do_global_dtors_aux
0804a010 D __dso_handle
w __gmon_start__
0804848a T __i686.get_pc_thunk.bx
08049f0c d __init_array_end
08049f0c d __init_array_start
08048420 T __libc_csu_fini
08048430 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804a014 A _edata
0804a01c A _end
080484bc T _fini
080484d8 R _fp_hw
080482bc T _init
08048330 T _start
0804a014 b completed.6990
0804a00c W data_start
0804a018 b dtor_idx.6992
080483c0 t frame_dummy
080483e4 T main
U memcpy@@GLIBC_2.0
3.4 ldd
使用ldd命令查看二进制程序的共享链接库依赖,包括库的名称、起始地址,这样可以确定段错误到底是发生在了自己的程序中还是依赖的共享库中。以程序2.3为例:
panfeng@ubuntu:~/segfault$ ldd ./segfault3
linux-gate.so.1 => (0x00e08000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00675000)
/lib/ld-linux.so.2 (0x00482000)
如何在没有core文件的情况下用dmesg+addr2line定位段错误
前言
在现网环境下,程序奔溃后不一定会留下core文件,原因有很多,比如存储空间不足就是其中一个常见的原因。此时我们只能依据linux记录的错误日志来定位问题。
涉及linux命令
本文涉及以下几条命令
1. dmesg命令,用于获取程序出错时的堆栈地址
1)dmesg |grep -E ‘segfault|general‘
可以通过该命令过滤出发生崩溃的程序,以及对应的堆栈信息。之前看网上的其他文章仅过滤segfault,但我在实践中发现"general protection"的提示信息也在告诉我们进程崩了。目前我只遇到segfault和general这两种情况,如果还有其他的过滤条件可以给我留言。
举例:
[root@vmware ~] dmesg |grep -E ‘segfault|general‘
[ 374.549753] a.out[57228]: segfault at 0 ip 00000000004004fd sp 00007ffe7296f610 error 6 in a.out[400000+1000]
[ 429.110096] b.out[96783]: segfault at 0 ip 00000000004004fd sp 00007ffcc3e697c0 error 6 in b.out[400000+1000]
字段说明:
1)ip:指令指针寄存器,字段后面的数字就是test程序出错时程序执行的位置
2)sp:堆栈指针寄存器
3)error:错误码,由三个字位组成的,从高到底分别为bit2 bit1和bit0
bit2: 值为1表示是用户态程序内存访问越界,值为0表示是内核态程序内存访问越界
bit1: 值为1表示是写操作导致内存访问越界,值为0表示是读操作导致内存访问越界
bit0: 值为1表示没有足够的权限访问非法地址的内容,值为0表示访问的非法地址根本没有对应的页面,也就是无效地址
4)b.out后面紧跟着的地址(这里是400000)这个在定位时也有用到,不知道该怎么描述,知道的给我留言
2)dmesg |grep 进(线)程名
通过进程或线程名来过滤。这里之所以强调线程,因为我在实践中发现dmesg里的信息可能只有线程名,所以推荐在给线程取名时使用统一前缀,比如你的主进程为Test,那么线程可以取Test_A,Test_A,这样过滤时 grep Test就能过滤出所有想要的信息
举例:
[root@vmware ~] dmesg |grep a.out
[ 374.549753] a.out[57228]: segfault at 0 ip 00000000004004fd sp 00007ffe7296f610 error 6 in a.out[400000+1000]
3)dmesg -C
dmesg命令查看到的信息在重启后将会被清空,若当前错误信息太多也可以通过该命令手动清空dmesg信息,以便下次问题的定位。-C(大写)参数为静默清空,如果清空前还想打印一次,可以通过-c(小写)参数。
注:
cat /var/log/messages |grep xxx
这里也保存进程奔溃信息,且重启后依然存在。
举例:
[root@vmware ~] cat /var/log/messages|grep b.out
May 8 09:24:04 vmware kernel: b.out[96783]: segfault at 0 ip 00000000004004fd sp 00007ffcc3e697c0 error 6 in b.out[400000+1000]
May 8 09:24:04 vmware abrt-hook-ccpp: Process 96783 (b.out) of user 0 killed by SIGSEGV - dumping core
May 8 09:24:05 vmware abrt-server: Executable ‘/root/b.out‘ doesn‘t belong to any package and ProcessUnpackaged is set to ‘no‘
2. date 用于转换dmesg信息里的时间戳
date -d "1970-01-01 UTC `echo "$(date +%s)-$(cat /proc/uptime|cut -f 1 -d‘ ‘)+时间戳"|bc `seconds"
举例:
[ 672.091250] a.out[26520]: segfault at 0 ip 00000000004004fd sp 00007ffe51b27fe0 error 6 in a.out[400000+1000]
[root@vmware ~] date -d "1970-01-01 UTC `echo "$(date +%s)-$(cat /proc/uptime|cut -f 1 -d‘ ‘)+672.091250"|bc `seconds"
2019年 05月 08日 星期三 09:40:02 CST
3. ldd 用于获取进程所依赖的动态库,以及所在位置
4. addr2line,将dmesg获取到的地址转换为代码中发成错误的文件、行号及函数名
奔溃发生的位置不同,该命令的使用方式也有所不同
1)在主进程中奔溃
addr2line -e 进程名 IP指令地址 -f
举例:
#include <stdio.h>
int main()
{
int *p = NULL;
*p = 0;
return 0;
}
[root@vmware ~] gcc a.c -g
[root@vmware ~] ./a.out
段错误(吐核)
[root@vmware ~] dmesg |grep a.out
[ 1310.167335] a.out[122089]: segfault at 0 ip 00000000004004fd sp 00007ffcf08f3ab0 error 6 in a.out[400000+1000]
[root@vmware ~] addr2line -e a.out 00000000004004fd -f
func
/root/a.c:5
通过该例子我们可以看到,程序发生段错误的函数以及具体位置。需要注意的是如果编译程序时没有加上-g参数,就只能显示出函数名,显示不出具体所在文件的位置.
2)动态库中奔溃
addr2line -e 进程名 “IP指令地址-动态库后紧跟着的地址” -f
举例:
#include<stdio.h> #include<string.h> void func() { int *p = NULL; memcpy(p, "test", 4); } int main() { func(); return 0; }
[root@vmware ~] dmesg |grep a.out [ 6807.501481] a.out[72684]: segfault at 0 ip 00007f6559bc7463 sp 00007fff80625b18 error 6 in libc-2.17.so[7f6559a7c000+1b6000] [root@vmware ~] ldd a.out linux-vdso.so.1 => (0x00007ffc643f6000) libc.so.6 => /lib64/libc.so.6 (0x00007f83ef206000) /lib64/ld-linux-x86-64.so.2 (0x00007f83ef5e2000)
[root@vmware ~] addr2line -e /lib64/libc.so.6 14B463 -f
__memcpy_ssse3_back
:?
这个这个例子我们可以看到,段错误发生的位置是在a.out进程调用的libc库里,因此addr2line指向的地址使用14B463 = 00007f6559bc7463 - 7f6559a7c000
以上是关于linux c 段错误如何定位的主要内容,如果未能解决你的问题,请参考以下文章
linux下利用backtrace追踪函数调用堆栈以及定位段错误
如何在没有core文件的情况下用dmesg+addr2line定位段错误
如何在没有core文件的情况下用dmesg+addr2line定位段错误