emdbg.debug.gdb

GDB Debugger

This module manages the invocation of GDB in various contexts for user interaction or automated scripting.

Installation

Unfortunately, the official arm-none-eabi-gcc v9 and v10 from ARM only come with the Python 2.7 API, whose support has reached end-of-life in 2020. The newer v11 and v12 versions removed Python support altogether.

Therefore you need to install a third-party toolchain. We tested and recommend the xpack v12 toolchain, since it has been adapted from the official compiler sources from ARM to include a standalone Python 3.11 runtime and thus is the closest we have to an official toolchain. We strongly recommend to only symlink the arm-none-eabi-gdb-py3 binary into your path, and keep the remaining arm-none-eabi-gcc at v9 as done for PX4.

Ubuntu

sudo install -d -o $USER /opt/xpack

curl -L https://github.com/xpack-dev-tools/arm-none-eabi-gcc-xpack/releases/download/v12.2.1-1.2/xpack-arm-none-eabi-gcc-12.2.1-1.2-linux-x64.tar.gz | \
        tar -xvzf - -C /opt/xpack/
# Only link the -py3 into your path
ln -s /opt/xpack/xpack-arm-none-eabi-gcc-12.2.1-1.2/bin/arm-none-eabi-gdb-py3 \
      /opt/gcc-arm-none-eabi-9-2020-q2-update/bin/arm-none-eabi-gdb-py3

macOS

On macOS you additionally need to clear the quarantine flags after expansion:

sudo install -d -o $USER /opt/xpack

if [[ $(arch) == 'arm64' ]]; then export gdbarch='arm'; else export gdbarch='x'; fi
curl -L "https://github.com/xpack-dev-tools/arm-none-eabi-gcc-xpack/releases/download/v12.2.1-1.2/xpack-arm-none-eabi-gcc-12.2.1-1.2-darwin-${gdbarch}64.tar.gz" | \
        tar -xvzf - -C /opt/xpack/

# Clear the quarantine flag
sudo xattr -r -d com.apple.quarantine /opt/xpack/

# Only link the -py3 into your path
ln -s /opt/xpack/xpack-arm-none-eabi-gcc-12.2.1-1.2/bin/arm-none-eabi-gdb-py3 \
      $HOMEBREW_PREFIX/bin/arm-none-eabi-gdb-py3

Command Line Interface

These commands runs the probe in the background and launches GDB in the foreground either using the Text User Interface (TUI) or the web-based GDBGUI.

Launch GDB and connect it to a running extended remote server:

python3 -m emdbg.debug.gdb --elf path/to/firmware.elf --ui=cmd remote --port IP_ADDRESS:2331

Launch GDB and connect it to a J-Link backend:

python3 -m emdbg.debug.gdb --elf path/to/firmware.elf --ui=tui jlink -device STM32F765II

If provided with the --python flag, then the Python GDB is used and the functionality of emdbg.debug.px4 is made available inside GDB as user commands:

python3 -m emdbg.debug.gdb --python --elf path/to/firmware.elf jlink -device STM32F765II

To access peripheral registers in the debugger, you must specify the CMSIS-SVD files:

python3 -m emdbg.debug.gdb --python --elf path/to/firmware.elf --svd path/to/STM32F7x5.svd jlink -device STM32F765II

Coredumps can be analyzed offline as well (see emdbg.debug.crashdebug):

python3 -m emdbg.debug.gdb -py --elf path/to/firmware.elf crashdebug --dump coredump.txt
# This also works with PX4 hardfault logs
python3 -m emdbg.debug.gdb -py --elf path/to/firmware.elf crashdebug --dump px4_hardfault.log

User Commands

GDB has a Python API for providing much deeper access than given by the standard scripting API. The emdbg.debug.px4 modules provide a library of tools for analyzing PX4 at debug time.

You can call the modules directly:

(gdb) python print(px4.all_tasks_as_table(gdb))

Note
The emdbg.debug.px4 tools are accessible inside the GDB command prompt only as a top-level px4 Python module to avoid loading in all of the emdbg modules.

For convenience, several user commands wrap our Python plugins:

px4_discover

Shows information about the connected device.

(gdb) px4_discover
                                ╷               ╷        ╷                     ╷
  Device                        │ Revision      │ Flash  │ Package             │ UID
 ═══════════════════════════════╪═══════════════╪════════╪═════════════════════╪══════════════════════════
  0x451: STM32F76xx, STM32F77xx │ 0x1001: rev Z │ 2048kB │ LQFP208 or TFBGA216 │ 20373658375650140045002c
                                ╵               ╵        ╵                     ╵

px4_reset

Resets and halts the device using the correct command for the backend.

px4_tasks

px4_tasks [--files]

Pretty prints a table of all NuttX threads and their state with optional file handle names. This is similar to the NSH command top, however, does not require a live system to work. You inspect other thread stacks by switching to it using px4_switch_task [PID].

(gdb) px4_tasks
                ╷      ╷                        ╷                 ╷         ╷        ╷       ╷       ╷      ╷      ╷     ╷       ╷
                │      │                        │                 │         │        │ Stack │ Avail │      │      │     │       │
  struct tcb_s* │  pid │ Task Name              │ Location        │ CPU(ms) │ CPU(%) │ Usage │ Stack │ Prio │ Base │ FDs │ State │ Waiting For
 ═══════════════╪══════╪════════════════════════╪═════════════════╪═════════╪════════╪═══════╪═══════╪══════╪══════╪═════╪═══════╪══════════════════════════════════════
     0x2002673c │    0 │ Idle Task              │ nx_start        │   10256 │   53.8 │   398 │   726 │    0 │    0 │   3 │ RUN   │
     0x2007c310 │    1 │ hpwork                 │ nxsem_wait      │       0 │    0.0 │   292 │  1224 │  249 │  249 │   3 │ w:sem │ 0x200208e8 -1 waiting
     0x2007cde0 │    2 │ lpwork                 │ nxsem_wait      │       6 │    0.0 │   500 │  1576 │   50 │   50 │   3 │ w:sem │ 0x200208fc -1 waiting
     0x2007da10 │    3 │ nsh_main               │ nxsem_wait      │       0 │    0.0 │  1980 │  3040 │  100 │  100 │   4 │ w:sem │ 0x20020594 -1 waiting
     0x2007f0c0 │    4 │ wq:manager             │ nxsem_wait      │       0 │    0.0 │   588 │  1232 │  255 │  255 │   5 │ w:sem │ 0x2007fce8 -1 waiting: 1x wq:manager
     0x2007fe20 │    5 │ wq:lp_default          │ nxsem_wait      │      74 │    0.4 │  1388 │  1896 │  205 │  205 │   5 │ w:sem │ 0x2000072c -1 waiting
     0x20001e40 │    6 │ Telnet daemon          │ nxsem_wait      │       0 │    0.0 │   556 │  1984 │  100 │  100 │   1 │ w:sem │ 0x20002ad0 -1 waiting
     0x20002f20 │    7 │ netinit                │ nxsem_wait      │       1 │    0.0 │   764 │  2024 │   49 │   49 │   4 │ w:sem │ 0x20027014 -1 waiting
     0x20004980 │   60 │ wq:hp_default          │ nxsem_wait      │     182 │    1.0 │  1052 │  1872 │  237 │  237 │   5 │ w:sem │ 0x20005c54 -1 waiting
     0x20005d10 │   63 │ wq:I2C3                │ nxsem_wait      │      62 │    0.3 │   736 │  2312 │  244 │  244 │   5 │ w:sem │ 0x200066bc -1 waiting
     0x200040e0 │   88 │ dataman                │ nxsem_wait      │       0 │    0.0 │  1068 │  1376 │   90 │   90 │   5 │ w:sem │ 0x200072d0 -1 waiting
     0x20007fb0 │  141 │ wq:ttyS5               │ nxsem_wait      │      95 │    0.6 │  1084 │  1704 │  229 │  229 │   5 │ w:sem │ 0x2000873c -1 waiting
     0x20005300 │  217 │ wq:SPI3                │ nxsem_wait      │     724 │    4.0 │  1484 │  2368 │  251 │  251 │   5 │ w:sem │ 0x2000a864 -1 waiting
     0x20005090 │  235 │ wq:SPI1                │ nxsem_wait      │     479 │    2.6 │  1780 │  2368 │  253 │  253 │   5 │ w:sem │ 0x2000c224 -1 waiting
     0x2000b5b0 │  250 │ wq:I2C4                │ nxsem_wait      │     100 │    0.5 │   960 │  2312 │  243 │  243 │   5 │ w:sem │ 0x2000cb4c -1 waiting
     0x2000d810 │  393 │ wq:nav_and_controllers │ nxsem_wait      │     516 │    3.0 │  1248 │  2216 │  242 │  242 │   5 │ w:sem │ 0x2000ff4c -1 waiting
     0x2000e170 │  394 │ wq:rate_ctrl           │ nxsem_wait      │     213 │    1.2 │  1532 │  3120 │  255 │  255 │   5 │ w:sem │ 0x20010ba4 -1 waiting
     0x200128a0 │  396 │ wq:INS0                │ nxsem_wait      │     956 │    5.2 │  4244 │  5976 │  241 │  241 │   5 │ w:sem │ 0x2001409c -1 waiting
     0x200149c0 │  398 │ wq:INS1                │ nxsem_wait      │     887 │    4.8 │  4244 │  5976 │  240 │  240 │   5 │ w:sem │ 0x200161bc -1 waiting
     0x20016430 │  400 │ commander              │ nxsig_timedwait │     242 │    1.3 │  1468 │  3192 │  140 │  140 │   5 │ w:sig │ signal
     0x20009ab0 │  403 │ ekf2                   │ nxsig_timedwait │     167 │    0.9 │  1300 │  2000 │  237 │  237 │   3 │ w:sig │ signal
     0x2003c0b0 │  411 │ mavlink_if0            │ nxsig_timedwait │    1327 │    7.3 │  1916 │  3064 │  100 │  100 │   5 │ w:sig │ signal
     0x20040890 │  413 │ mavlink_rcv_if0        │ nxsem_wait      │      89 │    0.5 │   212 │  6056 │  175 │  175 │   5 │ w:sem │ 0x20041ad8 -1 waiting
     0x2003baa0 │  625 │ gps                    │ nxsem_wait      │      11 │    0.1 │  1220 │  1936 │  205 │  205 │   4 │ w:sem │ 0x200436d0 -1 waiting
     0x20004dd0 │  786 │ mavlink_if1            │ nxsig_timedwait │     420 │    2.4 │  1916 │  3048 │  100 │  100 │   5 │ w:sig │ signal
     0x20046970 │  788 │ mavlink_rcv_if1        │ nxsem_wait      │      79 │    0.4 │   212 │  6056 │  175 │  175 │   5 │ w:sem │ 0x20047bb8 -1 waiting
     0x200439f0 │  894 │ mavlink_if2            │ nxsig_timedwait │     978 │    5.5 │  1860 │  3048 │  100 │  100 │   5 │ w:sig │ signal
     0x2004b270 │  904 │ mavlink_rcv_if2        │ nxsem_wait      │      72 │    0.4 │   212 │  6056 │  175 │  175 │   5 │ w:sem │ 0x2004c4b8 -1 waiting
     0x20042590 │  975 │ uxrce_dds_client       │ nxsem_wait      │       5 │    0.0 │   540 │  9920 │  100 │  100 │   4 │ w:sem │ 0x2004f8d0 -1 waiting
     0x20042850 │ 1014 │ navigator              │ nxsem_wait      │      26 │    0.1 │  1068 │  2104 │  105 │  105 │   7 │ w:sem │ 0x2004e250 -1 waiting
     0x2004d560 │ 1333 │ logger                 │ nxsem_wait      │      64 │    0.4 │  3116 │  3616 │  230 │  230 │   3 │ w:sem │ 0x20054720 -1 waiting
     0x200482a0 │ 1400 │ wq:uavcan              │ nxsem_wait      │     284 │    1.5 │  2252 │  3600 │  236 │  236 │   5 │ w:sem │ 0x20058784 -1 waiting
     0x2004e610 │ 1401 │ log_writer_file        │ nxsem_wait      │       0 │    0.0 │   388 │  1144 │   60 │   60 │   3 │ w:sem │ 0x2004ece0 -1 waiting
                ╵      ╵                        ╵                 ╵         ╵        ╵       ╵       ╵      ╵      ╵     ╵       ╵
Processes: 33 total, 1 running, 32 sleeping
CPU usage: 44.4% tasks, 1.8% sched, 53.8% idle
Uptime: 19.33s total, 0.62s interval

px4_files

px4_files

Pretty prints a table of open files and their private data handle.

(gdb) px4_files
                ╷            ╷                              ╷
  struct inode* │ i_private* │ Name                         │ Tasks
 ═══════════════╪════════════╪══════════════════════════════╪═══════════════════════════════════════════════════════════════════════════════════════════════════════════
     0x2007c070 │ 0x20020570 │ console                      │ Idle Task, commander, dataman, gimbal, gps, hpwork, init, log_writer_file, logger, lpwork, mavlink_if0,
                │            │                              │ mavlink_if1, mavlink_if2, mavlink_rcv_if0, mavlink_rcv_if1, mavlink_rcv_if2, navigator, wq:I2C2, wq:I2C4,
                │            │                              │ wq:INS0, wq:SPI1, wq:SPI2, wq:SPI3, wq:ff_escs, wq:hp_default, wq:lp_default, wq:manager,
                │            │                              │ wq:nav_and_controllers, wq:rate_ctrl, wq:ttyS7
     0x2007f100 │            │ console_buf                  │ commander, dataman, gimbal, gps, init, log_writer_file, logger, mavlink_if0, mavlink_if1, mavlink_if2,
                │            │                              │ mavlink_rcv_if0, mavlink_rcv_if1, mavlink_rcv_if2, navigator, wq:I2C2, wq:I2C4, wq:INS0, wq:SPI1,
                │            │                              │ wq:SPI2, wq:SPI3, wq:ff_escs, wq:hp_default, wq:lp_default, wq:manager, wq:nav_and_controllers,
                │            │                              │ wq:rate_ctrl, wq:ttyS7
     0x2007c1c0 │ 0x200201d0 │ ttyS6                        │ mavlink_if1, mavlink_rcv_if1
     0x2007c130 │ 0x20020000 │ ttyS3                        │ mavlink_if2, mavlink_rcv_if2
     0x20001600 │ 0x20021228 │ can0                         │ wq:I2C2, wq:I2C4, wq:INS0, wq:SPI1, wq:SPI2, wq:SPI3, wq:ff_escs, wq:hp_default, wq:lp_default,
                │            │                              │ wq:manager, wq:nav_and_controllers, wq:rate_ctrl, wq:ttyS7
     0x20001730 │ 0x2002ad00 │ gpin4                        │ wq:I2C2, wq:I2C4, wq:INS0, wq:SPI1, wq:SPI2, wq:SPI3, wq:ff_escs, wq:hp_default, wq:lp_default,
                │            │                              │ wq:manager, wq:nav_and_controllers, wq:rate_ctrl, wq:ttyS7
     0x20001760 │ 0x2002ad3c │ gpin5                        │ wq:I2C2, wq:I2C4, wq:INS0, wq:SPI1, wq:SPI2, wq:SPI3, wq:ff_escs, wq:hp_default, wq:lp_default,
                │            │                              │ wq:manager, wq:nav_and_controllers, wq:rate_ctrl, wq:ttyS7
     0x20002170 │ 0x200021a0 │ microsd                      │ commander, dataman, log_writer_file, logger
     0x2005e720 │ 0x2005e6e0 │ pipe1                        │ mavlink, mavlink_shell
     0x2005e510 │ 0x2005e650 │ pipe0                        │ mavlink, mavlink_if0, mavlink_rcv_if0, mavlink_shell
     0x2007c160 │ 0x200200e8 │ ttyS4                        │ mavlink_if0, mavlink_rcv_if0
     0x2001c7b0 │ 0x2001c740 │ vehicle_local_position0      │ navigator
     0x2003a050 │ 0x2003a010 │ mission0                     │ navigator
     0x2001a920 │ 0x2001a8c0 │ vehicle_status0              │ navigator
     0x2000f520 │ 0x2003a190 │ vehicle_roi0                 │ gimbal
     0x20041010 │ 0x20040fa0 │ position_setpoint_triplet0   │ gimbal
     0x200410f0 │ 0x20041080 │ gimbal_manager_set_attitude0 │ gimbal
     0x2007c0a0 │ 0x200203a0 │ ttyS0                        │ gps
     0x200014b0 │ 0x20001490 │ led0                         │ commander
     0x2001a4c0 │ 0x2001a450 │ vehicle_command_ack0         │ commander
                ╵            ╵                              ╵

px4_dmesg

px4_dmesg

Prints the dmesg buffer of PX4.

(gdb) px4_dmesg
$1 = ConsoleBuffer(2394B/4095B: [0 -> 2394]) =
HW arch: PX4_FMU_V6X
HW type: V6X010010
HW version: 0x010
HW revision: 0x010
PX4 git-hash: 1cac91d5a9d19dc081bc54d0ea2b7d26ed64c8d8
PX4 version: 1.14.0 40 (17694784)
PX4 git-branch: develop
Vendor version: 3.0.0 64 (50331712)
OS: NuttX
OS version: Release 11.0.0 (184549631)
OS git-hash: b25bc43cd81e257c5e63ac17c7c4331510584af6
Build datetime: Feb 23 2024 17:18:44
Build uri: localhost
Build variant: default
Toolchain: GNU GCC, 9.3.1 20200408 (release)
PX4GUID: 000600000000373833333430510d002c0045

px4_perf

px4_perf [NAME] [-s {pointer,name,events,elapsed,average,least,most,rms,interval,first,last}]

options:
  NAME                  Regex filter for perf counter names.
  -s, --sort {pointer,name,events,elapsed,average,least,most,rms,interval,first,last},
                        Column name to sort the table by.

Pretty prints a table of all performance counters and their values. You can regex filter for the names of counters and sort the table by column.

(gdb) px4_perf -s events
                  ╷                                                  ╷        ╷         ╷         ╷         ╷         ╷           ╷          ╷         ╷
  perf_ctr_count* │ Name                                             │ Events │ Elapsed │ Average │   Least │    Most │       RMS │ Interval │   First │  Last
 ═════════════════╪══════════════════════════════════════════════════╪════════╪═════════╪═════════╪═════════╪═════════╪═══════════╪══════════╪═════════╪═══════
       0x30013e60 │ mission_dm_cache_miss                            │      0 │         │         │         │         │           │          │         │
       0x3800af50 │ rc_update: cycle                                 │      0 │       - │       - │       - │       - │         - │          │         │
       0x3800af90 │ rc_update: cycle interval                        │      0 │         │       - │       - │       - │         - │        - │       - │     -
       0x3800c610 │ icm42688p: FIFO reset                            │      4 │         │         │         │         │           │          │         │
       0x38004600 │ param: get                                       │  20689 │         │         │         │         │           │          │         │
       0x24018870 │ param: find                                      │  27540 │         │         │         │         │           │          │         │
       0x3000de70 │ mavlink: tx run elapsed                          │  29431 │   2.9 s │  99.9µs │  54  µs │   2.7ms │  84.289µs │          │         │
       0x30002c90 │ mavlink: tx run elapsed                          │  29453 │   3.5 s │ 118.4µs │  64  µs │   1.6ms │  90.483µs │          │         │
       0x30014410 │ uavcan: cycle interval                           │  29462 │         │   3  ms │ 194  µs │  65.8ms │ 513.188µs │    1.5 m │   1.2 s │ 1.5 m
       0x38008910 │ board_adc: sample                                │  70952 │ 192.9ms │   2.7µs │   2  µs │ 850  µs │  13.039µs │          │         │
                  ╵                                                  ╵        ╵         ╵         ╵         ╵         ╵           ╵          ╵         ╵

px4_switch_task

px4_switch_task PID

Saves the current execution environment and loads the target task environment. You can use this to inspect the local stack and backtrace of other tasks even when they are not running. The original execution environment is loaded automatically on continue or quit.

Warning
The original execution environment cannot be reset when GDB crashes! In that case, there will be memory corruption on the device.

(gdb) px4_switch_task 799
Switched to task 'mavlink_rcv_if0' (799).
(gdb) backtrace
#0  arm_switchcontext () at armv7-m/gnu/arm_switchcontext.S:79
#1  0x0800b3ce in nxsem_wait (sem=sem@entry=0x2003c3f0) at semaphore/sem_wait.c:155
#2  0x08175234 in nxsem_tickwait (sem=sem@entry=0x2003c3f0, start=1842, delay=delay@entry=10) at semaphore/sem_tickwait.c:122
#3  0x08170ca6 in nx_poll (fds=fds@entry=0x2003c464, nfds=nfds@entry=1, timeout=timeout@entry=10) at vfs/fs_poll.c:415
#4  0x08170dca in poll (fds=fds@entry=0x2003c464, nfds=nfds@entry=1, timeout=timeout@entry=10) at vfs/fs_poll.c:493
#5  0x080f944e in MavlinkReceiver::run (this=0x20038ff8, this@entry=<error reading variable: value has been optimized out>) at src/modules/mavlink/modules__mavlink_unity.cpp:12323
#6  0x080f9c96 in MavlinkReceiver::start_trampoline (context=<error reading variable: value has been optimized out>) at src/modules/mavlink/modules__mavlink_unity.cpp:12745
#7  0x08014904 in pthread_startup (entry=<optimized out>, arg=<optimized out>) at pthread/pthread_create.c:59
#8  0x00000000 in ?? ()

px4_registers

px4_registers

Pretty prints a table with all register values.

(gdb) px4_registers
            ╷                  ╷                      ╷
  Name      │      Hexadecimal │              Decimal │                                                           Binary
 ═══════════╪══════════════════╪══════════════════════╪══════════════════════════════════════════════════════════════════
  r0        │                0 │                    0 │                                                                0
  r1        │         200267c4 │            537028548 │                                   100000000000100110011111000100
  r2        │         2007ccfc │            537382140 │                                   100000000001111100110011111100
  r3        │         200267c4 │            537028548 │                                   100000000000100110011111000100
  r4        │         2002673c │            537028412 │                                   100000000000100110011100111100
  r5        │         200267f4 │            537028596 │                                   100000000000100110011111110100
  r6        │         200267f0 │            537028592 │                                   100000000000100110011111110000
  r7        │         20026734 │            537028404 │                                   100000000000100110011100110100
  r8        │                0 │                    0 │                                                                0
  r9        │                0 │                    0 │                                                                0
  r10       │                0 │                    0 │                                                                0
  r11       │                0 │                    0 │                                                                0
  r12       │                0 │                    0 │                                                                0
  sp        │         20036b44 │            537094980 │                                   100000000000110110101101000100
  lr        │          800b14b │            134263115 │                                     1000000000001011000101001011
  pc        │          800b14a │            134263114 │                                     1000000000001011000101001010
  xpsr      │         41000000 │           1090519040 │                                  1000001000000000000000000000000
  d0        │                0 │                    0 │                                                                0
  ...       │              ... │                  ... │                                                              ...
  d15       │ ffffffff00000000 │ 18446744069414584320 │ 1111111111111111111111111111111100000000000000000000000000000000
  fpscr     │                0 │                    0 │                                                                0
  msp       │         20036b44 │            537094980 │                                   100000000000110110101101000100
  psp       │                0 │                    0 │                                                                0
  primask   │                0 │                    0 │                                                                0
  basepri   │               f0 │                  240 │                                                         11110000
  faultmask │                0 │                    0 │                                                                0
  control   │                4 │                    4 │                                                              100
  s0        │                0 │                    0 │                                                                0
  ...       │              ... │                  ... │                                                              ...
  s31       │         ffffffff │           4294967295 │                                 11111111111111111111111111111111
            ╵                  ╵                      ╵

px4_interrupts

px4_interrupts

Pretty prints a table of all non-empty NuttX interrupts showing their state (E=enabled, P=pending, A=active), priority, function pointer, name, and argument.

(gdb) px4_interrupts
      ╷     ╷      ╷           ╷                             ╷
  IRQ │ EPA │ Prio │ Address   │ Function                    │ Argument
 ═════╪═════╪══════╪═══════════╪═════════════════════════════╪══════════════════════════════
  -13 │ e   │ -1   │ 0x8009914 │ arm_hardfault               │ 0x0
   -5 │     │ 0    │ 0x8009a08 │ arm_svcall                  │ 0x0
   -1 │     │ 80   │ 0x8013c40 │ stm32_timerisr              │ 0x0
    8 │ ep  │ 80   │ 0x8175054 │ stm32_exti2_isr             │ 0x0
   11 │ ep  │ 80   │ 0x800949c │ stm32_dmainterrupt          │ 0x20020740 <g_dma>
   12 │ e   │ 80   │ 0x800949c │ stm32_dmainterrupt          │ 0x20020758 <g_dma+24>
   13 │ e   │ 80   │ 0x800949c │ stm32_dmainterrupt          │ 0x20020770 <g_dma+48>
   14 │ e   │ 80   │ 0x800949c │ stm32_dmainterrupt          │ 0x20020788 <g_dma+72>
   15 │ e   │ 80   │ 0x800949c │ stm32_dmainterrupt          │ 0x200207a0 <g_dma+96>
   16 │ e   │ 80   │ 0x800949c │ stm32_dmainterrupt          │ 0x200207b8 <g_dma+120>
   17 │ e   │ 80   │ 0x800949c │ stm32_dmainterrupt          │ 0x200207d0 <g_dma+144>
   19 │ e   │ 80   │ 0x8132bb8 │ can1_irq(int, void*, void*) │ 0x0
   20 │ e   │ 80   │ 0x8132bb8 │ can1_irq(int, void*, void*) │ 0x0
   21 │ e   │ 80   │ 0x8132bb8 │ can1_irq(int, void*, void*) │ 0x0
   23 │ e a │ 80   │ 0x8175104 │ stm32_exti95_isr            │ 0x0
      ╵     ╵      ╵           ╵                             ╵

px4_gpios

px4_gpios [-f FILTER] [-ff FUNCTION_FILTER] [-pf PIN_FILTER]
          [-s {pin,config,i,o,af,name,function}] [-c COLUMNS]

options:
  -f, --filter FILTER
                        Regex filter for FMU names.
  -ff, --function-filter FUNCTION_FILTER
                        Regex filter for FMU functions.
  -pf, --pin-filter PIN_FILTER
                        Regex filter for GPIO pin names.
  -s, --sort {pin,config,i,o,af,name,function}
                        Column name to sort the table by.
  -c, --columns COLUMNS
                        Number of columns to print.

Reads the GPIO peripheral space and prints a table of the individual pin configuration, input/output state and alternate function. If a pinout is provided, the pins will be matched with their names and functions.

  • Config: Condensed view with omitted defaults.
    MODER: IN=Input, OUT=Output, ALT=Alternate Function, AN=Analog,
    OTYPER: +OD=OpenDrain, (PushPull omitted),
    PUPDR: +PU=PullUp, +PD=PullDown, (Floating omitted),
    SPEEDR: +M=Medium, +H=High, +VH=Very High, (Low omitted).
    LOCKR: +L=Locked, (Unlocked omitted).

  • Input (IDR), Output (ODR): _=Low, ^=High
    Input only shown for IN, OUT, and ALT.
    Output only shown for OUT.

  • Alternate Function (AFR): only shown when config is ALT.
    Consult the datasheet for device-specific mapping.

(gdb) px4_gpios -c 1
      ╷           ╷   ╷   ╷    ╷                 ╷
  Pin │ Config    │ I │ O │ AF │ Name            │ Function
 ═════╪═══════════╪═══╪═══╪════╪═════════════════╪═══════════════════════════════════════
  A0  │ AN        │   │   │    │ ADC1_IN0        │ SCALED_VDD_3V3_SENSORS1
  A1  │ ALT+VH    │ ^ │   │ 11 │ ETH_REF_CLK     │ ETH_REF_CLK
  A2  │ ALT+VH    │ ^ │   │ 11 │ ETH_MDIO        │ ETH_MDIO
  A3  │ IN        │ ^ │   │    │ USART2_RX       │ USART2_RX_TELEM3
  A4  │ AN        │   │   │    │ ADC1_IN4        │ SCALED_VDD_3V3_SENSORS2
  A5  │ ALT+H     │ ^ │   │  5 │ SPI1_SCK        │ SPI1_SCK_SENSOR1_ICM20602
  A6  │ IN        │ _ │   │    │ SPI6_MISO       │ SPI6_MISO_EXTERNAL1
  A7  │ ALT+VH    │ _ │   │ 11 │ ETH_CRS_DV      │ ETH_CRS_DV
  A8  │ ALT+H     │ _ │   │  1 │ TIM1_CH1        │ FMU_CH4
  A9  │ IN+PD     │ _ │   │    │ USB_OTG_FS_VBUS │ VBUS_SENSE
  A10 │ ALT+H     │ ^ │   │  1 │ TIM1_CH3        │ FMU_CH2
  A11 │ ALT+VH    │ _ │   │ 10 │ USB_OTG_FS_DM   │ USB_D_N
  A12 │ ALT+VH    │ _ │   │ 10 │ USB_OTG_FS_DP   │ USB_D_P
  A13 │ ALT+PU+VH │ _ │   │  0 │ SWDIO           │ FMU_SWDIO
  A14 │ ALT+PD    │ _ │   │  0 │ SWCLK           │ FMU_SWCLK
  A15 │ OUT       │ ^ │ ^ │    │                 │ SPI6_nCS2_EXTERNAL1
  B0  │ AN        │   │   │    │ ADC1_IN8        │ SCALED_VDD_3V3_SENSORS3
  B1  │ AN        │   │   │    │ ADC1_IN9        │ SCALED_V5
  B2  │ ALT+H     │ _ │   │  7 │ SPI3_MOSI       │ SPI3_MOSI_SENSOR3_BMI088

You can also regex filter and sort pin names:

(gdb) px4_gpios -ff US?ART -s name
      ╷           ╷   ╷   ╷    ╷            ╷
  Pin │ Config    │ I │ O │ AF │ Name       │ Function
 ═════╪═══════════╪═══╪═══╪════╪════════════╪═════════════════════════════
  H14 │ IN        │ ^ │   │    │ UART4_RX   │ UART4_RX
  H13 │ IN        │ _ │   │    │ UART4_TX   │ UART4_TX
  C9  │ ALT       │ _ │   │  7 │ UART5_CTS  │ UART5_CTS_TELEM2
  C8  │ OUT       │ _ │ _ │    │ UART5_RTS  │ UART5_RTS_TELEM2
  D2  │ ALT+PU+VH │ ^ │   │  8 │ UART5_RX   │ UART5_RX_TELEM2
  B9  │ ALT+PU+VH │ ^ │   │  7 │ UART5_TX   │ UART5_TX_TELEM2
  E10 │ ALT       │ ^ │   │  8 │ UART7_CTS  │ UART7_CTS_TELEM1
  E9  │ OUT       │ _ │ _ │    │ UART7_RTS  │ UART7_RTS_TELEM1
  F6  │ ALT+PU+VH │ ^ │   │  8 │ UART7_RX   │ UART7_RX_TELEM1
  E8  │ ALT+PU+VH │ ^ │   │  8 │ UART7_TX   │ UART7_TX_TELEM1
  E0  │ IN        │ ^ │   │    │ UART8_RX   │ UART8_RX_GPS2
  E1  │ IN        │ ^ │   │    │ UART8_TX   │ UART8_TX_GPS2
  B15 │ ALT+PU+VH │ ^ │   │  4 │ USART1_RX  │ USART1_RX_GPS1
  B14 │ ALT+PU+VH │ ^ │   │  4 │ USART1_TX  │ USART1_TX_GPS1
  D3  │ IN        │ ^ │   │    │ USART2_CTS │ USART2_CTS_TELEM3
  D4  │ IN        │ ^ │   │    │ USART2_RTS │ USART2_RTS_TELEM3
  A3  │ IN        │ ^ │   │    │ USART2_RX  │ USART2_RX_TELEM3
  D5  │ IN        │ ^ │   │    │ USART2_TX  │ USART2_TX_TELEM3
  D9  │ ALT+PU+VH │ ^ │   │  7 │ USART3_RX  │ USART3_RX_DEBUG
  D8  │ ALT+PU+VH │ ^ │   │  7 │ USART3_TX  │ USART3_TX_DEBUG
  C7  │ ALT+PU+VH │ ^ │   │  8 │ USART6_RX  │ USART6_RX_FROM_IO__RC_INPUT
  C6  │ ALT+PU+VH │ ^ │   │  8 │ USART6_TX  │ USART6_TX_TO_IO__NC
      ╵           ╵   ╵   ╵    ╵            ╵

px4_backtrace

Prints a backtrace using Python to show absolute file path names without resolving function arguments, which can otherwise cause GDB to segfault.

(gdb) px4_backtrace
#0  0x081671ea in perf_set_elapsed(perf_counter_t, int64_t) at /PX4-Autopilot/src/lib/perf/perf_counter.cpp:286
#1  0x0812e62e in MixingOutput::updateLatencyPerfCounter(actuator_outputs_s const&) at /PX4-Autopilot/src/lib/mixer_module/mixer_module.cpp:1098
#2  0x0812eb92 in MixingOutput::updateStaticMixer() at /PX4-Autopilot/src/lib/mixer_module/mixer_module.cpp:748
#3  0x0812ebcc in MixingOutput::updateStaticMixer() at /PX4-Autopilot/src/lib/mixer_module/mixer_module.cpp:648
#4  0x08059dea in PX4IO::Run() at /PX4-Autopilot/src/drivers/px4io/px4io.cpp:541
#5  0x08059dea in PX4IO::Run() at /PX4-Autopilot/src/drivers/px4io/px4io.cpp:519
#6  0x081717fc in px4::WorkQueue::Run() at /PX4-Autopilot/platforms/common/px4_work_queue/WorkQueue.cpp:187
#7  0x08171938 in px4::WorkQueueRunner(void*) at /PX4-Autopilot/platforms/common/px4_work_queue/WorkQueueManager.cpp:236
#8  0x08014904 in pthread_startup() at /PX4-Autopilot/platforms/nuttx/NuttX/nuttx/libs/libc/pthread/pthread_create.c:59

px4_rbreak

px4_rbreak (file):function:+offset | (file):function:regex

Finds the absolute location of a relative line number offset or regex pattern inside a function inside an optional file name, then sets a breakpoint on that location. This allows you to reliably set breakpoints inside files whose line numbering and content is changing during development. Remember to escape all special regex characters for the match to work correctly!

(gdb) px4_rbreak :nxsem_boostholderprio:nxsched_set_priority\(htcb, *rtcb->sched_priority\);
Breakpoint 1 at 0x800b5c0: file semaphore/sem_holder.c, line 380.
(gdb) px4_rbreak sem_holder.c:nxsem_restoreholderprio:+20
Breakpoint 2 at 0x800b620: file semaphore/sem_holder.c, line 550.

px4_coredump

px4_coredump [--memory start:size] [--file coredump_{datetime}.txt] [--flash]

Dumps the memories into a coredump file suffixed with the current date and time. The coredump file can be passed to the CrashDebug debug backend. By default, the SRAM memories and all peripherals listed in the SVD file of the target are copied. Optionally, the non-volatile FLASH memory can also be dumped for later analysis.

(gdb) px4_coredump
Starting coredump...
Coredump completed in 4.5s

px4_pshow

px4_pshow PERIPHERAL [REGISTER]

Visualize the bit fields of one or all registers of a peripheral using the arm-gdb plugin. This requires the CMSIS-SVD file of the device, which is defaulted for FMUv5x/v6x. For other devices, GDB must be launched with the correct --svd command line option.

Note that this command loads the SVD using arm loadfile st SVDFILE and then acts as an alias for arm inspect /hab st PERIPHERAL [REGISTER].

(gdb) px4_pshow DMA1 S7CR
DMA1.S7CR                        = 00001000000000010000010001010100                   // stream x configuration register
    EN                             ...............................0 - 0               // Stream enable / flag stream ready when read low
    DMEIE                          ..............................0. - 0               // Direct mode error interrupt enable
    TEIE                           .............................1.. - 1               // Transfer error interrupt enable
    HTIE                           ............................0... - 0               // Half transfer interrupt enable
    TCIE                           ...........................1.... - 1               // Transfer complete interrupt enable
    PFCTRL                         ..........................0..... - 0               // Peripheral flow controller
    DIR                            ........................01...... - 1               // Data transfer direction
    CIRC                           .......................0........ - 0               // Circular mode
    PINC                           ......................0......... - 0               // Peripheral increment mode
    MINC                           .....................1.......... - 1               // Memory increment mode
    PSIZE                          ...................00........... - 0               // Peripheral data size
    MSIZE                          .................00............. - 0               // Memory data size
    PINCOS                         ................0............... - 0               // Peripheral increment offset size
    PL                             ..............01................ - 1               // Priority level
    DBM                            .............0.................. - 0               // Double buffer mode
    CT                             ............0................... - 0               // Current target (only in double buffer mode)
    ACK                            ...........0.................... - 0               // ACK
    PBURST                         .........00..................... - 0               // Peripheral burst transfer configuration
    MBURST                         .......00....................... - 0               // Memory burst transfer configuration
    CHSEL                          ...0100......................... - 4               // Channel selection

px4_pwatch

px4_pwatch [options] [PERIPHERAL | PERIPHERAL.REGISTER]+

options:
  --add, -a           Add these peripherals.
  --remove, -r        Remove these peripherals.
  --reset, -R         Reset watcher to peripheral reset values.
  --quiet, -q         Stop automatically reporting.
  --loud, -l          Automatically report on GDB stop event.
  --all, -x           Show all logged changes.
  --watch-write, -ww  Add a write watchpoint on registers.
  --watch-read, -wr   Add a read watchpoint on registers.

Visualize the differences in peripheral registers on every GDB stop event. You can combine this with a GDB watchpoint to watch for changes in a peripheral register file.

Add all peripheral registers to the watchlist: px4_pwatch -a PER.

Add a single peripheral register to the watchlist: px4_pwatch -a PER.REG.

Remove all peripheral registers from the watchlist: px4_pwatch -r PER.

Remove a single peripheral register: px4_pwatch -r PER.REG or px4_pwatch -r for all.

Disable automatic reporting: px4_pwatch -q.

Enable automatic reporting: px4_pwatch -l.

Fetch last difference report: px4_pwatch PER or px4_pwatch for all.

Reset peripheral values: px4_pwatch -R PER or px4_pwatch -R for all.

Hint: You can specify multiple peripheral and register names per command.

(gdb) px4_pwatch --loud --add DMA1 DMA2.S0CR UART4.CR1 I2C2
(gdb) continue
Continuing.
^C
Program received signal SIGINT, Interrupt.
nx_start () at init/nx_start.c:805
805       for (; ; )
Differences for DMA1:
- DMA1.LISR                        = 00000000000000000000110000000000                   // low interrupt status register
-     HTIF1                          .....................1.......... - 1               // Stream x half transfer interrupt flag (x=3..0)
-     TCIF1                          ....................1........... - 1               // Stream x transfer complete interrupt flag (x = 3..0)
+ DMA1.LISR                        = 00000000000000000000000000000000                   // low interrupt status register
+     HTIF1                          .....................0.......... - 0               // Stream x half transfer interrupt flag (x=3..0)
+     TCIF1                          ....................0........... - 0               // Stream x transfer complete interrupt flag (x = 3..0)
- DMA1.HISR                        = 00000000000000000000000000110001                   // high interrupt status register
-     FEIF4                          ...............................1 - 1               // Stream x FIFO error interrupt flag (x=7..4)
-     HTIF4                          ...........................1.... - 1               // Stream x half transfer interrupt flag (x=7..4)
-     TCIF4                          ..........................1..... - 1               // Stream x transfer complete interrupt flag (x=7..4)
+ DMA1.HISR                        = 00000000000000000000000000000000                   // high interrupt status register
+     FEIF4                          ...............................0 - 0               // Stream x FIFO error interrupt flag (x=7..4)
+     HTIF4                          ...........................0.... - 0               // Stream x half transfer interrupt flag (x=7..4)
+     TCIF4                          ..........................0..... - 0               // Stream x transfer complete interrupt flag (x=7..4)

Attach a write watchpoint to a register range: px4_pwatch -a -ww PER.REG.
Other watchpoints: write=-ww, read=-wr, and write+read=-ww -wr.

(gdb) px4_pwatch --add --loud --watch-write DMA1
watch *(uint8_t[256]*)0x40026000
Hardware watchpoint 1: *(uint8_t[256]*)0x40026000
(gdb) continue
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
stm32_dmasetup (handle=0x20020770 <g_dma+48>, paddr=1073757196, maddr=<optimized out>, ntransfers=ntransfers@entry=18, scr=1024) at chip/stm32_dma.c:696
696       dmast_putreg(dmast, STM32_DMA_SNDTR_OFFSET, ntransfers);
Differences for DMA1:
- DMA1.S2PAR                       = 00000000000000000000000000000000                   // stream x peripheral address register
-     PA                             00000000000000000000000000000000 - 00000000        // Peripheral address
+ DMA1.S2PAR                       = 01000000000000000011110000001100                   // stream x peripheral address register
+     PA                             01000000000000000011110000001100 - 40003c0c        // Peripheral address
- DMA1.S2M0AR                      = 00000000000000000000000000000000                   // stream x memory 0 address register
-     M0A                            00000000000000000000000000000000 - 00000000        // Memory 0 address
+ DMA1.S2M0AR                      = 00100000000000110000000111100000                   // stream x memory 0 address register
+     M0A                            00100000000000110000000111100000 - 200301e0        // Memory 0 address

Debugging HardFaults

When attaching GDB to your target, it enables exception vector catching so that any fault triggers a breakpoint before the NuttX hardfault handler takes over. This allows you to perform a backtrace and see the problematic location immediately instead of using the hardfault log (see emdbg.debug.crashdebug):

Program received signal SIGTRAP, Trace/breakpoint trap.
exception_common () at armv7-m/arm_exception.S:144
144             mrs             r0, ipsr                                /* R0=exception number */
(gdb) bt
#0  exception_common () at armv7-m/arm_exception.S:144
#1  <signal handler called>
#2  inode_insert (parent=0x0, peer=0x0, node=0x2007c010) at inode/fs_inodereserve.c:117
#3  inode_reserve (path=path@entry=0x81895f4 "/dev/console", mode=mode@entry=438, inode=inode@entry=0x20036bac) at inode/fs_inodereserve.c:222
#4  0x0800ac90 in register_driver (path=path@entry=0x81895f4 "/dev/console", fops=fops@entry=0x8189968 <g_serialops>, mode=mode@entry=438, priv=priv@entry=0x20020570 <g_usart3priv>) at driver/fs_registerdriver.c:78
#5  0x0800a6f2 in uart_register (path=path@entry=0x81895f4 "/dev/console", dev=dev@entry=0x20020570 <g_usart3priv>) at serial/serial.c:1743
#6  0x080093a6 in arm_serialinit () at chip/stm32_serial.c:3660
#7  0x08014554 in up_initialize () at common/arm_initialize.c:122
#8  0x0800b122 in nx_start () at init/nx_start.c:656
#9  0x080082be in __start () at chip/stm32_start.c:273
#10 0x08000306 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

To understand what exactly triggered the hardfault, you can use the arm scb /h command to display the fault registers:

(gdb) arm scb /h
CPUID                            = 411fc270                   // CPUID Base Register
    Variant                        ..1..... - Revision: r1pX
    Architecture                   ...f.... - ARMv7-M
    PartNo                         ....c27. - Cortex-M7
    Revision                       .......0 - Patch: rXp0
CFSR                             = 00008200                   // Configurable Fault Status Register
    MMFSR                          ......00 - 00              // MemManage Fault Status Register
    BFSR                           ....82.. - 82              // BusFault Status Register
    BFARVALID                      ....8... - 1               // Indicates if BFAR has valid contents.
    PRECISERR                      .....2.. - 1               // Indicates if a precise data access error has occurred, and the processor has written the faulting address to the BFAR.
    UFSR                           0000.... - 0000            // UsageFault Status Register
HFSR                             = 40000000                   // HardFault Status Register
    FORCED                         4....... - 1               // Indicates that a fault with configurable priority has been escalated to a HardFault exception.
DFSR                             = 00000008                   // Debug Fault Status Register
    VCATCH                         .......8 - Vector catch triggered // Indicates triggering of a Vector catch
MMFAR                            = 00000008                   // MemManage Fault Address Register
BFAR                             = 00000008                   // BusFault Address Register
AFSR                             = 00000000                   // Auxiliary Fault Status Register

In this example, a precise bus fault occurred when accessing address 8. Since the bus fault is precise, we can walk up the stack frames to the exact offending instruction:

(gdb) frame 2
#2  inode_insert (parent=0x0, peer=0x0, node=0x2007c010) at inode/fs_inodereserve.c:117
117           node->i_peer    = parent->i_child;

In this example, the parent pointer is zero, which attempts to perform a read at offset 8, which corresponds to offsetof(parent, i_child):

(gdb) p &((struct inode *)0)->i_child
$1 = (struct inode **) 0x8

Python API

  1# Copyright (c) 2020-2022, Niklas Hauser
  2# Copyright (c) 2023, Auterion AG
  3# SPDX-License-Identifier: BSD-3-Clause
  4
  5"""
  6.. include:: gdb.md
  7
  8## Python API
  9"""
 10
 11from __future__ import annotations
 12import os
 13import sys
 14import subprocess
 15import signal
 16import tempfile
 17import shlex
 18import time
 19from pathlib import Path
 20from contextlib import contextmanager
 21
 22import logging
 23LOGGER = logging.getLogger("debug:gdb")
 24
 25from .utils import listify
 26from .backend import ProbeBackend
 27from . import remote
 28
 29# -----------------------------------------------------------------------------
 30def command_string(backend: ProbeBackend, source: Path = None,
 31                   config: list[Path] = None, commands: list[str] = None,
 32                   ui: str = None, svd: Path = None, socket: Path = None,
 33                   with_python: bool = True, coredump: Path = None) -> str:
 34    """
 35    Constructs a command string to launch GDB with the correct options.
 36    By default, this disables pagination and command confirmation.
 37
 38    :param backend: a debug backend implementation.
 39    :param source: Path to a ELF file.
 40    :param config: List of GDB configuration files.
 41    :param commands: List of GDB commands to execute during launch.
 42    :param ui: The frontend configuration for GDB
 43               - `batch` or None: No UI, launches with `-nx -nh -batch`.
 44               - `cmd`: Default GDB UI with only a command prompt available.
 45               - `tui`: Launches in text user interface with layout split.
 46               - `gdbgui`: Launches in background using GDBGUI as frontend.
 47    :param svd: Path to the CMSIS-SVD file for the connected device.
 48    :param socket: Path to socket file, which is used for RPyC communication.
 49    :param with_python: Uses `arm-none-eabi-gdb-py3` and loads the Python
 50                        debug modules in `emdbg.debug.px4` as `px4`.
 51    """
 52    debug_dir = Path(__file__).parent.resolve()
 53    cmds = [
 54        "set pagination off", "set print pretty", "set history save",
 55        "set mem inaccessible-by-default off", "set confirm off",
 56        "set filename-display absolute", "set disassemble-next-line on",
 57        "maintenance set internal-error backtrace on",
 58        "maintenance set internal-warning backtrace on",
 59        "set substitute-path /__w/PX4_firmware_private/PX4_firmware_private/ .",
 60        f"source {debug_dir}/data/orbuculum.gdb",
 61        f"source {debug_dir}/data/cortex_m.gdb"]
 62    if (backend_gdb := debug_dir / f"data/{backend.name}.gdb").exists():
 63        cmds += [f"source {backend_gdb}"]
 64    cmds += listify(backend.init(source))
 65    args = [f"-c {coredump}"] if coredump else []
 66    args += [f'-ex "{a}"' for a in cmds] + ["-q"]
 67    args += list(map('-x "{}"'.format, listify(config)))
 68
 69    gdb = "arm-none-eabi-gdb"
 70    if with_python or socket:
 71        gdb += "-py3"
 72        # Import packages from both the host the from emdbg
 73        args += [f'-ex "python import sys; sys.path.append(\'{debug_dir}\');"']
 74        args += [f'-ex "python import sys; sys.path.append(\'{path}\');"'
 75                 for path in sys.path if "-packages" in path]
 76        # We need to do this terrible hackery since pkg_resources fails on the first import
 77        args += ['-ex "python exec(\'try: import cmsis_svd;\\\\nexcept: pass\\\\nimport cmsis_svd\')"',
 78                 '-ex "python exec(\'try: import arm_gdb;\\\\nexcept: pass\\\\nimport arm_gdb\')"']
 79        # If available, set the default SVD file here
 80        if svd:
 81            args += [f'-ex "python import px4; px4._SVD_FILE=\'{svd}\'"']
 82        # Finally we can import the PX4 GDB user commands
 83        args += [f'-ex "source {debug_dir}/remote/px4.py"']
 84
 85    if socket:
 86        # Import the API bridge that uses rpyc
 87        args += [f'-ex "python socket_path = \'{socket}\'"',
 88                 f'-ex "source {debug_dir}/remote/gdb_api_bridge.py"']
 89        cmd = "{gdb} -nx -nh {args} {source}"
 90
 91    elif ui is None or "batch" in ui:
 92        cmd = "{gdb} -nx -nh -batch {args} {source}"
 93
 94    elif "cmd" in ui:
 95        cmd = "{gdb} {args} {source}"
 96
 97    elif "tui" in ui:
 98        cmd = '{gdb} -tui -ex "layout split" -ex "focus cmd" {args} -ex "refresh" {source}'
 99
100    elif "gdbgui" in ui:
101        cmd = "gdbgui {source} --gdb-cmd='{gdb} {args} {source}'"
102
103    else:
104        raise ValueError("Unknown UI mode! '{}'".format(ui))
105
106    args += list(map('-ex "{}"'.format, listify(commands)))
107    return cmd.format(gdb=gdb, args=" ".join(args), source=source or "")
108
109
110# -----------------------------------------------------------------------------
111@contextmanager
112def call_rpyc(backend: ProbeBackend, source: Path, config: list[Path] = None,
113              commands: list[str] = None, svd: Path = None) -> remote.rpyc.Gdb:
114    """
115    Launches GDB in the background and connects to it transparently via a RPyC
116    bridge. You can therefore use the [GDB Python API][gdbpy] as well as the
117    modules in `emdbg.debug.px4` directly from within this process instead of
118    using the GDB command line.
119
120    This function returns a `remote.rpyc.Gdb` object, which can be used as a
121    substitute for `import gdb`, which is only available *inside* the GDB
122    Python environment.
123    Note, however, that the access is quite slow, since everything has to be
124    communicated through asynchronous IPC.
125
126    [gdbpy]: https://sourceware.org/gdb/onlinedocs/gdb/Python-API.html
127
128    :param backend: a debug backend implementation.
129    :param source: Path to a ELF file.
130    :param config: List of GDB configuration files.
131    :param commands: List of GDB commands to execute during launch.
132    :param svd: Path to the CMSIS-SVD file for the connected device.
133
134    :return: `remote.rpyc.Gdb` object which can be used instead of `import gdb`.
135    """
136    from rpyc import BgServingThread
137    from rpyc.utils.factory import unix_connect
138
139    with tempfile.TemporaryDirectory() as socket_dir, backend.scope():
140        socket = Path(socket_dir) / "socket"
141        gdb_command = command_string(backend, source, config, commands, svd=svd, socket=socket)
142        rgdb = None
143        try:
144            LOGGER.debug(gdb_command)
145            rgdb_process = subprocess.Popen(gdb_command, cwd=os.getcwd(),
146                                            stdin=subprocess.PIPE, stdout=subprocess.PIPE,
147                                            start_new_session=True, shell=True)
148            LOGGER.info(f"Starting {rgdb_process.pid}...")
149            # TODO: Add a timeout so it doesn't get stuck forever
150            while(True):
151                try:
152                    conn = unix_connect(str(socket))
153                    break
154                except (ConnectionRefusedError, FileNotFoundError):
155                    time.sleep(0.1)
156
157            BgServingThread(conn, callback=lambda: None)
158            rgdb = remote.rpyc.Gdb(conn, backend, rgdb_process)
159            yield rgdb
160
161        except KeyboardInterrupt:
162            pass
163
164        finally:
165            LOGGER.info(f"Stopping {rgdb_process.pid}.")
166            if rgdb is not None:
167                rgdb.quit()
168            else:
169                os.killpg(os.getpgid(rgdb_process.pid), signal.SIGQUIT)
170            os.waitpid(os.getpgid(rgdb_process.pid), 0)
171
172# -----------------------------------------------------------------------------
173@contextmanager
174def call_mi(backend: ProbeBackend, source: Path = None, config: list[Path] = None,
175            commands: list[str] = None, svd: Path = None,
176            with_python: bool = True) -> remote.mi.Gdb:
177    """
178    Launches GDB in the background using [pygdbmi][] and connects to its command
179    prompt via the [GDB/MI Protocol][gdbmi].
180    This method does not allow accessing the Python API directly, instead you
181    must issue command strings inside the GDB command prompt.
182    However, this method is significantly faster and most stable than `call_rpyc()`.
183
184    :param backend: a debug backend implementation.
185    :param source: Path to a ELF file.
186    :param config: List of GDB configuration files.
187    :param commands: List of GDB commands to execute during launch.
188    :param svd: Path to the CMSIS-SVD file for the connected device.
189    :param with_python: Uses `arm-none-eabi-gdb-py3` and loads the Python
190                        debug modules in `emdbg.debug.px4` as `px4`.
191
192    :return: `remote.mi.Gdb` object which can be used to issue commands and read
193             the responses.
194
195    [gdbmi]: https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html
196    [pygdbmi]: https://cs01.github.io/pygdbmi/
197    """
198    from pygdbmi.gdbcontroller import GdbController
199
200    gdb_command = command_string(backend, source, config, commands, "cmd", svd, with_python=with_python)
201    gdb_command += " --interpreter=mi3"
202
203
204    with backend.scope():
205        rgdb = None
206        try:
207            LOGGER.info("Starting...")
208            LOGGER.debug(gdb_command)
209            mi = GdbController(shlex.split(gdb_command))
210            rgdb = remote.mi.Gdb(backend, mi)
211            yield rgdb
212
213        finally:
214            if rgdb is not None:
215                rgdb.quit()
216            LOGGER.info("Stopping.")
217
218
219# -----------------------------------------------------------------------------
220def _empty_signal_handler(sig, frame):
221    pass
222
223def call(backend: ProbeBackend, source: Path = None, config: list[Path] = None,
224         commands: list[str] = None, ui: str = None, svd: Path = None,
225         with_python: bool = True, coredump: Path = None) -> int:
226    """
227    Launches the backend in the background and GDB as a blocking process in
228    the foreground for user interaction.
229
230    :param backend: a debug backend implementation.
231    :param source: Path to a ELF file.
232    :param config: List of GDB configuration files.
233    :param commands: List of GDB commands to execute during launch.
234    :param ui: The frontend configuration for GDB
235               - `cmd`: Default GDB UI with only a command prompt available.
236               - `tui`: Launches in text user interface with layout split.
237               - `gdbgui`: Launches in background using GDBGUI as frontend.
238    :param svd: Path to the CMSIS-SVD file for the connected device.
239    :param with_python: Uses `arm-none-eabi-gdb-py3` and loads the Python
240                        debug modules in `emdbg.debug.px4` as `px4`.
241    """
242    gdb_command = command_string(backend, source, config, commands, ui, svd,
243                                 with_python=with_python, coredump=coredump)
244
245    signal.signal(signal.SIGINT, _empty_signal_handler)
246    with backend.scope():
247        try:
248            LOGGER.info("Starting...")
249            LOGGER.debug(gdb_command)
250            # This call is now blocking
251            return subprocess.call(gdb_command, cwd=os.getcwd(), shell=True)
252        except KeyboardInterrupt:
253            pass
254        finally:
255            LOGGER.info("Stopping.")
256
257
258# -----------------------------------------------------------------------------
259def _add_subparser(subparser):
260    parser = subparser.add_parser("remote", help="Use a generic extended remote as Backend.")
261    parser.add_argument(
262        "--port",
263        default="localhost:3333",
264        help="Connect to this host:port.")
265    parser.set_defaults(backend=lambda args: ProbeBackend(args.port))
266
267# -----------------------------------------------------------------------------
268if __name__ == "__main__":
269    import argparse, emdbg
270    from . import jlink, crashdebug, openocd
271
272    parser = argparse.ArgumentParser(description="Debug with GDB")
273    parser.add_argument(
274        "--elf",
275        dest="source",
276        type=Path,
277        help="The ELF files to use for debugging.")
278    parser.add_argument(
279        "--ui",
280        default="cmd",
281        choices=["tui", "gdbgui", "cmd", "batch"],
282        help="Use GDB via TUI or GDBGUI.")
283    parser.add_argument(
284        "-py", "--python",
285        dest="with_python",
286        action="store_true",
287        default=False,
288        help="Use GDB with Python API and load PX4 tools.")
289    parser.add_argument(
290        "-c", "--core",
291        type=Path,
292        default=None,
293        help="Use coredump file.")
294    parser.add_argument(
295        "--svd",
296        type=Path,
297        help="The CMSIS-SVD file to use for this device, requires `--python` flag.")
298    parser.add_argument(
299        "-x",
300        dest="config",
301        action="append",
302        type=Path,
303        help="Use these GDB init files.")
304    parser.add_argument(
305        "-ex",
306        dest="commands",
307        action="append",
308        help="Extra GDB commands.")
309    parser.add_argument(
310        "-v",
311        dest="verbosity",
312        action="count",
313        default=0,
314        help="Verbosity level.")
315
316    subparsers = parser.add_subparsers(title="Backend", dest="backend")
317
318    # Add generic backends
319    _add_subparser(subparsers)
320    # Add specific backends
321    crashdebug._add_subparser(subparsers)
322    jlink._add_subparser(subparsers)
323    openocd._add_subparser(subparsers)
324
325    args = parser.parse_args()
326    emdbg.logger.configure(args.verbosity)
327
328    call(args.backend(args) if args.backend else ProbeBackend(), ui=args.ui,
329         source=args.source, config=args.config, commands=args.commands,
330         svd=args.svd, with_python=args.with_python, coredump=args.core)
LOGGER = <Logger debug:gdb (WARNING)>
def command_string( backend: emdbg.debug.backend.ProbeBackend, source: pathlib.Path = None, config: list[pathlib.Path] = None, commands: list[str] = None, ui: str = None, svd: pathlib.Path = None, socket: pathlib.Path = None, with_python: bool = True, coredump: pathlib.Path = None) -> str:
 31def command_string(backend: ProbeBackend, source: Path = None,
 32                   config: list[Path] = None, commands: list[str] = None,
 33                   ui: str = None, svd: Path = None, socket: Path = None,
 34                   with_python: bool = True, coredump: Path = None) -> str:
 35    """
 36    Constructs a command string to launch GDB with the correct options.
 37    By default, this disables pagination and command confirmation.
 38
 39    :param backend: a debug backend implementation.
 40    :param source: Path to a ELF file.
 41    :param config: List of GDB configuration files.
 42    :param commands: List of GDB commands to execute during launch.
 43    :param ui: The frontend configuration for GDB
 44               - `batch` or None: No UI, launches with `-nx -nh -batch`.
 45               - `cmd`: Default GDB UI with only a command prompt available.
 46               - `tui`: Launches in text user interface with layout split.
 47               - `gdbgui`: Launches in background using GDBGUI as frontend.
 48    :param svd: Path to the CMSIS-SVD file for the connected device.
 49    :param socket: Path to socket file, which is used for RPyC communication.
 50    :param with_python: Uses `arm-none-eabi-gdb-py3` and loads the Python
 51                        debug modules in `emdbg.debug.px4` as `px4`.
 52    """
 53    debug_dir = Path(__file__).parent.resolve()
 54    cmds = [
 55        "set pagination off", "set print pretty", "set history save",
 56        "set mem inaccessible-by-default off", "set confirm off",
 57        "set filename-display absolute", "set disassemble-next-line on",
 58        "maintenance set internal-error backtrace on",
 59        "maintenance set internal-warning backtrace on",
 60        "set substitute-path /__w/PX4_firmware_private/PX4_firmware_private/ .",
 61        f"source {debug_dir}/data/orbuculum.gdb",
 62        f"source {debug_dir}/data/cortex_m.gdb"]
 63    if (backend_gdb := debug_dir / f"data/{backend.name}.gdb").exists():
 64        cmds += [f"source {backend_gdb}"]
 65    cmds += listify(backend.init(source))
 66    args = [f"-c {coredump}"] if coredump else []
 67    args += [f'-ex "{a}"' for a in cmds] + ["-q"]
 68    args += list(map('-x "{}"'.format, listify(config)))
 69
 70    gdb = "arm-none-eabi-gdb"
 71    if with_python or socket:
 72        gdb += "-py3"
 73        # Import packages from both the host the from emdbg
 74        args += [f'-ex "python import sys; sys.path.append(\'{debug_dir}\');"']
 75        args += [f'-ex "python import sys; sys.path.append(\'{path}\');"'
 76                 for path in sys.path if "-packages" in path]
 77        # We need to do this terrible hackery since pkg_resources fails on the first import
 78        args += ['-ex "python exec(\'try: import cmsis_svd;\\\\nexcept: pass\\\\nimport cmsis_svd\')"',
 79                 '-ex "python exec(\'try: import arm_gdb;\\\\nexcept: pass\\\\nimport arm_gdb\')"']
 80        # If available, set the default SVD file here
 81        if svd:
 82            args += [f'-ex "python import px4; px4._SVD_FILE=\'{svd}\'"']
 83        # Finally we can import the PX4 GDB user commands
 84        args += [f'-ex "source {debug_dir}/remote/px4.py"']
 85
 86    if socket:
 87        # Import the API bridge that uses rpyc
 88        args += [f'-ex "python socket_path = \'{socket}\'"',
 89                 f'-ex "source {debug_dir}/remote/gdb_api_bridge.py"']
 90        cmd = "{gdb} -nx -nh {args} {source}"
 91
 92    elif ui is None or "batch" in ui:
 93        cmd = "{gdb} -nx -nh -batch {args} {source}"
 94
 95    elif "cmd" in ui:
 96        cmd = "{gdb} {args} {source}"
 97
 98    elif "tui" in ui:
 99        cmd = '{gdb} -tui -ex "layout split" -ex "focus cmd" {args} -ex "refresh" {source}'
100
101    elif "gdbgui" in ui:
102        cmd = "gdbgui {source} --gdb-cmd='{gdb} {args} {source}'"
103
104    else:
105        raise ValueError("Unknown UI mode! '{}'".format(ui))
106
107    args += list(map('-ex "{}"'.format, listify(commands)))
108    return cmd.format(gdb=gdb, args=" ".join(args), source=source or "")

Constructs a command string to launch GDB with the correct options. By default, this disables pagination and command confirmation.

Parameters
  • backend: a debug backend implementation.
  • source: Path to a ELF file.
  • config: List of GDB configuration files.
  • commands: List of GDB commands to execute during launch.
  • ui: The frontend configuration for GDB
    • batch or None: No UI, launches with -nx -nh -batch.
    • cmd: Default GDB UI with only a command prompt available.
    • tui: Launches in text user interface with layout split.
    • gdbgui: Launches in background using GDBGUI as frontend.
  • svd: Path to the CMSIS-SVD file for the connected device.
  • socket: Path to socket file, which is used for RPyC communication.
  • with_python: Uses arm-none-eabi-gdb-py3 and loads the Python debug modules in emdbg.debug.px4 as px4.
@contextmanager
def call_rpyc( backend: emdbg.debug.backend.ProbeBackend, source: pathlib.Path, config: list[pathlib.Path] = None, commands: list[str] = None, svd: pathlib.Path = None) -> emdbg.debug.remote.rpyc.Gdb:
112@contextmanager
113def call_rpyc(backend: ProbeBackend, source: Path, config: list[Path] = None,
114              commands: list[str] = None, svd: Path = None) -> remote.rpyc.Gdb:
115    """
116    Launches GDB in the background and connects to it transparently via a RPyC
117    bridge. You can therefore use the [GDB Python API][gdbpy] as well as the
118    modules in `emdbg.debug.px4` directly from within this process instead of
119    using the GDB command line.
120
121    This function returns a `remote.rpyc.Gdb` object, which can be used as a
122    substitute for `import gdb`, which is only available *inside* the GDB
123    Python environment.
124    Note, however, that the access is quite slow, since everything has to be
125    communicated through asynchronous IPC.
126
127    [gdbpy]: https://sourceware.org/gdb/onlinedocs/gdb/Python-API.html
128
129    :param backend: a debug backend implementation.
130    :param source: Path to a ELF file.
131    :param config: List of GDB configuration files.
132    :param commands: List of GDB commands to execute during launch.
133    :param svd: Path to the CMSIS-SVD file for the connected device.
134
135    :return: `remote.rpyc.Gdb` object which can be used instead of `import gdb`.
136    """
137    from rpyc import BgServingThread
138    from rpyc.utils.factory import unix_connect
139
140    with tempfile.TemporaryDirectory() as socket_dir, backend.scope():
141        socket = Path(socket_dir) / "socket"
142        gdb_command = command_string(backend, source, config, commands, svd=svd, socket=socket)
143        rgdb = None
144        try:
145            LOGGER.debug(gdb_command)
146            rgdb_process = subprocess.Popen(gdb_command, cwd=os.getcwd(),
147                                            stdin=subprocess.PIPE, stdout=subprocess.PIPE,
148                                            start_new_session=True, shell=True)
149            LOGGER.info(f"Starting {rgdb_process.pid}...")
150            # TODO: Add a timeout so it doesn't get stuck forever
151            while(True):
152                try:
153                    conn = unix_connect(str(socket))
154                    break
155                except (ConnectionRefusedError, FileNotFoundError):
156                    time.sleep(0.1)
157
158            BgServingThread(conn, callback=lambda: None)
159            rgdb = remote.rpyc.Gdb(conn, backend, rgdb_process)
160            yield rgdb
161
162        except KeyboardInterrupt:
163            pass
164
165        finally:
166            LOGGER.info(f"Stopping {rgdb_process.pid}.")
167            if rgdb is not None:
168                rgdb.quit()
169            else:
170                os.killpg(os.getpgid(rgdb_process.pid), signal.SIGQUIT)
171            os.waitpid(os.getpgid(rgdb_process.pid), 0)

Launches GDB in the background and connects to it transparently via a RPyC bridge. You can therefore use the GDB Python API as well as the modules in emdbg.debug.px4 directly from within this process instead of using the GDB command line.

This function returns a remote.rpyc.Gdb object, which can be used as a substitute for import gdb, which is only available inside the GDB Python environment. Note, however, that the access is quite slow, since everything has to be communicated through asynchronous IPC.

Parameters
  • backend: a debug backend implementation.
  • source: Path to a ELF file.
  • config: List of GDB configuration files.
  • commands: List of GDB commands to execute during launch.
  • svd: Path to the CMSIS-SVD file for the connected device.
Returns

remote.rpyc.Gdb object which can be used instead of import gdb.

@contextmanager
def call_mi( backend: emdbg.debug.backend.ProbeBackend, source: pathlib.Path = None, config: list[pathlib.Path] = None, commands: list[str] = None, svd: pathlib.Path = None, with_python: bool = True) -> emdbg.debug.remote.mi.Gdb:
174@contextmanager
175def call_mi(backend: ProbeBackend, source: Path = None, config: list[Path] = None,
176            commands: list[str] = None, svd: Path = None,
177            with_python: bool = True) -> remote.mi.Gdb:
178    """
179    Launches GDB in the background using [pygdbmi][] and connects to its command
180    prompt via the [GDB/MI Protocol][gdbmi].
181    This method does not allow accessing the Python API directly, instead you
182    must issue command strings inside the GDB command prompt.
183    However, this method is significantly faster and most stable than `call_rpyc()`.
184
185    :param backend: a debug backend implementation.
186    :param source: Path to a ELF file.
187    :param config: List of GDB configuration files.
188    :param commands: List of GDB commands to execute during launch.
189    :param svd: Path to the CMSIS-SVD file for the connected device.
190    :param with_python: Uses `arm-none-eabi-gdb-py3` and loads the Python
191                        debug modules in `emdbg.debug.px4` as `px4`.
192
193    :return: `remote.mi.Gdb` object which can be used to issue commands and read
194             the responses.
195
196    [gdbmi]: https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html
197    [pygdbmi]: https://cs01.github.io/pygdbmi/
198    """
199    from pygdbmi.gdbcontroller import GdbController
200
201    gdb_command = command_string(backend, source, config, commands, "cmd", svd, with_python=with_python)
202    gdb_command += " --interpreter=mi3"
203
204
205    with backend.scope():
206        rgdb = None
207        try:
208            LOGGER.info("Starting...")
209            LOGGER.debug(gdb_command)
210            mi = GdbController(shlex.split(gdb_command))
211            rgdb = remote.mi.Gdb(backend, mi)
212            yield rgdb
213
214        finally:
215            if rgdb is not None:
216                rgdb.quit()
217            LOGGER.info("Stopping.")

Launches GDB in the background using pygdbmi and connects to its command prompt via the GDB/MI Protocol. This method does not allow accessing the Python API directly, instead you must issue command strings inside the GDB command prompt. However, this method is significantly faster and most stable than call_rpyc().

Parameters
  • backend: a debug backend implementation.
  • source: Path to a ELF file.
  • config: List of GDB configuration files.
  • commands: List of GDB commands to execute during launch.
  • svd: Path to the CMSIS-SVD file for the connected device.
  • with_python: Uses arm-none-eabi-gdb-py3 and loads the Python debug modules in emdbg.debug.px4 as px4.
Returns

remote.mi.Gdb object which can be used to issue commands and read the responses.

def call( backend: emdbg.debug.backend.ProbeBackend, source: pathlib.Path = None, config: list[pathlib.Path] = None, commands: list[str] = None, ui: str = None, svd: pathlib.Path = None, with_python: bool = True, coredump: pathlib.Path = None) -> int:
224def call(backend: ProbeBackend, source: Path = None, config: list[Path] = None,
225         commands: list[str] = None, ui: str = None, svd: Path = None,
226         with_python: bool = True, coredump: Path = None) -> int:
227    """
228    Launches the backend in the background and GDB as a blocking process in
229    the foreground for user interaction.
230
231    :param backend: a debug backend implementation.
232    :param source: Path to a ELF file.
233    :param config: List of GDB configuration files.
234    :param commands: List of GDB commands to execute during launch.
235    :param ui: The frontend configuration for GDB
236               - `cmd`: Default GDB UI with only a command prompt available.
237               - `tui`: Launches in text user interface with layout split.
238               - `gdbgui`: Launches in background using GDBGUI as frontend.
239    :param svd: Path to the CMSIS-SVD file for the connected device.
240    :param with_python: Uses `arm-none-eabi-gdb-py3` and loads the Python
241                        debug modules in `emdbg.debug.px4` as `px4`.
242    """
243    gdb_command = command_string(backend, source, config, commands, ui, svd,
244                                 with_python=with_python, coredump=coredump)
245
246    signal.signal(signal.SIGINT, _empty_signal_handler)
247    with backend.scope():
248        try:
249            LOGGER.info("Starting...")
250            LOGGER.debug(gdb_command)
251            # This call is now blocking
252            return subprocess.call(gdb_command, cwd=os.getcwd(), shell=True)
253        except KeyboardInterrupt:
254            pass
255        finally:
256            LOGGER.info("Stopping.")

Launches the backend in the background and GDB as a blocking process in the foreground for user interaction.

Parameters
  • backend: a debug backend implementation.
  • source: Path to a ELF file.
  • config: List of GDB configuration files.
  • commands: List of GDB commands to execute during launch.
  • ui: The frontend configuration for GDB
    • cmd: Default GDB UI with only a command prompt available.
    • tui: Launches in text user interface with layout split.
    • gdbgui: Launches in background using GDBGUI as frontend.
  • svd: Path to the CMSIS-SVD file for the connected device.
  • with_python: Uses arm-none-eabi-gdb-py3 and loads the Python debug modules in emdbg.debug.px4 as px4.