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"))