linux编程入门(九)-程序崩溃之后的排错及定位

当我们写程序时候难免会因为各种问题崩掉,如果是开发阶段,我们可以开gdb跟踪调试,但如果到了线上,就不能用gdb了,这时候我们可以把崩溃时候的调用栈信息打印出来,然后定位到具体崩溃的代码位置.
想要定位到具体的行号,需要在编译的时候加入-g参数,表示编译时候加入调试信息,调试信息里有相关的信息可以使地址转为行号.
下面介绍几个可以定位到崩溃位置的方法:

使用core文件

core文件其实是程序崩溃后的内存数据,也叫core dump或者dump文件,当得到core文件后就可以用gdb打开core文件,就能定位崩溃的位置了.

接下来我们先准备好一段测试代码,后面就用这段代码搞点事情
代码里我们故意除以0,使程序崩溃

float div(int a, int b){
    float c = a/b;
    return c;
}

int main(int argc, char** argv){
    (void)argc;
    (void)argv;

    int a = 10;
    int b = 0;
    float c = div(a, b);
    printf("div: %d/%d=%.2f\n", a, b, c);

    return 0;
}

假如我们编译好的程序叫main,执行后,会显示Floating point exception,也就是除0错误了

bash$ ./main
Floating point exception (core dumped)

这时候看一下当前目录有没有生成core文件,默认应该是没有.

bash$ ls
main  main.cpp  main.o  Makefile

当没有产生core文件的时候,就是开关没开,需要先打开开关
先查看一下ulimit -c的值

bash$ ulimit -c
0

上面显示结果为0,表示禁止生成core文件,下面我们设置为不限制core大小

bash$ ulimit -c unlimited

# 再用ulimit -c查看一下
bash$ ulimit -c
unlimited

然后再运行下程序看看,就生成core文件了

bash$ ./main
Floating point exception (core dumped)
bash$ ls
core  main  main.cpp  main.o  Makefile

接着用gdb打开core文件,打开就是崩溃的位置

bash$ gdb --core=./core main
GNU gdb (Ubuntu 8.2-0ubuntu1) 8.2
Copyright (C) 2018 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 "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
    .

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main...done.
[New LWP 15220]
Core was generated by `./main'.
Program terminated with signal SIGFPE, Arithmetic exception.
#0  0x0000559cc0dbe143 in div (a=10, b=0) at main.cpp:5
5               float c = a/b;
(gdb)

可以bt看一下调用栈

(gdb) bt
#0  0x0000559cc0dbe143 in div (a=10, b=0) at main.cpp:5
#1  0x0000559cc0dbe182 in main (argc=1, argv=0x7ffd65f14f88) at main.cpp:15

上面说的ulimit -c unlimited这种方法在重启机器后就会失效,想永久打开生成core,需要修改些配置,这里大家可以下去自己研究一下,后面我们就说一下,假如没有core文件的时候,我们该怎么办.

在代码崩溃的时候把调用栈打印出来

core文件其实是靠不住的,因为core文件是程序的内存数据,当程序特别大的时候core文件就特别大,如果机器磁盘已经快满了,再想生成core文件就生不成了,这时候我们可以自己在程序里把程序崩溃时候的关键信息打印出来,就不需要太依赖core文件了
好,自己动手,丰衣足食.

想要打印调用栈,我们想到的就是bt这个词,也就是backtrace,也可以上网搜一下程序打印调用栈,得出来的结果就是用backtrace这个函数可以打印调用栈,我们可以man backtrace看一下这个函数,结果man里就有一段测试backtrace的代码,太爽了,man手册就是好用,里面有很多测试代码.
比如想学网络编程,就man一下socket,打开后,向下翻手册,如果没有看到测试代码就注意最后面有个SEE ALSO,这里会列出和socket相关的函数,挨个man这些函数,总有你想看到的测试代码,到时候拿来练习就可以了.

接下来我们就直接上码了,对着程序走一遍就会了

#include 
#include 
#include 
#include 
#include 
#include 

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

    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);
}

void dump_backtrace(int signum){
    printf("dump_backtrace on signal:%d\n", signum);
    signal(signum,SIG_DFL);
    myfunc3();
}

/* "static" means don't export the symbol... */
static void myfunc2(void){
    myfunc3();
}

void myfunc(int ncalls){
    if (ncalls > 1)
        myfunc(ncalls - 1);
    else
        myfunc2();
}

void test_sig_segv(){
    //这个就是段错误了
    int* p = NULL;
    printf("*p = %d\n", *p);
}

void test_sig_abort(){
    abort();
}

void test_sig_abort_assert(){
    assert( 0 );
}

void test_sig_abort_free(){
    int* p = (int*)malloc(sizeof(int));
    *p = 1;
    printf("test_sig_abort_free: 0x%p %d\n", p, *p );
    free(p);
    printf("free once\n");
    free(p);
    printf("free twice\n");
}

void test_sig_fpe(){
    int a = 10;
    int b = 0;
    int c = a / b;
    printf("%d / %d = %d\n", a, b, c);
}

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

    int pid = getpid();
    printf("pid %d\n", pid);
#if 0
    //这里是man手册里backtrace的测试代码,直接打印调用栈
    if (argc != 2) {
        fprintf(stderr, "%s num-calls\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    myfunc(atoi(argv[1]));
    exit(EXIT_SUCCESS);
#else
    //这里是先注册信号,当程序运行出错的时候,
    //捕捉到信号后打印调用栈,
    //实际使用中也是这种办法
    signal(SIGSEGV, dump_backtrace); //segmentation violation
    signal(SIGABRT, dump_backtrace); //abort program (formerly SIGIOT)
    signal(SIGFPE, dump_backtrace); //floating-point exception

    test_sig_segv();
    //test_sig_abort();
    //test_sig_abort_assert();
    //test_sig_abort_free();
    //test_sig_fpe();

#endif
    return 0;
}

这里需要把Makefile也贴一下,注意下面加上了链接时候用的参数-rdynamic,这个参数的作用是让链接器把所有符号添加到动态符号表中,听起来比较抽象,后面咱们通过例子来看一下效果就明白了.

LINK    = @echo linking $@ && g++
GCC     = @echo compiling $@ && g++
GC      = @echo compiling $@ && gcc
AR              = @echo generating static library $@ && ar crv
FLAGS   = -g -DDEBUG -W -Wall -fPIC
#FLAGS   = -DDEBUG -W -Wall -fPIC
#FLAGS   = -DNDEBUG -W -Wall -fPIC
#FLAGS   = -g -DNDEBUG -W -Wall -fPIC
GCCFLAGS =
DEFINES =
HEADER  = -I./
LIBS    =
LINKFLAGS =

#注意这里不加-rdynamic的话,backtrace显示的只有地址,不能显示地址对应的符号
LINKFLAGS += -rdynamic

#LIBS    += -lrt
#LIBS    += -pthread

OBJECT := main.o \

BIN_PATH = ./

TARGET = main

$(TARGET) : $(OBJECT)
        $(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS)

.cpp.o:
        $(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $<

.c.o:
        $(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $<

install: $(TARGET)
        cp $(TARGET) $(BIN_PATH)

clean:
        rm -rf $(TARGET) *.o *.so *.a

上面的大多数代码都是从man backtrace里扒出来的,但是man手册里的代码是调用程序就直接打印调用栈了,和我们的需求不符,我们希望在程序崩溃的时候再打印,那就得改一下,程序崩溃的时候会触发一些相应的信号,我们只要在程序里捕捉到这些信号,然后在收到信号的时候再打印调用栈就可以了.
下面我们看看捕捉信号这几行代码

# 这部分代码里加了注释,下面的signal意思是注册一个信号,当发生该信号时,回调后面的函数,
# 下面是注册了三个信号, 
# SIGSEGV是段错误,这错误就是指针相关的问题一般会引起该错误,比如取一个空指针的值
# SIGABRT是当程序用了abort()或者assert之类的函数时候会触发
# SIGFPE也就是除0时候发生的了
    //这里是先注册信号,当程序运行出错的时候,
    //捕捉到信号后打印调用栈,
    //实际使用中也是这种办法
    signal(SIGSEGV, dump_backtrace); //segmentation violation
    signal(SIGABRT, dump_backtrace); //abort program (formerly SIGIOT)
    signal(SIGFPE, dump_backtrace); //floating-point exception

然后我们再看一下dump_backtrace函数

#当程序崩溃后,我们把收到的信号值打了出来,
#然后用signal(signum,SIG_DFL)让程序默认处理该信号
#接着用myfunc3把调用栈打印了出来,实际项目中我们可以把这些信息写到日志里
void dump_backtrace(int signum){
    printf("dump_backtrace on signal:%d\n", signum);
    signal(signum,SIG_DFL);
    myfunc3();
}

myfunc3里具体输出调用栈我们就不说了,都是抄来的东西,有兴趣的同学可以自己研究一下.
下面我们看一下程序运行效果,在编译的时候,咱们先把Makefile里的
LINKFLAGS += -rdynamic 这行注释掉

bash$ ./main
pid 15562
dump_backtrace on signal:11
backtrace() returned 7 addresses
./main(+0x1223) [0x555d55255223]
./main(+0x1321) [0x555d55255321]
/lib/x86_64-linux-gnu/libc.so.6(+0x41100) [0x7f6585563100]
./main(+0x136c) [0x555d5525536c]
./main(+0x14ce) [0x555d552554ce]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f658554609b]
./main(+0x113a) [0x555d5525513a]
Segmentation fault (core dumped)

代码里我们用的是引起段错误的代码,所以信号为11,也就是SIGSEGV,下面是各信号的编号和含义

    查看signal man手册,可以看到下面的signal定义。
     1     SIGHUP       terminate process    terminal line hangup
     2     SIGINT       terminate process    interrupt program
     3     SIGQUIT      create core image    quit program
     4     SIGILL       create core image    illegal instruction
     5     SIGTRAP      create core image    trace trap
     6     SIGABRT      create core image    abort program (formerly SIGIOT)
     7     SIGEMT       create core image    emulate instruction executed
     8     SIGFPE       create core image    floating-point exception
     9     SIGKILL      terminate process    kill program
     10    SIGBUS       create core image    bus error
     11    SIGSEGV      create core image    segmentation violation
     12    SIGSYS       create core image    non-existent system call invoked
     13    SIGPIPE      terminate process    write on a pipe with no reader
     14    SIGALRM      terminate process    real-time timer expired
     15    SIGTERM      terminate process    software termination signal
     16    SIGURG       discard signal       urgent condition present on socket
     17    SIGSTOP      stop process         stop (cannot be caught or ignored)
     18    SIGTSTP      stop process         stop signal generated from keyboard
     19    SIGCONT      discard signal       continue after stop
     20    SIGCHLD      discard signal       child status has changed
     21    SIGTTIN      stop process         background read attempted from control terminal
     22    SIGTTOU      stop process         background write attempted to control terminal
     23    SIGIO        discard signal       I/O is possible on a descriptor (see fcntl(2))
     24    SIGXCPU      terminate process    cpu time limit exceeded (see setrlimit(2))
     25    SIGXFSZ      terminate process    file size limit exceeded (see setrlimit(2))
     26    SIGVTALRM    terminate process    virtual time alarm (see setitimer(2))
     27    SIGPROF      terminate process    profiling timer alarm (see setitimer(2))
     28    SIGWINCH     discard signal       Window size change
     29    SIGINFO      discard signal       status request from keyboard
     30    SIGUSR1      terminate process    User defined signal 1
     31    SIGUSR2      terminate process    User defined signal 2

可以看到上面输出了程序的pid为15562,接着有./main(+0x1223)的字样,有好几行,每一行就是一层调用栈,最上面的是最后调用的函数,其实就是myfunc3()打印调用栈的函数,崩溃的地方在往下几行里面,这里我们仅介绍一下从地址得到函数的方法. 这里面的+0x1223就是代码的地址,我们需要用另一个工具把该地址翻译为代码行号.

./main(+0x1223) [0x555d55255223]

用addr2line把地址转为函数名和行号,注意只有编译时候加了-g参数的时候才会行到行号

# addr2line的参数说明 
# -e表示指明程序名为main
# -s表示输入代码名的时候不要带路径
# -f表示显示函数名
# -C表示显示demangle后的函数名,demangle是针对mangle来说的,程序在编译后函数名都被转为函数符号了,该符号不便于我们识别,所以需要demangle一下
bash$ addr2line -e main 0x1223 -sfC
myfunc3()
main.cpp:14

# 下面是只显示代码文件和行号的参数
bash$ addr2line -e main 0x1223 -s
main.cpp:14

# 下面是没有经过demangle的函数名
bash$ addr2line -e main 0x1223 -sf
_Z7myfunc3v
main.cpp:14

也可以用gdb查看地址所在的函数名,但是这样查不到行号,没有addr2line好用

# 注意,gdb只能加载main,不要run,一run就无法解析地址了
Reading symbols from ./main...done.
(gdb) i symbol 0x1223
myfunc3() + 46 in section .text
(gdb)

小知识
可以用nm查看程序里的符号,比如我们nm一下main函数

bash$ nm main
                 U abort@@GLIBC_2.2.5
                 U __assert_fail@@GLIBC_2.2.5
                 U backtrace@@GLIBC_2.2.5
                 U backtrace_symbols@@GLIBC_2.2.5
0000000000004010 B __bss_start
0000000000004010 b completed.7931
                 w __cxa_finalize@@GLIBC_2.2.5
0000000000004000 D __data_start
0000000000004000 W data_start
0000000000001140 t deregister_tm_clones
00000000000011b0 t __do_global_dtors_aux
0000000000003d60 t __do_global_dtors_aux_fini_array_entry
0000000000004008 D __dso_handle
0000000000003d68 d _DYNAMIC
0000000000004010 D _edata
0000000000004018 B _end
                 U exit@@GLIBC_2.2.5
0000000000001544 T _fini
00000000000011f0 t frame_dummy
0000000000003d58 t __frame_dummy_init_array_entry
0000000000002384 r __FRAME_END__
                 U free@@GLIBC_2.2.5
                 U getpid@@GLIBC_2.2.5
0000000000003f58 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
00000000000020e0 r __GNU_EH_FRAME_HDR
0000000000001000 t _init
0000000000003d60 t __init_array_end
0000000000003d58 t __init_array_start
0000000000002000 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000001540 T __libc_csu_fini
00000000000014e0 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
0000000000001460 T main
                 U malloc@@GLIBC_2.2.5
                 U perror@@GLIBC_2.2.5
                 U printf@@GLIBC_2.2.5
                 U puts@@GLIBC_2.2.5
0000000000001170 t register_tm_clones
                 U signal@@GLIBC_2.2.5
                 U __stack_chk_fail@@GLIBC_2.4
0000000000001110 T _start
0000000000004010 D __TMC_END__
0000000000001421 T _Z12test_sig_fpev
0000000000001358 T _Z13test_sig_segvv
00000000000012ec T _Z14dump_backtracei
0000000000001384 T _Z14test_sig_abortv
00000000000013b0 T _Z19test_sig_abort_freev
000000000000138d T _Z21test_sig_abort_assertv
0000000000001330 T _Z6myfunci
0000000000001330 t _Z6myfunci.localalias.0
00000000000011f5 T _Z7myfunc3v
0000000000001324 t _ZL7myfunc2v
00000000000020c0 r _ZZ21test_sig_abort_assertvE19__PRETTY_FUNCTION__

可以看到里面函数名都是mangle后的符号,可以用c++filt demangle一下
bash$ c++filt _Z6myfunci
myfunc(int)

下面我们把Makefile里的
LINKFLAGS += -rdynamic
再解屏,看看效果

bash$ ./main
pid 15697
dump_backtrace on signal:11
backtrace() returned 7 addresses
./main(_Z7myfunc3v+0x2e) [0x55e95c4a0223]
./main(_Z14dump_backtracei+0x35) [0x55e95c4a0321]
/lib/x86_64-linux-gnu/libc.so.6(+0x41100) [0x7f46e4fff100]
./main(_Z13test_sig_segvv+0x14) [0x55e95c4a036c]
./main(main+0x6e) [0x55e95c4a04ce]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f46e4fe209b]
./main(_start+0x2a) [0x55e95c4a013a]
Segmentation fault (core dumped)

可以看到生成的不是地址了,而是./main(函数符号+偏移地址),那这样我们怎么求出崩溃地址呢?
这里就需要用到上面小知识里说到的nm了,我们可以从nm里查到相应符号的地址,再加上偏移地址,就可以得到崩溃时候的地址了.

# 假如我们要求下面_Z7myfunc3v+0x2e的地址
./main(_Z7myfunc3v+0x2e) [0x55e95c4a0223]

bash$ nm main | grep _Z7myfunc3v
00000000000011f5 T _Z7myfunc3v

#查到地址是00000000000011f5, 然后再用0x11f5 + 0x2e就可以了
#先把16进制转为10进制
bash$ printf "%d %d\n" 0x11f5 0x2e
4597 46

#再expr求一下
bash$ expr 4597 + 46
4643

#再转为16进制
bash$ printf "%x\n" 4643
1223

#再addr2line看一下就可以得到行号了
bash$ addr2line -e main 0x1223 -sfC
myfunc3()
main.cpp:14

接着我们再做几个测试,之前我们用的是debug, -g编译参数,下面我们分别做release和不带-g的测试
看看崩溃后是否还能依据地址找到代码位置

# 先测试-DDEBUG 不带 -g
# Makefile里用这句
FLAGS   = -DDEBUG -W -Wall -fPIC

# 为了能直接得到地址,咱们把-rdynamic去掉,这样就不用再求地址了
#LINKFLAGS += -rdynamic

运行一下程序

bash$ ./main
pid 19316
dump_backtrace on signal:11
backtrace() returned 7 addresses
./main(+0x1223) [0x560986db5223]
./main(+0x1321) [0x560986db5321]
/lib/x86_64-linux-gnu/libc.so.6(+0x41100) [0x7fc880e69100]
./main(+0x136c) [0x560986db536c]
./main(+0x14ce) [0x560986db54ce]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7fc880e4c09b]
./main(+0x113a) [0x560986db513a]
Segmentation fault (core dumped)

addr2line看一下,结论,debug模式下,去掉-g看不到行号了,因为没有调试信息了

bash$ addr2line -e main 0x1223 -sfC
myfunc3()
??:?

再来用release, -g测试一下

#Makefile里用这句
FLAGS   = -g -DNDEBUG -W -Wall -fPIC

运行一下,让程序崩一个,注意,代码没变, release和debug崩的地址变了,debug是0x1223,release是0x1213

bash$ ./main
pid 19340
dump_backtrace on signal:11
backtrace() returned 7 addresses
./main(+0x1213) [0x5602bc813213]
./main(+0x1311) [0x5602bc813311]
/lib/x86_64-linux-gnu/libc.so.6(+0x41100) [0x7fd0aedd5100]
./main(+0x135c) [0x5602bc81335c]
./main(+0x14a2) [0x5602bc8134a2]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7fd0aedb809b]
./main(+0x112a) [0x5602bc81312a]
Segmentation fault (core dumped)

再来addr2line看一下,结论: release下,带-g可以看到行号和函数符号

bash$ addr2line -e main 0x1213 -sfC
myfunc3()
main.cpp:14

再测试最后一种情况,release不带-g

#Makefile里用这句,其他的FLAGS屏掉
FLAGS   = -DNDEBUG -W -Wall -fPIC

编完,跑一下程序

bash$ ./main
pid 19367
dump_backtrace on signal:11
backtrace() returned 7 addresses
./main(+0x1213) [0x55801b953213]
./main(+0x1311) [0x55801b953311]
/lib/x86_64-linux-gnu/libc.so.6(+0x41100) [0x7f823c12b100]
./main(+0x135c) [0x55801b95335c]
./main(+0x14a2) [0x55801b9534a2]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f823c10e09b]
./main(+0x112a) [0x55801b95312a]
Segmentation fault (core dumped)

addr2line看一下,结论,release不带-g看不到行号,依然能看到函数符号

bash$ addr2line -e main 0x1213 -sfC
myfunc3()
??:?

总结一下:

  • 不管是release还是debug,都能看到函数符号
  • 带-g能看到行号
  • 不带-g看不到行号
    所以线上时候我们一般会打个release的程序,但是带-g,便于定位

使用dmesg求出崩溃所在的位置

最后这个办法是linux自带的,当程序崩溃后,系统会记个log,当我们没有core,也没有自己打印backtrace,或者由于一些原因,core或log被删掉了,总之程序崩了之后什么线索都没有的时候,就可以用dmesg定位了.

我们这次用debug, -g先编好程序,跑一下

bash$ ./main
pid 19429
dump_backtrace on signal:11
backtrace() returned 7 addresses
./main(+0x1223) [0x55fa146b3223]
./main(+0x1321) [0x55fa146b3321]
/lib/x86_64-linux-gnu/libc.so.6(+0x41100) [0x7f5faa0ee100]
./main(+0x136c) [0x55fa146b336c]
./main(+0x14ce) [0x55fa146b34ce]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f5faa0d109b]
./main(+0x113a) [0x55fa146b313a]
Segmentation fault (core dumped)

再用dmesg查一下崩溃log,因为dmesg会输出一大堆东西,我们过滤一下,只看带main的,因为我们程序名叫main

bash$ dmesg | grep main
... # 前面的略
[159708.390262] traps: main[12287] trap divide error ip:55644504443b sp:7fffc2a9c230 error:0 in main[556445044000+1000]
... # 中间的略
[239814.618107] main[19429]: segfault at 0 ip 000055fa146b336c sp 00007ffd4b2b4180 error 4 in main[55fa146b3000+1000]

上面我截了两行内容,dmesg最下面的是最后一次发生崩溃的记录,说一下输出的内容

[系统开启的秒数] 程序名[pid]: 错误原因 ip 指令地址 sp 栈顶地址 错误号 main[基址+大小]
[239814.618107] main[19429]: segfault at 0 ip 000055fa146b336c sp 00007ffd4b2b4180 error 4 in main[55fa146b3000+1000]

下面的错误原因分别有trap divide error,表示除0的错,另一个segfault at 0是空指针引起的段错误, 后面的error号也不一样.
拿到这个log的时候,我们看到最后一条记录main[19429],pid正好与最后一次我们运行程序时候打印的pid相同,表示是同一次崩溃了.

下面我们开始算术,求出崩溃地址.
我们需要关注的是 ip 000055fa146b336c 和 main[55fa146b3000+1000] 这两列,ip指的是指令在崩溃的时候指向的地址, main后面是程序的基址,所以我们可以想到ip - bp 就是偏移地址(bp是基址的意思),也就是程序崩溃时候的地址了,下面我们验证一下

# 先把ip, bp转为10进制,方便expr运算
bash$ printf "%d %d %d\n" 0x000055fa146b336c 0x55fa146b3000 0x1000
94532572754796 94532572753920 4096

# 再epxr算一下 addr = ip - bp
bash$ expr 94532572754796 - 94532572753920 + 4096
4972

# 再把4972转为16进制
printf "%x\n" 4972
136c

下面我们拿addr2line查一下这个地址的行号,定位了

bash$ addr2line -e main 0x136c -sfC
test_sig_segv()
main.cpp:54

下面就是引起崩溃的位置了,确实是54行

    51  void test_sig_segv(){
    52          //这个就是段错误了
    53          int* p = NULL;
    54          printf("*p = %d\n", *p);
    55  }

你可能感兴趣的:(linux编程入门(九)-程序崩溃之后的排错及定位)