段错误定位方法


​ 段错误一般指程序访问到非法内存地址,造成程序异常退出。如若是必现的段错误,通过加printf定位调试即可定位,这里提供两种快速定位段错误的方法。

  • core dumped文件结合gdb定位
  • backtrace定位

core dumped文件结合gdb定位

一般会在在代码中做Core文件记录,用于分析段错误位置。

core文件生成

以 demo.c为例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
#ifdef CORE_DUMP
    system("ulimit -c unlimited");												/* 设置产生core文件时不限制文件大小 */
    system("echo \"/tmp/debug/core-%e-%p-%t\" > /proc/sys/kernel/core_pattern");/* 设置core文件的保存路径以及文件命名方式*/
#endif
    int i=0;
    scanf("%d",i);
    printf("%d\n",i);
    return 0;
}

core 文件参数说明:

%p 段错误发生所在的PID
%u 段错误发生所在的实际UID
%G 段错误发生所在的实际GID
%s 引发段错误发生的信号
%t 段错误发生的时间戳,表示为自1970年1月1日00:00:00 +0000(UTC)以来的秒数
%H 主机名(与uname(2)返回的节点名相同)
%e 可执行文件名(无路径前缀)
%E 可执行文件的路径名
%C 崩溃过程的核心文件大小软资源限制

在程序执行前先手动运行ulimit -c unlimited,避免因为shell上下文环境不同无法生成文件

Makefile:

all:
gcc -g -gdwarf-2 -DCORE_DUMP demo.c -o demo

编译选项说明:

-g: 生成调试信息

-gwarf-2: 正常显示函数参数的值

编译后执行:

image

目录下已生成core文件

image

其中1656223395是时间戳,可以知道段错误何时发生。可以使用在线工具转换,如下:

image

gdb分析

PC Linux程序

如果是纯PC Linux程序,使用以下命令直接查看:

gdb exe.bin core_file #gdb 可执行程序 core文件

在gbd环境内,使用bt查看堆栈信息,如下:

image

从上面可以看出,段错误发生在第10行。

嵌入式程序

嵌入式程序需要设置运行环境所用的库路径,类似下面截图:

image

同样可以获取到段错误位置。

backtrace定位

这种方法则通过在代码中加入相关函数,用于捕获相关信号,分析得到段错误位置。

以下bt_test.c程序为例:

#include <stdio.h>
#include <signal.h>
#include <execinfo.h>
#include <stdlib.h>
#include <string.h>

void print_trace(void)
{
	void* array[300];
	size_t size;
	char** strings;
	size_t i;
	size = backtrace(array, 300);
	strings = backtrace_symbols(array, size);
	if (NULL == strings)
	{
		perror("backtrace_symbols");
		exit(-1);
	}
	printf("Obtained %zd stack frames.\n", size);
	for(i = 0 ; i < size; i++)
	{
		printf("%s\n", strings[i]);
	}
	free(strings);
	strings = NULL;
	exit(0);
}

void sighandler_dump_stack(int sig)
{
	(void)sig;
	print_trace();
}

int main()
{
	signal(SIGSEGV, sighandler_dump_stack);
	int i = 0;
	scanf("%d",i);
	printf("%d\n",i);
	return 0;
}

编译

gcc -rdynamic bt_test.c -g

其中-rdynamic用来通知链接器将所有符号添加到动态符号表中

执行程序触发段错误

root@VM-0-15-ubuntu:~/bt_test# ./a.out 
1
Obtained 8 stack frames.
./a.out(print_trace+0x32) [0x5592f8fee27b]
./a.out(sighandler_dump_stack+0x14) [0x5592f8fee361]
/lib/x86_64-linux-gnu/libc.so.6(+0x43090) [0x7efce512b090]
/lib/x86_64-linux-gnu/libc.so.6(+0x68335) [0x7efce5150335]
/lib/x86_64-linux-gnu/libc.so.6(__isoc99_scanf+0xb2) [0x7efce514b162]
./a.out(main+0x3a) [0x5592f8fee39e]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7efce510c083]
./a.out(_start+0x2e) [0x5592f8fee18e]
root@VM-0-15-ubuntu:~/bt_test# 

可以看到程序在main函数+0x3a触发信号,反汇编分析

objdump -d a.out
root@VM-0-15-ubuntu:~/bt_test# objdump -d a.out 

a.out:     file format elf64-x86-64


Disassembly of section .init:

0000000000001000 <_init>:
    1000:	f3 0f 1e fa          	endbr64 
    1004:	48 83 ec 08          	sub    $0x8,%rsp
    1008:	48 8b 05 d9 2f 00 00 	mov    0x2fd9(%rip),%rax        # 3fe8 <__gmon_start__>
    100f:	48 85 c0             	test   %rax,%rax
    1012:	74 02                	je     1016 <_init+0x16>
    1014:	ff d0                	callq  *%rax
    1016:	48 83 c4 08          	add    $0x8,%rsp
    101a:	c3                   	retq   

... //省略无用信息

0000000000001364 <main>:
    1364:	f3 0f 1e fa          	endbr64 
    1368:	55                   	push   %rbp
    1369:	48 89 e5             	mov    %rsp,%rbp
    136c:	48 83 ec 10          	sub    $0x10,%rsp
    1370:	48 8d 35 d6 ff ff ff 	lea    -0x2a(%rip),%rsi        # 134d <sighandler_dump_stack>
    1377:	bf 0b 00 00 00       	mov    $0xb,%edi
    137c:	e8 9f fd ff ff       	callq  1120 <signal@plt>
    1381:	c7 45 fc 00 00 00 00 	movl   $0x0,-0x4(%rbp)
    1388:	8b 45 fc             	mov    -0x4(%rbp),%eax
    138b:	89 c6                	mov    %eax,%esi
    138d:	48 8d 3d 9e 0c 00 00 	lea    0xc9e(%rip),%rdi        # 2032 <_IO_stdin_used+0x32>
    1394:	b8 00 00 00 00       	mov    $0x0,%eax
    1399:	e8 a2 fd ff ff       	callq  1140 <__isoc99_scanf@plt>
    139e:	8b 45 fc             	mov    -0x4(%rbp),%eax
    13a1:	89 c6                	mov    %eax,%esi
    13a3:	48 8d 3d 8b 0c 00 00 	lea    0xc8b(%rip),%rdi        # 2035 <_IO_stdin_used+0x35>
    13aa:	b8 00 00 00 00       	mov    $0x0,%eax
    13af:	e8 5c fd ff ff       	callq  1110 <printf@plt>
    13b4:	b8 00 00 00 00       	mov    $0x0,%eax
    13b9:	c9                   	leaveq 
    13ba:	c3                   	retq   
    13bb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

... //省略无用信息

Disassembly of section .fini:

0000000000001438 <_fini>:
    1438:	f3 0f 1e fa          	endbr64 
    143c:	48 83 ec 08          	sub    $0x8,%rsp
    1440:	48 83 c4 08          	add    $0x8,%rsp
    1444:	c3                   	retq   
root@VM-0-15-ubuntu:~/bt_test# 

可以看到main函数入口地址是0x1364,0x1364+0x3a=0x139e为段错误发生的位置。可以结合汇编分析段错误原因,这里不展开。

最后使用addr2line查看段错误函数和相关代码函数

addr2line -e a.out 0x139e -afs
root@VM-0-15-ubuntu:~/bt_test# addr2line -e a.out 0x139e -afs
0x000000000000139e
main
test.c:41
root@VM-0-15-ubuntu:~/bt_test# 
posted @ 2022-06-26 15:21  刘锐滨  阅读(186)  评论(0)    收藏  举报