Lacti's Archive

gdb 간단 사용법 정리

April 01, 2008

GDB는 GNU Debugger의 약자로 console 디버거이다. console 디버거이므로 gdb는 gdb shell을 제공하고, 사용자는 그 shell에 명령을 입력함으로써 디버깅을 수행할 수 있다.

먼저 디버깅을 수행하기 위해서는 소스 코드를 컴파일할 때 디버깅 정보를 포함하도록 해야한다. gcc에서 이에 해당하는 옵션은 -g이다. 예를 들어 test.c라는 파일이 있다면 다음과 같이 수행하면 된다.

gcc -g test.c

gdb에서 디버깅을 시작하는 방법은 두 가지가 있다. gdb 실행 시 디버깅할 파일을 입력하는 방법과 gdb 내에 들어가서 file 명령을 이용해 파일을 입력하는 방식이다.

$ gdb a.out 혹은, (gdb) file a.out

file 명령은 symbol table를 불러와서 디버깅 준비를 하는 명령이다. 이와 헷갈릴 수 있는 명령으로는 exec가 있는데 이는 단순히 지정된 파일을 실행만 할 뿐 디버깅을 하지는 못한다.

gdb를 시작할 때 디버깅할 파일 이름을 입력하는게 편해보이지만 gdb 내부에서 file 명령을 이용하여 디버깅할 파일명을 입력할 때는 gdb의 shell에 의해 파일명을 자동 완성 해주기 때문에 더 편하다.

gdb shell이라는 것은 gdb를 실행하였을 때 (gdb)라는 prompt를 띄우며 사용자의 입력을 기다리고 있는 것을 지칭한다. 이 shell은 bash와 마찬가지로 tab키를 통한 자동 완성을 지원해주기 때문에 좀 더 편하게 디버깅을 수행할 수 있다. 게다가 뒤에서 언급할 이야기이지만 명령어의 축약형, ↑키를 통한 shell 명령 history-back 기능과 그리고 아무것도 입력하지 않고 return 키를 쳤을 때 방금 전에 수행한 명령을 다시 수행해주는 기능을 제공해 주기 때문에 좀 더 적은 타이핑typing으로 디버깅을 수행할 수 있도록 해준다.

symbol table을 읽어왔으니 프로그램의 수행을 시작해야 한다. 수행을 시작하는 명령어는 run이다. gdb는 명령어의 축약형이 존재하기 때문에 단순히 r로 실행할 수 있다. 만약 실행 인자를 주어야하는 경우에는 r 뒤에 작성하면 된다.

(gdb) r arg0 arg1 arg2

아직 breakpoints를 설정하지 않았기 때문에 프로그램을 수행하면 그대로 수행을 마치고 종료해버린다. 따라서 breakpoints를 걸어주어야 하는데, 이 때 사용하는 명령어는 break이다. b로 축약하여 사용할 수 있다. b 뒤에는 여러 symbol이 올 수 있는데 기본적으로 줄 번호나 함수 이름을 사용할 수 있다.

(gdb) b 37
(gdb) b main

gdb에서는 list 명령을 이용하여 소스를 볼 수 있다. 물론 gcc -g 옵션으로 컴파일을 수행한 c/c++ source 파일이 그 이름 그대로 그 위치에 존재하고 있어야 한다. 그렇지 않으면 source 파일을 찾을 수 없기 때문에 list를 보여줄 수 없다는 에러 메시지가 출력된다. 이 명령은 l 이라는 축약 명령을 갖는다. l 뒤에는 보고자하는 줄 번호가 올 수 있다. 줄 번호를 입력하지 않으면 소스를 처음 줄부터 10줄을 화면에 출력하고, 줄 번호를 입력할 경우에 그 줄 번호를 기준으로 위로 5줄, 지정한 줄, 아래로 4줄 해서 총 10줄을 보여준다. 그리고 l을 지속적으로 명령할 경우 현재까지 출력된 소스 코드 이후의 소스를 이어서 10줄씩 더 볼 수 있다. 이는 위에서 언급한 대로 l을 치지 않고 그냥 return 키를 쳐도 된다.

(gdb) l
(gdb) l 15

breakpoint를 걸고 디버깅을 수행하면 해당 지점에서 break되고 gdb는 사용자의 명령을 기다리게 된다. 이 때 한 줄씩 진행하는 명령과 다시 동작을 재개하는 명령이 있다. 다시 동작을 재개하는 명령은 continue이다. 축약으로 c로 사용할 수 있는데 별다른 사항은 없고, 언급했듯이 단순히 break된 프로그램의 진행을 다시 수행하도록 한다.

(gdb) c

한 줄씩 진행하는 명령은 두 가지 동작으로 나뉜다. 그것은 현재 수행할 명령 행의 특성에 따라 나뉘는데, 예를 들어 현재 수행할 명령이 함수 호출 명령일 때 해당 함수에 대한 symbol table이 있어서 jumping이 가능하면 그 내부의 진행 상황까지 디버깅할 수 있다. 이 경우 그 함수 내부로 진입(step into)하여 디버깅을 진행할 것인지, 아니면 그냥 지나쳐(step over) 갈 것인지 선택할 수 있다. 전자의 진입(step into)의 명령은 step이고, 축약은 s이다. 후자의 진행(step over)의 명령은 next이고 축약은 n이다. 두 명령 모두 뒤에 숫자를 입력하여 몇 개의 명령이나 진행할 것인지 지정할 수 있다. 이 명령 또한 l과 마찬가지로 처음에 한 번만 입력하고 그 다음부터는 그냥 return키만 입력하여 사용하는 경우가 많다.

(gdb) s
(gdb) n 10

디버깅을 진행하다보면 내부에 존재하는 어떤 값을 출력해보고 싶을 때가 있다. 이 때 사용하는 명령은 print이다. 축약은 p이다. 이 때 p 뒤에는 출력하고자 하는 대상이 오면 된다. 함수를 입력하면 함수가 존재하는 메모리 주소가 나오고 symbol table에 함수가 있는 경우에는 함수의 인자가 추가로 더 나온다. 변수명을 입력하면 변수의 내용이 나온다. 만약 변수가 배열, 구조체나 객체인 경우에는 그 내부에 존재하는 원소나 멤버를 출력해준다.

또한 메모리 주소를 출력하기 위해 & 연산자를 사용할 수 있고, 반대로 메모리 주소에 있는 내용을 출력하기 위해 * 연산자를 사용할 수 있다. 따라서 특정 변수의 메모리 주소를 보거나 메모리 주소 내에 존재하는 내용을 출력할 수 있다. 실제로 디버깅을 하다가 객체의 내용을 보고 싶을 때, class 내부에서 간단히 print (*this)를 사용하면 된다.

그리고 일반 수식도 지원하기 때문에 변수에 어떤 값을 연산한 결과 등도 출력이 가능하다.

(gdb) p main
(gdb) p i
(gdb) p &i
(gdb) p *((char*)0x804834A)
(gdb) p i * 21 - 2

다음으로 디버깅 도중에 존재하는 변수의 값을 변경하거나 디버깅에 사용되는 변수를 정의하는 set 명령이 있다. sstep 명령의 축약이므로 set 명령은 축약이 존재하지 않는다. set 뒤에는 변수를 정의하기 위한 expression이 들어간다. 예를 들어 i 변수의 값을 11로 변경하고 싶으면 set i = 11을 수행하면 된다.

이를 응용하여 특정 메모리의 값도 변경할 수 있다. 이는 set 명령이 print 명령과 마찬가지로 &*을 지원해주기 때문인데, * 연산자를 통해서 특정 위치의 메모리의 값을 변경할 수 있다.

(gdb) set i = 11
(gdb) set *((int*)0xBFD8EB9B) = 23

그리고 디버깅에 사용되는 변수를 정의할 수도 있다. 프로그램 내에 존재하는 일반 변수와 구별하기 위해서 변수 이름 앞에 반드시 $를 붙여야 한다. 이러한 변수는 예를 들면 이중 포인터로 구성된 배열의 내용을 볼 때 사용할 수 있다. 다음과 같이 $i를 정의한 뒤 print 명령으로 배열의 첫번째 원소를 출력하게 하고, return 키를 계속 입력함으로써 이중 포인터로 구성된 배열의 내용을 살펴볼 수 있다.

(gdb) set $i = 0
(gdb) p env[$i++]

그리고 지금까지 설정한 변수를 보려면 show convience를 수행하면 된다.

(gdb) show convience
$i = 3

break를 특정 소스의 지점에 거는 것이 아니라 변수 자체에 걸 수도 있다. 해당 변수를 watchpoints로 걸어두면 된다. 명령은 watch이며 뒤에는 expression이 들어간다. watch를 통해 watchpoints를 걸어두면 해당 변수의 값이 변할 때 break된다.

(gdb) watch i

bwatch를 통해 breakpoints와 watchpoints를 보려면 info란 명령을 사용하면 된다. info 명령은 현재 디버깅에 대한 여러 정보를 보여주는 명령으로 그 축약은 i이다. i로 볼 수 있는 정보는 매우 많은데, 그 정보들의 목록을 보려면 그냥 i만 입력하고 return 키를 누르면 된다.

  • 대표적으로 많이 사용되는 i 명령은 위에서 언급한 breakpoints를 보는 i breakpoints 명령인데, 줄여서 i b로 가능하다.
  • 또, i line 명령은 현재 디버깅하고 있는 위치를 출력해준다.
  • i program은 수행되고 있는 프로그램의 process 정보를 보여준다.
  • i args는 현재 호출된 함수로 넘어온 인자의 정보를,
  • i local은 현재 함수에서 사용하는 지역 변수에 대한 정보를 출력한다.
  • 등록한 watchpoints를 보기 위해서 i watchpoints를 수행해도 되지만 breakpoints와 watchpoints는 동일하게 취급되기 때문에 이왕이면 짧은 명령인 i b로 보는게 편하다.

정리하면 다음과 같다.

명령 설명
i b 등록된 breakpoints와 watchpoints를 본다.
i line 현재 디버깅하고 있는 위치 정보를 본다.
i program 현재 프로그램의 process 정보를 본다.
i args 현재 함수로 넘어온 인자의 정보를 본다.
i local 현재 함수의 지역 변수를 본다.

정의한 breakpoints와 watchpoints를 제거하려면 clear 명령과 delete 명령을 사용해야한다. clear 명령은 특정 symbol에 걸려있는 breakpoints나 watchpoints를 제거하는 것으로 뒤에 변수명이나 함수명이 올 수 있다. 반면에 delete는 제거하고자 하는 breakpoint number를 입력해서 제거할 수 있다.

breakpoint number는 gdb 구동시 0부터 시작하여 breakpoints나 watchpoints를 설정할 때 자동으로 1씩 증가하는 값이다. 이 번호는 breakpoints를 설정할 때 볼 수 있고, 또 i b명령을 통해서도 볼 수 있다. delete의 축약 명령은 d로, d 뒤에 number를 입력함으로써 해당 번호의 breakpoints를 제거할 수도 있고, 그냥 d만을 명령함으로써 현재 설정된 모든 breakpoints를 제거할 수도 있다.

(gdb) clear main
(gdb) d 1
(gdb) d

지금까지 GDB의 기초 사용법에 대해 정리하였다. 지금까지 언급한 명령을 표로 정리하면 다음과 같다.

명령 인자 설명
file 파일명 파일의 symbol table을 읽어온다.
r [실행 인자] 프로그램을 실행한다.
b 함수, 줄 번호 breakpoints를 설정한다.
l [줄 번호] source 코드를 본다.
c break 상태에서 다시 진행을 재개한다.
s [진행할 행 수] step into하며 진행한다.
n [진행할 행 수] step over하며 진행한다.
p 변수, 함수 등 지정된 변수의 값이나 함수의 주소를 출력한다.
set 변수 등 변수나 메모리의 값을 설정하거나 디버깅 변수를 설정한다.
watch 변수 등 watchpoints를 설정한다.
i b, args 등 breakpoints나 인자 정보 등을 출력한다.
clear 변수, 함수 지정된 변수나 함수에 걸린 breakpoints나 watchpoints를 제거한다.
d [bpnum] 지정된 breakpoint number의 breakpoint나 watchpoint를 제거한다.
Loading script...