임베디드

GDB 디버깅에 대해서

embeddedworld 2025. 3. 22. 18:16

소프트웨어 개발에서 디버깅은 필수적인 과정이다. 특히 시스템 프로그래밍이나 저수준 개발을 할 때는 강력한 디버깅 도구가 필요하다. GNU Debugger(GDB)는 C, C++, 그리고 어셈블리 수준의 디버깅을 지원하는 가장 강력한 도구 중 하나다. 이번 글에서는 GDB의 기본 개념부터 실전 활용까지 다룬다.

1. GDB란 무엇인가?


GDB(GNU Debugger)는 프로그램 실행을 제어하며, 오류를 분석하고 문제를 해결하는 데 사용되는 디버거다. 주요 기능은 다음과 같다.
• 브레이크포인트 설정: 특정 지점에서 실행을 멈추고 상태를 확인
• 단계별 실행(Step-by-step execution): 코드 한 줄씩 실행하며 흐름을 분석
• 메모리 및 변수 검사: 변수 값, 레지스터, 스택 상태 확인
• 어셈블리 코드 디버깅: 저수준에서 명령어 단위로 코드 실행 추적
• 코어 덤프 분석: 비정상 종료된 프로그램의 상태를 확인

2. GDB 설치 및 실행


GDB는 대부분의 리눅스 배포판에 기본 제공되지만, 설치되지 않았다면 아래 명령어로 설치할 수 있다.

# Ubuntu/Debian 계열
sudo apt install gdb

# Fedora
sudo dnf install gdb

# Arch Linux
sudo pacman -S gdb

GDB를 실행하는 방법은 간단하다. 디버깅할 바이너리 파일을 인자로 주면 된다.

gdb ./my_program

디버깅을 원활하게 수행하려면 반드시 디버깅 심볼(debug symbols) 이 포함된 상태로 컴파일해야 한다. 이를 위해 -g 옵션을 추가한다.

gcc -g -o my_program my_program.c


3. GDB 기본 명령어


GDB는 다양한 명령어를 제공하지만, 디버깅에서 가장 많이 쓰이는 기본 명령어는 다음과 같다.

GDB 명령어 종류


4. 실전 예제: 코드 디버깅


간단한 C 프로그램을 작성한 후 GDB로 디버깅해보자.

4.1 디버깅할 프로그램


아래와 같은 debug_test.c 파일을 만든다.

#include <stdio.h>

void my_function(int x) {
    int result = 10 / x;  // x가 0이면 오류 발생
    printf("Result: %d\n", result);
}

int main() {
    int a = 0;
    my_function(a);
    return 0;
}


컴파일할 때 -g 옵션을 추가하여 디버깅 심볼을 포함시킨다.

gcc -g -o debug_test debug_test.c

4.2 GDB로 디버깅 시작


GDB를 실행하고 프로그램을 로드한다.

gdb ./debug_test

이제 브레이크포인트를 설정해보자. buggy_function 함수에서 오류가 발생할 것이므로, 해당 함수에 브레이크포인트를 설정한다.

(gdb) break my_function
Breakpoint 1 at 0x1149: file debug_test.c, line 5.

프로그램을 실행한다.

(gdb) run
Starting program: ./debug_test

이제 buggy_function에서 실행이 멈춘다. 변수 x의 값을 확인해보자.

(gdb) print x
$1 = 0

변수 x가 0이므로 10 / x 연산에서 오류가 발생할 것이다.

코드를 한 줄 실행하려면 next 또는 step 명령어를 사용한다.

(gdb) next

오류가 발생하면 GDB는 SIGFPE(0으로 나누기 오류)를 감지하고 메시지를 출력한다.

Program received signal SIGFPE, Arithmetic exception.

백트레이스를 출력하면 어디에서 오류가 발생했는지 확인할 수 있다.

(gdb) bt
#0  my_function (x=0) at debug_test.c:5
#1  0x0000000000401145 in main () at debug_test.c:10

이제 원인을 파악했으니, buggy_function에서 x가 0일 때 예외 처리를 추가하여 수정할 수 있다.


5. 어셈블리 코드 분석


GDB는 어셈블리 코드 단위의 디버깅도 지원한다. 특정 함수의 어셈블리 코드를 확인하려면 disassemble 명령어를 사용한다.

(gdb) disassemble my_function
Dump of assembler code for function my_function:
   0x0000000000401136 <+0>:  push   %rbp
   0x0000000000401137 <+1>:  mov    %rsp,%rbp
   0x000000000040113a <+4>:  mov    %edi,-0x4(%rbp)
   0x000000000040113d <+7>:  mov    -0x4(%rbp),%eax
   0x0000000000401140 <+10>: cdq    
   0x0000000000401141 <+11>: idiv   %ecx
   ...

이 정보를 활용하면 특정 레지스터 값과 함께 프로그램 흐름을 더 정밀하게 분석할 수 있다.

6. 마치며


GDB는 단순한 소스 코드 디버깅뿐만 아니라 어셈블리 수준의 분석도 지원하는 강력한 도구다. 특히 커널, 드라이버, 임베디드 시스템 개발자라면 필수적으로 익혀야 할 디버거다. GDB를 자유자재로 활용하면 디버깅 속도가 빨라지고, 문제를 더 깊이 이해할 수 있다.