본문 바로가기

Software

gdb 디버거 사용법 및 다양한 기능 설명

1 Introduction

본 포스트에서는 리눅스에서 사용하는 C/C++ 전용 디버거 gdb의 사용법 및 다양한 기능에 대해 설명한다. gdb에는 다양한 기능들이 많지만 본 포스트에서는 간단한 코드 디버깅에 초점을 맞춰서 필자가 주로 사용하는 옵션들에 대해서만 설명한다. 포스트에서 설명하는 모든 내용들은 우분투 18.04 LTS 환경에서 테스트하였다.

gdb는 대부분의 경우 우분투에 미리 설치되어 있겠지만 만약 설치되어 있지 않은 경우 아래 명령어를 통해 설치한다.

$ sudo apt install gdb

2 Debug with cmake

gdb로 디버깅을 수행하기 위해서는 컴파일 당시 디버깅과 관련된 옵션을 설정해줘야 한다. 디버깅 옵션없이 컴파일하는 경우 디버깅 심볼들이 바이너리 파일에 저장되지 않으므로 소스코드의 어느 부분을 실행하는지에 대한 정보를 알 수 없다. gcc/g++를 사용하는 경우 -g 옵션을 추가해줘야 정상적으로 디버깅을 할 수 있으며 cmake를 사용하는 경우 아래와 같은 타입으로 빌드해야 정상적으로 디버깅을 수행할 수 있다.

$ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
$ make
# or
$ cmake -DCMAKE_BUILD_TYPE=Debug ..
$ make

 

RelWithDebInfo 타입의 경우 실제 바이너리의 실행속도가 Release  Debug 의 중간 정도이며 컴파일 당시 컴파일러가 자체적으로 코드 최적화를 진행하는데 이렇게 코드 최적화가 되고 난 다음의 코드를 디버깅할 수 있다. 따라서 몇몇 변수는 컴파일 단계에서 이미 최적화되어 그 값을 확인할 수 없는 단점이 있다. Debug 타입의 경우 바이너리의 실행속도가 매우 느린 대신 컴파일러의 최적화없이 컴파일하므로 모든 변수의 정보를 볼 수 있다.

 

만약 디버깅 정보없이 가장 빠른 속도로 실행되는 바이너리를 컴파일하기 위해서는 다음과 같이 Release 타입으로 설정하면 된다.

# Build with Release type (cannot debugging using gdb).
$ cmake -DCMAKE_BUILD_TYPE=Release ..
$ make

 

2.1 Quick debug using bash script

필자는 코드를 디버깅할 때 gdb의 수많은 옵션들을 일일히 타이핑하지 않고 별도의 bash 스크립트를 작성하여 이를 실행하는 방법을 주로 사용한다. Bash 스크립트를 사용하면 디버깅할 때마다 터미널에 긴 명령어를 입력하지 않고도 빠르고 편리하게 코드를 디버깅할 수 있다. 아래는 필자가 사용하는 bash 스크립트의 예시이다.

MVG_EXAMPLE=~/gitrepo/starlaw/cmake_projects/mvg_example

case $1 in
    r)  # Release 
        cd ${MVG_EXAMPLE}/build
        cmake -DCMAKE_BUILD_TYPE=Release ..
        make -j
        ;;
    rd) # RelWithDebInfo
        cd ${MVG_EXAMPLE}/reldebug
        cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
        make -j
        ;;
    d) # Debug
        cd ${MVG_EXAMPLE}/debug
        cmake -DCMAKE_BUILD_TYPE=Debug ..
        make -j
        case $2 in
            1)
                gdb -q --ex="r" -args ./compute_projection_matrix
                ;;
            2)
                gdb -q --ex="b compute_projection_matrix.cc:87" --ex="r" -args ./compute_projection_matrix
                ;;
        esac
        ;;
esac

 

위 스크립트의 이름이 run_mvg_example.sh 인 경우 이를 디버깅하기 위해서 다음과 같이 실행한다.

$ ./run_mvg_example.sh d 1
# or 
$ ./run_mvg_example.sh d 2

 

2.2 .gdbinit 

.gdbinit 은 gdb가 실행할 때 로딩하는 환경설정 파일이다. .gdbinit 파일을 홈폴더(~/)에 놓으면 gdb가 실행할 때 자동으로 불러온다. 현재 필자가 사용 중인 .gdbinit은 다음과 같다.

set disassembly-flavor intel  # gdb를 intel CPU 문법으로 설정
set max-value-size unlimited  # gdb를 실행 중인 바이너리의 최대 메모리 사이즈 설정
set print thread-events off   # Thread start, exit과 같은 메시지를 출력하지 않음

3 Useful features

gdb에는 매우 많은 명령어들이 존재하지만 해당 섹션에서는 필자가 주로 사용하는 명령어들만 설명한다. Control 키바인딩은 <C-{...}> 과 같이 표현하였다.

3.1 Command options

해당 섹션에서는 터미널에서 gdb를 실행할 때 사용하는 다양한 옵션들에 대해 설명한다.

3.1.1 -q

gdb를 시작할 때 나오는 메세지를 출력하지 않는 옵션

3.1.2 -tui

현재 디버깅 중인 코드 화면을 보여주는 옵션. <C-x> a 키를 통해 토글할 수 있다.

3.1.3 --ex

gdb가 시작할 때 동시에 실행할 명령어를 입력하는 옵션

3.1.4 --args

디버깅하는 프로그램의 파라미터를 입력하는 옵션

3.2 Keybindings

해당 섹션에서는 gdb가 실행되고 난 후 디버깅을 수행하면서 사용할 수 있는 키바인딩들에 대해 설명한다.

3.2.1 r (run)

현재 프로그램을 실행한다. Breakpoint가 설정되어 있다면 해당 breakpoint까지 코드를 실행한다.

3.2.2 n (next)

현재 커서 위의 라인을 실행하고 다음 라인으로 이동한다. 즉, n을 통해 다음 라인으로 넘어갈 경우 함수 안으로 들어가지 않는다. 한 번 n을 실행하면 다음부터는 엔터키를 통해 n 명령을 계속 수행할 수 있다.

3.2.3 s (step)

현재 커서 위의 라인을 실행하고 해당 함수 및 클래스 내부로 이동한다. 즉, s을 통해 다음 라인으로 넘어갈 경우 함수 안으로 들어간다. 한 번 s을 실행하면 다음부터는 엔터키를 통해 s 명령을 계속 수행할 수 있다.

3.2.4 t (thread)

현재 실행 중인 스레드의 정보를 표시한다. 멀티스레드 프로그래밍을 하는 경우 여러 스레드의 정보가 표시된다.

3.2.5 bt (backtrace)

함수의 stack frame 정보를 표시한다. Stack frame이란 함수가 이전 함수들의 호출로 인해 계층 관계가 생겼을 경우 이러한 함수 계층 구조를 stack 자료구조로 저장해놓은 데이터를 말한다.

3.2.6 f (frame)

현재 stack frame의 정보를 표시한다. f {#} 와 같이 특정 숫자를 입력하면 #번째 stack frame으로 이동한다.

3.2.7 i (info)

breakpoint 또는 thread 등 여러 정보를 출력한다

3.2.8 p (print)

특정 함수나 변수의 값을 확인한다

3.2.9 b (breakpoint)

breakpoint를 설정한다. b {#} 과 같이 뒤에 번호를 붙이면 #번째 라인에 breakpoint를 설정한다.

3.2.10 d (delete)

breakpoint를 제거한다. d {#} 과 같이 뒤에 번호를 붙이면 #번째 breakpoint가 제거된다.

3.2.11 c (continue)

현재 커서로부터 다음 breakpoint까지 실행한다. 만약 breakpoint가 없는 경우 프로그램을 실행하는 것과 동일한 효과가 나타난다.

3.2.12 q (quit)

gdb를 종료한다

3.2.13 l (list)

특정 라인으로 이동한다. 함수 이름을 입력하는 경우 특정 함수로 이동한다.

3.2.14 cond (condition)

breakpoint의 조건을 설정한다. 위 그림에서는 for 루프에서 i=345일 때 2번 breakpoint가 작동하도록 설정하였다.

3.2.15 <C-x> a

-tui 옵션과 동일하게 현재 디버깅 중인 코드 화면을 표시한다. 해당 키를 한 번 더 입력하면 토글된다.

3.2.16 <C-l>

터미널을 새로고침한다. <C-x> a 키를 통해 디버깅하는 경우 간혹 터미널의 그래픽이 깨지는 경우가 발생하는데 이 때 <C-l> 을 사용하면 그래픽이 다시 원상복구된다.

3.2.17 <C-p,n>

이전 명령어들을 네비게이션한다. <C-p> 를 통해 이전 명령어를 탐색할 수 있고 <C-n> 을 통해 다음 명령어를 탐색할 수 있다.