developer tip

줄 번호 정보와 함께 gcc를 사용하여 C ++에 대한 스택 추적을 얻는 방법은 무엇입니까?

optionbox 2020. 12. 24. 23:37
반응형

줄 번호 정보와 함께 gcc를 사용하여 C ++에 대한 스택 추적을 얻는 방법은 무엇입니까?


assert개발자의 실수를 포착하기 위해 매크로와 같은 독점적 스택 추적을 사용합니다. 오류가 발견되면 스택 추적이 인쇄됩니다.

gcc의 쌍 backtrace()/ backtrace_symbols()방법이 충분하지 않습니다.

  1. 이름이 엉망입니다.
  2. 라인 정보 없음

첫 번째 문제는 abi :: __ cxa_demangle 로 해결할 수 있습니다 .

그러나 두 번째 문제는 더 어렵습니다. backtrace_symbols () 대체품을 찾았습니다 . 이것은 gcc의 backtrace_symbols ()보다 낫습니다. 라인 번호를 검색 할 수 있고 (-g로 컴파일 된 경우) -rdynamic으로 컴파일 할 필요가 없기 때문입니다.

Hoverer 코드는 GNU 라이센스이므로 IMHO는 상용 코드에서 사용할 수 없습니다.

어떤 제안?

추신

gdb는 함수에 전달 된 인수를 출력 할 수 있습니다. 아마도 요청하기에는 이미 너무 많을 것입니다. :)

추신 2

비슷한 질문 (노바에게 감사드립니다)


얼마 전에 나는 비슷한 질문에 답했다 . 행 번호와 파일 이름도 인쇄하는 방법 # 4에서 사용할 수있는 소스 코드를 살펴 봐야합니다.

  • 방법 # 4 :

줄 번호를 인쇄하기 위해 방법 # 3에서 약간 개선했습니다. 이것은 방법 # 2에서도 작동하도록 복사 할 수 있습니다.

기본적으로 addr2line사용 하여 주소를 파일 이름과 줄 번호로 변환합니다.

아래 소스 코드는 모든 로컬 함수에 대한 줄 번호를 인쇄합니다. 다른 라이브러리의 함수가 호출되면 ??:0파일 이름 대신 몇 가지가 표시 될 수 있습니다 .

#include <stdio.h>
#include <signal.h>
#include <stdio.h>
#include <signal.h>
#include <execinfo.h>

void bt_sighandler(int sig, struct sigcontext ctx) {

  void *trace[16];
  char **messages = (char **)NULL;
  int i, trace_size = 0;

  if (sig == SIGSEGV)
    printf("Got signal %d, faulty address is %p, "
           "from %p\n", sig, ctx.cr2, ctx.eip);
  else
    printf("Got signal %d\n", sig);

  trace_size = backtrace(trace, 16);
  /* overwrite sigaction with caller's address */
  trace[1] = (void *)ctx.eip;
  messages = backtrace_symbols(trace, trace_size);
  /* skip first stack frame (points here) */
  printf("[bt] Execution path:\n");
  for (i=1; i<trace_size; ++i)
  {
    printf("[bt] #%d %s\n", i, messages[i]);

    /* find first occurence of '(' or ' ' in message[i] and assume
     * everything before that is the file name. (Don't go beyond 0 though
     * (string terminator)*/
    size_t p = 0;
    while(messages[i][p] != '(' && messages[i][p] != ' '
            && messages[i][p] != 0)
        ++p;

    char syscom[256];
    sprintf(syscom,"addr2line %p -e %.*s", trace[i], p, messages[i]);
        //last parameter is the file name of the symbol
    system(syscom);
  }

  exit(0);
}


int func_a(int a, char b) {

  char *p = (char *)0xdeadbeef;

  a = a + b;
  *p = 10;  /* CRASH here!! */

  return 2*a;
}


int func_b() {

  int res, a = 5;

  res = 5 + func_a(a, 't');

  return res;
}


int main() {

  /* Install our signal handler */
  struct sigaction sa;

  sa.sa_handler = (void *)bt_sighandler;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART;

  sigaction(SIGSEGV, &sa, NULL);
  sigaction(SIGUSR1, &sa, NULL);
  /* ... add any other signal here */

  /* Do something */
  printf("%d\n", func_b());
}

이 코드는 다음과 같이 컴파일해야합니다. gcc sighandler.c -o sighandler -rdynamic

프로그램은 다음을 출력합니다.

Got signal 11, faulty address is 0xdeadbeef, from 0x8048975
[bt] Execution path:
[bt] #1 ./sighandler(func_a+0x1d) [0x8048975]
/home/karl/workspace/stacktrace/sighandler.c:44
[bt] #2 ./sighandler(func_b+0x20) [0x804899f]
/home/karl/workspace/stacktrace/sighandler.c:54
[bt] #3 ./sighandler(main+0x6c) [0x8048a16]
/home/karl/workspace/stacktrace/sighandler.c:74
[bt] #4 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x3fdbd6]
??:0
[bt] #5 ./sighandler() [0x8048781]
??:0

따라서 gdb 스택 추적이 가지고 있고 애플리케이션을 종료하지 않는 모든 기능을 사용하여 스택 추적을 인쇄하는 독립 실행 형 함수 를 원합니다 . 대답은 비대화 형 모드에서 gdb 실행을 자동화하여 원하는 작업 만 수행하는 것입니다.

이는 자식 프로세스에서 gdb를 실행하고 fork ()를 사용하고 애플리케이션이 완료 될 때까지 기다리는 동안 스택 추적을 표시하도록 스크립팅하여 수행됩니다. 이는 코어 덤프를 사용하지 않고 응용 프로그램을 중단하지 않고 수행 할 수 있습니다. 나는이 질문을보고 이것을하는 방법을 배웠습니다 : 프로그램에서 gdb를 호출하여 스택 추적을 인쇄하는 것이 더 낫습니까?

이 질문과 함께 게시 된 예제는 작성된대로 정확히 작동하지 않았으므로 여기에 "고정"버전이 있습니다 (Ubuntu 9.04에서 실행했습니다).

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

void print_trace() {
    char pid_buf[30];
    sprintf(pid_buf, "%d", getpid());
    char name_buf[512];
    name_buf[readlink("/proc/self/exe", name_buf, 511)]=0;
    int child_pid = fork();
    if (!child_pid) {           
        dup2(2,1); // redirect output to stderr
        fprintf(stdout,"stack trace for %s pid=%s\n",name_buf,pid_buf);
        execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
        abort(); /* If gdb failed to start */
    } else {
        waitpid(child_pid,NULL,0);
    }
}

참조 된 질문에서 볼 수 있듯이 gdb는 사용할 수있는 추가 옵션을 제공합니다. 예를 들어, "bt"대신 "bt full"을 사용하면 더 자세한 보고서가 생성됩니다 (로컬 변수가 출력에 포함됨). gdb에 대한 맨 페이지는 가볍지 만 여기에서 전체 문서를 볼 수 있습니다 .

이것은 gdb를 기반으로하므로 출력에 demangled names , line-numbers , function arguments , 선택적으로 지역 변수도 포함 됩니다. 또한 gdb는 스레드를 인식하므로 스레드 별 메타 데이터를 추출 할 수 있어야합니다.

다음은이 메서드에서 볼 수있는 스택 추적 유형의 예입니다.

0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
[Current thread is 0 (process 15573)]
#0  0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
#1  0x0000000000400bd5 in print_trace () at ./demo3b.cpp:496
2  0x0000000000400c09 in recursive (i=2) at ./demo3b.cpp:636
3  0x0000000000400c1a in recursive (i=1) at ./demo3b.cpp:646
4  0x0000000000400c1a in recursive (i=0) at ./demo3b.cpp:646
5  0x0000000000400c46 in main (argc=1, argv=0x7fffe3b2b5b8) at ./demo3b.cpp:70

참고 : 이것이 valgrind 사용과 호환되지 않는 것으로 나타났습니다 (Valgrind의 가상 머신 사용 때문일 수 있음). 또한 gdb 세션 내에서 프로그램을 실행할 때도 작동하지 않습니다 ( "ptrace"의 두 번째 인스턴스를 프로세스에 적용 할 수 없음).


본질적으로 동일한 질문에 대한 강력한 토론이 있습니다 : How to generate a stacktrace when my gcc C ++ app crash . 런타임에 스택 추적을 생성하는 방법에 대한 많은 논의를 포함하여 많은 제안이 제공됩니다.

그 스레드에서 개인적으로 가장 좋아하는 대답 은 크래시 당시 전체 애플리케이션 상태 (함수 인수, 줄 번호 및 얽 히지 않은 이름 포함) 를 볼 수 있는 코어 덤프 를 활성화하는 것이 었습니다 . 이 접근 방식의 또 다른 이점은 어설 션 뿐만 아니라 세그멘테이션 오류처리되지 않은 예외 에도 적용된다는 것 입니다.

다른 Linux 셸은 다른 명령을 사용하여 코어 덤프를 활성화하지만 다음과 같이 애플리케이션 코드 내에서 수행 할 수 있습니다.

#include <sys/resource.h>
...
struct rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
assert( setrlimit( RLIMIT_CORE, &core_limit ) == 0 ); // enable core dumps for debug builds

충돌 후 좋아하는 디버거를 실행하여 프로그램 상태를 검사하십시오.

$ kdbg executable core

다음은 몇 가지 샘플 출력입니다.

대체 텍스트

명령 줄의 코어 덤프에서 스택 추적을 추출 할 수도 있습니다.

$ ( CMDFILE=$(mktemp); echo "bt" >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} temp.exe core )
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 22857]
#0  0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#0  0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#1  0x00007f4189be7bc3 in abort () from /lib/libc.so.6
#2  0x00007f4189bdef09 in __assert_fail () from /lib/libc.so.6
#3  0x00000000004007e8 in recursive (i=5) at ./demo1.cpp:18
#4  0x00000000004007f3 in recursive (i=4) at ./demo1.cpp:19
#5  0x00000000004007f3 in recursive (i=3) at ./demo1.cpp:19
#6  0x00000000004007f3 in recursive (i=2) at ./demo1.cpp:19
#7  0x00000000004007f3 in recursive (i=1) at ./demo1.cpp:19
#8  0x00000000004007f3 in recursive (i=0) at ./demo1.cpp:19
#9  0x0000000000400849 in main (argc=1, argv=0x7fff2483bd98) at ./demo1.cpp:26

GPL 라이선스 코드는 개발 과정에서 도움을주기위한 것이므로 최종 제품에 포함 할 수 없습니다. GPL은 GPL이 아닌 호환 코드와 연결된 GPL 라이선스 코드를 배포하는 것을 제한합니다. 사내에서 GPL 코드 만 사용하는 한 괜찮습니다.


이를 위해 google glog 라이브러리를 사용하십시오. 새로운 BSD 라이센스가 있습니다.

stacktrace.h 파일에 GetStackTrace 함수가 포함되어 있습니다.

편집하다

여기 http://blog.bigpixel.ro/2010/09/09/stack-unwinding-stack-trace-with-gcc/ 에서 프로그램 주소를 파일 이름과 줄 번호로 변환하는 addr2line이라는 유틸리티가 있음을 발견했습니다 .

http://linuxcommand.org/man_pages/addr2line1.html


여기에 다른 접근 방식이 있습니다. debug_assert () 매크로 프로그래밍 설정 조건부 브레이크 포인트 . 디버거에서 실행중인 경우 assert식이 false 일 때 중단 점에 도달 하고 라이브 스택을 분석 할 수 있습니다 (프로그램이 종료되지 않음). 디버거에서 실행하지 않는 경우 debug_assert () 실패로 인해 프로그램이 중단되고 스택을 분석 할 수있는 코어 덤프가 생성됩니다 (이전 답변 참조).

이 접근법의 장점은 일반 어설 션과 비교하여 debug_assert가 트리거 된 후 (디버거에서 실행될 때) 프로그램을 계속 실행할 수 있다는 것입니다. 즉, debug_assert ()는 assert ()보다 약간 더 유연합니다.

   #include <iostream>
   #include <cassert>
   #include <sys/resource.h> 

// note: The assert expression should show up in
// stack trace as parameter to this function
void debug_breakpoint( char const * expression )
   {
   asm("int3"); // x86 specific
   }

#ifdef NDEBUG
   #define debug_assert( expression )
#else
// creates a conditional breakpoint
   #define debug_assert( expression ) \
      do { if ( !(expression) ) debug_breakpoint( #expression ); } while (0)
#endif

void recursive( int i=0 )
   {
   debug_assert( i < 5 );
   if ( i < 10 ) recursive(i+1);
   }

int main( int argc, char * argv[] )
   {
   rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
   setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
   recursive();
   }

참고 : 때때로 디버거 내에서 "조건부 중단 점"설정이 느려질 수 있습니다. 프로그래밍 방식으로 중단 점을 설정함으로써이 메서드의 성능은 일반 assert ()의 성능과 동일해야합니다.

참고 :이 내용은 Intel x86 아키텍처에만 해당됩니다. 다른 프로세서에는 중단 점 생성을위한 다른 명령이있을 수 있습니다.


A는 늦게 비트,하지만 당신은 사용할 수 있습니다 libbfb refdbg은에서와 같이 파일 이름과 LINENUMBER를 가져 symsnarf.c . libbfb는 내부적으로 addr2linegdb


해결책 중 하나는 실패한 assert 핸들러에서 "bt"스크립트로 gdb를 시작하는 것입니다. 이러한 gdb-starting을 통합하는 것은 그리 쉬운 일이 아니지만 역 추적, 인수 및 demangle 이름을 모두 제공합니다 (또는 c ++ filt 프로그램을 통해 gdb 출력을 전달할 수 있음).

두 프로그램 (gdb 및 c ++ filt)은 애플리케이션에 링크되지 않으므로 GPL은 전체 애플리케이션을 오픈 소스로 요구하지 않습니다.

역 추적 기호와 함께 사용할 수있는 동일한 접근 방식 (GPL 프로그램 실행)입니다. % eip의 ascii 목록과 exec 파일 (/ proc / self / maps)의 맵을 생성하고 별도의 바이너리로 전달하면됩니다.


신뢰할 수있는 모든 것을 수행하는 작은 C ++ 클래스 인 DeathHandler 를 사용할 수 있습니다 .


줄 번호가 현재 eip 값과 관련이 있다고 생각합니다.

솔루션 1 :
그런 다음 Linux에서 작업하는 것을 제외하고는 GetThreadContext () 와 같은 것을 사용할 수 있습니다 . 나는 조금 훑어 보았고 비슷한 것을 발견했습니다. ptrace () :

ptrace () 시스템 호출은 부모 프로세스가 다른 프로세스의 실행을 관찰 및 제어하고 핵심 이미지 및 레지스터를 검사하고 변경할 수있는 수단을 제공합니다. [...] 부모는 fork (2)를 호출하고 결과 자식이 PTRACE_TRACEME를 수행하고 (일반적으로) exec (3)를 수행하도록하여 추적을 시작할 수 있습니다. 또는 부모는 PTRACE_ATTACH를 사용하여 기존 프로세스의 추적을 시작할 수 있습니다.

이제 저는 여러분이 작업중인 실제 프로그램 인 자식에게 전송되는 신호를 확인하는 '메인'프로그램을 수행 할 수 있다고 생각했습니다. waitid ()fork() 호출 :

이러한 모든 시스템 호출은 호출 프로세스의 자식에서 상태 변경을 대기하고 상태가 변경된 자식에 대한 정보를 얻는 데 사용됩니다.

SIGSEGV (또는 이와 유사한 것)가 포착 ptrace()되면 eip의 값 을 얻기 위해 호출 합니다.

추신 : 저는 이러한 시스템 호출을 사용한 적이 없기 때문에 (사실, 전에는 본 적이 없습니다.) 가능할지 모르겠습니다. 적어도이 링크가 유용하기를 바랍니다. ;)

솔루션 2 : 첫 번째 솔루션은 매우 복잡합니다. 그렇죠? 훨씬 더 간단한 방법을 생각해 냈습니다. signal ()을 사용하여 관심있는 신호를 포착 eip하고 스택에 저장된 값 을 읽는 간단한 함수를 호출합니다 .

...
signal(SIGSEGV, sig_handler);
...

void sig_handler(int signum)
{
    int eip_value;

    asm {
        push eax;
        mov eax, [ebp - 4]
        mov eip_value, eax
        pop eax
    }

    // now you have the address of the
    // **next** instruction after the
    // SIGSEGV was received
}

asm 구문은 Borland의 것이므로 GAS. ;)


여기에 세 번째 대답이 있습니다. 여전히 코어 덤프를 활용하려고합니다.

"어설 션과 유사한"매크로가 애플리케이션을 종료해야하는지 (어설 션이 수행하는 방식) 또는 스택 추적을 생성 한 후 계속 실행되어야하는지에 대한 질문에서 완전히 명확하지 않았습니다.

이 답변에서는 스택 추적을 표시하고 계속 실행하려는 경우를 다루고 있습니다. 아래 coredump () 함수를 작성하여 코어 덤프 를 생성하고 자동으로 스택 추적추출한 다음 프로그램을 계속 실행했습니다.

사용법은 assert ()와 동일합니다. 물론 차이점은 assert ()는 프로그램을 종료하지만 coredump_assert ()는 종료 하지 않는다는 것입니다.

   #include <iostream>
   #include <sys/resource.h> 
   #include <cstdio>
   #include <cstdlib>
   #include <boost/lexical_cast.hpp>
   #include <string>
   #include <sys/wait.h>
   #include <unistd.h>

   std::string exename;

// expression argument is for diagnostic purposes (shows up in call-stack)
void coredump( char const * expression )
   {

   pid_t childpid = fork();

   if ( childpid == 0 ) // child process generates core dump
      {
      rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
      setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
      abort(); // terminate child process and generate core dump
      }

// give each core-file a unique name
   if ( childpid > 0 ) waitpid( childpid, 0, 0 );
   static int count=0;
   using std::string;
   string pid = boost::lexical_cast<string>(getpid());
   string newcorename = "core-"+boost::lexical_cast<string>(count++)+"."+pid;
   string rawcorename = "core."+boost::lexical_cast<string>(childpid);
   int rename_rval = rename(rawcorename.c_str(),newcorename.c_str()); // try with core.PID
   if ( rename_rval == -1 ) rename_rval = rename("core",newcorename.c_str()); // try with just core
   if ( rename_rval == -1 ) std::cerr<<"failed to capture core file\n";

  #if 1 // optional: dump stack trace and delete core file
   string cmd = "( CMDFILE=$(mktemp); echo 'bt' >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} "+exename+" "+newcorename+" ; unlink ${CMDFILE} )";
   int system_rval = system( ("bash -c '"+cmd+"'").c_str() );
   if ( system_rval == -1 ) std::cerr.flush(), perror("system() failed during stack trace"), fflush(stderr);
   unlink( newcorename.c_str() );
  #endif

   }

#ifdef NDEBUG
   #define coredump_assert( expression ) ((void)(expression))
#else
   #define coredump_assert( expression ) do { if ( !(expression) ) { coredump( #expression ); } } while (0)
#endif

void recursive( int i=0 )
   {
   coredump_assert( i < 2 );
   if ( i < 4 ) recursive(i+1);
   }

int main( int argc, char * argv[] )
   {
   exename = argv[0]; // this is used to generate the stack trace
   recursive();
   }

프로그램을 실행하면 3 개의 스택 트레이스가 표시됩니다.

Core was generated by `./temp.exe'.                                         
Program terminated with signal 6, Aborted.
[New process 24251]
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1  0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2  0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3  0x0000000000401f5f in recursive (i=2) at ./demo3.cpp:60
#4  0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#5  0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#6  0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24259]
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1  0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2  0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3  0x0000000000401f5f in recursive (i=3) at ./demo3.cpp:60
#4  0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61
#5  0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#6  0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#7  0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24267]
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1  0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2  0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3  0x0000000000401f5f in recursive (i=4) at ./demo3.cpp:60
#4  0x0000000000401f70 in recursive (i=3) at ./demo3.cpp:61
#5  0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61
#6  0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#7  0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#8  0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66

내 해결책은 다음과 같습니다.

#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <zconf.h>
#include "regex"

std::string getexepath() {
    char result[PATH_MAX];
    ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
    return std::string(result, (count > 0) ? count : 0);
}

std::string sh(std::string cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
    if (!pipe) throw std::runtime_error("popen() failed!");
    while (!feof(pipe.get())) {
        if (fgets(buffer.data(), 128, pipe.get()) != nullptr) {
            result += buffer.data();
        }
    }
    return result;
}


void print_backtrace(void) {
    void *bt[1024];
    int bt_size;
    char **bt_syms;
    int i;

    bt_size = backtrace(bt, 1024);
    bt_syms = backtrace_symbols(bt, bt_size);
    std::regex re("\\[(.+)\\]");
    auto exec_path = getexepath();
    for (i = 1; i < bt_size; i++) {
        std::string sym = bt_syms[i];
        std::smatch ms;
        if (std::regex_search(sym, ms, re)) {
            std::string addr = ms[1];
            std::string cmd = "addr2line -e " + exec_path + " -f -C " + addr;
            auto r = sh(cmd);
            std::regex re2("\\n$");
            auto r2 = std::regex_replace(r, re2, "");
            std::cout << r2 << std::endl;
        }
    }
    free(bt_syms);
}

void test_m() {
    print_backtrace();
}

int main() {
    test_m();
    return 0;
}

산출:

/home/roroco/Dropbox/c/ro-c/cmake-build-debug/ex/test_backtrace_with_line_number
test_m()
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:57
main
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:61
??
??:0

"??" 및 "?? : 0"이 추적은 내 소스가 아닌 libc에 있기 때문에


지금까지 제공된 AFAICS의 모든 솔루션은 공유 라이브러리에서 함수 이름과 줄 번호를 인쇄하지 않습니다. 그것이 내가 필요했던 것이므로 / proc / id / maps를 사용하여 공유 라이브러리 주소를 해결하기 위해 karlphillip의 솔루션 (및 비슷한 질문의 다른 답변)을 변경했습니다.

#include <stdlib.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <execinfo.h>
#include <stdbool.h>

struct Region { // one mapped file, for example a shared library
    uintptr_t start;
    uintptr_t end;
    char* path;
};

static struct Region* getRegions(int* size) { 
// parse /proc/self/maps and get list of mapped files 
    FILE* file;
    int allocated = 10;
    *size = 0;
    struct Region* res;
    uintptr_t regionStart = 0x00000000;
    uintptr_t regionEnd = 0x00000000;
    char* regionPath = "";
    uintmax_t matchedStart;
    uintmax_t matchedEnd;
    char* matchedPath;

    res = (struct Region*)malloc(sizeof(struct Region) * allocated);
    file = fopen("/proc/self/maps", "r");
    while (!feof(file)) {
        fscanf(file, "%jx-%jx %*s %*s %*s %*s%*[ ]%m[^\n]\n",  &matchedStart, &matchedEnd, &matchedPath);
        bool bothNull = matchedPath == 0x0 && regionPath == 0x0;
        bool similar = matchedPath && regionPath && !strcmp(matchedPath, regionPath);
        if(bothNull || similar) {
            free(matchedPath);
            regionEnd = matchedEnd;
        } else {
            if(*size == allocated) {
                allocated *= 2;
                res = (struct Region*)realloc(res, sizeof(struct Region) * allocated);
            }

            res[*size].start = regionStart;
            res[*size].end = regionEnd;
            res[*size].path = regionPath;
            (*size)++;
            regionStart = matchedStart;
            regionEnd = matchedEnd;
            regionPath = matchedPath;
        }
    }
    return res;
}

struct SemiResolvedAddress {
    char* path;
    uintptr_t offset;
};
static struct SemiResolvedAddress semiResolve(struct Region* regions, int regionsNum, uintptr_t address) {
// convert address from our address space to
// address suitable fo addr2line 
    struct Region* region;
    struct SemiResolvedAddress res = {"", address};
    for(region = regions; region < regions+regionsNum; region++) {
        if(address >= region->start && address < region->end) {
            res.path = region->path;
            res.offset = address - region->start;
        }
    }
    return res;
}

void printStacktraceWithLines(unsigned int max_frames)
{
    int regionsNum;
    fprintf(stderr, "stack trace:\n");

    // storage array for stack trace address data
    void* addrlist[max_frames+1];

    // retrieve current stack addresses
    int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));
    if (addrlen == 0) {
        fprintf(stderr, "  <empty, possibly corrupt>\n");
        return;
    }
    struct Region* regions = getRegions(&regionsNum); 
    for (int i = 1; i < addrlen; i++)
    {
        struct SemiResolvedAddress hres =
                semiResolve(regions, regionsNum, (uintptr_t)(addrlist[i]));
        char syscom[256];
        sprintf(syscom, "addr2line -C -f -p -a -e %s 0x%jx", hres.path, (intmax_t)(hres.offset));
        system(syscom);
    }
    free(regions);
}

참조 URL : https://stackoverflow.com/questions/4636456/how-to-get-a-stack-trace-for-c-using-gcc-with-line-number-information

반응형