스택 트레이스를 인쇄하기 위해 내부 프로그램에서 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에서도 동작하도록 카피할 수 있습니다.
주소를 파일 이름과 행 번호로 변환합니다.
다음의 소스 코드는, 모든 로컬 기능의 행 번호를 인쇄합니다.다른 라이브러리에서 함수가 호출되면 몇 가지 기능이 표시될 수 있습니다.??: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
'programing' 카테고리의 다른 글
Vuex Store에서 네스트된 어레이를 관리하고 컴포넌트를 전달하는 방법 (0) | 2022.08.30 |
---|---|
예외는 catch and finally 절에 던져집니다. (0) | 2022.08.30 |
C에서 0을 선두로 하는 인쇄 (0) | 2022.08.29 |
size_t의 올바른 printf 형식 지정자: %zu 또는 %Iu? (0) | 2022.08.29 |
타이프스크립트:VueX 스토어 모듈을 참조하면 VueJs 2.5에 대한 네임스페이스 오류가 표시됨 (0) | 2022.08.29 |