GDB(GNU Debugger)는 프로그램을 디버깅하고 분석하는데 사용되는 강력한 도구이다. 이 글에서는 GDB의 기본적인 사용법에 대해 알아본다.
GDB 설치
GDB를 사용하기 위해선 먼저 설치해야 합니다. 다음은 Ubuntu 기반 시스템에서 GDB를 설치하는 명령어이다.
sudo apt-get install gdb
컴파일 옵션
gdb로 디버깅할 바이너리에 디버깅 정보를 포함하기 위해 -g
옵션주고 컴파일 한다.
/* hello.c */
#include <stdio.h>
int main(int argc, const char *argv[])
{
printf("Hello World!!\n");
return 0;
}
gcc -g -o hello hello.c
GDB 시작하기
gdb를 실행하는 방법은 gdb 실행시 디버깅 하려는 바이너리를 함께 입력하면 된다.
$ gdb hello
GNU gdb (Debian 13.1-3) 13.1
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
... ...
(gdb)
또는, gdb를 먼저 실행 후 file 로 바이너리를 부른다.
$ gdb
...
(gdb) file hello
Reading symbols from hello...
(gdb)
바이너리 실행
run(r)
: 프로세스를 새로 실행한다. 이미 실행 중이라면 재시작한다.continue(c)
: 마지막 중단 지점에서 다음 중단점까지 프로세스를 재개한다.next(n)
: 실행 중인 프로세스를 한줄 실행한다. 함수 실행 시 내부로 진입 Xstep(s)
: 실행 중인 프로세스를 한줄 실행한다. 함수 실행시 내부로 진입 Ofinish(fin)
: 현재 함수를 수행하고 빠져나간 후 리턴 깞을 출력한다.
(gdb) run
Starting program: /home/raspi/work/gdb/hello
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".
Hello World!!
[Inferior 1 (process 2220) exited normally]
(gdb)
실행 시 에러 발생
아래 코드는 메모리가 할당되지 않은 포인터에 strcpy
를 하여 에러가 발생한다.
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *ptr;
strcpy(ptr, "abcd");
return 0;
}
아래와 같이 segv.c:7 strcpy
함수에서 SIGSEGV
(Segmentation Fault) 에러가 발생한다.
$ gcc -g segv.c
$ gdb a.out
...
(gdb) r
Starting program: /home/winuser/work/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Program received signal SIGSEGV, Segmentation fault.
0x000055555555513c in main (argc=1, argv=0x7fffffffe2e8) at segv.c:7
7 strcpy(ptr, "abcd");
(gdb)
breakpoint
설정
프로그램 실행 도중 특정 위치에서 실행을 멈추고자 할 때 breakpoint를 설정한다.
break(b)
설정
break(b)
는 breakpoint를 걸 지점을 정할 때 사용하는데, 설정하는 방법은 다음과 같다.
- break <함수이름>
- break <라인번호>
- break <파일이름:라인번호>
- break <파일이름:함수이름>
- break +<offset>
- break -<offset>
- break *address
- break <…> if <condition> : 조건부 break
info(i) break(b)
break 지정 내역을 확인할 때 사용한다.
info break
info b
i b
clear
지정한 breakpoint를 지운다.
- clear <설정된 함수이름>
- clear <설정된 라인번호>
- clear <설정된 파일이름:라인번호>
- clear <설정된 파일이름:함수이름>
delete(d)
breakpoint를 지운다.
- delete : 설정된 모든 breakpoint를 지운다.
- delete <breakpoint 번호>
- delete <breakpoint 번호> <breakpoint 번호> // 번호에 해당하는 breakpoint를 모두 지운다.
disable(dis)
/ enable(en)
breakpoint을 활성화/비활성화 한다.
enable(en) | 모든 breakpoint 활성화 |
enable <breakpoint number> | 해당 breakpoint 활성화 |
enable <breakpoint number> <breakpoint number> | 해당 breakpoint 모두 활성화 |
disable(dis) | 모든 중단점 활성화 |
disable <breakpoint number> | 해당 중단점 비활성화 |
disable <breakpoint number1> <breakpoint number2> | 해당 중단점 모두 비활성화 |
<ctrl + c>
프로세스가 실행 중 중단점에 도달하지 않을 때 사용하여 일시 중단 가능하다.
Callstack 확인
backtrace
현재 위치의 callstack를 출력한다.
backtrace(bt) | 현재 실행 위치의 함수 call stack을 출력한다. |
bt N | 현재 실행 위치의 함수 call stack 중 처음 N개 출력 |
bt -N | 현재 실행 위치의 함수 call stack 중 마지막 N개 출력 |
bt full | local 변수들도 함께 출력 |
값 출력 / 할당
print
변수나 주소 등을 출력한다.
print(p) <val> | 변수 출력 |
p <func::val> | 해당 함수의 변수를 출력한다. |
p *<ptr> | 포인터의 값 |
p <addr> | 주소에 있는 값 |
p arr[n] | arr 배열의 n번째 값 출력 |
p -pretty *<구조체> | 예쁘게 출력한다. 구조체를 출력할 때 용이하다. |
p/x <val> | x(16진수) 형식으로 변수 출력 |
display
매 실행(step, next, continue 등) 마다 출력한다.
display expr | 매 실행시 마다 출력한다. |
display/fmt expr | 매 실행시 마다 fmt 포멧 형식으로 출력한다. |
info display(dis) | 설정된 display를 출력한다. |
enable(en) display(dis) | 모든 display를 활성화 시킨다. |
en dis <번호> <번호> | 번호에 해당하는 display를 활성화 시킨다. |
disable(dis) display(dis) | 모든 display를 비활성화 시킨다. |
dis dis <번호> <번호> | 번호에 해당하는 display를 비활성화 시킨다. |
set
변수/주소 등에 값을 할당한다.
set var <variable_name>=<value> | 변수값을 주어진 값으로 변경한다. |
기타
return
현재 함수를 수행하지 않고 빠져나간다.
return | 현재 함수를 수행하지 않고 빠져나간다. |
return -1 | 현재 함수를 수행하지 않고 빠져나간다. 리턴값은 -1 |
list(l)
소스 파일을 출력한다.
list | 현재 위치의 소스 출력 |
list 100 | 100번째 라인 주변 소스 출력 |
list funtion | 지정 함수의 소스 출력 |
list – | 직전에 출력한 소스 출력 |
set listsize count | 출력하는 라인수를 count로 설정한다. |
set listsize unlimited | 출력하는 라인수 무제한 |