emdbg.analyze.calltrace

Call Graph Generation using GDB

During manual debugging, some semi-automatic tracing and logging options can be enabled without changing the binary. Note that GDB needs to halt the target for a small time for every breakpoint, therefore it will slow down the execution quite significantly. YOU MUST NOT USE THIS TOOL IN FLIGHT!

Here is a simple way to generate call graphs of a point of interest:

# Sample all calls to I2C for ~3min, automatically generates a SVG.
python3 -m emdbg.analyze.calltrace -v \
        --px4-dir path/to/PX4-Autopilot --target px4_fmu-v5x --jlink \
        --sample 180 --ex "break stm32_i2c_transfer"

Note that you can specify multiple tracepoints that can either be break- or watchpoints, and even contain conditions:

  • --ex "break function1" --ex "break function2": multiple breakpoints.
  • --ex "break function1 if variable >= 2": conditional breakpoint.
  • --ex "awatch variable": watchpoint for write and read access.
  • --ex "watch variable1" --ex "rwatch variable2": write and read watchpoints.
  • --ex "watch *(type*)0xdeadbeef": watchpoint on a cast memory location.

Note that GDB always reads the variable memory of a triggered watchpoint to determine and display what changed. Therefore, if you want to trap access to an entire peripheral, there will be significant side-effects from GDB reading registers!

Examples

SDMMC Register Access

python3 -m emdbg.patch nuttx_sdmmc_reg_access --apply -v
make px4_fmu-v5x
python3 -m emdbg.analyze.calltrace -v --target px4_fmu-v5x --type FileSystem \
    --sample 180 --ex load --ex "mon reset halt" --ex px4_calltrace_sdmmc --stlink
# Get a coffee, this is gonna take a while, then check for a calltrace_*.svg file
 1# Copyright (c) 2023, Auterion AG
 2# SPDX-License-Identifier: BSD-3-Clause
 3
 4"""
 5.. include:: calltrace.md
 6"""
 7
 8if __name__ == "__main__":
 9    import re
10    import emdbg
11    import argparse
12    from pathlib import Path
13    from .utils import read_gdb_log
14    from ..bench.fmu import _arguments
15
16    values = {
17        "FileSystem": emdbg.analyze.FileSystemBacktrace,
18        "SPI": emdbg.analyze.SpiBacktrace,
19        "I2C": emdbg.analyze.I2cBacktrace,
20        "CAN": emdbg.analyze.CanBacktrace,
21        "UART": emdbg.analyze.UartBacktrace,
22        "Semaphore": emdbg.analyze.SemaphoreBacktrace,
23        "Generic": emdbg.analyze.Backtrace,
24    }
25    def _modifier(parser):
26        parser.add_argument(
27            "--log-prefix",
28            type=Path,
29            default="calltrace",
30            help="Log file name.")
31        parser.add_argument(
32            "--sample",
33            default=60,
34            type=int,
35            help="Sample time in seconds.")
36        parser.add_argument(
37            "--type",
38            choices=values.keys(),
39            default="Generic",
40            help="The backtrace class to use.")
41
42    args, backend = _arguments("Generate call graphs of a function or memory location", _modifier)
43    print(f"Logging for: {', '.join(args.commands)}")
44
45    calltrace = "".join(filter(lambda c: re.match(r"[\w\d]", c), '_'.join(args.commands)))
46    calltrace = Path(f"{args.log_prefix}_{calltrace}.txt")
47    with emdbg.bench.fmu(args.px4_dir, args.target, backend=backend, upload=False) as bench:
48        for ex in args.commands:
49            bench.gdb.execute(ex)
50            if any(ex.startswith(b) for b in ["break ", "watch ", "awatch ", "rwatch "]):
51                bench.gdb.execute("px4_commands_backtrace")
52        bench.gdb.execute(f"px4_log_start {calltrace}")
53        bench.gdb.continue_nowait()
54
55        bench.sleep(args.sample)
56
57        # halt the debugger and stop logging
58        bench.gdb.interrupt_and_wait()
59        bench.gdb.execute("px4_log_stop")
60
61        backtraces = re.split(r"(?:Breakpoint|Hardware .*?watchpoint) \d", read_gdb_log(calltrace)[20:])
62        emdbg.analyze.callgraph_from_backtrace(backtraces, values.get(args.type),
63                                               output_graphviz=calltrace.with_suffix(".svg"))