組み込み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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    /* 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
    "
    , 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
    "
    , 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)
    "
    , 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構造で、命令レジスタの内容を印刷する.