programing

스택 트레이스를 인쇄하기 위해 내부 프로그램에서 gdb를 호출하는 가장 좋은 방법?

bestcode 2022. 8. 29. 22:13
반응형

스택 트레이스를 인쇄하기 위해 내부 프로그램에서 gdb를 호출하는 가장 좋은 방법?

다음과 같은 기능을 사용합니다.

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

void print_trace() {
    char pid_buf[30];
    sprintf(pid_buf, "--pid=%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);
    }
}

출력에 print_trace의 상세 내용이 표시됩니다.

다른 방법은 뭐가 있을까요?

다른 답변(지금은 삭제)에서 회선 번호도 보고 싶다고 말씀하셨습니다.어플리케이션 내부에서 gdb를 호출할 때 어떻게 해야 하는지 잘 모르겠습니다.

gdb를 사용하지 않고 함수 이름과 각 행 번호를 사용하여 간단한 스택 트레이스를 인쇄하는 몇 가지 방법을 알려드리겠습니다.대부분은 Linux Journal의 매우 좋은 기사에서 인용되었습니다.

  • 방법 #1:

첫 번째 방법은 실행 경로를 정확히 파악하기 위해 인쇄 및 로그 메시지로 배포하는 것입니다.복잡한 프로그램에서는 일부 GCC 고유의 매크로를 사용하여 다소 간소화할 수 있더라도 이 옵션은 번거롭고 지루할 수 있습니다.예를 들어 다음과 같은 디버깅매크로에 대해 생각해 보겠습니다.

 #define TRACE_MSG fprintf(stderr, __FUNCTION__     \
                          "() [%s:%d] here I am\n", \
                          __FILE__, __LINE__)

이 매크로는 잘라내어 붙여넣음으로써 프로그램 전체에 빠르게 전파할 수 있습니다.더 이상 필요하지 않은 경우 no-op으로 정의하기만 하면 됩니다.

  • 방법 #2: (회선번호는 기재되어 있지 않지만, 방법 4에서는 기재되어 있습니다.)

단, 스택의 역추적을 얻는 더 좋은 방법은 glibc에서 제공하는 특정 지원 기능 중 일부를 사용하는 것입니다.키 1은 백트레이스()입니다.이 백트레이스에서는 스택프레임을 호출 포인트부터 프로그램 시작까지 네비게이트하여 반환 주소의 배열을 제공합니다.그런 다음 nm 명령어를 사용하여 오브젝트파일을 확인함으로써 각 주소를 코드 내의 특정 함수의 본체에 매핑할 수 있습니다.또는 backtrace_symbols()를 사용하여 보다 간단한 방법으로 작업을 수행할 수도 있습니다.이 함수는 역추적()에 의해 반환된 반환 주소 목록을 문자열 목록으로 변환합니다.각 문자열에는 함수 내의 함수 이름 오프셋과 반환 주소가 포함됩니다.문자열 목록은 힙 공간에서 할당되므로(malloc()를 호출한 경우와 같음) 작업이 완료되면 즉시 해제()해야 합니다.

페이지에는 소스 코드 예가 있으므로 꼭 읽어보시기 바랍니다.주소를 함수 이름으로 변환하려면 -rdynamic 옵션을 사용하여 응용 프로그램을 컴파일해야 합니다.

  • 방법 #3: (방법 2를 사용하는 더 좋은 방법)

이 기술의 더욱 유용한 응용 프로그램은 스택 백트레이스를 신호 핸들러 내부에 삽입하고 프로그램이 수신할 수 있는 모든 "불량" 신호를 후자가 포착하는 것입니다(SIGSEGV, SIGBUS, SIGIL, SIGFPE 등).이렇게 하면 프로그램이 불행히도 크래쉬하고 디버거를 사용하여 실행하지 않은 경우 스택 트레이스를 가져와 어디서 장애가 발생했는지 알 수 있습니다.이 기법은, 프로그램이 응답을 정지했을 경우에, 프로그램의 루프 위치를 파악하기 위해서도 사용할 수 있습니다.

이 기법의 실장은, 여기를 참조해 주세요.

  • 방법 #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

최신 Linux 커널 버전 2012/04/28 업데이트(위)sigaction시그니처는 사용되지 않습니다.또한 이 답변에서 실행 가능한 이름을 가져와 조금 개선했습니다.다음은 최신 버전입니다.

char* exe = 0;

int initialiseExecutableName() 
{
    char link[1024];
    exe = new char[1024];
    snprintf(link,sizeof link,"/proc/%d/exe",getpid());
    if(readlink(link,exe,sizeof link)==-1) {
        fprintf(stderr,"ERRORRRRR\n");
        exit(1);
    }
    printf("Executable name initialised: %s\n",exe);
}

const char* getExecutableName()
{
    if (exe == 0)
        initialiseExecutableName();
    return exe;
}

/* get REG_EIP from ucontext.h */
#define __USE_GNU
#include <ucontext.h>

void bt_sighandler(int sig, siginfo_t *info,
                   void *secret) {

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

  /* Do something useful with siginfo_t */
  if (sig == SIGSEGV)
    printf("Got signal %d, faulty address is %p, "
           "from %p\n", sig, info->si_addr, 
           uc->uc_mcontext.gregs[REG_EIP]);
  else
    printf("Got signal %d\n", sig);

  trace_size = backtrace(trace, 16);
  /* overwrite sigaction with caller's address */
  trace[1] = (void *) uc->uc_mcontext.gregs[REG_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] %s\n", 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 filename of the symbol
    system(syscom);

  }
  exit(0);
}

다음과 같이 초기화합니다.

int main() {

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

  sa.sa_sigaction = (void *)bt_sighandler;
  sigemptyset (&sa.sa_mask);
  sa.sa_flags = SA_RESTART | SA_SIGINFO;

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

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

}

Linux를 사용하는 경우 표준 C 라이브러리에는 다음과 같은 기능이 포함되어 있습니다.backtrace는, 프레임의 리턴 주소와 라고 하는 다른 함수로 어레이를 채웁니다.backtrace_symbols주소의 취득처가 됩니다.backtrace해당하는 기능명을 검색합니다.이것들은 GNU C 라이브러리 매뉴얼에 기재되어 있습니다.

이러한 값은 인수 값이나 소스 회선 등은 표시되지 않으며, 발신측 스레드에만 적용됩니다.단, GDB를 실행하는 것보다 훨씬 더 빠르고(아마도 덜 엉망이 될 수 있습니다) 그래야 제자리를 찾을 수 있습니다.

노바멋진 답변을 올렸어요요컨대

따라서 gdb 스택트레이스가 가진 모든 기능을 사용하여 스택트레이스를 인쇄하고 어플리케이션을 종료하지 않는 스탠드아론 함수를 필요로 합니다.답은 비인터랙티브모드로 gdb의 기동을 자동화하여 원하는 작업만 실행하는 것입니다.

이는 gdb를 자 프로세스로 실행하고 fork()를 사용하여 응용 프로그램이 완료될 때까지 스택트레이스를 표시하도록 스크립팅함으로써 이루어집니다.이것은 코어 덤프를 사용하지 않고 응용 프로그램을 중단하지 않고 실행할 수 있습니다.

이것이 당신이 찾고 있는 것이라고 생각합니다.@Vi

그래abort()단순하게? 더 단순하게?

이렇게 하면 고객이 핵심 파일을 보낼 수 있습니다(어플리케이션에 관여하고 있는 사용자가 많지 않아 디버깅을 강요할 수 없습니다).

언급URL : https://stackoverflow.com/questions/3151779/best-way-to-invoke-gdb-from-inside-program-to-print-its-stacktrace

반응형