gdb 간단 사용법 정리
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
명령이 있다. s
는 step
명령의 축약이므로 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
b
나 watch
를 통해 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를 제거한다. |