21.10.4.1.1. `.gdbinit` 스크립트에 `target extended-remote :2331` 매핑 후, 특정 센서 데이터가 수신되는 순간(Watchpoint) 실행을 정지시키는 메모리 브레이크포인트 심화 기법

21.10.4.1.1. .gdbinit 스크립트에 target extended-remote :2331 매핑 후, 특정 센서 데이터가 수신되는 순간(Watchpoint) 실행을 정지시키는 메모리 브레이크포인트 심화 기법

이전 장에서 우분투의 2331 포트에 J-Link 서버(통역사)를 띄워두었다.
이제 GCC 툴체인에 포함된 강력한 해커, 즉 **GDB 클라이언트(arm-none-eabi-gdb)**를 켜서 저 포트로 돌격시킬 차례다.

하지만 매번 터미널에 gdb를 치고 들어가서 target remote localhost:2331 을 타이핑하고, 보드를 리셋(Reset)하고 세팅하는 것은 미친 짓이다.
우리는 이 침투 과정을 자동화하기 위해 프로젝트 최상단 폴더에 .gdbinit 이라는 암살 지령서(스크립트)를 작성해 둘 것이다.

1. .gdbinit 스크립트 작성

PX4 소스코드 루트 디렉터리에 .gdbinit 파일을 하나 숨겨 만들고 다음과 같이 적어 넣는다.

# [.gdbinit 파일 내용]

# 1. 아까 띄워둔 2331번 포트로 GDB 서버에 접속한다.
target extended-remote localhost:2331

# 2. 픽스호크 보드가 디버깅 연결 충격으로 오동작하지 않게 잠시 실행을 멈춘다(halt).
monitor halt

# 3. 보드의 CPU를 물리적으로 한 번 초기화(Reset)한다. (비행 중이라면 절대 금지!)
monitor reset

# 4. NuttX 운영체제가 GDB의 스레드 추적을 인식할 수 있게끔 설정 파일을 먹여준다.
# (PX4 빌드 폴더에 기본 내장되어 있음)
source build/px4_fmu-vX_default/NuttX/nuttx/tools/nuttx-gdbinit

# 5. 이제 멈춰있던 심장을 다시 뛰게 만든다(부팅 시작).
continue

이제 터미널에서 C++ 심벌이 듬뿍 담긴 엘프(.elf) 파일을 쥐여주며 GDB를 실행시켜 보자.

$ arm-none-eabi-gdb build/px4_fmu-vX_default/px4_fmu-vX_default.elf

엔터를 치는 순간 GDB는 자동으로 .gdbinit 지령서를 읽고, 2331 포트를 통해 픽스호크에 접속하여 보드를 리셋한 뒤 펌웨어 부팅을 시작한다.
터미널 창의 맨 아래에는 (gdb) 라는 무시무시한 권력의 프롬프트가 깜빡이고 있을 것이다.

2. 브레이크포인트(Breakpoint)의 함정 파기

가장 기본적이지만 강력한 디버깅은 “내 소스 코드의 특정 줄(Line)에 도달하면 모든 세상의 시간을 멈춰라” 라고 명령하는 것이다.
만약 내 payload_autodrop 모듈의 150번 줄에서 예외가 발생한다고 의심된다면, (gdb) 프롬프트에 다음과 같이 명령을 내린다.

(gdb) b PayloadAutoDrop.cpp:150
Breakpoint 1 at 0x802c0b4: file PayloadAutoDrop.cpp, line 150.

명령을 치는 즉시 J-Link 디버거는 STM32 실리콘 칩 내부의 하드웨어 브레이크포인트 레지스터에 전기 신호를 보내 덫을 놓는다.
그리고 펌웨어가 돌아가다 정확히 저 150번 줄에 도달하는 순간, 하늘을 날던 드론은 프로펠러 모터의 PWM 신호마저 그 상태로 굳어버린 채 우주(CPU)의 시간이 완벽히 정지(Target halted)된다.

3. 궁극의 흑마술: 메모리 워치포인트(Watchpoint)

단순한 라인 멈춤을 넘어, 임베디드 디버깅의 꽃이라 불리는 ‘워치포인트(Watchpoint)’ 기술이 있다.
이것은 소스 코드의 특정 줄이 아니라, “메모리상에 있는 특정 변수의 ’값’이 누군가에 의해 변조되는 그 찰나의 순간에 멈춰라” 라는 악랄한 함정이다.

예를 들어 드론이 비행하다가 가끔씩 알 수 없는 이유로 내 FSM 변수(_current_state)가 쓰레기값으로 바뀌어 추락한다고 가정하자. 수십만 줄의 커널 코드 중 도대체 누가 내 변수를 건드린단 말인가?

이때 GDB에서 메모리 주소나 변수명에 워치포인트를 건다.

(gdb) watch _current_state
Hardware watchpoint 2: _current_state
(gdb) continue

보드는 정상적으로 수십만 번의 루프를 돌며 비행한다. 그러다 갑자기 누군가의 미친 코드(예: 버퍼 오버플로우)가 내 _current_state 메모리 주소를 침범해 값을 쓰는 그 순간!
전기가 튀기도 전에 하드웨어 하이재킹이 일어나며 CPU가 멈춘다.
그 자리에서 터미널에 bt (Backtrace) 명령어를 치면, 내 변수를 강간하려던 그 악질 범인 함수의 콜 스택(Call Stack)이 백일하에 폭로된다.

이러한 터미널 텍스트 기반의 GDB 디버깅은 해커들에게는 낭만적이지만, 일반 백엔드 개발자들에게는 너무 가혹한 환경이다.
마지막 대미를 장식할 21.10.4.2장에서는, 이 칙칙한 GDB 터미널을 우리가 매일 같이 쓰는 Visual Studio Code (VSCode)의 화려한 그래픽(UI) 화면으로 끌어와, 마우스 클릭만으로 브레이크포인트를 걸고 변수를 들여다보는 launch.json 세팅법을 마무리 지어보자.