본문 바로가기

Programming/C & C++

GDB 사용하기

이 글은 어딘가에서 가져왔었던 글인데 출처가 명확히 기억나지 않는다.


GDB 사용하기

1. GDB

GDB같은 디버거의 목적은 다른 프로그램 수행 중에 그 프로그램 ‘내부에서’ 무슨 일이 일어나고 있는지 보여주거나 프로그램이 잘못 실행되었을 때 무슨 일이 일어나고 있는지 보여주는 것이다. GDB는C, C++, Modula-2로 짠 프로그램을 디버그 할 수 있다.

쉘에서 gdb로 GDB를 시작하면 quit로 종료명령을 주기전까지는 터미널로부터 명령라인을 읽어 들인다. help명령을 사용하여 gdb내부에서 도움말을 볼 수 있다.

디버깅을 하기 위해서는 –g옵션을 주고 컴파일/링크 해야 한다. 만약 링크가 libg.a를 찾을 수 없다고 하면서 실패하게 되면, /usr/lib/ligb.a를 갖고 있지 않기 때문이다. 그 파일은 특별한 라이브러리로서 디버깅 가능 C라이브러리이다. libc 패키지에 포함되어 있거나 또는 libc 소스 코드를 받아서 컴파일 하면 생긴다. /usr/lib/libc.a를 /usr/lib/libg.a로 링크 시켜도 된다.


  • 코어파일 분석하기

코어파일은 충돌할 당시 프로세스의 메모리 이미지를 덤프한 것이다. 코어파일을 gdb와 함께 사용하여 프로그램의 상태를 조사하고 실패 원인을 규명할 수 있다. 어떤 예기치 않은 일이 발생하여 비정상적인 종료가 발생할 때 운영체계는 디스크에 코어 파일을 남긴다.메모리에 관한 문제는 Checker 패키지를 사용하여 예방할 수 있다. 하지만 메모리 fault를 일으키는 경우에는 충돌하면서 파일을 덤프한다. 코어파일은 일반적으로 프로세스를 실행시킨 현재 작업 디렉토리에 생성되지만 프로그램 내에서 작업 디렉토리를 바꾸는 경우도 있다.

보통 리눅스는 부팅시에 코어 파일을 만들지 않도록 세팅되어 있다. 코어 파일 생성을 가능케 하려고 한다면 그것을 다시 가능케 하는 셀의 내장 명령을 사용한다.

만약C쉘 호환 쉘(tcsh)을 쓰고 있다면 다음과 같이 명령을 내린다.

% limit core unlimited

만약 본쉘류( sh , bash , zsh , pdksh )를 사용하고 있다면,

$ ulimit –c unlimited

와 같은 명령을 내린다.

코어파일을 함께 사용하기 위해선 다음과 같이 한다.

% gdb program core


  • 실행 중인 프로그램 디버깅하기

gdb는 이미 실행중인 프로그램도 디버깅할 수 있게 해준다. 프로세스 실행을 가로채고 조사한 뒤 다시 원래 상태로 실행하도록 할 수 있다. attach명령을 사용하여 실행중인 프로세서에 gdb를 붙인다. attach 명령을 사용하기 위해서는 프로세스에 해당하는 실행 프로그램에 허가권을 가지고 있어야 한다. 예를 들어 프로세스 ID 254번으로 실행 중인 pgmseq 프로그램이 있다면 다음과 같이 한다.

% gdb pgmseq

% attach 254

다음과 같이 해도 된다.

% gdb pgmseq 254

일단 gdb가 실행 중인 프로세스에 부착되면 프로그램을 일시 중지 시키고 gdb명령을 사용할 수 있도록 제어권을 가져온다. break를 사용하여 중지점을 사용할 수 있고 중지점에 이를 때까지 실행하도록 continue 명령을 사용할 수 있다.

detach명령을 사용하여 gdb를 실행 중인 프로세스에서 떼어 낸다. 필요에 따라 다른 프로세스에 대하여 attach명령을 사용할 수 있다.


2. gdb시작하기

% gdb - gdb를 먼저 실행 후 file이라는 명령으로 program을 부른다.

% gdb program - 일반적인 방법이다.

% gdb program core - 코어파일을 사용할 때 동시에 인자로 준다.

% gdb program 1234 - 실행중인 프로세스를 디버그 하려면 프로세스 ID를 두 번째 인자로 주면 된다. 이 명령은 gdb를 (‘1234’ 란 이름의 파일이 없다면) 프로세스 1234에 접속시킨다.(gdb는 core파일을 먼저 찾는다.)


실행절차

% gcc –g test.c –o test

% gdb test

이 명령을 실행하면 다음과 같은 메시지가 나타난다.

% gdb test

GNU gdb 4.18

Copyright 1998 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

Welcome to change it and/or distribute copies of it under certain conditions.

Type “show copying” to see the conditions.

There is absolutely no warranty for GDB. Type “show warranty” for details.

This GDB was configured as “i386-redhat-linux”...

(gdb)


3. 많이 사용하는 GDB명령어

list  : 현재 위치에서 소스 파일의 내용을 10줄 보여준다

list 2, 15 : 소스 파일의2 ~ 15 까지를 보여준다.

run  : 프로그램을 시작한다.(break가 있다면 break까지 실행)

run arg : 새로운 인수를 가지고 프로그램을 시작한다. 

arg는 “*”나 “[…]”를 포함할 수도 있다.

쉘의 사용까지도 확장될 수 있다.

“<”,“>” , “>>”같은 입출력 방향 재지정 기호도 또한 허용된다.

break  : 특정 라인이나 함수에 정지점을 설정한다.

break function : 현재 파일 안의 함수 function에 정지점을 설정한다.

break file:function : 파일file안의 function에 정지점을 설정한다.

watch : 감시점 설정(감시점은 어떤사건이 일어날 때에만 작동한다)

until : 실행중 line까지 실행

clear  : 특정 라인이나 함수에 있던 정지점을 삭제한다.

delete  : 몇몇 정지점이나 자동으로 출력되는 표현을 삭제한다.

next  : 다음 행을 수행한다. 서브루틴을 호출하면서 계속 수행한다. 호출이 발생하지 않으면 step와 같다.

next n : 이를 n번 수행하라는 의미

step  : 한 줄씩 실행 시킨다. 함수를 포함하고 있으면 함수 내부로 들어가서 한 줄씩 실행시킨다. 

print  : print expr : 수식의 값을 보여준다.

display  : 현재 display된 명령의 목록을 보여준다.

bt  : 프로그램 스택을 보여준다. (backtrace)

kill  : 디버깅 중인 프로그램의 실행을 취소한다.

file  : file program : 디버깅할 프로그램으로서 파일을 사용한다.

cont  : continue : 현재 위치에서 프로그램을 계속 실행한다.

help  : 명령에 관한 정보를 보여주거나 일반적인 정보를 보여준다.

quit  : gdb에서 빠져나간다.


4. gdb 해보기


예제1

% vi test.c

1 #include <stdio.h>

2

3 main()

4 {

5     int i;

6     double j;

7     /*다음은i/2+i의 값을 출력하는 문이다.

8     i가1이면 j는1.5가 되어야 하지만 실제는 그렇지 않다.*/

9     for( i=0; i<5 ; i++){

10         j=i/2+i;

11         printf(“j is %f \n”,j);

12     }

13 }

% gcc –g test.c –o test

% test

실행이 되지 않으면 mv test a.out으로 하여a.out을 실행시킨다. 실행을 시키면 원하는 답이 아니다. 그러면 gdb를 해보자.

% gdb a.out

(gdb) list // list는 소스 내용을 10줄씩 보여준다.

1 #include <stdio.h>

2

3 main()

4 {

5 int i;

6 double j;

7 /*다음은i/2+i의 값을 출력하는 문이다.

8 i가1이면 j는1.5가 되어야 하지만 실제는 그렇지 않다.*/

9 ( i=0; i<5 ; i++){

j=i/2+i;

(gdb) b 9 // break 9 : for 문에 이상이 있다고 판단하여 line 9에 breakpoint를 잡는다.

Breakpoint 1 at 0x80483d6: file test.c, line 9.

(gdb) r // run : breakpoint까지 실행된다.

Starting program: /home/pllab/chowing/gdb/a.out

Breakpoint 1, main () at test.c:9

9 for( i=0; i<5 ; i++){

(gdb) s // step : 한줄 실행시킨다.

j=i/2+i;

(gdb) s

11 printf(“j is %f \n”,j);

(gdb) p j // print j : j의 값을 본다.

$2 = 0

(gdb) n

j is 0.000000

for( i=0; i<5 ; i++){

(gdb) display i

(gdb) display j

(gdb) n

11 printf(“j is %f \n”,j);

2: j = 1

1: i = 1

// 10 line에서 실행 후 i=1일 때, j=1이므로 10 line에서 잘못된 것을 알 수 있다.

// 10 line을 j = (double) i/2 + i; 로 고친다.

(gdb) quit


예제2

% vi hab.c

1 #include <stdio.h>

2

3 int hab(int x, int y);

4

5 main(void)

6 {

7     int a, b,dab;

8     printf(“정수a, b를 입력하시오”);

9     scanf(“%d %d”,&a,&b);

10    dab = hab(a,b);

11     printf(“n%d + %d = %d \n”,a,b,dab);

12 }

13 int hab(int x, int y)

14 {

15     return (x + y);

16 }

// 이 프로그램은 이상은 없다. 스택을 보기 위한 것이다.

// 여러 곳에서 호출되는 함수 안에서 충돌이 일어날 경우를 생각해 보자. 이 때는 함수가 어디로부터 호출되었는지 그리고 어떤 상황에서 충돌이 일어났는지 파악하고자 할 것이다.

backtrace (bt) 명령을 이용하면 충돌이 일어난 시점에서 프로그램의 현재 호출 스택(call stack) 상태를 볼 수 있다. 호출 스택은 현재 함수까지 이르는 호출 목록이다. 함수를 호출할 때마다 보관된 레지스터 값, 함수 전달 인수, 지역 변수 등의 자료를 스택에 push한다. 이렇게 해서 각 함수들은 스택상에 일정 공간을 차지한다. 특정함수에 대하여 스택에서 사용되고 있는 메로리 부분을 스택프레임(frame)이라 부르며 호출 스택은 이러한 스택 프레임을 순서대로 정렬한 목록이다.

% gdb hab

(gdb) b 10 // Breakpoint 2 at 0x8048428: file hab.c, line 10.

(gdb) r

Starting program: /home/pllab/chowing/gdb/hab

정수a, b를 입력하시오3 4

breakpoint 2, main () at hab.c:10

10 dab = hab(a,b);

(gdb) bt // 현재 스택에 main이 있다.

#0 main () at hab.c:10

(gdb) s

hab (x=3, y=4) at hab.c:15

15 return (x + y);

(gdb) bt // 지금은 스택에 hab이 있다.

#0 hab (x=3, y=4) at hab.c:15

#1 0x8048435 in main () at hab.c:10

(gdb) frame 0 // hab의 상태를 점검하기 위해서 스택 프레임0번으로 이동

#0 hab (x=3, y=4) at hab.c:15

15 return (x + y);

(gdb) up // hab이 어떻게 호출되었는가를 보기 위하여 상위 스택프레임으로 이동

#1 0x8048435 in main () at hab.c:10

dab = hab(a,b);

(gdb) finish

(gdb) info program // 프로그램의 실행 상태를 보여 준다.

Using the running image of child Pid 12909.

Program stopped at 0x804843d.

It stopped after being stepped.

(gdb) info locals // 현재 함수 내에서 모든 지역 변수 이름과 값을 출력한다.

a = 3

b = 4

dab = 7

(gdb) info variables // 소스파일 순서대로 프로그램 내에 알려져 있는 모든 변수를 출력한다.

(gdb) info address a // 어떤 변수가 어디에 저장되어 있는지에 대하여 알려 준다.

Symbol “a” is a local variable at frame offset -4.

// a가 스택프레임 꼭대기로부터4바이트 아래에 놓여 있다는 뜻이다.

(gdb) info frame // 현재 프레임 정보를 보여 준다.

Stack level 0, frame at 0xbffff848:

eip = 0x804843d in main (hab.c:11); saved eip 0x400301eb

source language c.

Arglist at 0xbffff848, args:

Locals at 0xbffff848, Previous frame’s sp is 0x0

Saved registers:

ebp at 0xbffff848, eip at 0xbffff84c


예제3

% vi core.c

1 #include <stdio.h>

2

3 main()

4 {

5     char *bug = NULL;

6

7     strcpy(bug,“debug”);

8     printf(“bug is %s \n”,bug);

9

10     return;

11 }

12

% coredebug

Segmentation fault

// core 파일 생성

% gdb coredebug

(gdb) b 7

Breakpoint 1, main () at core.c:7

7 strcpy(bug,”debug”);

(gdb) p bug

$1 = 0x0 // gdb 에서0x0는 null이다. 즉 번지가 없다.

(gdb) s

Program received signal SIGSEGV, Segmentation fault.

0x40075434 in ?? ()

// strcpy에서 segmentation fault가 발생한 것을 알 수 있다.

// bug에 번지를 할당하면 된다.

% gdb corebug core // core파일을 이용하면 bug정보가 나온다.

GNU gdb 4.18

Copyright 1998 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type “show copying” to see the conditions.

There is absolutely no warranty for GDB. Type “show warranty” for details.

This GDB was configured as “i386-redhat-linux”...

warning: core file may not match specified executable file.

Core was generated by ‘a.out’.

Program terminated with signal 11, 세그멘테이션 오류.

Reading symbols from /lib/libc.so.6...done.

Reading symbols from /lib/ld-linux.so.2...done.

#0 strcpy (dest=0x0, src=0x8048490 “debug”) at ../sysdeps/generic/strcpy.c:38

../sysdeps/generic/strcpy.c: 그런 파일이나 디렉토리가 없음.

gdb는 signal 11 번과 함께 코어 파일이 생성되었음을 알려 준다. 여기서는 허가받지 않은 메모리 공간에 읽기, 쓰기를 시도했기 때문에 커널이 프로세스에게 signal 11을 보냈다.

이 시그널로 인해 프로세스는 종료하면서 코어 파일을 덤프한다.


  • 기타 기능

gdb는 매우 많은 기능을 가진 프로그램이다.

breakpoint

중지점을 조건적으로 설정할 수 있다. 즉 어떤 동작이 참일 때만 작동하도록 할 수 있다. Ex) break 184 if (stace == 0)

info break를 사용하면 모든 중지점과 감시점 목록을 보여 주고 그 상태도 보여 준다.

disable를 사용하여 작동불능으로 할 수 있고 enable를 사용하여 가능하게 할 수도 있다.

인스트럭션 레벨 디버깅

gdb를 통해 인스트럭션 레벨의 디버깅을 할 수 있으므로 프로그램의 매우 깊은 내부까지 조사할 수 있다.

(gdb) disass play //play함수에 대한 디스어셈블리.

(gdb) display/ i $pc //현재의 인스트럭션을 보여준다. $pc는 gdb내부 변수로서 현재 인스트럭션의 위치를 가리키는 프로그램 카운터이다.

'Programming > C & C++' 카테고리의 다른 글

The Joel Test: 나은 코딩을 위한 12단계  (0) 2017.10.22
GCC의 컴파일 옵션에 관해서...  (0) 2017.10.22
C 에서의 printf와 C++의 cout의 차이점?  (0) 2017.10.22
GDB manual  (0) 2017.10.22
컴파일러 선택하기  (0) 2017.10.22