段错误定位方法

--------------------------------------------------------------------
段错误定位方法:
方法一:麻烦
两种方法来解决问题:
一种是生成core dump文件, 然后用gdb调试这个文件;
另一种是不生成core dump文件, 而使用其他工具来定位问题.
使用dmesg和addr2line命令,以-g选项编译
例:test.c
#include

int main(void)

{undefined

int *p = NULL;

*p = 0;

printf("bad\n");

return 0;

}

$ gcc -O3 -g -o test test.c
$ ./test
段错误(吐核) ---执行结果打印
$ dmesg | grep segfault ---查看段错误信息
[422855.897248] test[63448]: segfault at 0 ip 0000000000400449 sp 00007ffd06202b70 error 6 in test[400000+1000]---执行结果打印
$addr2line -e test 0000000000400449 ---注意其中指令指针寄存器(IP)的值, 接下来调用addr2line命令, 把IP所指的地址转换为源码行号
/root/tmp/test.c:6 ---执行结果打印,可见排查出源码第6行有错

$addr2line -e test 0000000000400449-400000 ---如果段错误出在动态库而非可执行程序中, 在调用addr2line命令时, 需要将dmesg输出中IP的值减去行最后的地址值


方法二:好用
使用execinfo.h里的backtrace函数及信号处理机制, 来让程序在发生段错误时自动打印调用堆栈:

#include <execinfo.h>

#define BACKTRACE_SIZE 256

void segv_handler(int sig)

{

void *func[BACKTRACE_SIZE];

//char **symb = NULL;

int size;

size = backtrace(func, BACKTRACE_SIZE);

backtrace_symbols_fd(func, size, STDERR_FILENO);

exit(1);

}

int main(void)

{undefined

int *p = NULL;

signal(SIGSEGV, segv_handler);

*p = 0xdeadbeef; //---------------------Line 29

return 0;

}

然后以-g选项编译, 运行:

$ gcc -O3 -g -o auto auto.c

$ ./auto

./auto[0x400654]

/lib64/libc.so.6(+0x35250)[0x7fbbe4283250]

./auto[0x400543] //----------------出错地址,下2行是信号SIGSEGV的处理函数调用

/lib64/libc.so.6(__libc_start_main+0xf5)[0x7fbbe426fb35]

./auto[0x40057e]

输出的调用堆栈逆序打印, 最先调用的在最下面, 我们可以判断出是第3行的 ./auto[0x400543] 导致了段错误,
因为前面2行是信号SIGSEGV的处理函数调用. 运行addr2line:

$ addr2line -e auto 0x400543

/root/tmp/auto.c:29 ----可知第29行代码 *p = 0xdeadbeef; 引起错误.
---------------------------------------------------------------------------
#define SEGMENTATION_FAULT_DEBUG

#ifdef SEGMENTATION_FAULT_DEBUG
#include <execinfo.h>

#define BACKTRACE_SIZE 256

void segv_handler(int sig)
{
void *func[BACKTRACE_SIZE];
int size;
// int j=0, nptrs=0;
// char **symb = NULL;

printf("<----------BACKTRACE START---------->\n");

size = backtrace(func, BACKTRACE_SIZE);

//方法1(与方法2输出相似)
//backtrace_symbols_fd(func, size, STDERR_FILENO);//实测无输出

//方法2
symb = backtrace_symbols(func, nptrs);
if (symb == NULL) {
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}

for (j = 0; j < nptrs; j++)
printf("%s\n", symb[j]);

free(symb);
symb = NULL;

printf("<----------BACKTRACE END---------->\n");

exit(1);
}
#endif

main()
{
#ifdef SEGMENTATION_FAULT_DEBUG
signal(SIGSEGV, segv_handler);
#endif

----------------------------------------------------------------------------
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void trace(int signo)
{
int j, nptrs;
#define SIZE 100
void *buffer[100];
char **strings;

printf("signo: %d\n", signo);

nptrs = backtrace(buffer, SIZE);
printf("backtrace() returned %d addresses\n", nptrs);

/* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
* would produce similar output to the following: */

strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL) {
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}

for (j = 0; j < nptrs; j++)
printf("%s\n", strings[j]);

free(strings);

if (SIGSEGV == signo || SIGQUIT == signo) {
exit(0);
}
}

void segfault(void)
{
int *p = NULL;
*p = 1; //--------------------------------Line 41
}

int main(int argc, char *argv[])
{
signal(SIGSEGV, trace);
signal(SIGINT, trace);
signal(SIGQUIT, trace);

while (1) {
sleep(1);
if (time(0) % 7 == 0) {
segfault();
}
}

return 0;
}

编译方法 gcc -rdynamic seg.c -o seg -g


段错信息


signo: 11
backtrace() returned 6 addresses
./seg(trace+0x3c) [0x400aac]
/lib64/libc.so.6() [0x3c39635690]
./seg(segfault+0x10) [0x400b64] //-------------------出错地址, 函数segfault
./seg(main+0x83) [0x400bef]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x3c39621b45]
./seg() [0x4009a9]


使用 objdump -d seg 查看出错地址 0x400b64 是一个赋值操作

0000000000400b54 <segfault>:
400b54: 55 push %rbp
400b55: 48 89 e5 mov %rsp,%rbp
400b58: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
400b5f: 00
400b60: 48 8b 45 f8 mov -0x8(%rbp),%rax
400b64: c7 00 01 00 00 00 movl $0x1,(%rax) //*p = 1;
400b6a: 5d pop %rbp
400b6b: c3 retq

 

使用 addr2line 0x400b64 -e seg -afs 查看段错函数和对应的代码行数

0x0000000000400b64
segfault
seg.c:41 -----对应Line 41
----------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <execinfo.h>
#include <signal.h>

void dump(int signo)
{
void *buffer[30] = {0};
size_t size;
char **strings = NULL;
size_t i = 0;

size = backtrace(buffer, 30);
fprintf(stdout, "Obtained %zd stack frames.nm\n", size);
strings = backtrace_symbols(buffer, size);
if (strings == NULL)
{
perror("backtrace_symbols.");
exit(EXIT_FAILURE);
}

for (i = 0; i < size; i++)
{
fprintf(stdout, "%s\n", strings[i]);
}
free(strings);
strings = NULL;
exit(0);
}

void func_c()
{
*((volatile char *)0x0) = 0x9999;
}

void func_b()
{
func_c();
}

void func_a()
{
func_b();
}

int main(int argc, const char *argv[])
{
if (signal(SIGSEGV, dump) == SIG_ERR)
perror("can't catch SIGSEGV");
func_a();
return 0;
}

编译程序:

gcc -g -rdynamic test.c -o test; ./test

输出如下:
Obtained6stackframes.nm
./backstrace_debug(dump+0x45)[0x80487c9]
[0x468400]
./backstrace_debug(func_b+0x8)[0x804888c]
./backstrace_debug(func_a+0x8)[0x8048896]
./backstrace_debug(main+0x33)[0x80488cb]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113]

(这里有个疑问: 多次运行的结果是/lib/i368-linux-gnu/libc.so.6和[0x468400]的返回地址是变化的,但不变的是后三位, 不知道为什么)

接着:

objdump -d test > test.s

在test.s中搜索804888c如下:

8048884 <func_b>:
8048884: 55 push %ebp
8048885: 89 e5 mov %esp, %ebp
8048887: e8 eb ff ff ff call 8048877 <func_c>
804888c: 5d pop %ebp
804888d: c3 ret

其中80488c时调用(call 8048877)C函数后的地址,虽然并没有直接定位到C函数,通过汇编代码, 基本可以推出是C函数出问题了(pop指令不会导致段错误的)。

我们也可以通过addr2line来查看
addr2line 0x804888c -e backstrace_debug -f
输出:
func_b
/home/astrol/c/backstrace_debug.c:57
----------------------------------------------------------------------------
以下是简单的backtrace原理实现:

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

#define LEN 4
#define FILENAME "stack"

int backtrace(void **buffer, int size)
{
int i = 0;
unsigned long int reg_eip = 0;
unsigned long int reg_ebp = 0;
char cmd[size][64];

memset(cmd, 0, size * 64);
__asm__ volatile (
/* get current EBP */
"movl %%ebp, %0 \n\t"
:"=r"(reg_ebp) /* output register */
: /* input register */
:"memory" /* cloberred register */
);

for (i = 0; i < size; i++)
{
reg_eip = *(unsigned long int *)(reg_ebp + 4);
reg_ebp = *(unsigned long int *)(reg_ebp);
buffer[i] = (void *)reg_eip;
fprintf(stderr, "%p -> ", buffer[i]);
sprintf(cmd[i], "addr2line %p -e ", buffer[i]);
strncat(cmd[i], FILENAME" -f", strlen(FILENAME)+3);
system(cmd[i]);
puts("");
}

return size;
}

static void test2(void)
{
int i = 0;
void *buffer[LEN] = {0};
backtrace(buffer, LEN);
return;
}

static void test1(void)
{
test2();
}

static void test(void)
{
test1();
}

int main(int argc, const char *argv[])
{
test();
return 0;
}
---------------------------------------------------------------------

posted @ 2022-01-26 11:04  一路枫景  阅读(1417)  评论(0编辑  收藏  举报