14.1.1.4. Gỡ lỗi firmware

Gỡ lỗi trực tiếp trên phần cứng có nghĩa là tạm dừng bộ xử lý, đặt điểm dừng trong mã nguồn C, chạy từng bước, và kiểm tra các biến, bộ nhớ, thanh ghi và ngoại vi -- từ bên trong VS Code. Điều này cần ba thứ: một bản dựng debug, một đầu dò debug SWD (Segger J-Link), và tiện ích mở rộng Cortex-Debug điều khiển arm-none-eabi-gdb kết nối với J-Link GDB server.

14.1.1.4.1. Xây dựng bản dựng để gỡ lỗi

Luôn xây dựng lại mục tiêu với DEBUG=1

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

Một ảnh release (DEBUG=0) được biên dịch với -O2; trong trình gỡ lỗi bạn sẽ thấy <optimized out> cho nhiều biến, các hàm nội tuyến hợp nhất vào phần gọi chúng, và việc chạy từng bước nhảy lung tung khó lường. DEBUG=1 xây dựng với -Og -ggdb3, có thể gỡ lỗi trong khi vẫn khởi động được trên camera. File ELF mà bạn chỉ cho trình gỡ lỗi là:

build/<TARGET>/bin/firmware.elf

(Với Alif AE3, hãy gỡ lỗi build/OPENMV_AE3/bin/firmware_M55_HP.elf -- nhân hiệu năng cao.)

14.1.1.4.3. Thiết lập Cortex-Debug trong VS Code

Tạo .vscode/launch.json trong kho lưu trữ. Trường hợp đơn giản nhất -- VS Code, J-Link và bản dựng đều trên cùng một máy Linux / macOS -- sử dụng servertype: "jlink", khiến Cortex-Debug tự khởi động J-Link GDB server:

{
  "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"
    }
  ]
}

Thay đổi executabledevice cho bo mạch của bạn (xem bảng trên). Nhấn F5 để build-flash-and-run đến main và dừng tại đó.

Mẹo

Để tự động xây dựng lại mỗi khi bạn bắt đầu gỡ lỗi, hãy thêm một tác vụ build vào .vscode/tasks.json và tham chiếu từ cấu hình launch với "preLaunchTask". Ví dụ một tác vụ chạy make -j$(nproc) TARGET=OPENMV4 DEBUG=1, đặt tên "build-firmware", cộng với "preLaunchTask": "build-firmware" trong cấu hình trên, để F5 tự xây dựng lại, nạp flash và khởi động trình gỡ lỗi chỉ trong một bước.

Cảnh báo

Cortex-Debug cần arm-none-eabi-gdb. Nó được đi kèm trong SDK tại ~/openmv-sdk-<version>/gcc/bin nhưng không có trong PATH mặc định, nên việc gỡ lỗi thất bại với thông báo "GDB executable 'arm-none-eabi-gdb' was not found". Khắc phục bằng cách đặt armToolchainPath / gdbPath như đã chỉ ra ở trên, hoặc thêm ~/openmv-sdk-<version>/gcc/bin vào PATH của bạn (printenv PATH sau đó phải liệt kê nó).

14.1.1.4.4. Xem thanh ghi ngoại vi (SVD)

Trỏ Cortex-Debug vào file CMSIS SVD để xem thanh ghi ngoại vi đã giải mã (bộ định thời, DMA, giao diện camera, v.v.) theo tên và trường bit:

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

Với STM32 và MIMXRT, lấy SVD từ các gói ST / NXP CMSIS hoặc registry SVD của Cortex-Debug. Các SVD của Alif được đưa vào kho firmware tại lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (dùng ..._CM55_HP_View.svd cho nhân AE3 HP).

14.1.1.4.6. Gỡ lỗi dòng lệnh với gdbrunner

Thiết lập phiên GDB cho một mục tiêu nhúng theo cách thủ công là một quy trình năm bước: khởi động J-Link / ST-Link GDB server trong một cửa sổ với thiết bị, cổng và cờ giao diện đúng; chờ nó in Waiting for GDB connection; chạy arm-none-eabi-gdb trong cửa sổ thứ hai; gõ target remote localhost:<port>; trỏ gdb vào file ELF. Khi phiên gdb kết thúc, nhớ tắt cửa sổ server. gdbrunner là một CLI nhỏ gọn lại tất cả những điều đó thành một lệnh tiền cảnh duy nhất. Nó đi kèm trong môi trường Python của OpenMV SDK, nên không cần cài đặt gì thêm; điểm vào thông thường là mục tiêu make debug của kho firmware:

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

Lệnh này chạy gdbrunner với các đối số trình gỡ lỗi từ cấu hình bo mạch -- tên thiết bị J-Link và, nơi cần thiết, external flash loader của ST-Link -- với arm-none-eabi-gdb của SDK đã có trong PATH. Backend mặc định là J-Link; make DEBUGGER=STLINK debug hoạt động với đầu dò ST-Link thay thế.

gdbrunner cũng có thể được gọi trực tiếp (bên ngoài 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

Đối số vị trí đầu tiên chọn backend server (jlink, stlink, qemu); phần còn lại được chuyển tiếp đến backend đó, với các giá trị mặc định phù hợp với OpenMV cams. gdbrunner --help liệt kê danh sách cờ đầy đủ cho từng backend; bảng đối số của mỗi backend được điều khiển bởi JSON (src/gdbrunner/backends.json), nên việc thêm server mới là chỉnh sửa cấu hình chứ không phải mã.

Những gì gdbrunner làm cho công việc dòng lệnh:

  • Một tiến trình, vòng đời sạch sẽ. Server khởi động, gdb gắn vào khi cổng mở, server được kết thúc gọn gàng khi gdb thoát. Không có JLinkGDBServer bỏ lại sau phiên, không có hai terminal cần quản lý.

  • Tự động phát hiện STM32CubeProgrammer. Backend stlink tìm kiếm các vị trí cài đặt thông thường (~/STM32CubeProgrammer/, /opt/st/, cây plugin STM32CubeIDE) để tìm công cụ STM32CubeProgrammer, nên không cần gõ đường dẫn --cube-prog dài mỗi lần. SDK đi kèm bản sao riêng tại ~/openmv-sdk-<version>/stcubeprog/bin -- trỏ --cube-prog vào đó nếu không có cài đặt hệ thống.

  • Tôn trọng gdbinit theo dự án. Một .gdbinit trong thư mục hiện tại được nạp với -ix -- ghi đè ~/.gdbinit toàn cục -- nên các script gdb theo dự án (pretty-printer, macro dành cho bo mạch, tập điểm dừng) hoạt động bằng cách có mặt trong thư mục làm việc. make debug chạy từ gốc kho lưu trữ, nên .gdbinit ở đó được áp dụng.

  • Chạy thử. --dryrun in ra lệnh server mà không chạy nó, hữu ích để điều chỉnh lệnh gọi thành script bao ngoài, sao chép vào cấu hình launcher IDE, hoặc chỉ để kiểm tra những đối số mà gdbrunner đang tổ hợp.

  • Hiển thị đầu ra server. --show-output giữ stdout / stderr của server hiển thị. Mặc định ẩn nó (để giao diện gdb giữ sạch sẽ); bật cờ này khi chính server đang gặp sự cố.

  • Backend QEMU. qemu-system-arm gỡ lỗi một bản dựng firmware mà không cần cắm bo mạch vào. Mục tiêu MPS2_AN500 chọn backend này trong cấu hình bo mạch, nên make TARGET=MPS2_AN500 DEBUG=1 debug xây dựng cho máy mps2-an500 của QEMU và chạy từng bước mã độc lập nền tảng -- mọi thứ không chạm đến ngoại vi dành riêng cho cam -- trên không trung. (qemu-system-arm là cài đặt máy chủ, không phải một phần của SDK.)

Để chạy từng bước mức mã nguồn với rãnh điểm dừng và xem thanh ghi ngoại vi, thiết lập VS Code Cortex-Debug ở trên là công cụ tốt hơn; gdbrunner là công cụ phù hợp cho mọi thứ hoạt động ở dòng lệnh.

14.1.1.4.7. Sử dụng trình gỡ lỗi

Sau khi phiên đang chạy (bộ xử lý đã dừng tại main):

  • Điểm dừng -- nhấp vào rãnh cạnh dòng C, hoặc trong Debug Console break <file>:<line> / break <function>. Nhân Cortex-M có một số lượng nhỏ bộ so sánh điểm dừng phần cứng (thường 6--8 trên M7 / H7, 8 trên M55). Vượt quá số đó trên mã trong flash sẽ thất bại âm thầm -- hãy giữ số điểm dừng đang hoạt động ở mức vừa phải.

  • Chạy từng bước -- F10 bước qua (next), F11 bước vào (step), Shift+F11 bước ra (finish), F5 tiếp tục. Chạy từng bước ở mức lệnh là stepi / nexti trong Debug Console.

  • Biến / watch / call stack -- khung VariablesCall Stack hiển thị biến cục bộ và backtrace; thêm biểu thức vào Watch. Di chuột qua một biến trong mã nguồn để xem giá trị của nó. Bất kỳ thứ gì hiển thị <optimized out> có nghĩa là bạn không đang dùng bản dựng DEBUG=1.

  • Điểm giám sát (data breakpoint) -- watch <expr> dừng khi một biến bị ghi, rwatch khi đọc, awatch khi cả hai. Đơn vị DWT Cortex-M hỗ trợ ~4 điểm giám sát phần cứng -- vô giá để bắt ai đã làm hỏng một biến.

  • Thanh ghi và ngoại vi -- xem Cortex Registers hiển thị các thanh ghi nhân; khi svdFile được đặt, xem Peripherals giải mã mọi thanh ghi và trường bit ngoại vi (DMA, bộ định thời, giao diện camera / CSI, XSPI, v.v.) -- cách nhanh nhất để xem tại sao một driver đang hoạt động sai.

  • Bộ nhớ -- sử dụng trình xem bộ nhớ Cortex-Debug hoặc x/ của gdb để kiểm tra bộ đệm khung hình, DMA buffer và các cấu trúc dữ liệu trực tiếp.

  • printf không cần dừng (SWO/RTT) -- với các vấn đề nhạy cảm về thời gian, Segger RTT hoặc SWO cho phép printf với chi phí gần bằng không trong khi mục tiêu đang chạy. Xây dựng với DEBUG_PRINTF=1 và thêm rttConfig (RTT) hoặc swoConfig (SWO, cần xung nhịp nhân) của Cortex-Debug. Đây là công cụ phù hợp khi một điểm dừng sẽ thay đổi thời gian bạn đang cố quan sát.

  • Ngắt kết nối -- Stop trong phiên launch dừng mục tiêu; Disconnect trong phiên attach để camera tiếp tục chạy. Tắt nguồn camera để nó trở lại hoạt động bình thường sau đó.

14.1.1.4.8. Các cạm bẫy khi gỡ lỗi

  • Biến bị tối ưu hóa mất. Tất cả đều hiển thị <optimized out> -- bạn đã xây dựng với DEBUG=0. Xây dựng lại với DEBUG=1.

  • "GDB executable not found" -- gcc/bin của SDK không có trong PATH; đặt armToolchainPath / gdbPath.

  • "Cannot connect" / bản đồ bộ nhớ sai -- tên device sai hoặc thiếu; dùng chính xác chuỗi trong bảng.

  • Điểm dừng âm thầm không được kích hoạt -- quá nhiều điểm dừng phần cứng trên mã cư trú trong flash; hãy giảm bớt chúng.

  • Đường dẫn nguồn không khớp (ELF xây dựng bằng Docker) -- xây dựng với mục tiêu Docker build-firmware-dev (cùng đường dẫn tuyệt đối bên trong và bên ngoài container) hoặc đặt gdb set substitute-path.