anddoird 實現backtrace

  1. backtrace

    • 簡介

      • 有的系統沒有實現backtrace這個函數.
      • 常見的就有linux還有一些docker的庫.
    • 判斷是否支持backtrace

      #include<stdio.h>
      #include<dlfcn.h>
      int main()
      {
         void * bt = dlsym(NULL,"backtrace");
         if(NULL != bt)
         {
             printf("support\n");
         }
         else
         {
             printf("not support\n");
         }
         return 0;
      }
      
      
    • 輸出

      [root@localhost bt_support]# gcc test.c -ldl -g
      [root@localhost bt_support]# ./a.out
      support
      
      
  2. 堆棧

    • 說明

      • 查看某一個時刻,函數調用棧.
      • 崩潰 的時候輸出,可以精確的查看在什麼地方出的問題.
    • 常見工具

      • pstack -p pid查看正在執行的進程某一時刻的堆棧.
      • gdb.
    • 案例

      (gdb) bt
      #0  show () at test.c:3
      #1  0x0000000000400501 in cool () at test.c:7
      #2  0x0000000000400511 in main () at test.c:12
      
      
    • 注意

      • 必須要有調試信息.
      • 否則結果是??:00這類無意義數據.
  3. 獲取堆棧

    • 說明

      • 堆棧一般是代碼地址.
      • 也可能是共享庫的映射地址.
    • 獲取地址

      #include <execinfo.h>
      void show()
      {
         void * s[30] = {0};
         backtrace(s,30);
         return;
      }
      
      void cool()
      {
      show();
      }
      
      int main()
      {
         cool();
      }
      
      

      輸出

      (gdb) bt
      #0  show () at test.c:6
      #1  0x0000000000400587 in cool () at test.c:11
      #2  0x0000000000400597 in main () at test.c:16
      (gdb) x /3xg s
      0x7fffffffe320: 0x0000000000400576      0x0000000000400587
      0x7fffffffe330: 0x0000000000400597
      (gdb) p show
      $2 = {void ()} 0x40053d <show>
      
      • backtrace獲取到的地址和gdb bt指令獲取到的一樣.

      addr2line

      [root@localhost bt_support]# addr2line -fap 0x0000000000400576 0x0000000000400587 0x0000000000400597
      0x0000000000400576: show at /root/cfile/bt_support/test.c:6
      0x0000000000400587: cool at /root/cfile/bt_support/test.c:12
      0x0000000000400597: main at /root/cfile/bt_support/test.c:17
      
      • 得到的結果基本相同.
  4. 動態庫

    • 獲取堆棧

      test.c

      #define _GNU_SOURCE
      #include <dlfcn.h>
      #include <execinfo.h>
      void show()
      {
         void * s[30] = {0};
         int n = backtrace(s,30);
         int i;
         Dl_info info;
         for(i = 0 ; i < n; i++)
         {
             dladdr(s[i],&info);
             i+=0;
         }
         return;
      }
      
      void cool()
      {
      show();
      }
      
      

      main.c

      extern void cool();
      int main()
      {
         cool();
      }
      
      

      編譯

      gcc -fPIC -shared -o test.so test.c -g
      gcc ./test.so main.c -g -ldl
      

      調試輸出

      (gdb) p info
      $1 = {dli_fname = 0x7ffff7ff9640 "./test.so", dli_fbase = 0x7ffff7bd9000,
       dli_sname = 0x7ffff7bd942d "show", dli_saddr = 0x7ffff7bd9785 <show>}
      
      (gdb) printf "%x\n",info.dli_saddr - info.dli_fbase
      785
      
      (gdb) !addr2line -e ./test.so 806
      /root/cfile/bt_support/test.c:21
      
      (gdb) bt
      #0  show () at test.c:10
      #1  0x00007ffff7bd9806 in cool () at test.c:20
      #2  0x000000000040063b in main () at main.c:4
      
      • addr信息需要藉助dladdr.獲取某個地址的信息.
  5. 實現backtrace

    • 參考鏈接

    • 應用場景

      • 有的會沒有這個函數,比如android,docker.
    • 堆棧

      #include <unwind.h>
      
      struct BacktraceState
      {
         void** current;
         void** end;
      };
      
      static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg)
      {
         BacktraceState* state = static_cast<BacktraceState*>(arg);
         uintptr_t pc = _Unwind_GetIP(context);
         if (pc) {
             if (state->current == state->end) {
                 return _URC_END_OF_STACK;
             } else {
                 *state->current++ = reinterpret_cast<void*>(pc);
             }
         }
         return _URC_NO_REASON;
      }
      
      }
      
      size_t captureBacktrace(void** buffer, size_t max)
      {
         BacktraceState state = {buffer, buffer + max};
         _Unwind_Backtrace(unwindCallback, &state);
      
         return state.current - buffer;
      }
      
      • struct BacktraceState,頭尾指針的數組.存放void*類型數據的指針數組.
      • _Unwind_Reason_Code 回調函數返回值.
      • struct _Unwind_Context* context回調函數第一參數.
      • void* arg用戶用於存放數據的結構體,任意類型.
      • unwindCallback自定義的回調函數,傳入堆棧信息content,和用戶的容器指針.
      • _Unwind_GetIP獲取棧指針.
      • _Unwind_Backtrace 實際函數,回調和容器.
    • pipeline

  6. backtrace實現

    • 原型

    • 實現

      #include<stdio.h>
      #include <unwind.h>
      struct BacktraceState {
         void** start;
         void** current;
         void** end;
      };
      
      static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
        BacktraceState* state = (BacktraceState*)(arg);
        _Unwind_Ptr pc = _Unwind_GetIP(context);
        if (pc) {
             if (state->current == state->end) {
                 return _URC_END_OF_STACK;
             }
             if(NULL != state->start) {
                 *state->current = (void*)(pc);
             }
            state->current++;
         }
         return _URC_NO_REASON;
      }
      
      int Backtrace(void** buffer, int size) {
         BacktraceState state = {buffer, buffer, buffer + size};
         _Unwind_Backtrace(unwindCallback, &state);
         return state.current - state.start;
      }
      
      int main()
      {
         void * s[10] = {NULL};
         Backtrace(s,10);
         return 0;
      }
      
      
      • 編譯
      gcc -g backtrace_demo.c
      
  7. backtrace + addr2line

    • 說明

      • 將地址轉成可讀.
    • 案例

      #include<stdio.h>
      #include<unwind.h>
      typedef struct BacktraceState {
         void** start;
         void** current;
         void** end;
      } BacktraceState;
      
      static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
        BacktraceState* state = (BacktraceState*)(arg);
        _Unwind_Ptr pc = _Unwind_GetIP(context);
        if (pc) {
             if (state->current == state->end) {
                 return _URC_END_OF_STACK;
             }
             if(NULL != state->start) {
                 *state->current = (void*)(pc);
             }
            state->current++;
         }
         return _URC_NO_REASON;
      }
      
      int Backtrace(void** buffer, int size) {
         BacktraceState state = {buffer, buffer, buffer + size};
         _Unwind_Backtrace(unwindCallback, &state);
         return state.current - state.start;
      }
      
      int show()
      {
         void * s[10] = {NULL};
         int i = 0 , n = Backtrace(s,10);
         for( i = 0 ; i < n ;i ++)
         {
             printf("%p\n",s[i]);
         }
         return 0;
      }
      
      int main()
      {
         show();
         return 0;
      }
      
      

      編譯

      gcc backtrace_demo.c -g -o demo.out
      ./demo.out | addr2line -fapCe ./demo.out
      
  8. 最終版本

    • 簡介

      • 利用Backtrace獲取堆棧,再通過dladdr辨別是否爲可執行文件代碼.
      • 最後通過addr2line將結果輸出.
    • 案例

      #define _GNU_SOURCE
      #include<stdio.h>
      #include <unistd.h>
      #include<stdlib.h>
      #include<unwind.h>
      #include<dlfcn.h>
      typedef struct BacktraceState {
         void** start;
         void** current;
         void** end;
      } BacktraceState;
      
      static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
        BacktraceState* state = (BacktraceState*)(arg);
        _Unwind_Ptr pc = _Unwind_GetIP(context);
        if (pc) {
             if (state->current == state->end) {
                 return _URC_END_OF_STACK;
             }
             if(NULL != state->start) {
                 *state->current = (void*)(pc);
             }
            state->current++;
         }
         return _URC_NO_REASON;
      }
      
      int Backtrace(void** buffer, int size) {
         BacktraceState state = {buffer, buffer, buffer + size};
         _Unwind_Backtrace(unwindCallback, &state);
         return state.current - state.start;
      }
      
      void * main_base;
      extern int main();
      void __attribute__((constructor)) ms()
      {
         Dl_info info;
         dladdr(main,&info);
         main_base = info.dli_fbase;
      }
      
      int show()
      {
         void * s[10] = {NULL};
         int i = 0 , n = Backtrace(s,10);
         for( i = 0 ; i < n ;i ++)
         {
             char buf[100]={0};
             Dl_info info;
             if(dladdr(s[i],&info)) {
                 sprintf(buf,"addr2line -fpia -e %s %p",info.dli_fname,info.dli_fbase == main_base ? (unsigned long)s[i] : s[i] - info.dli_fbase);
             }
             //printf("%s %p\n",buf,s[i]);
             system(buf);
         }
         printf("\n");
         return 0;
      }
      
      int cool()
      {
         show();
         return 0;
      }
      
      

      test.c

      extern int cool();
      int main()
      {
         cool();
         return 0;
      }
      
    • 編譯

      gcc -fPIC backtrace_demo.c -shared -o backtrace_demo.so -ldl -g
      gcc ./backtrace_demo.so test.c -ldl -g
      
    • 輸出

      [root@localhost chap01]# ./a.out
      0x0000000000000ab0: Backtrace at xxxxxx/backtrace_demo.c:31
      0x0000000000000b2f: show at xxxxxx/backtrace_demo.c:46
      0x0000000000000c1a: cool at xxxxxx/backtrace_demo.c:64
      0x000000000040063b: main at xxxxxx/test.c:5
      0x0000000000022555: __libc_start_main at /usr/src/debug/glibc-2.17-c758a686/csu/../csu/libc-start.c:300
      0x0000000000400569: _start at ??:?
      
      
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章