본문 바로가기
  • 경제적 자유를 얻는 그날까지
엔지니어링/프로그래밍

[Flightmare 시리즈 4] Docker 속 ROS 노드를 VS Code로 해부하기 (GDB 디버깅 완벽 가이드)

by 베니스상인 2026. 1. 18.

 

지난 Part 3에서는 C++로 fmp_waypoint 노드를 작성해 드론을 목표 좌표로 이동시키는 데 성공했습니다. 코드가 짧을 때는 괜찮았지만, 로직이 복잡해지거나 Segmentation Fault가 발생할 때마다 printf로 로그를 찍어 확인하는 것은 너무나 비효율적이었습니다.

우리의 목표는 명확합니다. "VS Code의 강력한 디버깅 기능(Breakpoints, Variable Watch)을 Docker 컨테이너 내부의 ROS 노드에 적용하는 것."

하지만 호스트(Host) PC와 **도커(Container)**라는 격리된 환경 차이 때문에 수많은 난관(GLIBC 버전, 라이브러리 경로 등)을 넘어야 했습니다. 오늘은 그 "삽질"의 기록과 최종 해결책을 공유합니다.

 


 

0. 기술적 배경 (Background Knowledge)

 

본격적인 설정에 앞서, 우리가 해결해야 할 기술적 장벽이 무엇인지 짚고 넘어갑시다. (이 내용을 알면 에러 로그가 보입니다.)

1) Remote Debugging (원격 디버깅) 구조

우리가 사용하는 VS Code는 호스트(Windows/Ubuntu)에 있지만, 실제 코드가 돌아가는 곳은 Docker 컨테이너(Ubuntu 18.04) 내부입니다. 따라서 VS Code Remote - Containers 확장을 통해, VS Code의 '두뇌(Server)'를 컨테이너 안으로 심어야 합니다. 그리고 그 안에서 **GDB(GNU Debugger)**를 실행해 ROS 노드의 메모리를 들여다보는 구조입니다.

 

2) Debug Symbol (디버그 심볼)

C++ 컴파일러(GCC)는 기본적으로 코드 최적화를 수행하며 사람이 읽을 수 있는 변수명이나 줄 번호 정보를 삭제합니다. 디버거가 코드를 한 줄씩 짚어가며 멈추게 하려면 Debug 모드로 빌드하여 이 정보(Symbol Table)를 남겨둬야 합니다.

 

3) GLIBC (GNU C Library) 버전 호환성

Flightmare가 사용하는 ROS MelodicUbuntu 18.04 기반입니다. 이곳의 시스템 라이브러리(glibc) 버전은 2.27입니다. 하지만 최신 VS Code(1.86+) 서버는 2.28 이상을 요구합니다. 이 "버전 불일치"가 접속 실패의 주원인입니다.

 


 

1. 빌드 설정 변경: Debug 모드

 

가장 먼저 해야 할 일은 지난 시간에 만든 fmp_waypoint 노드를 디버깅 가능한 상태로 다시 빌드하는 것입니다.

터미널에서 단순히 catkin build만 치면 디버깅 정보를 포함하지 않습니다. 브레이크 포인트를 걸어도 "Unverified Breakpoint"라며 회색 원이 뜨고 멈추지 않는 이유가 바로 이겁니다.

 

Docker 컨테이너 내부 터미널에서 다음 명령어를 실행합니다.

Bash
 
 
Bash
 
# 1. 기존 빌드 아티팩트 정리 (Clean)
# Release 모드와 Debug 모드의 오브젝트 파일이 섞이면 링킹 에러가 날 수 있으므로 깔끔하게 지웁니다.
catkin clean myprj_flightmare_2 -y

# 2. Debug 모드로 빌드 (핵심!)
# -DCMAKE_BUILD_TYPE=Debug 옵션이 들어가면 컴파일러에 '-g' 플래그가 추가되어 심볼 테이블이 생성됩니다.
catkin build myprj_flightmare_2 --cmake-args -DCMAKE_BUILD_TYPE=Debug

# 3. 환경 변수 갱신
# 빌드된 라이브러리 경로를 시스템에 알립니다.
source devel/setup.bash

 

만약 catkin의 build 폴더내 여러개의 빌드 결과물이 있다면 clean으로 삭제할 수 없습니다. 해당 과정을 생략하고 그냥 재빌드를 해서 덮어쓰기 하시면 됩니다.

 


2. VS Code 환경 구축 (feat. 버전 다운그레이드)

 

"자, 이제 VS Code의 Remote - Containers 기능으로 접속해 볼까?"

호기롭게 접속을 시도했으나, 예상치 못한 에러 메시지가 발목을 잡았습니다.

Error: Missing GLIBC >= 2.28! from /lib/x86_64-linux-gnu/libc-2.27.so

이 에러는 최근 로보틱스 개발자들 사이에서 악명 높은 이슈입니다. 원인은 다음과 같습니다.

  1. Flightmare 환경: ROS Melodic을 사용하므로 베이스 이미지가 Ubuntu 18.04입니다. 이 OS의 시스템 표준 라이브러리(GLIBC) 버전은 2.27입니다.
  2. VS Code 업데이트: 2024년 2월경 업데이트된 VS Code(1.86 버전 이상)는 원격 서버를 구동하기 위해 GLIBC 2.28 이상(Ubuntu 20.04) 을 필수 요구사항으로 변경했습니다.

즉, **"최신 VS Code가 너무 똑똑해져서 옛날 OS(ROS Melodic)를 더 이상 지원하지 않는 상황"**이 된 것입니다.

 

해결책은 아래와 같은 과정으로 진행하면 됩니다.

  1. VS Code 1.85.2 설치 (OS에 맞는 버전 다운로드)
  2. 자동 업데이트 끄기 (필수): 설정(Ctrl+,) -> Update: Mode -> none
  3. Dev Containers 확장 설치 후 Attach to Running Container 실행

[Step 1] 기존 VS Code 삭제 및 다운로드

먼저 호스트(Ubuntu)에 설치된 최신 VS Code를 삭제하고, 터미널을 이용해 구버전을 다운로드합니다. 웹사이트를 찾아다닐 필요 없이 wget 명령어로 한 번에 해결할 수 있습니다.

Bash
 
# 1. 기존 최신 버전 삭제 (설정 파일은 유지됩니다)
sudo apt remove code

# 2. 1.85.2 버전 다운로드 (Microsoft 공식 링크)
wget -O code_1.85.2_amd64.deb "https://update.code.visualstudio.com/1.85.2/linux-deb-x64/stable"

[Step 2] 구버전 설치 및 업데이트 영구 차단 (중요!)

다운로드한 파일을 설치합니다. 그리고 가장 중요한 것은 **"자동 업데이트를 막는 것"**입니다. 이걸 하지 않으면 다음날 시스템 업데이트 때 다시 최신 버전으로 돌아가 버립니다.

Bash
 
# 1. 설치 진행
sudo apt install ./code_1.85.2_amd64.deb

# 2. 패키지 업데이트 고정 (Hold)
# 이 명령어를 입력하면 'sudo apt upgrade'를 해도 VS Code는 업데이트되지 않습니다.
sudo apt-mark hold code

확인: 터미널에 code set on hold. 라는 메시지가 뜨면 성공입니다.

[Step 3] VS Code 내부 설정 변경

시스템 레벨에서 막았지만, VS Code 자체 설정에서도 업데이트를 꺼주는 것이 안전합니다.

  1. VS Code 실행 (code .)
  2. 단축키 Ctrl + , 로 설정 진입
  3. 검색창에 update mode 입력
  4. Update: Mode 값을 default에서 none (또는 manual)으로 변경

[Step 4] Dev Containers 확장 설치 및 접속

이제 준비가 끝났습니다. 도커 컨테이너에 접속해 봅시다.

  1. 확장 프로그램 설치: 좌측 메뉴의 Extensions(블럭 아이콘)에서 Dev Containers (Microsoft)를 검색해 설치합니다.
  2. 컨테이너 실행: 터미널에서 먼저 Flightmare 도커를 실행해 둡니다. (이미 켜져 있다면 패스)
  3. 접속 (Attach):
    • VS Code 창의 왼쪽 맨 아래 구석에 있는 파란색(또는 초록색) >< 아이콘을 클릭합니다.
    • 상단 메뉴가 뜨면 Attach to Running Container... 를 선택합니다.
    • 목록에서 실행 중인 Flightmare 컨테이너를 선택합니다.

[Step 5] 접속 성공 확인

새로운 VS Code 창이 열리면서 좌측 하단이 Dev Container: ... 로 바뀐다면 접속 성공입니다. 확실하게 검증하기 위해 VS Code 내부 터미널(Ctrl + ) 을 열고 아래 명령어를 입력해 보세요.

Bash
 
whoami
# 결과가 'root' 라고 나와야 정상입니다. ('swift'가 나오면 실패!)

이제 우리는 호스트의 쾌적한 GUI를 사용하면서, 실제 코드는 컨테이너 내부의 ROS 환경에서 실행하고 디버깅할 수 있는 완벽한 준비를 마쳤습니다.

 

 

3. 컨테이너 내부 설정 (GDB 설치)

 

컨테이너에 접속해서 터미널을 열어보면 프롬프트가 root@... 로 바뀐 것을 볼 수 있습니다. 이제 호스트가 아닌 컨테이너 내부입니다. 디버깅을 하려면 gdb라는 도구가 필요한데, Flightmare 도커 이미지는 경량화를 위해 이를 기본 설치하지 않았습니다. 실행해보면 command not found가 뜹니다.

Bash
 
apt-get update
apt-get install -y gdb

설치 후 which gdb를 쳤을 때 /usr/bin/gdb 경로가 나온다면 성공입니다.

 

 

 

4. launch.json 작성 (핵심 트러블 슈팅)

이제 이 포스팅의 하이라이트입니다. VS Code에게 "어떤 프로그램을", "어떻게 디버깅할지" 알려주는 설정 파일인 .vscode/launch.json을 작성해야 합니다.

단순히 "생성" 버튼을 눌러서 만든 기본 설정으로는 ROS 노드가 절대 실행되지 않습니다. 제가 겪었던 대표적인 에러들은 다음과 같습니다.

  • Error 1: miDebuggerPath is invalid. (GDB를 못 찾음)
  • Error 2: error while loading shared libraries: libflightlib.so: cannot open shared object file (라이브러리 실종)
  • Error 3: libroscpp.so: cannot open shared object file (ROS 시스템 라이브러리 실종)

이 에러들은 모두 환경 변수(Environment Variable) 와 관련이 있습니다. 터미널에서 source devel/setup.bash를 하면 LD_LIBRARY_PATH가 자동으로 잡히지만, VS Code 디버거가 실행될 때는 이 환경 변수를 제대로 물려받지 못하는 경우가 태반입니다.

최종 해결책: 절대 경로 하드코딩 (Hard-coding)

가장 확실한 방법은 변수($HOME, ${env:...})를 믿지 않고, 시스템의 절대 경로를 설정 파일에 직접 박아넣는 것입니다.

JSON

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) ROS Node Debug",
            "type": "cppdbg",
            "request": "launch",
            
            // [1] 디버깅할 실행 파일의 정확한 경로
            // catkin build를 하면 devel/lib/패키지명/실행파일명 위치에 생성됩니다.
            "program": "${workspaceFolder}/devel/lib/myprj_flightmare_2/fmp_waypoint",
            
            "args": [],
            "stopAtEntry": true,  // true로 설정하면 main 함수 시작점에서 자동으로 멈춥니다. (연결 확인용)
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            
            // [2] 컨테이너 내부에 설치한 GDB 경로 명시
            "miDebuggerPath": "/usr/bin/gdb",
            
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            
            // [3] 핵심: 라이브러리 경로 직접 주입 (Environment)
            // VS Code가 실행될 때 이 경로들을 강제로 LD_LIBRARY_PATH에 넣습니다.
            // 주의: /root/... 는 컨테이너 내부의 경로입니다. 호스트 경로를 쓰면 안 됩니다!
            "env": {
                "LD_LIBRARY_PATH": "/opt/ros/melodic/lib:/root/workspace/catkin_ws/flightmare_ws/devel/lib",
                "ROS_MASTER_URI": "http://localhost:11311",
                "ROS_IP": "127.0.0.1"
            }
        }
    ]
}

코드 설명:

  • LD_LIBRARY_PATH: 리눅스에서 프로그램이 실행될 때 .so (동적 라이브러리) 파일을 찾는 경로입니다.
    • /opt/ros/melodic/lib: ROS 핵심 기능(roscpp 등)이 있는 곳.
    • /root/workspace/.../devel/lib: 우리가 빌드한 flightlib, myprj 라이브러리가 있는 곳. 이 두 곳을 콜론(:)으로 연결하여 명시해 주어야 비로소 실행 파일이 라이브러리를 찾아 로딩할 수 있습니다.

 

 

5. 디버깅 시작! (Execution)

 

모든 준비가 끝났습니다. 이제 Part 3에서 작성했던 코드를 한 줄 한 줄 실행하며 분석해 봅시다.

Step 1. 시뮬레이션 환경 준비

디버거를 실행하기 전에, ROS Master와 Unity 시뮬레이터는 켜져 있어야 합니다. VS Code 내에서 터미널을 분할(Split)하여 실행하면 편합니다.

  • Terminal 1: roscore
  • Terminal 2: ./RPG_Flightmare.x86_64 (Unity 렌더링 창 실행)

Step 2. 브레이크 포인트 설정

src/fmp_waypoint.cpp 파일을 엽니다. 드론의 상태를 업데이트하는 루프 안쪽에 빨간 점(Breakpoint)을 찍어봅시다.

C++
 
    while (ros::ok()) {
        flightlib::QuadState current_state = quad.getState();
        
        // 여기에 브레이크 포인트를 겁니다! (F9)
        current_state.p = target_position; 
        
        quad.setState(current_state);
        // ...

Step 3. 디버깅 시작 (F5)

VS Code에서 F5 키를 누릅니다. 상단에 컨트롤 바가 나타나고, 하단 상태 표시줄이 주황색으로 변하며 디버깅 모드에 진입합니다. launch.json에서 stopAtEntry: true로 설정했다면, main 함수 첫 줄에서 노란색 화살표와 함께 멈출 것입니다.

Step 4. 변수 값 추적 (Watch)

다시 Continue (F5) 를 누르면 루프 안의 브레이크 포인트까지 실행되다가 멈춥니다. 이제 왼쪽 VARIABLES 패널을 보세요.

  • current_state: 드론의 현재 위치, 속도, 자세(Quaternion) 정보가 트리 구조로 펼쳐집니다.
  • target_position: 우리가 설정한 (5.0, 5.0, 5.0) 값이 정확히 들어있는지 확인할 수 있습니다.

만약 드론이 이상한 곳으로 간다면, 여기서 target_position 값이 쓰레기 값인지, 아니면 current_state가 제대로 갱신되지 않는지 눈으로 직접 확인할 수 있습니다. printf로 수백 줄의 로그를 뒤지는 고통에서 해방되는 순간입니다.


 

6. 트러블 슈팅 요약 (FAQ)

 

제가 이 환경을 구축하면서 겪었던 대표적인 문제와 해결책을 요약합니다. 여러분은 이 길을 피해 가시길 바랍니다.

Q1. No such file or directory 에러가 계속 나요.

  • A. launch.json의 program 경로를 확인하세요. devel/lib/... 아래에 실행 파일이 실제로 존재하는지 터미널에서 ls 명령어로 확인해야 합니다. 없다면 빌드가 실패한 것입니다.

Q2. libflightlib.so를 못 찾는다고 해요.

  • A. LD_LIBRARY_PATH 문제입니다. launch.json의 env 섹션에 /root/workspace/catkin_ws/flightmare_ws/devel/lib 경로가 정확히(오타 없이) 포함되었는지 확인하세요. catkin build 후 라이브러리가 .private 폴더에 생성되는 경우도 있으니 find 명령어로 실제 위치를 찾아보는 것이 가장 좋습니다.

Q3. 브레이크 포인트를 무시하고 그냥 넘어가요.

  • A. 100% 확률로 Release 모드로 빌드된 것입니다. 본문 1번 항목을 참조하여 catkin clean 후 -DCMAKE_BUILD_TYPE=Debug 옵션을 넣어 다시 빌드하세요.

 


 

에필로그: 진정한 개발의 시작

이제 우리는 Flightmare + ROS + Docker 라는 복잡한 환경 위에서 VS Code GDB 디버깅이라는 강력한 무기를 손에 넣었습니다.

단순히 코드를 짜는 것을 넘어, 실행 중인 프로세스의 메모리를 들여다보고 제어 흐름을 장악할 수 있게 되었습니다. 이것은 개발 생산성을 10배 이상 높여줄 뿐만 아니라, 앞으로 마주할 수많은 복잡한 알고리즘(경로 계획, 충돌 회피, 강화학습 등)을 구현하는 데 있어 든든한 기반이 될 것입니다.

 

Happy Coding & Safe Flight! 🚁

댓글