0-程序崩溃分析小例子

程序奔溃分析
最近工作中遇到多次程序崩溃的情况,分析起来非常头疼,从网上找了两片有用的博文,对解决问题,提供了很大的帮助,因此在这里记录下。
主要分为两种情况,有现场的跑飞和没有现场的Segmentation fault

1. 有现场

1.1. 测试代码

有代码如下:

#include 
#include 
#include 


void getmemory(char *p)
{
	p=(char *)malloc(100);
	strcpy(p,"helloworld");
	return;
}
void test1_printf()
{
	printf("aefawfaeeaa  /n");
	return;
}


void test1_printf2()
{
	int bbb  = 0x5555;
	
	printf("test = %d  /n",bbb);
	return;
}


void test1_printf3()
{
	printf("aefawfaeeaa  /n");
	return;
}


void test1_printf4()
{
	int bbb  = 0x5555;
	
	printf("test = %d  /n",bbb);
	return;
}



void teswtaaaaa(void )
{
		char*str=NULL;
	getmemory(str);
	*str = 12;
	printf("%s /n",str);
	return;
}




int main(int argc,char *argv[])
{
	test1_printf();

	test1_printf2();
	

	teswtaaaaa();


	test1_printf3();


	test1_printf4();

	
	
#if 0
	char strss[10] = {0};
	strss[11] = 6;
	
	printf("%s /n",strss);
	
#endif


	return 0;
}


编译:
编译以Hisi3559A为例:
生成.o :aarch64-himix100-linux-gcc -c test.c -Wall -Werror -g
生成可执行文件:aarch64-himix100-linux-gcc -o test test.o -Wall -Werror -g

说明:
-Wall :选项打开所有最常用到的编译警告,强烈建议打开,可以捕捉到许多在C编程中最常发生的错误。
-Werror:将警告当做错误
-g:. 创建符号表,符号表包含了程序中使用的变量名称的列表。
. 关闭所有的优化机制,以便程序执行过程中严格按照原来的C代码进行

1.2. 测试运行

放到单板上运行有如下错误:

test[1775]: unhandled level 2 translation fault (11) at 0x00000000, esr 0x92000046
pgd = ffffffc020807000
[00000000] *pgd=00000000617ac003, *pud=00000000617ac003
, *pmd=0000000000000000

CPU: 1 PID: 1775 Comm: test Tainted: P           O    4.9.37 #1
Hardware name: Hisilicon HI3559AV100 DEMO Board (DT)
task: ffffffc021f53000 task.stack: ffffffc02100c000
PC is at 0x4006d8
LR is at 0x4006d0
pc : [<00000000004006d8>] lr : [<00000000004006d0>] pstate: 60000000
sp : 0000007ffbbcae20
x29: 0000007ffbbcae20 x28: 0000000000000000 
x27: 0000000000000000 x26: 0000000000000000 
x25: 0000000000000000 x24: 0000000000000000 
x23: 0000000000000000 x22: 0000000000000000 
x21: 0000000000400490 x20: 0000000000000000 
x19: 0000000000400728 x18: 0000000000000000 
x17: 0000000000411000 x16: 0000007fabc52780 
x15: 0000000000000482 x14: 0000000000000005 
x13: 0000000000000000 x12: 0000000000000000 
x11: 0000000000000018 x10: 0101010101010101 
x9 : ffffff80ffffffc8 x8 : 0000000000000003 
x7 : 000000000000007b x6 : 000000006e2f2020 
x5 : 0000000000000071 x4 : 0000000000413020 
x3 : 0000000000000071 x2 : 726f776f6c6c6568 
x1 : 000000000000000c x0 : 0000000000000000 


1.3. 错误分析

1.3.1. ARM PC寄存器

PC 代表程序计数器,流水线使用三个阶段,因此指令分为三个阶段执行:1.取指(从存储器装载一条指令);2.译码(识别将要被执行的指令);3.执行(处理 指令并将结果写回寄存器)。而R15(PC)总是指向“正在取指”的指令,而不是指向“正在执行”的指令或正在“译码”的指令

1.3.2. ARM LR寄存器:

异常的发生会导致程序正常运行的被打断, 并将控制流转移到相应的异常处理(异常响应),有些异常(fiq、irq)事件处理后,系统还希望能回 到当初异常发生时被打断的源程序断点处继续完成源程序的执行(异常返回),这就需要一种解决方案, 用于记录源程序的断点位置,以便正确的异常返回。
类似的还有子程序的调用和 返回。在主程序中(通过子程序调用指令)调用子程序时,也需要记录下主程序中的调用点位置,以便将来的子程序的返回。
在ARM处理器中使用 R14实现对断点和调用点的记录,即使用R14用作返 回连接寄存器(LR)。在硬件上和指令执行上,CPU 自动完成相应返回点的记录。在ARM 汇编语言程序设计时,R14和LR通用。
ARM处理器相应异常时,会自动完成将当前的PC保存到LR寄存器。

出错时 这两个寄存器值如下:

PC is at 0x4006d8
LR is at 0x4006d0

1.3.3. NM命令

nm命令被用于显示二进制目标文件的符号表

aarch64-himix100-linux-nm test

显示如下

        U abort@@GLIBC_2.17
0000000000411040 B __bss_end__
0000000000411040 B _bss_end__
0000000000411038 B __bss_start
0000000000411038 B __bss_start__
00000000004004d8 t call_weak_fn
0000000000411038 b completed.7391
0000000000411028 D __data_start
0000000000411028 W data_start
00000000004004f0 t deregister_tm_clones
0000000000400570 t __do_global_dtors_aux
0000000000410df8 t __do_global_dtors_aux_fini_array_entry
0000000000411030 D __dso_handle
0000000000410e08 d _DYNAMIC
0000000000411038 D _edata
0000000000411040 B _end
0000000000411040 B __end__
00000000004007a4 T _fini
00000000004005a0 t frame_dummy
0000000000410df0 t __frame_dummy_init_array_entry
00000000004007f8 r __FRAME_END__
00000000004005e0 T getmemory
0000000000410fd8 d _GLOBAL_OFFSET_TABLE_
                w __gmon_start__
0000000000400408 T _init
0000000000410df8 t __init_array_end
0000000000410df0 t __init_array_start
00000000004007b8 R _IO_stdin_used
0000000000410e00 d __JCR_END__
0000000000410e00 d __JCR_LIST__
00000000004007a0 T __libc_csu_fini
0000000000400728 T __libc_csu_init
                U __libc_start_main@@GLIBC_2.17
00000000004006f8 T main
                U malloc@@GLIBC_2.17
                U printf@@GLIBC_2.17
0000000000400530 t register_tm_clones
0000000000400490 T _start
0000000000400624 T test1_printf
0000000000400644 T test1_printf2
0000000000400670 T test1_printf3
0000000000400690 T test1_printf4
00000000004006bc T teswtaaaaa
0000000000411038 D __TMC_END__

1.3.4. objdump 命令

objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它以一种可阅读的格式让你更多地了解二进制文件可能带有的附加信息

 aarch64-himix100-linux-objdump -x -s -d test

截取部分内容如下

00000000004006bc :
  4006bc:	a9be7bfd 	stp	x29, x30, [sp, #-32]!
  4006c0:	910003fd 	mov	x29, sp
  4006c4:	f9000fbf 	str	xzr, [x29, #24]
  4006c8:	f9400fa0 	ldr	x0, [x29, #24]
  4006cc:	97ffffc5 	bl	4005e0 
  4006d0:	f9400fa0 	ldr	x0, [x29, #24]
  4006d4:	52800181 	mov	w1, #0xc                   	// #12
  4006d8:	39000001 	strb	w1, [x0]
  4006dc:	90000000 	adrp	x0, 400000 <_init-0x408>
  4006e0:	911fc000 	add	x0, x0, #0x7f0
  4006e4:	f9400fa1 	ldr	x1, [x29, #24]
  4006e8:	97ffff66 	bl	400480 
  4006ec:	d503201f 	nop
  4006f0:	a8c27bfd 	ldp	x29, x30, [sp], #32
  4006f4:	d65f03c0 	ret

可以看到是函数teswtaaaaa中的某条指定错误

1.3.5. addr2line 工具

使用addr2line工具,有的时候可以直接使用这个工具定位到应用程序的行数:

 addr2line  0x4006d8   -e test -f

打印如下:

$ addr2line  0x4006d8   -e test -f
teswtaaaaa
/home/lie/code_test/test.c:49

2. 无现场

当遇到崩溃现场没有打印出PC和LR时,如下确定问题出现在什么地方 呢?
这个时候就用到了Linux下强大的调试工具GDB

2.1. GDB命令

同样是上面的程序,在桌面Linux环境下运行结果如下:

[root@localhost test]# ./test 
Segmentation fault (core dumped)
[root@localhost test]# 

这个时候,我们用GDB进行调试:

[root@localhost test]# gdb test
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/xxx/test/test...done.
(gdb) 
(gdb) run
Starting program: /home/***/test/test 

Program received signal SIGSEGV, Segmentation fault.
0x080484e7 in teswtaaaaa () at test.c:49
49              *str = 12;
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.107.el6.i686

从上图能看出,出错的地方在49行

3. 总结

通过对上面两种情况的分析能够看出:

  • 有现场时利用objdump可以反汇编出程序所有的符号,而addr2line可以利用PC地址确定出错代码的行数
  • 没有现场时,可以利用GDB进行调试,确定出错的位置

你可能感兴趣的:(程序崩溃分析,c语言,linux,c++)