• backtrace输出任意线程栈信息


    acktrace多用在程序运行出现异常时打印异常线程的调用栈信息,如:在Linux中如何利用backtrace信息解决程序崩溃的问题_gongmin856的博客-CSDN博客_backtrace。backtrace相关函数说明如:在Linux中如何利用backtrace信息解决程序崩溃的问题_gongmin856的博客-CSDN博客_backtrace

    backtrace可输出当前线程的调用栈信息,在一个多线程的架构中,如果某一个线程出现死锁或者卡死现象,如何找出问题线程的异常点?

    下面先简要说下线程崩溃异常(内存访问越界,除0等错误异常,非死锁或卡死异常)的方法。

    1. 首先需要注册signal函数应用程序调试-signal和backtrace_vector_s的博客-CSDN博客_backtrace返回值一直是0)如:signal(SIGSEGV, SigSegv_handler),这个是当内存访问异常时会执行到SigSegv_handler,在SigSegv_handler函数内可调用如下print_backtrace代码输出问题线程调用栈信息

    backtrace输出调用栈示例代码如下:

    1. void print_backtrace(int signum)
    2. {
    3. #define BACKTRACE_SIZE 30
    4. void* buffer[BACKTRACE_SIZE] = {0};
    5. int pointer_num = backtrace(buffer, BACKTRACE_SIZE);
    6. char** string_buffer = backtrace_symbols(buffer, pointer_num);
    7. printf("[%s:%d] signal received num:%u\n", __func__, __LINE__, signum);
    8. printf("print backtrace begin\n");
    9. if(string_buffer != NULL)
    10. {
    11. for(int i = 0; i < pointer_num; i++)
    12. {
    13. printf("%s\n", string_buffer[i]);
    14. }
    15. }
    16. else
    17. {
    18. printf("print backtrace null\n");
    19. }
    20. printf("print backtrace end\n");
    21. free(string_buffer);
    22. return;
    23. }

    2. 编译时加入-funwind-tables(-ffunction-sections可不加),否则backtrace返回值会是0,无法输出调用栈信息。也可以在链接时加入-rdynamic,这样在输出的调用栈中可以看到函数信息。-g选项可以加进去。

    3. 当出现问题时,根据输出的调用栈信息,在PC上输入:arm-oe-linux-gnueabi-addr2line -e bin address -a -f -p -C

    其中:bin为目标程序,该目标程序可为非strip目标文件(没有加strip生成的目标文件信息更多,可查询strip作用。若程序被strip又没有加-rdynamic选项,则查找的地址可能不准确)。address为backtrace输出的十六进制地址,通过输出可以看出异常地址。

    这个监测原理是当程序运行出现异常时,由系统发起注册信号给异常线程,从而输出异常线程时的调用栈。

    基于以上信息,输出相关运行线程时的调用栈信息步骤如下:

    1. 在主线程中安装信号signal(SIGUSR1 SigSegv_handler),注册信号函数SigSegv_handler函数可以与其他异常共用或者另写;(使用用户自定义信号SIGUSR1,也可以使用其它信号)

    2. 需要建立一个类似看门狗线程,被监测线程设定超时时间,周期更新看门狗,同时被监测线程需要将线程自身id(通过pthread_self获取)传送给看门狗线程;

    3. 如果看门狗线程发现某一个线程未及时喂狗时,则通过pthread_kill(thread_id, SIGUSR1)给问题线程发送信号,在注册信号函数内输出调用栈信息。若长时间未喂狗,还可以给问题线程发送退出信号来退出整个进程;

    4. 基于以上设置也可以在需要时输出相关线程栈。

    示例代码如下,在函数test_fun1内死循环调用while_a函数。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include //SIGQUIT /usr/include/bits/signum.h
    6. #include // ESRCH /usr/include/asm-/error-bash.h
    7. #include
    8. #include // syscall(SYS_gettid)
    9. int while_a(int a)
    10. {
    11. return a*a;
    12. }
    13. void test_fun1(void)
    14. {
    15. int a = 1;
    16. while(1)
    17. {
    18. a += while_a(a);
    19. }
    20. }
    21. void* thread_fun1(void* arg)
    22. {
    23. pid_t pid;
    24. pthread_t self;
    25. pid = getpid();
    26. self = pthread_self(); // 与pthread_create创建的相同
    27. printf("[%s:%d] pid:%d tid:%ld self:%lu\n", __func__, __LINE__, pid, syscall(SYS_gettid), self); // syscall(SYS_gettid) 与ps -T 查看的线程ID相同
    28. test_fun1();
    29. return (void*)0;
    30. }
    31. void* thread_fun2(void* arg)
    32. {
    33. pid_t pid;
    34. pthread_t self;
    35. pid = getpid();
    36. self = pthread_self();
    37. printf("[%s:%d] pid:%d tid:%ld self:%lu\n", __func__, __LINE__, pid, syscall(SYS_gettid), self);
    38. while(1)
    39. {
    40. printf("%s.\n", __func__);
    41. sleep(1);
    42. }
    43. return (void*)0;
    44. }
    45. void print_backtrace(int signum)
    46. {
    47. #define BACKTRACE_SIZE 30
    48. void* buffer[BACKTRACE_SIZE] = {0};
    49. int pointer_num = backtrace(buffer, BACKTRACE_SIZE);
    50. char** string_buffer = backtrace_symbols(buffer, pointer_num);
    51. printf("[%s:%d] signal received num:%u\n", __func__, __LINE__, signum);
    52. printf("print backtrace begin\n");
    53. if(string_buffer != NULL)
    54. {
    55. for(int i = 0; i < pointer_num; i++)
    56. {
    57. printf("%s\n", string_buffer[i]);
    58. }
    59. }
    60. else
    61. {
    62. printf("print backtrace null\n");
    63. }
    64. printf("print backtrace end\n");
    65. free(string_buffer);
    66. return;
    67. }
    68. int main(int argc ,char *argv[])
    69. {
    70. pthread_t tid1, tid2;
    71. int err1, err2;
    72. int res_kill;
    73. struct sigaction sa_usr;
    74. sa_usr.sa_flags = 0;
    75. sa_usr.sa_handler = print_backtrace; //信号处理函数
    76. sigaction(SIGUSR1, &sa_usr, NULL);
    77. err1 = pthread_create(&tid1, NULL, thread_fun1, NULL);
    78. err2 = pthread_create(&tid2, NULL, thread_fun2, NULL);
    79. printf("[%s:%d]tid:%lu,err1:%d. tid2:%lu,err2:%d.\n", __func__, __LINE__, tid1, err1, tid2, err2);
    80. if(err1!=0 || err2!=0)
    81. {
    82. return 0;
    83. }
    84. sleep(1);
    85. res_kill = pthread_kill(tid1,0); // sig是0呢,这是一个保留信号,一个作用是用来判断线程是不是还活着
    86. printf("[%s:%d]pthread_kill ret:%d\n", __func__, __LINE__, res_kill);
    87. if(res_kill == ESRCH)
    88. {
    89. printf("the specified thread did not exists or already quit\n");
    90. }
    91. else if(res_kill == EINVAL)
    92. {
    93. printf("signal is invalid\n");
    94. }
    95. else
    96. {
    97. printf("the specified thread is alive\n");
    98. }
    99. sleep(1);
    100. res_kill = pthread_kill(tid1, SIGUSR1); // SIGUSR1 用户自定义,进入自定义函数
    101. printf("[%s:%d]pthread_kill ret:%d\n", __func__, __LINE__, res_kill);
    102. sleep(3);
    103. res_kill = pthread_kill(tid1, SIGQUIT); // SIGQUIT未定义信号, 进程退出
    104. printf("[%s:%d]pthread_kill ret:%d\n", __func__, __LINE__, res_kill);
    105. pthread_join(tid1, NULL);
    106. pthread_join(tid2, NULL);
    107. printf("[%s:%d]main thread end\n", __func__, __LINE__);
    108. return 0;
    109. }

    使用gcc -pthread -funwind-tables -o backtrace_test backtrace_test.c,运行程序后,输出的调用栈信息看不到函数名,只能看到相关地址。使用addr2line -e backtrace_test 0x4009f0 -a -f -p -C查看地址,对应处的函数显示出来。如下图:

    使用ida pro 7.6打开backtrace_test目标文件,Jump to address:4009f0处,按F5后转换的伪代码如下,通过伪代码可以更清楚的看到代码问题处。

    加入-rdynamic选项,gcc -pthread -rdynamic -funwind-tables -o backtrace_test backtrace_test.c后,在backtrace内部即可打印出调用栈时的函数信息。

    不加-rydnamic,加入-g选项,gcc -pthread -funwind-tables -g -o backtrace_test backtrace_test.c,addr2line后可看到出问题的行号。

     使用strip去掉符号表(减小程序大小,strip 命令从 XCOFF 对象文件中有选择地除去行号信息、重定位信息、调试段、typchk 段、注释段、文件头以及所有或部分符号表。),使用addr2line后看不到函数位置,ida也无法定到。当出问题后,需要使用未strip的目标程序进行问题查找。

     

    使用file backtrace_test能看到该文件被strip了。

    当程序中有库函数时可参考:c语言 backtrace_学术马的博客-CSDN博客_backtrace 头文件

  • 相关阅读:
    链式设计模式——装饰模式和职责链模式
    电容知识点
    电动化浪潮的助力中国汽车产业崛起
    LOAM框架后端优化总结
    docker迁移容器
    _kbhit函数详解
    JavaScript(Array,String,window对象)入门
    Hadoop-MapReduce
    计算一个区间时间差值,时间保留剩下的差值
    聊一聊 C# 的线程本地存储TLS到底是什么
  • 原文地址:https://blog.csdn.net/zisehuoxia/article/details/127097183