티스토리 뷰
디버깅 방법에는 많은 종류가 있지만, 사건이 발생 했을때 바로 무언가를 남겨주는 것이 최고의 디버그 메시지가 아닌가 싶다. 관련해서 backtrace 와 addr2line 을 소개하도록 한다.
일단 관련 된 소스는 아래와 같다. 소스의 95%가 vim에서 제공되는 backtrace의 man page 내용이다. 해당 소스는 문제가 있고 죽도록 설계(?)되어 있다. (man page의 오리지널 소스가 그렇다는 것은 아님)
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
/* sig ==> signal number */
void calltrace(int sig)
{
int j, nptrs;
#define SIZE 3
void *buffer[100];
char **strings;
nptrs = backtrace(buffer, SIZE);
printf("backtrace() returned %d addresses\n", nptrs);
/* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
would produce similar output to the following: */
strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL) {
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (j = 0; j < nptrs; j++)
printf(">>> %s\n", strings[j]);
free(strings);
exit(255);
}
void
myfunc3(void)
{
fprintf(stderr, "%s \n", __func__);
int *p = NULL;
*p = 1;
}
static void /* "static" means don't export the symbol... */
myfunc2(void)
{
fprintf(stderr, "%s \n", __func__);
myfunc3();
}
void
myfunc(int ncalls)
{
fprintf(stderr, "%s \n", __func__);
if (ncalls > 1)
myfunc(ncalls - 1);
else
myfunc2();
}
int
main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "%s num-calls\n", argv[0]);
exit(EXIT_FAILURE);
}
(void) signal (SIGSEGV, calltrace);
myfunc(atoi(argv[1]));
exit(EXIT_SUCCESS);
}
위 소스에는 man 페이지에서 제공되지 않는 항목이 두가지 있다. 첫번째는 시그널 등록이다. 개발하는 프로세스가 언제 어떤 이유로든 죽을 수 있기 때문에 발생하는 시그널을 잡도록 한다.
통상 SIGSEGV(Segmentation Fault)이 많이 발생되므로 예제에서는 해당 시그널만 등록했다.
(void) signal (SIGSEGV, calltrace);
의미는 SIGSEGV가 발생되면 calltrace라는 함수를 호출하라는 뜻이고, calltrace 함수 역할은 backtrace를 통해 특정 깊이 만큼의 함수 trace를 출력해준다.
테스트를 해보자. 일단 아래와 같이 일반적인 방법으로 컴파일을 하고 실행하도록 한다.
$ gcc prog.c -o prog
실행해서 결과를 확인한다.
무언가 메모리 주소로 보이는 것이 출력된다. 하지만 이것만으로는 그 어떤것도 확인할 수가 없다. (지금 우리 실력으로는 말이다.)
그래서 이번에는 gcc에 -rdynamic 이라는 옵션을 줘서 컴파일 하도록 한다.
$ gcc -rdynamic prog.c -o prog
오! 이번에는 죽는 위치의 힌트(?) 정도를 제공 받게 되었다.
myfunc3 이라는 함수의 0x2b 만큼 떨어진 곳에서 문제가 발생된 모양이다. 그게 어딘데?
그걸 찾을 수 있는 방법에는 컴파일시에 System.map을 생성하거나 nm 명령어 등을 사용할 수 있지만 우리는 이번 포스팅의 목적에 맞도록 addr2line를 사용하도록 한다. addr2line에 인자로 죽은 위치로 추정되는 0x8048923을 넣어보자.
prog 프로세스가 죽는 정확한 위치를 찍어줄 것이다!
$ addr2line -e prog 0x8048923
??:?
출력되지 않았다. 당황하지 말고.. 컴파일시에 추가적으로 -g 옵션을 주도록 하자.
$ gcc -g -rdynamic prog.c -o prog
그리고 다시 확인해보도록 하자.
정확하게 문제가 되는 소스의 위치를 찾았다.
/root/bin/oops/prog.c:39
해당 소스의 위치는 아래와 같다.
void
myfunc3(void)
{
fprintf(stderr, "%s \n", __func__);
int *p = NULL;
*p = 1; // HERE
}
예제 소스에서는 print로 죽는 위치를 찍어놨지만, 실제 운영할때는 별도의 파일 디버깅 등을 통해 유연하게 사용하도록 하자.
'개발 > Linux' 카테고리의 다른 글
logger 명령어의 -f 옵션 정확히 알자 (0) | 2015.12.08 |
---|---|
프롬프트 메시지(prompt string)를 변경하자 (0) | 2015.12.01 |
ipc 확인/추가/삭제 (0) | 2015.11.20 |
IPv6 address percent sign(%) (0) | 2015.08.26 |
mariadb mips cross-compile 문제 (0) | 2015.08.22 |
- Total
- Today
- Yesterday