GDB 사용하기 에서 GDB 의 기본적인 사용법에 대해서 익혔다면, 여기서는 기본적인 것을 토대로 여러가지 환경에서의 디버깅에 대해서 다룰 것이다.

코어파일을 위한 디버깅

코어파일(core)은 프로세스에 예외가 발생하여 중지되는 순간의 프로세스 이미지를 의미한다. 앞의 문서 GDB 사용하기 에서 예로 들었던 bug.c 를 컴파일하여 실행하면, 코어파일이 생성된다.
만일 생성되지 않는다면, 쉘에서 코어 파일의 크기가 0 으로 설정되어 있는 경우이기 때문에

#ulimit -S -c 10000000

위와 같이 명령을 내려서 파일의 제한 크기를 늘리면 된다. 이제 다시 실행해보자.
코어 파일이 생성되었을 것이다. 나의 경우, 'core.3108' 라는 파일이 생성되었다. 뒤에 붙은 숫자는 PID 를 의미한다.
이렇게 만들어진 코어파일을 이용하여 디버깅을 하기 위해서는 다음과 같이 실행하면 된다.

#gdb a.out core.3108

이 상태에서 'bt' 명령을 이용해서 디버깅 하면 된다.

실행 중인 프로세스 디버깅

실행 중인 프로세스의 디버깅의 가장 흔한 경우는 무한 루프에 빠졌을 때이다.
예를 들어 어떤 프로그램의 실행이 무한 루프로 인해 실행되고 있을 때, 이 프로세스의 PID 값을 알아내서 GDB 를 이용해서 디버깅 할 수 있다.
여기서는 무한 루프 프로세스의 PID 가 3217 이라고 한다면,

#gdb a.out
(gdb) attach 3217

이런식으로 하면, 무한 루프에서 프로세스가 중지되고 디버깅 가능한 상태가 된다.
문제의 원인을 찾았으면,

(gdb) detach

명령을 통해 프로세스를 놓아야 한다.

멀티프로세스 프로그램 디버깅

이 경우에는 GDB-5.3 버전에서는 디버깅할 수 없기 때문에, 앞에서 설명했듯이 6.2 로 업데이트를 해야 한다.
여기서 사용할 프로그램은 다음과 같다.

#include <stdio.h>
#include <unistd.h>
 
int main()
{
    int pid, var, i = 0x3fffffff;
 
    pid = fork();
    if(pid) printf("%d\n", pid);
 
    while(i--);
 
    if(!pid){
        for (i=0; i<10000; i++){
            var += i;
            sleep(1);
        }
    }
 
    for(i=0; i<10000; i++){
        var -= i;
        sleep(1);
    }
    return 0;
}

작성후에 컴파일을 한다. 여기서 터미널 세션을 하나 더 만든다. 그리고 컴파일한 프로그램을 실행시킨다.

#./a.out
32818

새로 만든 터미널 세션에서 다음을 입력한다.

#gdb a.out 32818

소스 프로그램의 특정상 0x3ffffffff 이 0 이 되지 전까지 부분이 delay 를 주는 부분이다. 이 시간안에 새로운 터미널 세션에서 GDB 를 실행시켜야 한다. 만일 그렇지 않으면,

Attaching to program: /root/a.out, process 28303
Reading symbols from /lib/tls/libc.so.6...done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0xffffe002 in ?? ()

이런식으로 메세지가 출력될 것이다. 이 상황은 정상적으로 디버깅할 수 없는 상황이므로 처음부터 다시 프로그램을 실행시켜야 한다.

Attaching to program: /root/a.out, process 28324
Reading symbols from /lib/tls/libc.so.6...done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0x080483d2 in main () at fork.c:11
11          while(i--);

위와 같은 메세지가 출력되었다면, 제대로 실행시킨 것이다.

(gdb) b 14
(gdb) b 15   /* 지연 루프인 while(i--) 바로 뒤에 breakpoint 를 설정
(gdb) c

이제부터는 일반적인 프로세스 디버깅과 동일하다.

멀티스레드 프로그램 디버깅

여기서 사용할 프로그램은 다음과 같다.

#include <stdio.h>
#include <pthread.h>
 
int var;
 
void *add(void *n){
    while(1){
        var++;      /* add 스레드에 breakpoint 설정을 해야 함*/
        printf("add thread!\n");
    }
}
 
void *sub(void *n){
    while(1){
        var--;      /* sub 스레드에 breakpoint 설정을 해야함*/
        printf("sub thread!\n");
    }
}
 
int main(){
    pthread_t p_thread[2];
 
    pthread_create(&p_thread[0], NULL, add, NULL);
    pthread_create(&p_thread[1], NULL, sub, NULL);
 
    pthread_join(p_thread[0], NULL);   /* 스레드 종료 */
    pthread_join(p_thread[1], NULL);   /* 스레드 종료 */
 
    return 0;
}

간략히 프로그램 설명을 하자면, 수행도중에 add, sub 스레드를 생성한다. 무한 루프가 되기때문에 두 스레드가 번갈아 가면서 화면에 출력한다.

#gcc -o thread thread.c -lpthread -g

다음과 같이 컴파일한다. 실행파일과 함께 GDB 를 실행한다.

#gdb thread

스레드 종료 함수인 pthread_join() 앞에 breakpoint 를 잡는다.

(gdb) b 26
(gdb) info threads
  3 Thread 1090694208 (LWP 28440)  0xffffe002 in ?? ()
  2 Thread 1082305728 (LWP 28439)  0xffffe002 in ?? ()
* 1 Thread 1073914496 (LWP 28432)  main () at thread.c:26

총 3 개의 스레드를 볼 수 있다. 나머지 add 와 sub 스레드는 아직 실행되지 않았기 때문에 '??' 로 나왔다.

(gdb) b 8 thread 2
(gdb) b 15 thread 3
(gdb) c

각 스레드의 진입에 breakpoint 를 설정했기 때문에, 차례대로 add 태스크와 sub 태스크에 각각 breakpoint 가 걸려서 멈춘다.

(gdb) c
(gdb) info threads
  3 Thread 1090694208 (LWP 28488)  sub (n=0x0) at thread.c:17
* 2 Thread 1082305728 (LWP 28487)  add (n=0x0) at thread.c:9
  1 Thread 1073914496 (LWP 28480)  0xffffe002 in ?? ()
(gdb) c
(gdb) info threads
  3 Thread 1090694208 (LWP 28488)  sub (n=0x0) at thread.c:17
  2 Thread 1082305728 (LWP 28487)  add (n=0x0) at thread.c:9
* 1 Thread 1073914496 (LWP 28480)  0xffffe002 in ?? ()

※ 각 스레드의 지역 변수의 경우, 예를 들어 add 스레드에 있는 지역변수를 현재 sub 스레드에 디버깅 중에는 변수 값을 출력할 수 없다.
보고 싶다면, 보기를 원하는 스레드로 옮긴 후 봐야 한다.

(gdb) thread [N]

여기서는

(gdb) thread 2 또는 thread 3

할 수 있다.

프로그램 디버깅 중에 시그널 보내기

프로그램이 수행도중 특정 시그널이 발생하게 되면 등록된 시그널 핸들러 함수가 실행된다. gdb 를 이용해서 디버깅 중에 강제적(?)으로 시그널을 발생시켜야 할 경우가 있다.
가장 흔한 시그널 중에 하나인 SIGINT 의 경우, 'Ctrl + C' 키의 조합으로 발생가능하다. 하지만, gdb 에서 이와 같은 방법으로 시그널 발생시 gdb 자체적으로 디버깅을 종료할 것인지를 묻는다.
이것은 명백히 우리가 원하는 오퍼레이션이 아니다. 아래의 예제 코드를 보자!

void mz_my_signal(int s_signal)  
{  
 switch(s_signal)  
 {  
  case SIGINT:  
       g_mz_break = 1;  
       (void)signal(s_signal, mz_my_signal);  
       break;  
  default:  
       (void)fprintf(stdout, "unknown signal ! (%d)\n", s_signal);  
       break;  
 }  
}
...
int main()
{
...
(void)signal(SIGINT, mz_my_signal);  
...
}  

시그널이 발생해야 하는 지점에서 아래와 같이 입력한다.

(gdb) signal SIGINT

또한 'info signals' 명령어를 실행하면, 발생시킬 수 있는 시그널의 종류를 볼 수 있다.

Signal        Stop      Print   Pass to program Description
 
SIGHUP        Yes       Yes     Yes             Hangup
SIGINT        Yes       Yes     No              Interrupt
SIGQUIT       Yes       Yes     Yes             Quit
SIGILL        Yes       Yes     Yes             Illegal instruction
SIGTRAP       Yes       Yes     No              Trace/breakpoint trap
SIGABRT       Yes       Yes     Yes             Aborted
SIGEMT        Yes       Yes     Yes             Emulation trap
SIGFPE        Yes       Yes     Yes             Arithmetic exception
SIGKILL       Yes       Yes     Yes             Killed
SIGBUS        Yes       Yes     Yes             Bus error
SIGSEGV       Yes       Yes     Yes             Segmentation fault
SIGSYS        Yes       Yes     Yes             Bad system call
SIGPIPE       Yes       Yes     Yes             Broken pipe
SIGALRM       No        No      Yes             Alarm clock
SIGTERM       Yes       Yes     Yes             Terminated
SIGURG        No        No      Yes             Urgent I/O condition
SIGSTOP       Yes       Yes     Yes             Stopped (signal)
SIGTSTP       Yes       Yes     Yes             Stopped (user)
SIGCONT       Yes       Yes     Yes             Continued
SIGCHLD       No        No      Yes             Child status changed
SIGTTIN       Yes       Yes     Yes             Stopped (tty input)
SIGTTOU       Yes       Yes     Yes             Stopped (tty output)
SIGIO         No        No      Yes             I/O possible
SIGXCPU       Yes       Yes     Yes             CPU time limit exceeded
SIGXFSZ       Yes       Yes     Yes             File size limit exceeded
SIGVTALRM     No        No      Yes             Virtual timer expired
SIGPROF       No        No      Yes             Profiling timer expired
SIGWINCH      No        No      Yes             Window size changed
SIGLOST       Yes       Yes     Yes             Resource lost
SIGUSR1       Yes       Yes     Yes             User defined signal 1
SIGUSR2       Yes       Yes     Yes             User defined signal 2
SIGPWR        Yes       Yes     Yes             Power fail/restart
SIGPOLL       No        No      Yes             Pollable event occurred
SIGWIND       Yes       Yes     Yes             SIGWIND
SIGPHONE      Yes       Yes     Yes             SIGPHONE
SIGWAITING    No        No      Yes             Process's LWPs are blocked
SIGLWP        No        No      Yes             Signal LWP
...
  • computer/rtcclab/gdb_사용하기_-_2.활용.txt
  • Last modified: 4 years ago
  • by likewind