14.1.1.4. 펌웨어 디버깅

온하드웨어 디버깅이란 프로세서를 정지시키고, C 소스에 중단점을 설정하며, 한 단계씩 실행하고, 변수와 메모리, 레지스터, 주변장치를 검사하는 것을 의미합니다 – 모두 VS Code 내부에서 수행합니다. 이를 위해서는 세 가지가 필요합니다: 디버그 빌드, SWD 디버그 프로브(Segger J-Link), 그리고 J-Link GDB 서버를 대상으로 arm-none-eabi-gdb를 구동하는 Cortex-Debug 확장.

14.1.1.4.1. 디버깅용 빌드

항상 DEBUG=1로 타깃을 다시 빌드하십시오:

make -j$(nproc) TARGET=<TARGET> DEBUG=1

릴리스(DEBUG=0) 이미지는 -O2로 컴파일됩니다. 디버거에서는 많은 변수가 <optimized out>으로 표시되고, 인라인된 함수는 호출자 안으로 합쳐지며, 단계 실행이 예측할 수 없게 건너뜁니다. DEBUG=1-Og -ggdb3으로 빌드되어 디버깅이 가능하면서도 카메라에서 여전히 부팅됩니다. 디버거가 가리키는 ELF는 다음과 같습니다:

build/<TARGET>/bin/firmware.elf

(Alif AE3의 경우 build/OPENMV_AE3/bin/firmware_M55_HP.elf를 디버깅하십시오 – 고성능 코어입니다.)

14.1.1.4.3. VS Code Cortex-Debug 설정

저장소에 .vscode/launch.json을 생성하십시오. 가장 간단한 경우 – VS Code, J-Link, 빌드가 모두 동일한 Linux / macOS 머신에 있는 경우 – 는 servertype: "jlink"를 사용하며, 이는 Cortex-Debug가 직접 J-Link GDB 서버를 시작하도록 합니다:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "OpenMV J-Link",
      "type": "cortex-debug",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "executable": "${workspaceFolder}/build/OPENMV4/bin/firmware.elf",
      "servertype": "jlink",
      "device": "STM32H743VI",
      "interface": "swd",
      "runToEntryPoint": "main",
      "armToolchainPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin",
      "gdbPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin/arm-none-eabi-gdb"
    }
  ]
}

보드에 맞게 executabledevice를 변경하십시오(위 표 참조). F5를 눌러 빌드-플래시-실행하여 main에서 멈추도록 하십시오.

디버깅을 시작할 때마다 자동으로 다시 빌드하려면 .vscode/tasks.json에 빌드 작업을 추가하고 launch 구성에서 "preLaunchTask"로 이를 참조하십시오. 예를 들어 make -j$(nproc) TARGET=OPENMV4 DEBUG=1을 실행하는 "build-firmware"라는 이름의 작업과, 위 구성의 "preLaunchTask": "build-firmware"를 사용하면 F5가 다시 빌드하고, 플래시하고, 디버거를 단일 단계로 시작합니다.

경고

Cortex-Debug에는 arm-none-eabi-gdb가 필요합니다. 이는 SDK의 ~/openmv-sdk-<version>/gcc/bin에 포함되어 있지만 기본적으로 PATH에 있지 않으므로, 디버깅이 “GDB executable ‘arm-none-eabi-gdb’ was not found” 오류로 실패합니다. 위에서 보인 대로 armToolchainPath / gdbPath를 설정하거나, ~/openmv-sdk-<version>/gcc/binPATH에 추가하여(printenv PATH로 확인하면 표시되어야 함) 해결하십시오.

14.1.1.4.4. 주변장치 레지스터 보기 (SVD)

Cortex-Debug가 CMSIS SVD 파일을 가리키도록 하여 주변장치 레지스터(타이머, DMA, 카메라 인터페이스 등)를 이름과 비트필드별로 디코딩한 보기를 얻으십시오:

"svdFile": "/path/to/STM32H743.svd"

STM32 및 MIMXRT의 경우 ST / NXP CMSIS 팩이나 Cortex-Debug SVD 레지스트리에서 SVD를 받으십시오. Alif SVD는 펌웨어 저장소의 lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/에 벤더링되어 있습니다(AE3 HP 코어에는 ..._CM55_HP_View.svd를 사용하십시오).

14.1.1.4.6. gdbrunner를 사용한 명령줄 디버깅

임베디드 타깃을 대상으로 GDB 세션을 수동으로 설정하는 것은 다섯 단계의 번거로운 작업입니다: 한 창에서 올바른 장치, 포트, 인터페이스 플래그로 J-Link / ST-Link GDB 서버를 시작하고, Waiting for GDB connection이 출력될 때까지 기다리고, 두 번째 창에서 arm-none-eabi-gdb를 실행하고, target remote localhost:<port>를 입력하고, gdb가 ELF를 가리키도록 합니다. gdb 세션이 끝나면 서버 창을 종료하는 것을 잊지 마십시오. gdbrunner 는 이 모든 것을 하나의 포그라운드 명령으로 합치는 작은 CLI입니다. OpenMV SDK의 Python 환경에 포함되어 있어 설치할 것이 없습니다. 일반적인 진입점은 펌웨어 저장소의 make debug 타깃입니다:

make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug

이는 보드 구성의 디버거 인수 – J-Link 장치 이름과, 필요한 경우 ST-Link 외부 플래시 로더 – 로 gdbrunner를 실행하며, SDK의 arm-none-eabi-gdb가 이미 PATH에 있습니다. 기본 백엔드는 J-Link입니다. make DEBUGGER=STLINK debug는 대신 ST-Link 프로브로 작동합니다.

gdbrunner는 직접 호출할 수도 있습니다(SDK 외부에서는 pip install gdbrunner):

gdbrunner jlink --device STM32H743VI build/OPENMV4/bin/firmware.elf
gdbrunner stlink build/OPENMV4/bin/firmware.elf
gdbrunner qemu --machine mps2-an500 build/MPS2_AN500/bin/firmware.elf

첫 번째 위치 인수는 서버 백엔드(jlink, stlink, qemu)를 선택하고, 나머지는 OpenMV 캠에 맞는 기본값과 함께 해당 백엔드로 전달됩니다. gdbrunner --help는 백엔드별 전체 플래그 목록을 나열합니다. 각 백엔드의 인수 테이블은 JSON 기반(src/gdbrunner/backends.json)이므로, 새 서버를 추가하는 것은 코드 수정이 아니라 구성 편집입니다.

gdbrunner가 명령줄 작업을 위해 하는 일:

  • 하나의 프로세스, 깔끔한 수명 주기. 서버가 시작되고, 포트가 열리면 gdb가 연결되며, gdb가 종료되면 서버가 깔끔하게 종료됩니다. 세션 이후에 살아남는 고아 JLinkGDBServer도 없고, 관리할 두 개의 터미널도 없습니다.

  • STM32CubeProgrammer 자동 검색. stlink 백엔드는 STM32CubeProgrammer 도구를 찾기 위해 일반적인 설치 위치(~/STM32CubeProgrammer/, /opt/st/, STM32CubeIDE 플러그인 트리)를 검색하므로, 긴 --cube-prog 경로를 매번 입력할 필요가 없습니다. SDK는 ~/openmv-sdk-<version>/stcubeprog/bin에 자체 복사본을 번들로 제공합니다 – 시스템 설치가 없으면 --cube-prog를 그곳으로 가리키십시오.

  • 프로젝트별 gdbinit 적용. 현재 디렉터리의 .gdbinit-ix로 로드되어 – 사용자 전체의 ~/.gdbinit를 재정의하므로 – 프로젝트별 gdb 스크립팅(프리티 프린터, 보드별 매크로, 중단점 집합)이 작업 디렉터리에 존재하는 것만으로 적용됩니다. make debug는 저장소 루트에서 실행되므로 그곳의 .gdbinit가 적용됩니다.

  • 드라이 런. --dryrun은 서버 명령을 실행하지 않고 출력하며, 호출을 래퍼 스크립트에 맞추거나, IDE 런처 구성에 복사하거나, gdbrunner가 어떤 인수를 구성하는지 확인하는 데 유용합니다.

  • 서버 출력 표시. --show-output은 서버의 stdout / stderr를 표시된 상태로 유지합니다. 기본값은 이를 억제합니다(gdb의 UI가 깔끔하게 유지되도록). 서버 자체가 오작동할 때 이 플래그를 켜십시오.

  • QEMU 백엔드. qemu-system-arm은 보드를 꽂지 않은 상태로 펌웨어 빌드를 디버깅합니다. MPS2_AN500 타깃은 보드 구성에서 이 백엔드를 선택하므로, make TARGET=MPS2_AN500 DEBUG=1 debug는 QEMU의 mps2-an500 머신용으로 빌드하고 플랫폼 독립적인 코드 – 캠 전용 주변장치를 건드리지 않는 모든 것 – 를 비행 중에 단계 실행합니다. (qemu-system-arm은 SDK의 일부가 아니라 호스트 설치입니다.)

중단점 거터와 주변장치 레지스터 보기를 갖춘 소스 수준 단계 실행에는 위의 VS Code Cortex-Debug 설정이 더 나은 도구입니다. gdbrunner는 명령줄에 사는 모든 것에 적합한 도구입니다.

14.1.1.4.7. 디버거 사용하기

세션이 실행되면(프로세서가 main에서 정지된 상태):

  • 중단점 – C 줄 옆의 거터를 클릭하거나, Debug Console에서 break <file>:<line> / break <function>을 사용하십시오. Cortex-M 코어에는 소수의 하드웨어 중단점 비교기가 있습니다(일반적으로 M7 / H7에서 6–8개, M55에서 8개). 플래시에 있는 코드에서 이를 초과하면 조용히 실패합니다 – 활성 중단점 수를 적당하게 유지하십시오.

  • 단계 실행F10 스텝 오버(next), F11 스텝 인투(step), Shift+F11 스텝 아웃(finish), F5 계속. 명령어 수준 단계 실행은 Debug Console에서 stepi / nexti입니다.

  • 변수 / 감시 / 호출 스택VariablesCall Stack 창은 지역 변수와 백트레이스를 표시합니다. Watch에 표현식을 추가하십시오. 소스에서 변수 위에 마우스를 올리면 값을 볼 수 있습니다. <optimized out>으로 표시되는 것은 DEBUG=1 빌드에 있지 않다는 의미입니다.

  • 감시점(데이터 중단점)watch <expr>는 변수가 기록될 때 정지하고, rwatch는 읽기 시, awatch는 둘 중 어느 쪽이든 정지합니다. Cortex-M DWT 유닛은 약 4개의 하드웨어 감시점을 지원합니다 – 누가 변수를 손상시켰는지 잡는 데 매우 유용합니다.

  • 레지스터와 주변장치Cortex Registers 보기는 코어 레지스터를 표시합니다. svdFile이 설정되면 Peripherals 보기가 모든 주변장치 레지스터와 비트필드(DMA, 타이머, 카메라 / CSI 인터페이스, XSPI 등)를 디코딩합니다 – 드라이버가 왜 오작동하는지 가장 빠르게 확인하는 방법입니다.

  • 메모리 – Cortex-Debug 메모리 뷰어나 gdb x/를 사용하여 프레임 버퍼, DMA 버퍼, 구조체를 직접 검사하십시오.

  • 중단 없는 printf (SWO/RTT) – 타이밍에 민감한 문제의 경우, Segger의 RTT 또는 SWO를 사용하면 타겟이 실행되는 동안 거의 오버헤드 없이 printf를 사용할 수 있습니다. DEBUG_PRINTF=1로 빌드하고 Cortex-Debug의 rttConfig(RTT) 또는 swoConfig(SWO, 코어 클럭 필요)를 추가하세요. 브레이크포인트가 관찰하려는 타이밍을 변경시킬 수 있는 상황에서 적합한 도구입니다.

  • 연결 해제launch 세션에서 Stop은 타깃을 정지시키고, attach 세션에서 Disconnect는 카메라를 계속 실행 상태로 둡니다. 이후 정상 동작으로 되돌리려면 카메라의 전원을 껐다 켜십시오.

14.1.1.4.8. 디버깅 함정

  • 최적화로 제거된 변수. 모든 것이 <optimized out>으로 표시됩니다 – DEBUG=0으로 빌드한 것입니다. DEBUG=1로 다시 빌드하십시오.

  • “GDB executable not found” – SDK gcc/binPATH에 없습니다. armToolchainPath / gdbPath를 설정하십시오.

  • “Cannot connect” / 잘못된 메모리 맵 – 잘못되었거나 누락된 device 이름. 표의 정확한 문자열을 사용하십시오.

  • 중단점이 조용히 적중하지 않음 – 플래시에 상주하는 코드에 하드웨어 중단점이 너무 많습니다. 줄이십시오.

  • 소스 경로가 일치하지 않음(Docker로 빌드한 ELF) – Docker build-firmware-dev 타깃으로 빌드하거나(컨테이너 내부와 외부에서 동일한 절대 경로) gdb set substitute-path를 설정하십시오.