emdbg.bench.fmu

PX4 FMU test bench

The test bench consists of a PX4 FMU that is connected to a J-Link and a USB-Serial adapter for the NSH.

The main entry point is the emdbg.bench.fmu function, which orchestrates the entire setup and yields an initialized bench object.

The FMU firmware must be compiled externally and is automatically uploaded when the bench is set up. This ensures that you're always debugging the right firmware, otherwise you will see very weird behavior, since GDB uses the ELF file to locate statically allocated objects and understand types.

The FMU is then reset, held in that state, and then yielded from the function so that you can configure the test and then start the firmware execution when ready.

The FMU is configured for debugging by stopping all timers when the debugger halts the CPU, therefore from the device's perspective, no time is missed during debugging. If you want to know about the system load, you must start it before querying it's state, otherwise there is nothing to compare the current sample to.

GDB Configuration File

This module also loads a fmu.gdb configuration file with many target-specific GDB commands in addition to the PX4-specific commands defined by the Python modules in emdbg.debug.gdb.

  • px4_log_start {filename}: Starts logging of the GDB prompt to a file.
  • px4_log_stop: Stops GDB logging.
  • px4_fbreak {func}: Places a breakpoint on the return of a function.
  • px4_btfbreak {func}: Same as px4_fbreak but prints a backtrace too.
  • px4_breaktrace{,10,100} {func}: Sets a breakpoint on a function and calls px4_backtrace to produce a call chain that can be interpreted by emdbg.analyze.callgraph. The 10 and 100 suffixes indicate that the backtrace is only generated every 10 or 100 hits of the breakpoint.
  • px4_commands_backtrace{,10,100}: Attach a backtrace to a previously defined breakpoint or watchpoint. px4_breaktrace uses this for breakpoints.
  • px4_calltrace_semaphore_boosts: set breaktraces in the sem_holder.c file to log task (de-)boosts that can be interpreted by emdbg.analyze.priority.

To trace a watchpoint, you need to define it yourself and attach a backtrace:

(gdb) watch variable
(gdb) px4_commands_backtrace

Scripting

This module gives you all functionality to automate a test. Remember that the bench automatically uploads the firmware.

with emdbg.bench.fmu(px4_dir, target, nsh_serial) as bench:
    # FMU is flashed with the firmware, reset and halted
    # Configure your test now
    bench.gdb.execute("px4_log_start log.txt")
    # Now execute from the reset handler onwards
    bench.gdb.continue_nowait()
    # wait for NSH prompt
    bench.nsh.wait_for_prompt()
    # Start the system load monitor
    bench.restart_system_load_monitor()
    bench.sleep(3)
    # Update system load monitor
    bench.restart_system_load_monitor()
    # Wait a little
    bench.sleep(5)
    # interrupt and automatically continue
    with bench.gdb.interrupt_continue():
        # Print the task list and cpu load
        print(emdbg.debug.px4.all_tasks_as_table(bench.gdb))
        # Dump the core
        bench.gdb.coredump()
    bench.sleep(10)
    # interrupt at the end
    bench.gdb.interrupt_and_wait()
    # Deconfigure GDB
    bench.gdb.execute("px4_log_stop")

Command Line Interface

To quickly debug something interactively on the test bench, you can launch GDB directly with the debug backend of your choice:

  • --jlink: connect via J-Link debug probe.
  • --stlink: connect via STLink debug probe.
  • --orbtrace: connect via Orbtrace mini debug probe.
  • --coredump: Use the CrashDebug backend with a coredump or PX4 hardfault log.
python3 -m emdbg.bench.fmu --px4-dir path/to/PX4-Autopilot \
    --target px4_fmu-v5x_default -ui tui --stlink

python3 -m emdbg.bench.fmu --px4-dir path/to/PX4-Autopilot \
    --target px4_fmu-v6x_default --coredump px4_hardfault.log
  1# Copyright (c) 2023, Auterion AG
  2# SPDX-License-Identifier: BSD-3-Clause
  3
  4"""
  5.. include:: fmu.md
  6"""
  7
  8from __future__ import annotations
  9import time
 10from functools import cached_property
 11from pathlib import Path
 12from contextlib import contextmanager, nullcontext
 13import emdbg
 14
 15
 16class Fmu:
 17    """
 18    FMU test bench with optional nsh, power, and logic analyzer attachments.
 19    """
 20    def _DBGMCU_CONFIG(target):
 21        # Vector catch all exceptions, but not reset
 22        common = ["px4_enable_vector_catch"]
 23        # Halt all timers and peripherals while debugging
 24        if "fmu-v5x" in target:
 25            return ["set *0xE0042008 = 0xffffffff",
 26                    "set *0xE004200C = 0xffffffff"] + common
 27        if "fmu-v6x" in target or "fmu-v6s" in target:
 28            return ["set *0xE00E1034 = 0xffffffff",
 29                    "set *0xE00E103C = 0xffffffff",
 30                    "set *0xE00E104C = 0xffffffff",
 31                    "set *0xE00E1054 = 0xffffffff"] + common
 32        return []
 33
 34    def __init__(self, target: str, elf: Path,
 35                 gdb: "emdbg.debug.remote.gdb.Interface",
 36                 nsh: "emdbg.serial.protocol.Nsh" = None,
 37                 power: "emdbg.power.base.Base" = None,
 38                 io: "emdbg.io.digilent.Digilent" = None):
 39        self.elf: Path = elf
 40        """The ELF file under debug"""
 41        self.gdb: "emdbg.debug.remote.gdb.Interface" = gdb
 42        """The remote GDB access interface object"""
 43        self.nsh: "emdbg.serial.protocol.Nsh" = nsh
 44        """The NSH protocol controller"""
 45        self.power: "emdbg.power.base.Base" = power
 46        """The power relay controlling the FMU"""
 47        self.io: "emdbg.io.digilent.Digilent" = io
 48        """The Digilent Scope"""
 49        self._target = target
 50
 51    def _init(self):
 52        self.gdb.interrupt_and_wait()
 53        for cmd in self._DBGMCU_CONFIG(self._target):
 54            self.gdb.execute(cmd)
 55        self.restart_system_load_monitor()
 56
 57    def _deinit(self):
 58        self.gdb.interrupt_and_wait()
 59        self.gdb.execute("disconnect")
 60        self.gdb.interrupted = False
 61        # Give JLink some time to react to the disconnect before shut down
 62        time.sleep(1)
 63
 64    def restart_system_load_monitor(self):
 65        """See `emdbg.debug.px4.system_load.restart_system_load_monitor`"""
 66        with self.gdb.interrupt_continue():
 67            if self.gdb.type == "mi":
 68                self.gdb.execute("python px4.restart_system_load_monitor(gdb)")
 69            else:
 70                emdbg.debug.px4.restart_system_load_monitor(self.gdb)
 71
 72    def coredump(self, filename: Path = None):
 73        """
 74        Dumps the FMU core for later analysis with CrashDebug (see
 75        `emdbg.debug.crashdebug`).
 76
 77        :param filename: Default `coredump_{datetime}.txt`.
 78        """
 79        with self.gdb.interrupt_continue():
 80            if False:
 81                # Connection is remote, therefore we must use slower RPyC interface
 82                emdbg.debug.px4.coredump(self.gdb, filename=filename)
 83            else:
 84                # Executing directly on the GDB process is *significantly* faster!
 85                if filename: filename = f"--file '{filename}'"
 86                self.gdb.execute(f"px4_coredump {filename or ''}")
 87
 88    def upload(self, source: Path = None):
 89        """
 90        Uploads the ELF file to the FMU, resets the device, clears the NSH
 91        :param source: optional path to ELF file, default is the ELF file passed
 92                       to the constructor.
 93        """
 94        with self.gdb.interrupt_continue():
 95            self.gdb.execute("monitor reset")
 96            self.gdb.execute(f"load {source or self.elf}", timeout=60)
 97            self.reset()
 98            if self.nsh is not None:
 99                self.nsh.clear()
100
101    def power_on_reset(self, delay_off: float = 1, delay_on: float = 2):
102        """
103        Disconnects GDB, stops the backend, power cycles the entire bench,
104        restarts the backend, reconnects and reinitializes GDB.
105        **Only works if initialized with a power relay!**
106        """
107        if self.power is None:
108            LOGGER.warning("Power Relay not configured!")
109            return
110        # Disconnect and stop the backend
111        self._deinit()
112        self.gdb.backend.stop()
113        # Wait for JLink to shut down, os.waitpid is not enough
114        time.sleep(1)
115
116        # Have you tried turning it off and on again?
117        self.power.cycle(delay_off, delay_on)
118
119        # Restart and reattach the backend
120        self.gdb.backend.start()
121        # Wait for JLink to start up again, since it starts non-blocking
122        time.sleep(2)
123        # Reinitialize GDB to attach back to JLink
124        for cmd in self.gdb.backend.init(self.elf):
125            self.gdb.execute(cmd)
126        self._init()
127
128    def reset(self):
129        """Resets the FMU"""
130        with self.gdb.interrupt_continue():
131            self.gdb.execute("monitor reset")
132            self._init()
133
134    def sleep(self, seconds: float, func=None):
135        """
136        Sleeps and calls `func` every second. This can be useful for polling if
137        a condition has happened.
138        """
139        while(seconds > 0):
140            seconds -= 1
141            time.sleep(1)
142            if func is not None:
143                func(seconds)
144
145
146def _px4_config(px4_directory: Path, target: Path, commands: list[str] = None,
147                ui: str = None, speed: int = 16000, backend: str = None) -> tuple:
148    if "fmu-v5x" in target:
149        device = "STM32F765II"
150        config = "fmu_v5x"
151    elif "fmu-v6x" in target:
152        device = "STM32H753II"
153        config = "fmu_v6x"
154    elif "fmu-v6s" in target:
155        device = "STM32H743II"
156        config = "fmu_v6x"
157    else:
158        raise ValueError(f"Unknown device for '{target}'!")
159
160    px4_dir = Path(px4_directory).absolute().resolve()
161    data_dir = Path(__file__).parent.resolve() / "data"
162
163    if backend in ["stlink", "orbtrace"]:
164        config += f"_{backend}.cfg"
165        backend_obj = emdbg.debug.OpenOcdBackend(config=[data_dir / config])
166    elif backend == "jlink":
167        rtos_so = px4_dir / f"platforms/nuttx/NuttX/nuttx/tools/jlink-nuttx.so"
168        if not rtos_so.exists(): rtos_so = None
169        backend_obj = emdbg.debug.JLinkBackend(device, speed, rtos_so)
170    elif isinstance(backend, Path):
171        backend_obj = emdbg.debug.CrashProbeBackend(backend)
172    elif ":" in backend:
173        backend_obj = emdbg.debug.ProbeBackend(backend)
174    else:
175        raise ValueError(f"Unknown backend '{backend}'!")
176
177    boot_elf = None
178    if Path(target).suffix == ".elf":
179        elf = Path(target)
180    else:
181        if (elf := next((px4_dir / "build" / target).glob("*.elf"), None)) is None:
182            raise ValueError(f"Cannot find ELF file in build folder '{px4_dir}/build/{target}'!")
183        if "_default" in target: # try to find the bootloader elf too
184            boot_elf = next((px4_dir / "build" / target.replace("_default", "_bootloader")).glob("*.elf"), None)
185
186    cmds = [f"dir {px4_dir}",
187            f"source {data_dir}/fmu.gdb",
188            f"python px4._TARGET='{target.lower()}'"]
189    if boot_elf is not None:
190        cmds += [f"add-symbol-file {boot_elf}"]
191    if ui is not None and backend_obj.name != "crashdebug":
192        cmds += Fmu._DBGMCU_CONFIG(target)
193    cmds += (commands or [])
194
195    return backend_obj, elf, cmds
196
197
198# -----------------------------------------------------------------------------
199@contextmanager
200def debug(px4_directory: Path, target: str, serial: str = None,
201          digilent: str = None, power: "emdbg.power.base.Base" = None,
202          ui: str = None, commands: list[str] = None, with_rpyc: bool = False,
203          keep_power_on: bool = True, upload: bool = True, backend: str = None,
204          _FmuClass = Fmu) -> Fmu:
205    """
206    Launches and configures the Fmu test bench.
207    1. Switches on the power relay if specified.
208    2. Starts the J-Link GDB server in the background.
209    3. Launches GDB and connects it to the debug probe backend.
210    4. Connects and initializes the NSH if specified.
211    5. Connects and initializes the Digilent Scope if specified.
212    6. Loads the PX4 specific GDB scripts, the `emdbg.debug.px4`
213       modules Python modules, and the `fmu.gdb` script.
214    7. Uploads the latest firmware to the FMU.
215    8. Yields the test bench in halted reset state.
216
217    :param px4_directory: path to the PX4-Autopilot repository you want to debug.
218    :param target: target name as a string, for example, `px4_fmu-v5x`.
219                   Can also be a path to an ELF file.
220    :param serial: optional serial number of the USB-Serial bridge for the NSH.
221                   If `None`, then `Fmu.nsh` is empty and cannot be used.
222    :param digilent: optional serial number of the Digilent. If `None`, then
223                     `Fmu.io` is empty and cannot be used.
224    :param ui: If not `None`, then this launches the interactive debugger via
225               `emdbg.debug.gdb.call` instead of the scripting API.
226    :param commands: list of additional GDB commands to execute during launch.
227    :param with_rpyc: Use the RPyC GDB interface implementation, rather than the
228                      GDB/MI interface. See `emdbg.debug.remote`.
229    :param keep_power_on: Do not shut off the Fmu after the context has closed.
230    :param upload: Automatically upload the firmware after powering on the FMU.
231    :param backend: `openocd`, `jlink`, or `IP:PORT` for a GDB server on another machine.
232
233    :return: A configured test bench with the latest firmware.
234    """
235    backend, elf, cmds = _px4_config(px4_directory, target, commands, ui,
236                                     backend=backend or "openocd")
237
238    with (nullcontext() if power is None else power) as pwr:
239        try:
240            if ui is not None:
241                # Manual mode that only connects the debugger (blocking)
242                if power: pwr.on()
243                yield emdbg.debug.gdb.call(backend, elf, commands=cmds, ui=ui)
244            else:
245                # Turn off, then connect the serial
246                if power: pwr.off()
247                serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext()
248                scope = emdbg.io.analog_discovery(digilent) if digilent is not None else nullcontext()
249                with serial as nsh, scope as io:
250                    # Then power on and connect GDB to get the full boot log
251                    if power: pwr.on()
252                    gdb_call = emdbg.debug.gdb.call_rpyc if with_rpyc else emdbg.debug.gdb.call_mi
253                    debugger = gdb_call(backend, elf, commands=cmds)
254                    with debugger as gdb:
255                        bench = _FmuClass(target, elf, gdb, nsh, pwr, io)
256                        if upload: bench.upload()
257                        yield bench
258                        bench._deinit()
259        finally:
260            if not keep_power_on:
261                pwr.off(delay=0)
262
263
264# -----------------------------------------------------------------------------
265@contextmanager
266def shell(serial: str = None, power = None, keep_power_on: bool = True) -> Fmu:
267    """
268    Launches and configures the Fmu test bench.
269    1. Switches on the power relay if specified.
270    4. Connects and initializes the NSH if specified.
271    8. Yields the NSH after reset state.
272
273    :param serial: optional serial number of the USB-Serial bridge for the NSH.
274                   If `None`, then `Fmu.nsh` is empty and cannot be used.
275    :param keep_power_on: Do not shut off the Fmu after the context has closed.
276
277    :return: A configured test bench with the latest firmware.
278    """
279
280    if power is None: power = nullcontext()
281    with power as pwr:
282        try:
283            # Turn off, then connect the serial
284            pwr.off()
285            serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext()
286            with serial as nsh:
287                # Then power on and connect GDB to get the full boot log
288                pwr.on()
289                yield nsh
290        finally:
291            if not keep_power_on:
292                pwr.off(delay=0)
293
294# -----------------------------------------------------------------------------
295def _arguments(description, modifier=None):
296    import argparse, emdbg
297    parser = argparse.ArgumentParser(description=description)
298    parser.add_argument(
299        "--px4-dir",
300        default=".",
301        type=Path,
302        help="The PX4 root directory you are working on.")
303    parser.add_argument(
304        "--target",
305        default="px4_fmu-v5x",
306        help="The target you want to debug.")
307    group = parser.add_mutually_exclusive_group(required=True)
308    group.add_argument(
309        "--jlink",
310        default=False,
311        action="store_true",
312        help="Use a J-Link debug probe")
313    group.add_argument(
314        "--stlink",
315        default=False,
316        action="store_true",
317        help="Use an STLink debug probe")
318    group.add_argument(
319        "--orbtrace",
320        default=False,
321        action="store_true",
322        help="Use an ORBtrace mini debug probe")
323    group.add_argument(
324        "--remote",
325        help="Connect to a remote GDB server: 'IP:PORT'")
326    group.add_argument(
327        "--coredump",
328        type=Path,
329        help="Inspect a GDB coredump or PX4 hardfault log.")
330    parser.add_argument(
331        "--ui",
332        default="cmd",
333        choices=["tui", "gdbgui", "cmd", "batch"],
334        help="The user interface you want to use.")
335    parser.add_argument(
336        "-v",
337        dest="verbosity",
338        action="count",
339        default=0,
340        help="Verbosity level.")
341    parser.add_argument(
342        "-ex",
343        dest="commands",
344        action="append",
345        help="Extra GDB commands.")
346    if modifier: modifier(parser)
347    args = parser.parse_args()
348    emdbg.logger.configure(args.verbosity)
349    backend = args.remote
350    if args.stlink: backend = "stlink"
351    if args.jlink: backend = "jlink"
352    if args.orbtrace: backend = "orbtrace"
353    if args.coredump: backend = args.coredump
354    return args, backend
355
356
357if __name__ == "__main__":
358    args, backend = _arguments("Debug FMU")
359
360    with debug(args.px4_dir, args.target, ui=args.ui, commands=args.commands,
361               backend=backend) as gdb_call:
362        exit(gdb_call)
class Fmu:
 17class Fmu:
 18    """
 19    FMU test bench with optional nsh, power, and logic analyzer attachments.
 20    """
 21    def _DBGMCU_CONFIG(target):
 22        # Vector catch all exceptions, but not reset
 23        common = ["px4_enable_vector_catch"]
 24        # Halt all timers and peripherals while debugging
 25        if "fmu-v5x" in target:
 26            return ["set *0xE0042008 = 0xffffffff",
 27                    "set *0xE004200C = 0xffffffff"] + common
 28        if "fmu-v6x" in target or "fmu-v6s" in target:
 29            return ["set *0xE00E1034 = 0xffffffff",
 30                    "set *0xE00E103C = 0xffffffff",
 31                    "set *0xE00E104C = 0xffffffff",
 32                    "set *0xE00E1054 = 0xffffffff"] + common
 33        return []
 34
 35    def __init__(self, target: str, elf: Path,
 36                 gdb: "emdbg.debug.remote.gdb.Interface",
 37                 nsh: "emdbg.serial.protocol.Nsh" = None,
 38                 power: "emdbg.power.base.Base" = None,
 39                 io: "emdbg.io.digilent.Digilent" = None):
 40        self.elf: Path = elf
 41        """The ELF file under debug"""
 42        self.gdb: "emdbg.debug.remote.gdb.Interface" = gdb
 43        """The remote GDB access interface object"""
 44        self.nsh: "emdbg.serial.protocol.Nsh" = nsh
 45        """The NSH protocol controller"""
 46        self.power: "emdbg.power.base.Base" = power
 47        """The power relay controlling the FMU"""
 48        self.io: "emdbg.io.digilent.Digilent" = io
 49        """The Digilent Scope"""
 50        self._target = target
 51
 52    def _init(self):
 53        self.gdb.interrupt_and_wait()
 54        for cmd in self._DBGMCU_CONFIG(self._target):
 55            self.gdb.execute(cmd)
 56        self.restart_system_load_monitor()
 57
 58    def _deinit(self):
 59        self.gdb.interrupt_and_wait()
 60        self.gdb.execute("disconnect")
 61        self.gdb.interrupted = False
 62        # Give JLink some time to react to the disconnect before shut down
 63        time.sleep(1)
 64
 65    def restart_system_load_monitor(self):
 66        """See `emdbg.debug.px4.system_load.restart_system_load_monitor`"""
 67        with self.gdb.interrupt_continue():
 68            if self.gdb.type == "mi":
 69                self.gdb.execute("python px4.restart_system_load_monitor(gdb)")
 70            else:
 71                emdbg.debug.px4.restart_system_load_monitor(self.gdb)
 72
 73    def coredump(self, filename: Path = None):
 74        """
 75        Dumps the FMU core for later analysis with CrashDebug (see
 76        `emdbg.debug.crashdebug`).
 77
 78        :param filename: Default `coredump_{datetime}.txt`.
 79        """
 80        with self.gdb.interrupt_continue():
 81            if False:
 82                # Connection is remote, therefore we must use slower RPyC interface
 83                emdbg.debug.px4.coredump(self.gdb, filename=filename)
 84            else:
 85                # Executing directly on the GDB process is *significantly* faster!
 86                if filename: filename = f"--file '{filename}'"
 87                self.gdb.execute(f"px4_coredump {filename or ''}")
 88
 89    def upload(self, source: Path = None):
 90        """
 91        Uploads the ELF file to the FMU, resets the device, clears the NSH
 92        :param source: optional path to ELF file, default is the ELF file passed
 93                       to the constructor.
 94        """
 95        with self.gdb.interrupt_continue():
 96            self.gdb.execute("monitor reset")
 97            self.gdb.execute(f"load {source or self.elf}", timeout=60)
 98            self.reset()
 99            if self.nsh is not None:
100                self.nsh.clear()
101
102    def power_on_reset(self, delay_off: float = 1, delay_on: float = 2):
103        """
104        Disconnects GDB, stops the backend, power cycles the entire bench,
105        restarts the backend, reconnects and reinitializes GDB.
106        **Only works if initialized with a power relay!**
107        """
108        if self.power is None:
109            LOGGER.warning("Power Relay not configured!")
110            return
111        # Disconnect and stop the backend
112        self._deinit()
113        self.gdb.backend.stop()
114        # Wait for JLink to shut down, os.waitpid is not enough
115        time.sleep(1)
116
117        # Have you tried turning it off and on again?
118        self.power.cycle(delay_off, delay_on)
119
120        # Restart and reattach the backend
121        self.gdb.backend.start()
122        # Wait for JLink to start up again, since it starts non-blocking
123        time.sleep(2)
124        # Reinitialize GDB to attach back to JLink
125        for cmd in self.gdb.backend.init(self.elf):
126            self.gdb.execute(cmd)
127        self._init()
128
129    def reset(self):
130        """Resets the FMU"""
131        with self.gdb.interrupt_continue():
132            self.gdb.execute("monitor reset")
133            self._init()
134
135    def sleep(self, seconds: float, func=None):
136        """
137        Sleeps and calls `func` every second. This can be useful for polling if
138        a condition has happened.
139        """
140        while(seconds > 0):
141            seconds -= 1
142            time.sleep(1)
143            if func is not None:
144                func(seconds)

FMU test bench with optional nsh, power, and logic analyzer attachments.

Fmu( target: str, elf: pathlib.Path, gdb: emdbg.debug.remote.gdb.Interface, nsh: "'emdbg.serial.protocol.Nsh'" = None, power: emdbg.power.base.Base = None, io: emdbg.io.digilent.Digilent = None)
35    def __init__(self, target: str, elf: Path,
36                 gdb: "emdbg.debug.remote.gdb.Interface",
37                 nsh: "emdbg.serial.protocol.Nsh" = None,
38                 power: "emdbg.power.base.Base" = None,
39                 io: "emdbg.io.digilent.Digilent" = None):
40        self.elf: Path = elf
41        """The ELF file under debug"""
42        self.gdb: "emdbg.debug.remote.gdb.Interface" = gdb
43        """The remote GDB access interface object"""
44        self.nsh: "emdbg.serial.protocol.Nsh" = nsh
45        """The NSH protocol controller"""
46        self.power: "emdbg.power.base.Base" = power
47        """The power relay controlling the FMU"""
48        self.io: "emdbg.io.digilent.Digilent" = io
49        """The Digilent Scope"""
50        self._target = target
elf: pathlib.Path

The ELF file under debug

The remote GDB access interface object

nsh: "'emdbg.serial.protocol.Nsh'"

The NSH protocol controller

The power relay controlling the FMU

The Digilent Scope

def restart_system_load_monitor(self):
65    def restart_system_load_monitor(self):
66        """See `emdbg.debug.px4.system_load.restart_system_load_monitor`"""
67        with self.gdb.interrupt_continue():
68            if self.gdb.type == "mi":
69                self.gdb.execute("python px4.restart_system_load_monitor(gdb)")
70            else:
71                emdbg.debug.px4.restart_system_load_monitor(self.gdb)
def coredump(self, filename: pathlib.Path = None):
73    def coredump(self, filename: Path = None):
74        """
75        Dumps the FMU core for later analysis with CrashDebug (see
76        `emdbg.debug.crashdebug`).
77
78        :param filename: Default `coredump_{datetime}.txt`.
79        """
80        with self.gdb.interrupt_continue():
81            if False:
82                # Connection is remote, therefore we must use slower RPyC interface
83                emdbg.debug.px4.coredump(self.gdb, filename=filename)
84            else:
85                # Executing directly on the GDB process is *significantly* faster!
86                if filename: filename = f"--file '{filename}'"
87                self.gdb.execute(f"px4_coredump {filename or ''}")

Dumps the FMU core for later analysis with CrashDebug (see emdbg.debug.crashdebug).

Parameters
  • filename: Default coredump_{datetime}.txt.
def upload(self, source: pathlib.Path = None):
 89    def upload(self, source: Path = None):
 90        """
 91        Uploads the ELF file to the FMU, resets the device, clears the NSH
 92        :param source: optional path to ELF file, default is the ELF file passed
 93                       to the constructor.
 94        """
 95        with self.gdb.interrupt_continue():
 96            self.gdb.execute("monitor reset")
 97            self.gdb.execute(f"load {source or self.elf}", timeout=60)
 98            self.reset()
 99            if self.nsh is not None:
100                self.nsh.clear()

Uploads the ELF file to the FMU, resets the device, clears the NSH

Parameters
  • source: optional path to ELF file, default is the ELF file passed to the constructor.
def power_on_reset(self, delay_off: float = 1, delay_on: float = 2):
102    def power_on_reset(self, delay_off: float = 1, delay_on: float = 2):
103        """
104        Disconnects GDB, stops the backend, power cycles the entire bench,
105        restarts the backend, reconnects and reinitializes GDB.
106        **Only works if initialized with a power relay!**
107        """
108        if self.power is None:
109            LOGGER.warning("Power Relay not configured!")
110            return
111        # Disconnect and stop the backend
112        self._deinit()
113        self.gdb.backend.stop()
114        # Wait for JLink to shut down, os.waitpid is not enough
115        time.sleep(1)
116
117        # Have you tried turning it off and on again?
118        self.power.cycle(delay_off, delay_on)
119
120        # Restart and reattach the backend
121        self.gdb.backend.start()
122        # Wait for JLink to start up again, since it starts non-blocking
123        time.sleep(2)
124        # Reinitialize GDB to attach back to JLink
125        for cmd in self.gdb.backend.init(self.elf):
126            self.gdb.execute(cmd)
127        self._init()

Disconnects GDB, stops the backend, power cycles the entire bench, restarts the backend, reconnects and reinitializes GDB. Only works if initialized with a power relay!

def reset(self):
129    def reset(self):
130        """Resets the FMU"""
131        with self.gdb.interrupt_continue():
132            self.gdb.execute("monitor reset")
133            self._init()

Resets the FMU

def sleep(self, seconds: float, func=None):
135    def sleep(self, seconds: float, func=None):
136        """
137        Sleeps and calls `func` every second. This can be useful for polling if
138        a condition has happened.
139        """
140        while(seconds > 0):
141            seconds -= 1
142            time.sleep(1)
143            if func is not None:
144                func(seconds)

Sleeps and calls func every second. This can be useful for polling if a condition has happened.

@contextmanager
def debug( px4_directory: pathlib.Path, target: str, serial: str = None, digilent: str = None, power: emdbg.power.base.Base = None, ui: str = None, commands: list[str] = None, with_rpyc: bool = False, keep_power_on: bool = True, upload: bool = True, backend: str = None, _FmuClass=<class 'Fmu'>) -> Fmu:
200@contextmanager
201def debug(px4_directory: Path, target: str, serial: str = None,
202          digilent: str = None, power: "emdbg.power.base.Base" = None,
203          ui: str = None, commands: list[str] = None, with_rpyc: bool = False,
204          keep_power_on: bool = True, upload: bool = True, backend: str = None,
205          _FmuClass = Fmu) -> Fmu:
206    """
207    Launches and configures the Fmu test bench.
208    1. Switches on the power relay if specified.
209    2. Starts the J-Link GDB server in the background.
210    3. Launches GDB and connects it to the debug probe backend.
211    4. Connects and initializes the NSH if specified.
212    5. Connects and initializes the Digilent Scope if specified.
213    6. Loads the PX4 specific GDB scripts, the `emdbg.debug.px4`
214       modules Python modules, and the `fmu.gdb` script.
215    7. Uploads the latest firmware to the FMU.
216    8. Yields the test bench in halted reset state.
217
218    :param px4_directory: path to the PX4-Autopilot repository you want to debug.
219    :param target: target name as a string, for example, `px4_fmu-v5x`.
220                   Can also be a path to an ELF file.
221    :param serial: optional serial number of the USB-Serial bridge for the NSH.
222                   If `None`, then `Fmu.nsh` is empty and cannot be used.
223    :param digilent: optional serial number of the Digilent. If `None`, then
224                     `Fmu.io` is empty and cannot be used.
225    :param ui: If not `None`, then this launches the interactive debugger via
226               `emdbg.debug.gdb.call` instead of the scripting API.
227    :param commands: list of additional GDB commands to execute during launch.
228    :param with_rpyc: Use the RPyC GDB interface implementation, rather than the
229                      GDB/MI interface. See `emdbg.debug.remote`.
230    :param keep_power_on: Do not shut off the Fmu after the context has closed.
231    :param upload: Automatically upload the firmware after powering on the FMU.
232    :param backend: `openocd`, `jlink`, or `IP:PORT` for a GDB server on another machine.
233
234    :return: A configured test bench with the latest firmware.
235    """
236    backend, elf, cmds = _px4_config(px4_directory, target, commands, ui,
237                                     backend=backend or "openocd")
238
239    with (nullcontext() if power is None else power) as pwr:
240        try:
241            if ui is not None:
242                # Manual mode that only connects the debugger (blocking)
243                if power: pwr.on()
244                yield emdbg.debug.gdb.call(backend, elf, commands=cmds, ui=ui)
245            else:
246                # Turn off, then connect the serial
247                if power: pwr.off()
248                serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext()
249                scope = emdbg.io.analog_discovery(digilent) if digilent is not None else nullcontext()
250                with serial as nsh, scope as io:
251                    # Then power on and connect GDB to get the full boot log
252                    if power: pwr.on()
253                    gdb_call = emdbg.debug.gdb.call_rpyc if with_rpyc else emdbg.debug.gdb.call_mi
254                    debugger = gdb_call(backend, elf, commands=cmds)
255                    with debugger as gdb:
256                        bench = _FmuClass(target, elf, gdb, nsh, pwr, io)
257                        if upload: bench.upload()
258                        yield bench
259                        bench._deinit()
260        finally:
261            if not keep_power_on:
262                pwr.off(delay=0)

Launches and configures the Fmu test bench.

  1. Switches on the power relay if specified.
  2. Starts the J-Link GDB server in the background.
  3. Launches GDB and connects it to the debug probe backend.
  4. Connects and initializes the NSH if specified.
  5. Connects and initializes the Digilent Scope if specified.
  6. Loads the PX4 specific GDB scripts, the emdbg.debug.px4 modules Python modules, and the fmu.gdb script.
  7. Uploads the latest firmware to the FMU.
  8. Yields the test bench in halted reset state.
Parameters
  • px4_directory: path to the PX4-Autopilot repository you want to debug.
  • target: target name as a string, for example, px4_fmu-v5x. Can also be a path to an ELF file.
  • serial: optional serial number of the USB-Serial bridge for the NSH. If None, then Fmu.nsh is empty and cannot be used.
  • digilent: optional serial number of the Digilent. If None, then Fmu.io is empty and cannot be used.
  • ui: If not None, then this launches the interactive debugger via emdbg.debug.gdb.call instead of the scripting API.
  • commands: list of additional GDB commands to execute during launch.
  • with_rpyc: Use the RPyC GDB interface implementation, rather than the GDB/MI interface. See emdbg.debug.remote.
  • keep_power_on: Do not shut off the Fmu after the context has closed.
  • upload: Automatically upload the firmware after powering on the FMU.
  • **backend: openocd, jlink, or IP**: PORT for a GDB server on another machine.
Returns

A configured test bench with the latest firmware.

@contextmanager
def shell( serial: str = None, power=None, keep_power_on: bool = True) -> Fmu:
266@contextmanager
267def shell(serial: str = None, power = None, keep_power_on: bool = True) -> Fmu:
268    """
269    Launches and configures the Fmu test bench.
270    1. Switches on the power relay if specified.
271    4. Connects and initializes the NSH if specified.
272    8. Yields the NSH after reset state.
273
274    :param serial: optional serial number of the USB-Serial bridge for the NSH.
275                   If `None`, then `Fmu.nsh` is empty and cannot be used.
276    :param keep_power_on: Do not shut off the Fmu after the context has closed.
277
278    :return: A configured test bench with the latest firmware.
279    """
280
281    if power is None: power = nullcontext()
282    with power as pwr:
283        try:
284            # Turn off, then connect the serial
285            pwr.off()
286            serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext()
287            with serial as nsh:
288                # Then power on and connect GDB to get the full boot log
289                pwr.on()
290                yield nsh
291        finally:
292            if not keep_power_on:
293                pwr.off(delay=0)

Launches and configures the Fmu test bench.

  1. Switches on the power relay if specified.
  2. Connects and initializes the NSH if specified.
  3. Yields the NSH after reset state.
Parameters
  • serial: optional serial number of the USB-Serial bridge for the NSH. If None, then Fmu.nsh is empty and cannot be used.
  • keep_power_on: Do not shut off the Fmu after the context has closed.
Returns

A configured test bench with the latest firmware.