嵌入式arm開發C語言調用棧回溯實戰

嵌入式arm開發C語言調用棧回溯實戰

參考: https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes

  • 代碼

    #define _GNU_SOURCE
    #endif
    #ifndef __USE_GNU
    #define __USE_GNU
    #endif
    
    #include <execinfo.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <ucontext.h>
    #include <unistd.h>
    
    /* This structure mirrors the one found in /usr/include/asm/ucontext.h (arm: arm-linux-gnueabihf/libc/usr/include/asm-generic/ucontext.h) */
    typedef struct _sig_ucontext {
      unsigned long     uc_flags;
      struct ucontext   *uc_link;
      stack_t           uc_stack;
      struct sigcontext uc_mcontext;
      sigset_t          uc_sigmask;
    } sig_ucontext_t;
    
    void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
    {
      void *             array[50];
      void *             caller_address;
      char **            messages;
      int                size, i;
      sig_ucontext_t *   uc;
    
      uc = (sig_ucontext_t *)ucontext;
    
      /* Get the address at the time the signal was raised */
    #if defined(__i386__) // gcc specific
      caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific
    #elif defined(__x86_64__) // gcc specific
      caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific
    #else
      //#error Unsupported architecture. // TODO: Add support for other arch.
      caller_address = (void *) uc->uc_mcontext.arm_pc; // ARM 
    #endif
    
      fprintf(stderr, "signal %d (%s), address is %p from %p\n", 
        sig_num, strsignal(sig_num), info->si_addr, 
        (void *)caller_address);
    
      size = backtrace(array, 50);
    
      /* overwrite sigaction with caller's address */
      array[1] = caller_address;
    
      messages = backtrace_symbols(array, size);
    
      /* skip first stack frame (points here) */
      for (i = 1; i < size && messages != NULL; ++i)
      {
        fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);
      }
    
      free(messages);
    
      exit(EXIT_FAILURE);
    }
    
    int crash()
    {
      char * p = NULL;
      *p = 0;
      return 0;
    }
    
    int foo4()
    {
      crash();
      return 0;
    }
    
    int foo3()
    {
      foo4();
      return 0;
    }
    
    int foo2()
    {
      foo3();
      return 0;
    }
    
    int foo1()
    {
      foo2();
      return 0;
    }
    
    int main(int argc, char ** argv)
    {
      struct sigaction sigact;
    
      sigact.sa_sigaction = crit_err_hdlr;
      sigact.sa_flags = SA_RESTART | SA_SIGINFO;
    
      if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
      {
        fprintf(stderr, "error setting signal handler for %d (%s)\n", SIGSEGV, strsignal(SIGSEGV));
    
        exit(EXIT_FAILURE);
      }
    
      foo1();
    
      exit(EXIT_SUCCESS);
    }
    
  • 編譯

    arm-linux-gnueabihf-gcc -o test -rdynamic -mapcs-frame -funwind-tables -ffunction-sections backtrace.c
    
  • 運行

    signal 11 (Segmentation fault), address is (nil) from 0x10a32
    [bt]: (1) ./Test(crash+0xd) [0x10a32]
    [bt]: (2) ./Test(crash+0xd) [0x10a32]
    [bt]: (3) ./Test(foo4+0x7) [0x10a4c]
    [bt]: (4) ./Test(foo3+0x7) [0x10a5c]
    [bt]: (5) ./Test(foo2+0x7) [0x10a6c]
    [bt]: (6) ./Test(foo1+0x7) [0x10a7c]
    [bt]: (7) ./Test(main+0x5d) [0x10ae2]
    [bt]: (8) /lib/libc.so.6(__libc_start_main+0x9b) [0xb6ec9334]
    
  • 回溯

    arm-linux-gnueabihf-addr2line -e ./test -C -f 0x10a4c
    arm-linux-gnueabihf-objdump -s -d ./test --start-address=0x10a00 --stop-address=0x10a80
    
  • 更多參考

  • 自己動手實現arm函數棧幀回溯【轉】

    • 用的uclib版本較低,沒有這些函數,但又需要,只能自己實現了(較高的版本應該有這些函數,換版本很麻煩),而且可以加深自己對這方面的理解
  • 利用backtrace和ucontex定位segment錯誤

    • 上面通過backtrace可以大體得到”segmentfault”錯誤時的函數調用棧,然而僅憑backtrace還是不能得到引起異常的指令地址(甚至連引起異常的函數也無法得到)。在Redis的源碼中,看到了打印指令地址的方法。使用ucontext_t結構,打印出指令寄存器的內容。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章