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
Theemdbg.debug.px4
tools are accessible inside the GDB command prompt only as a top-levelpx4
Python module to avoid loading in all of theemdbg
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)
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 inemdbg.debug.px4
aspx4
.
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 ofimport 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 inemdbg.debug.px4
aspx4
.
Returns
remote.mi.Gdb
object which can be used to issue commands and read the responses.
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 inemdbg.debug.px4
aspx4
.