개요
GDB란?
C, C++, Rust, Go 등 다양한 언어로 개발된 프로그램을 코드 단위로 디버깅하기 위한 대표적인 리눅스 환경의 디버거로, 실행되기 전의 프로그램 혹은 실행되고 있는 중인 프로그램에 이 디버거를 붙여서 활용가능하다. 또한, 프로그램이 비정상적으로 종료되었을 경우 생성되는 Postmortem 파일인 코어파일을 디버깅하기 위해서도 활용가능하다.
원격 디버깅이란?
임베디드 리눅스로 동작하는 장치는 메모리 등이 현저히 부족한 환경이 대다수로, 대부분의 임베디드 장치에서는 디버깅을 위한 심볼이나 관련 도구들을 저장하기 힘들다. 그래서, 이를 해결하기 위해 디버깅을 위한 SDK 들은 모두 호스트 환경에 두고 이에 따라 타겟의 프로그램을 원격으로 디버깅할 수 있도록 타겟에는 에이전트를 두는 방식으로 원격 디버깅한다.
본 페이지에서는 GDB를 활용해 임베디드 리눅스 장치에 원격 디버깅을 위한 gdbserver 에이전트를 빌드시키는 방법과 호스트 환경에서 타겟의 에이전트를 통해 간단한 프로그램을 디버깅하는 방법을 설명한다. 또한, 비정상적인 동작(Seg Fault 등)으로 종료된 프로그램에 대한 코어 파일을 생성시키는 방법 그리고 코어 파일을 GDB를 활용해 원격으로 디버깅하는 방법을 설명하고 마무리한다.
임베디드 리눅스 GDB 활성화

임베디드 리눅스 장치에 gdbserver 에이전트를 빌드시키려면, 위 그림과 같이 이미지 레시피의 IMAGE_INSTALL 변수에 gdbserver를 추가해주어야 한다. (반드시 :append 사용하기)
이제, 이미지에 gdbserver를 빌드시킨 후 임베디드 리눅스 장치를 플래싱한다.
필자는 Raspberry Pi 4를 사용하고 있어서 SD 카드로 이미지를 구워주었다. 이를 위해서 bzcat과 dd 명령을 활용하였다.
# 이미지에 gdbserver를 빌드시키기
(Host)$ source oe-init-build-env build
(Host)$ bitbake <target_image>
# 빌드된 이미지를 타겟 장치에 플래싱
# Raspberry Pi 4의 경우는 아래 명령을 사용 (참조링크: https://pak-j.tistory.com/96)
(Host)$ cd tmp/deploy/images/raspberrypi4/
# SD 카드와 같은 Block 장치의 경우 fdisk 명령 등으로 완전히 초기화시킨 후 아래 명령 진행
(Host)$ bzcat core-image-full-cmdline-raspberrypi4.wic.bz2 | sudo dd of=/dev/<blkfile-name>
플래싱이 완료되었다면 타겟(임베디드 리눅스 장치)으로 접근(SSH 혹은 Serial 활용)하여 아래 명령을 통해 gdbserver가 정상적으로 빌드되었는지 확인한다.
(Target)$ gdbserver --version

타겟에 정상적으로 gdbserver가 빌드된 것을 확인하였다면, 이제 타겟을 디버깅하기 위한 개발용 SDK를 호스트 환경에 설치해주어야 한다. 이를 위해 아래 명령을 실행시킨다.
(Host)$ bitbake -c populate_sdk <target_image>
bitbake에서 -c 옵션은 특정 태스크(do_install, do_rootfs 등)만을 실행시키기 위해 활용한다. 이 -c 옵션을 활용해서 타겟 개발용 SDK를 호스트 환경에 설치해줄 수 있으며, 이때 사용하는 것이 populate_sdk 태스크이다.
타겟 개발용 SDK 설치가 완료되었다면, SDK를 호스트 환경에서 사용할 수 있도록 아카이브 해제와 디렉터리 위치 조정 등을 수행해주어야 한다. 이 과정은 한번에 수행할 수 있도록 Yocto에서 쉘 스크립트 형태로 제공한다.
cd build/tmp/deploy/sdk
./<target_image_sdk>.sh


쉘 스크립트(.sh)를 통한 설치 과정 중 SDK의 설치 경로를 Default로 설정하였다면, 타겟 개발용 SDK는 /opt/poky/<poky-version> 하위에 설치된다. 설치가 완료되었다면 이제 타겟 개발용 SDK가 설치된 경로로 이동하여 SDK 환경설정을 위한 스크립트 실행시켜준다.
cd /opt/poky/<poky-version>
source <sdk_env>
여기까지 진행되었다면 호스트에는 타겟 이미지의 디버깅 심볼과 타겟용 GDB가 들어있는 개발용 SDK 설치가 완료되었으며, 타겟에는 호스트 환경에서의 원격 디버깅을 위한 에이전트인 gdbserver 설치가 완료되었다.
이제 호스트와 타겟에 세팅된 디버깅 환경을 활용한 GDB 원격 디버깅 방법을 다음 섹션에서 설명한다.
임베디드 리눅스 GDB 원격 디버깅
먼저 타겟에서 디버깅할 애플리케이션에 gdbserver 에이전트를 다음과 같이 붙여준다.
# SSH 환경으로 설명
(Target)$ gdbserver :<port_number> <app_name>

위와 같이 타겟 환경을 세팅하였다면, 이제 호스트에서 타겟의 gdbserver 에이전트로 붙이기 위한 작업을 다음과 같이 수행한다.
# 쉘 스크립트를 통해 타겟 개발용 SDK 환경을 호스트에 설정하였다면,
# 환경 변수($GDB)에 타겟의 cross-gdb가 세팅되어 있다.
(Host)$ $GDB /opt/poky/<poky-version>/sysroots/<application-binary-path>
# 실행되는 타겟의 바이너리에 대한 디버깅 심볼을 찾기 위해 sysroot를 등록한다.
(Host)$ (gdb) set sysroot /opt/poky/<poky-version>/sysroots/<target-sysroot-directory>
# (Optional) 만약 애플리케이션 소스 파일이 타겟 sysroot에 존재하지 않다면,
# 아래 명령으로 직접 애플리케이션 소스 파일의 위치를 등록해준다.
(Host)$ (gdb) directory path/to/poky/…/<application-source-directory>/
# 타겟에서 대기 중인 gdbserver 에이전트에 아래 명령을 활용해 붙어준다.
(Host)$ (gdb) target remote <target-ip>:<gdbserver-port-number>
호스트에서 타겟의 gdbserver 에이전트에 붙게되었다면 이제 아래 사진과 같이 GDB 명령을 활용해 소스 레벨에서의 디버깅을 수행하면 된다. 본 페이지에서는 GDB 명령에 대한 내용은 생략한다.

만약 디버깅 할 애플리케이션이 타겟이 부팅될 때 자동으로 실행되어 초기 단계의 디버깅이 불가할 경우, 리눅스의 init 프로그램(SysVinit, Busybox, Systemd 등)에서 참조하는 데몬 실행 명령을 다음과 같이 설정해주면 된다.
# SysVinit
start-stop-daemon -S -x /usr/bin/gdbserver -- :<port-number> /usr/bin/<app-bin>
# systemd
ExecStart=/usr/bin/gdbserver :<port-number> /usr/bin/<app-bin>
또한, 이미 실행 중인 애플리케이션에 gdbserver 에이전트를 붙여주는 방법도 존재한다.
(Target)$ gdbserver --attach :<port-number> $(pidof <app-bin>)
임베디드 리눅스 코어파일 생성방법
애플리케이션의 소스레벨 문제(Seg Fault 등)가 항시 발생하지 않고, 간헐적으로 발생하는 경우가 존재할 것이다. 이때에는 GDB를 붙여놓아도 문제를 곧바로 찾아내긴 어렵기 때문에 Postmortem 방식으로 디버깅을 수행해주어야 한다. 다시 말해, 애플리케이션의 비정상적인 종료에 대한 사후 분석을 수행해야 한다.
이를 위해 리눅스에서는 비정상적인 종료가 발생된 애플리케이션의 메모리를 그대로 덤프를 떠서 바이너리 형식의 파일로 저장해두는 기능을 제공한다. 이때 생성된 파일을 코어 파일이라고 불리운다.
코어 파일을 생성하기 위해선, 리눅스 시스템에서는 다음과 같은 명령을 활용해 코어 파일의 생성 경로와 허용 용량을 세팅해주어야 한다.
# 코어 파일의 생성 경로를 지정
# %e = 실행된 애플리케이션의 파일 이름
# %p = 실행된 애플리케이션의 프로세스 ID(PID)
# %s = 실행된 애플리케이션이 종료될 때 발생된 시그널 번호
# %t = 실행된 애플리케이션의 코어 덤프 발생 타임스탬프
(Target)$ sudo vi /etc/sysctl.conf
(Target)$ (vi) kernel.core_pattern=/tmp/core.%e.%p.%s.%t

# 재부팅 되어야 위에 설정한 코어 덤프에 대한 설정이 시스템 상에 적용된다.
(Target)$ sudo reboot
# ulimit의 -c 옵션은 애플리케이션에서 생성할 코어 파일의 용량을 설정하기 위해 사용한다.
# 기본적으로 코어 파일의 허용 용량은 0 이다.
# ulimit 설정은 재부팅하면 사라지므로, 계속 유지를 위해선 데몬 스크립트에 넣어두는게 좋다.
(Target)$ sudo ulimit -c unlimited
테스트를 위해서 kill 명령을 활용해 SEGV 시그널을 프로세스에 발생시켜 Seg Fault가 일어나게끔 한다.

Seg Fault가 발생된 프로세스는 코어 덤프를 수행한 뒤 /tmp 하위에 코어 파일을 생성시킨다. 이 코어 파일은 호스트 환경에서 GDB를 활용해 분석(디버깅) 가능하다.
# 호스트에서 타겟에 저장된 코어 파일을 다운로드
(Host)$ scp <target-user>@<target-ip>:/tmp/<core-file> <download-path>
# 호스트에 타겟의 개발용 SDK 환경 세팅
(Host)$ cd /opt/poky/<poky-version>
(Host)$ source <sdk_env>
# 호스트에 타겟의 개발용 SDK 환경을 잘 세팅되었는지 확인
(Host)$ echo $GDB
(Host)$ echo $SDKTARGETSYSROOT
# Not Stripped 실행 파일 찾기
(Host)$ find ~/workspace/rpi4-poky/build/tmp/work -name onestep -type f
(Host)$ file <not-stripped-app-bin>

# 코어 파일을 Not Stripped 실행 파일과 함께 GDB로 열기
(Host)$ $GDB /path/to/<not-stripped-app-bin> /path/to/<core-file>
# 실행되는 타겟의 바이너리에 대한 디버깅 심볼을 찾기 위해 sysroot를 등록한다.
(Host)$ (gdb) set sysroot /opt/poky/<poky-version>/sysroots/<target-sysroot-directory>
# (Optional) 만약 애플리케이션 소스 파일이 타겟 sysroot에 존재하지 않다면,
# 아래 명령으로 직접 애플리케이션 소스 파일의 위치를 등록해준다.
(Host)$ (gdb) directory path/to/poky/…/<application-source-directory>/
# GDB 명령을 활용해 디버깅을 수행한다.
(Host)$ (gdb) list
(Host)$ (gdb) bt

위 디버깅을 통해 Sleep 도중에 kill 명령에 의해 SIGSEGV이 발생되어 비정상적인 종료가 일어난 것을 알 수 있다.
결론
본 페이지에서는 임베디드 리눅스 환경에서 GDB와 gdbserver를 활용하여 원격 디버깅 환경을 구성하고, 호스트에서 타겟 애플리케이션을 소스 레벨로 분석하는 방법을 정리하였다.
임베디드 장치는 저장공간과 메모리 제약으로 인해 타겟 내부에 모든 디버깅 도구와 심볼을 포함하기 어렵다. 따라서 타겟에는 gdbserver만 배치하고, 호스트에는 Yocto SDK를 설치하여 Cross-GDB, Debugging Symbol, Target sysroot를 활용하는 방식이 일반적이다.
또한 이미 실행 중인 프로세스에는 gdbserver --attach 방식으로 붙을 수 있으며, 부팅 시 자동 실행되는 애플리케이션은 init script 또는 systemd의 실행 명령을 gdbserver로 감싸 초기 실행 시점부터 디버깅할 수 있다.
마지막으로, 간헐적으로 발생하는 Segmentation Fault와 같은 문제는 코어 덤프를 활성화하여 사후 분석할 수 있다. 이때 코어 파일은 살아 있는 프로세스가 아니므로 gdbserver로 원격 attach하는 방식이 아니라, 호스트로 가져온 뒤 not-stripped 실행파일과 SDK sysroot를 함께 사용하여 GDB에서 분석해야 한다.
결국 임베디드 리눅스 디버깅 환경의 핵심은 다음 세 가지로 정리할 수 있다.
- 타겟에는 gdbserver를 포함시킨다.
- 호스트에는 Yocto SDK와 심볼 포함 실행파일을 준비한다.
- 실시간 문제는 원격 디버깅으로, 종료된 문제는 코어 덤프 분석으로 접근한다.
이 구성을 갖추면 제한적인 임베디드 환경에서도 애플리케이션의 실행 흐름, 크래시 원인, 메모리 상태를 비교적 빠르고 정확하게 추적할 수 있다.
References
임베디드 리눅스 프로그래밍 완전정복 3/e, 19장 GDB로 디버깅하기
'Linux > Embedded' 카테고리의 다른 글
| SysVinit(System V init)이란? (0) | 2026.06.05 |
|---|




