emdbg.debug.px4.device
1# Copyright (c) 2023, Auterion AG 2# SPDX-License-Identifier: BSD-3-Clause 3 4from __future__ import annotations 5from . import utils 6from dataclasses import dataclass 7from .base import Base 8from functools import cached_property 9from pathlib import Path 10import re, time 11import rich.box 12from rich.text import Text 13from rich.table import Table 14 15 16class Device(Base): 17 """ 18 Accessors for the state of ARM Cortex-M CPU, STM32 identifiers, uptime, and 19 NuttX kernel internals. 20 """ 21 _HRT_CNT = 0x4001_0424 22 _SCS_ICTR = 0xE000_E004 23 _SCB_CPUID = 0xE000_ED00 24 _SCB_VTOR = 0xE000_ED08 25 _SCS_SHPR = 0xE000_ED18 26 _NVIC_ISER = 0xE000_E100 27 _NVIC_ISPR = 0xE000_E200 28 _NVIC_IABR = 0xE000_E300 29 _NVIC_IPR = 0xE000_E400 30 31 # Try them all until one of them does not return zero 32 _DBG_IDCODE = [0xE004_2000, 0x5C00_1000] 33 34 @cached_property 35 def _IDCODE_REVISION(self): 36 return { 37 0x1000: "A", 38 0x1001: "Z", 39 0x1003: "Y", 40 0x1007: "4", 41 0x2001: "X", 42 0x2003: "Y", 43 }.get(self.rev, "") 44 45 @cached_property 46 def _IDCODE_DEVICE(self): 47 return { 48 0x0415: "STM32L47/L48xx", 49 0x0451: "STM32F76xx, STM32F77xx", 50 0x0450: "STM32H742, STM32H743/753, STM32H750", 51 0x0483: "STM32H723/733, STM32H725/735, STM32H730", 52 }.get(self.devid, "Unknown") 53 54 @cached_property 55 def _GPIOS(self): 56 return { 57 0x0415: list(range(0x4800_0000, 0x4800_2000, 0x400)), 58 # FMUv5x/v6x don't use ports J and K 59 0x0451: list(range(0x4002_0000, 0x4002_2001, 0x400)), 60 0x0450: list(range(0x5802_0000, 0x5802_2001, 0x400)), 61 0x0483: list(range(0x5802_0000, 0x5802_2C01, 0x400)), 62 }.get(self.devid, []) 63 64 @cached_property 65 def _SYSTEM_MEMORIES(self): 66 mems = [(0xE000_0000, 0x10_0000)] 67 mems += { 68 0x0415: [(0x1FFF_7500, 0x100)], 69 0x0451: [(0x1FF0_F420, 0x100), (0x1FFF_7B00, 0x100)], 70 **dict.fromkeys([0x0450, 0x0483], 71 [(0x1FF1_E800, 0x100), (0x58000524, 4)]) 72 }.get(self.devid, []) 73 return mems 74 75 @cached_property 76 def _PERIPHERALS(self): 77 if self._SVD_FILE is None: return [] 78 from cmsis_svd.parser import SVDParser 79 device = SVDParser.for_xml_file(self._SVD_FILE).get_device() 80 registers = [(per.base_address + r.address_offset, 81 per.base_address + r.address_offset + r.size//8) 82 for per in device.peripherals for r in per.registers] 83 registers.sort() 84 ranges = [] 85 cluster = 512 86 current = registers[0] 87 for (regl, regh) in registers[1:]: 88 if (current[1] + cluster) > regh: 89 current = (current[0], regh) 90 else: 91 ranges.append(current) 92 current = (regl, regh) 93 ranges.append(current) 94 return [(r[0], r[1] - r[0]) for r in ranges] 95 96 @cached_property 97 def _MEMORIES(self): 98 mems = { 99 0x0415: [ 100 (0x1000_0000, 0x20000), # SRAM2 101 (0x2000_0000, 0x20000), # SRAM1 102 ], 103 0x0451: [ 104 # (0x0000_0000, 0x04000), # ITCM 105 (0x2000_0000, 0x80000), # DTCM, SRAM1, SRAM2 106 ], 107 **dict.fromkeys([0x0450, 0x0483], [ 108 # (0x0000_0000, 0x10000), # ITCM 109 (0x2000_0000, 0x20000), # DTCM 110 (0x2400_0000, 0x80000), # AXI_SRAM 111 (0x3000_0000, 0x48000), # SRAM1, SRAM2, SRAM3 112 (0x3800_0000, 0x10000), # SRAM4 113 (0x3880_0000, 0x01000), # Backup SRAM 114 (0x5C00_1000, 4), # IDCODE 115 ]), 116 }.get(self.devid, []) 117 mems += self._PERIPHERALS 118 mems += self._SYSTEM_MEMORIES 119 return sorted(mems) 120 121 @cached_property 122 def _SVD_FILE(self): 123 return { 124 # 0x0415: Path(__file__).parents[1] / "data/STM32L4x6.svd", 125 0x0451: Path(__file__).parents[1] / "data/STM32F765.svd", 126 0x0450: Path(__file__).parents[1] / "data/STM32H753.svd", 127 # 0x0483: Path(__file__).parents[1] / "data/STM32H7x3.svd", 128 }.get(self.devid) 129 130 @dataclass 131 class Gpio: 132 port: str 133 index: int 134 moder: int 135 otyper: int 136 speedr: int 137 pupdr: int 138 idr: int 139 odr: int 140 lockr: int 141 afr: int 142 143 @dataclass 144 class Irq: 145 index: int 146 """Index of the IRQ starting at zero""" 147 handler: "gdb.Block" 148 """Function handler of the IRQ""" 149 priority: int 150 """Priority of the IRQ""" 151 is_enabled: bool 152 """Is interrupt enabled""" 153 is_pending: bool 154 """Is interrupt pending""" 155 is_active: bool 156 """Is interrupt active""" 157 158 @dataclass 159 class IrqNuttX(Irq): 160 arg: "gdb.Value" 161 """Optional argument passed to the IRQ handler""" 162 163 def __init__(self, gdb): 164 super().__init__(gdb) 165 self.architecture = self._arch.name() 166 self._hrt_counter = self.addr_ptr(self._HRT_CNT, "uint16_t") 167 168 @cached_property 169 def _hrt_base(self): 170 return self.lookup_static_symbol_in_function_ptr("base_time", "hrt_absolute_time") 171 172 def _read_bits(self, base, index, size): 173 shift = ((index * size) % 64) 174 offset = (index * size) // 64 175 mask = ((1 << size) - 1) << shift 176 bits = self.read_memory(base + offset, 8).cast("Q")[0] 177 return (bits & mask) >> shift 178 179 def _priority(self, index): 180 if index == -15: return -3 181 if index == -14: return -2 182 if index == -13: return -1 183 if index < 0: return self._read_bits(self._SCS_SHPR, index + 16, 8) 184 return self._read_bits(self._NVIC_IPR, index, 8) 185 186 def _enabled(self, index): 187 # if index < 0: return self._read_bits1(self._SCS_, index); 188 return self._read_bits(self._NVIC_ISER, index, 1) 189 190 def _pending(self, index): 191 return self._read_bits(self._NVIC_ISPR, index, 1) 192 193 def _active(self, index): 194 return self._read_bits(self._NVIC_IABR, index, 1) 195 196 @cached_property 197 def uptime(self) -> int: 198 """The uptime in microseconds as read from the NuttX high-resolution timer (HRT)""" 199 if self._hrt_base is None: return 0 200 return int(self._hrt_base.dereference() + self._hrt_counter.dereference()) 201 202 @cached_property 203 def max_interrupts(self) -> int: 204 """Maximum number of interrupts implemented on this device""" 205 return 32 * (self.read_uint(self._SCS_ICTR, 4) + 1) 206 207 @cached_property 208 def vector_table(self) -> list[Irq]: 209 """The vector table as stored inside the SCB->VTOR""" 210 # SCS = 0xE000E000, SCB = 0xE000ED00, SCB->VTOR = 0xE000ED08 211 # TODO: Cortex-M0 has no VTOR, Cortex-M0+ has one if implemented 212 vtor = self._gdb.Value(self._SCB_VTOR).cast(self.uint32.pointer()) 213 vtor = vtor.dereference().cast(self.uint32.pointer()) 214 entries = [] 215 for ii in range(self.max_interrupts): 216 ii -= 16 217 entries.append(self.Irq(ii, self.block(vtor[ii+16]), self._priority(ii), 218 self._enabled(ii), self._pending(ii), self._active(ii))) 219 return entries 220 221 @cached_property 222 def vector_table_nuttx(self) -> dict[int, IrqNuttX]: 223 """ 224 The index, handler and argument of the NuttX interrupt table if the 225 handler is not empty and is not `irq_unexpected_isr`. 226 """ 227 g_irqvector = self._gdb.lookup_global_symbol("g_irqvector") 228 vectors = {} 229 for ii, vector in enumerate(utils.gdb_iter(g_irqvector)): 230 if handler := vector["handler"]: 231 block = self.block(handler) 232 if block.function and block.function.name == "irq_unexpected_isr": 233 continue 234 ii -= 16 235 vectors[ii] = self.IrqNuttX(ii, block, self._priority(ii), 236 self._enabled(ii), self._pending(ii), 237 self._active(ii), vector["arg"]) 238 return vectors 239 240 @property 241 def gpios(self) -> list[Gpio]: 242 """List of GPIOs as read from the device.""" 243 result = [] 244 for jj, gper_addr in enumerate(self._GPIOS): 245 port = chr(ord("A") + jj) 246 gper = self.read_memory(gper_addr, 0x28).cast("I") 247 if gper[0] == 0xffffffff: continue 248 for ii in range(16): 249 _1v = lambda index: (gper[index] >> ii) & 0x1 250 _2v = lambda index: (gper[index] >> ii*2) & 0x3 251 moder, speedr, pupdr = _2v(0), _2v(2), _2v(3) 252 otyper, idr, odr, lockr = _1v(1), _1v(4), _1v(5), _1v(7) 253 afr = ((gper[9] << 32 | gper[8]) >> ii*4) & 0xf 254 g = self.Gpio(port, ii, moder, otyper, speedr, pupdr, idr, odr, lockr, afr) 255 result.append(g) 256 return result 257 258 def coredump(self, memories: list[tuple[int, int]] = None, with_flash: bool = False) -> tuple[str, int]: 259 """ 260 Reads the memories and registers and returns them as a formatted string 261 that is compatible with CrashDebug (see `emdbg.debug.crashdebug`). 262 263 :param memories: list of memory ranges (start, size) to dump 264 :param with_flash: also dump the entire non-volatile storage 265 :return: coredump formatted as string and coredump size 266 """ 267 if memories is None: 268 memories = self._MEMORIES 269 if with_flash and self.flash_size: 270 memories += [(0x0800_0000, self.flash_size)] 271 lines = [] 272 total_size = 0 273 for addr, size in memories: 274 try: 275 data = self.read_memory(addr, size).cast("I") 276 total_size += size 277 except Exception as e: 278 print(f"Failed to read whole range [{addr:#x}, {addr+size:#x}]! {e}") 279 data = [] 280 for offset in range(0, size, 4): 281 try: 282 data.append(self.read_memory(addr + offset, 4).cast("I")[0]) 283 total_size += 4 284 except Exception as e: 285 print(f"Failed to read uint32_t {addr+offset:#x}! {e}") 286 data.append(0) 287 continue 288 for ii, values in enumerate(utils.chunks(data, 4, 0)): 289 values = (hex(v & 0xffffffff) for v in values) 290 lines.append(f"{hex(addr + ii * 16)}: {' '.join(values)}") 291 292 for name, value in self.registers.items(): 293 if re.match(r"d\d+", name): 294 lines.append(f"{name:<28} {float(value):<28} (raw {value & 0xffffffffffffffff:#x})") 295 elif re.match(r"s\d+", name): 296 lines.append(f"{name:<28} {float(value):<28} (raw {value & 0xffffffff:#x})") 297 else: 298 lines.append(f"{name:<28} {hex(value & 0xffffffff):<28} {int(value)}") 299 300 return "\n".join(lines), total_size 301 302 @cached_property 303 def cpuid(self) -> int: 304 """The SCB->CPUID value""" 305 return self.read_uint(self._SCB_CPUID, 4) 306 307 @cached_property 308 def idcode(self) -> int: 309 """The STM32-specific DBG->IDCODE value""" 310 for addr in self._DBG_IDCODE: 311 if idcode := (self.read_uint(addr, 4) & 0xffff0fff): 312 return idcode 313 return 0 314 315 @cached_property 316 def flash_size(self) -> int: 317 """The FLASH size in bytes""" 318 addr = { 319 0x0415: 0x1FFF_75E0, 320 0x0451: 0x1FF0_F442, 321 0x0450: 0x1FF1_E880, 322 0x0483: 0x1FF1_E880, 323 }.get(self.devid) 324 if addr is None: return 0 325 return self.read_uint(addr, 2) * 1024 326 327 @cached_property 328 def line(self) -> str: 329 """The device family and name""" 330 if self.devid == 0x0483: 331 return self.read_string(0x1FF1_E8C0, length=4)[::-1] 332 return self._IDCODE_DEVICE 333 334 @cached_property 335 def uid(self) -> int: 336 """The device's unique identifier as a big integer""" 337 addr = { 338 0x0415: 0x1FFF_7590, 339 0x0451: 0x1FF0_F420, 340 0x0450: 0x1FF1_E800, 341 0x0483: 0x1FF1_E800, 342 }.get(self.devid) 343 if addr is None: return 0 344 return int.from_bytes(self.read_memory(addr, 3*4).tobytes(), "little") 345 346 @cached_property 347 def package(self) -> str: 348 """The device package""" 349 350 if self.devid == 0x0415: 351 return { 352 0b00000: "LQFP64", 353 0b00010: "LQFP100", 354 0b00011: "UFBGA132", 355 0b00100: "LQFP144, UFBGA144, WLCSP72, WLCSP81 or WLCSP86", 356 0b10000: "UFBGA169, WLCSP115", 357 0b10001: "WLCSP100", 358 }.get(self.read_uint(0x1FFF_7500, 1) & 0x1f, "Reserved") 359 if self.devid == 0x0451: 360 return { 361 0b111: "LQFP208 or TFBGA216", 362 0b110: "LQFP208 or TFBGA216", 363 0b101: "LQFP176", 364 0b100: "LQFP176", 365 0b011: "WLCSP180", 366 0b010: "LQFP144 ", 367 0b001: "LQFP100", 368 }.get(self.read_uint(0x1FFF_7BF1, 1) & 0x7, "Reserved") 369 if self.devid == 0x0450: 370 return { 371 0b0000: "LQFP100", 372 0b0010: "TQFP144", 373 0b0101: "TQFP176/UFBGA176", 374 0b1000: "LQFP208/TFBGA240", 375 }.get(self.read_uint(0x58000524, 1) & 0xf, "All pads enabled") 376 if self.devid == 0x0483: 377 return { 378 0b0000: "VFQFPN68 Industrial", 379 0b0001: "LQFP100 Legacy / TFBGA100 Legacy", 380 0b0010: "LQFP100 Industrial", 381 0b0011: "TFBGA100 Industrial", 382 0b0100: "WLCSP115 Industrial", 383 0b0101: "LQFP144 Legacy", 384 0b0110: "UFBGA144 Legacy", 385 0b0111: "LQFP144 Industrial", 386 0b1000: "UFBGA169 Industrial", 387 0b1001: "UFBGA176+25 Industrial", 388 0b1010: "LQFP176 Industrial", 389 }.get(self.read_uint(0x58000524, 1) & 0xf, "All pads enabled") 390 return "?" 391 392 @cached_property 393 def devid(self) -> int: 394 """The STM32-specific device id part of DBG->IDCODE""" 395 return self.idcode & 0xfff 396 397 @cached_property 398 def rev(self) -> int: 399 """The STM32-specific revision part of DBG->IDCODE""" 400 return self.idcode >> 16 401 402 403def discover(gdb, hint=None) -> Table: 404 """ 405 Reads the device identifier registers and outputs a human readable string if possible. 406 :return: description string 407 """ 408 dev = Device(gdb) 409 table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD) 410 table.add_column("Device") 411 table.add_column("Revision") 412 table.add_column("Flash") 413 table.add_column("Package") 414 table.add_column("UID") 415 table.add_column("Hint") 416 table.add_row(f"{dev.devid:#4x}: {dev.line}", 417 f"{dev.rev:#4x}: rev {dev._IDCODE_REVISION}", 418 f"{dev.flash_size//1024}kB", dev.package, 419 f"{dev.uid:x}", 420 hint or "") 421 return table 422 423 424def all_registers(gdb) -> dict[str, int]: 425 """Return a dictionary of register name and values""" 426 return Device(gdb).registers 427 428 429def all_registers_as_table(gdb, columns: int = 3) -> Table: 430 """ 431 Format the Cortex-M CPU+FPU registers and their values into a simple table. 432 433 :param columns: The number of columns to spread the registers across. 434 """ 435 table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD) 436 table.add_column("Name") 437 table.add_column("Hexadecimal", justify="right") 438 table.add_column("Decimal", justify="right") 439 table.add_column("Binary", justify="right") 440 for reg, value in all_registers(gdb).items(): 441 table.add_row(reg, f"{value:x}", str(value), f"{value:b}") 442 return table 443 444 445def vector_table(gdb) -> dict[int, Device.IrqNuttX]: 446 """Return a dictionary of NuttX interrupt numbers and their handlers with arguments""" 447 return Device(gdb).vector_table_nuttx 448 449 450def vector_table_as_table(gdb, columns: int = 1) -> Table: 451 """ 452 Format the NuttX interrupts and their handlers with arguments into a simple table. 453 454 :param columns: The number of columns to spread the interrupts across. 455 """ 456 def _fname(block): 457 count = 0 458 while block and block.function is None: 459 block = block.superblock 460 count += 1 461 if block.function: 462 return f"{block.function.name} {'^' * count}" 463 return f"{block} ?" 464 465 table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD) 466 table.add_column("IRQ", justify="right") 467 table.add_column("EPA") 468 table.add_column("Prio") 469 table.add_column("Address") 470 table.add_column("Function") 471 table.add_column("Argument") 472 for idx, irq in vector_table(gdb).items(): 473 table.add_row(str(idx), 474 ("e" if irq.is_enabled else " ") + 475 ("p" if irq.is_pending else " ") + 476 ("a" if irq.is_active else " "), 477 f"{irq.priority:x}", hex(irq.handler.start), 478 _fname(irq.handler), str(irq.arg) or "", 479 style="bold blue" if irq.is_active else None) 480 return table 481 482 483def coredump(gdb, memories: list[tuple[int, int]] = None, 484 with_flash: bool = False, filename: Path = None): 485 """ 486 Dumps the memories and register state into a file. 487 488 :param memories: List of (addr, size) tuples that describe which memories to dump. 489 :param with_flash: Also dump the entire non-volatile storage. 490 :param filename: Target filename, or `coredump_{datetime}.txt` by default. 491 """ 492 if filename is None: 493 filename = utils.add_datetime("coredump.txt") 494 print("Starting coredump...", flush=True) 495 start = time.perf_counter() 496 output, size = Device(gdb).coredump(memories, with_flash) 497 Path(filename).write_text(output) 498 end = time.perf_counter() 499 print(f"Dumped {size//1000}kB in {(end - start):.1f}s ({int(size/((end - start)*1000))}kB/s)") 500 501 502def all_gpios_as_table(gdb, pinout: dict[str, tuple[str, str]] = None, 503 fn_filter = None, sort_by = None, columns: int = 2) -> Table: 504 """ 505 Reads the GPIO peripheral space and prints a table of the individual pin 506 configuration, input/output state and alternate function. If a pinout is 507 provided, the pins will be matched with their names and functions. 508 509 Config: Condensed view with omitted defaults. 510 MODER: IN=Input, OUT=Output, ALT=Alternate Function, AN=Analog, 511 OTYPER: +OD=OpenDrain, (+PP=PushPull omitted), 512 PUPDR: +PU=PullUp, +PD=PullDown, (+FL=Floating omitted), 513 SPEEDR: +M=Medium, +H=High, +VH=Very High, (+L=Low omitted). 514 515 Input (IDR), Output (ODR): _=Low, ^=High 516 Input only shown for IN, OUT, and ALT. 517 Output only shown for OUT. 518 519 Alternate Function (AFR): only shown when config is ALT. 520 Consult the datasheet for device-specific mapping. 521 522 :param pinout: A map of port+index -> (name, function). 523 :param pin_filter: A filter function that gets passed the entire row as a 524 list and returns `True` if row is accepted. 525 :param sort_by: The name of the column to sort by: default is `PIN`. 526 :param columns: The number of columns to spread the GPIO table across. 527 """ 528 rows = [] 529 for gpio in Device(gdb).gpios: 530 name = f"{gpio.port}{gpio.index}" 531 if pinout is not None: 532 list(pinout.get(name, [""]*2)) 533 config = "".join([["IN", "OUT", "ALT", "AN"][gpio.moder], 534 ["", "+OD"][gpio.otyper], 535 ["", "+PU", "+PD", "+!!"][gpio.pupdr], 536 ["", "+M", "+H", "+VH"][gpio.speedr], 537 ["", "+L"][gpio.lockr]]) 538 idr = "" if gpio.moder == 3 else ["_", "^"][gpio.idr] 539 odr = ["_", "^"][gpio.odr] if gpio.moder == 1 else "" 540 afr = str(gpio.afr) if gpio.moder == 2 else "" 541 row = [name, config, idr, odr, afr] 542 if pinout is not None: 543 row += list(pinout.get(name, [""]*2)) 544 if fn_filter is None or fn_filter(row): 545 rows.append(row) 546 if sort_by is not None: 547 def _sort(row): 548 pinf = ord(row[0][0]) * 100 + int(row[0][1:]) 549 if sort_by == 0: return pinf 550 if sort_by == 4: return -1 if row[4] == "" else row[4] 551 if sort_by == 5: return (row[5], row[6], pinf) 552 if sort_by == 6: return (row[6], row[5], pinf) 553 return row[sort_by] 554 rows.sort(key=_sort) 555 556 table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD) 557 table.add_column("Pin") 558 table.add_column("Config") 559 table.add_column("I") 560 table.add_column("O") 561 table.add_column("AF", justify="right") 562 if pinout is not None: 563 table.add_column("Name") 564 table.add_column("Function") 565 for row in rows: 566 table.add_row(*row) 567 return table
17class Device(Base): 18 """ 19 Accessors for the state of ARM Cortex-M CPU, STM32 identifiers, uptime, and 20 NuttX kernel internals. 21 """ 22 _HRT_CNT = 0x4001_0424 23 _SCS_ICTR = 0xE000_E004 24 _SCB_CPUID = 0xE000_ED00 25 _SCB_VTOR = 0xE000_ED08 26 _SCS_SHPR = 0xE000_ED18 27 _NVIC_ISER = 0xE000_E100 28 _NVIC_ISPR = 0xE000_E200 29 _NVIC_IABR = 0xE000_E300 30 _NVIC_IPR = 0xE000_E400 31 32 # Try them all until one of them does not return zero 33 _DBG_IDCODE = [0xE004_2000, 0x5C00_1000] 34 35 @cached_property 36 def _IDCODE_REVISION(self): 37 return { 38 0x1000: "A", 39 0x1001: "Z", 40 0x1003: "Y", 41 0x1007: "4", 42 0x2001: "X", 43 0x2003: "Y", 44 }.get(self.rev, "") 45 46 @cached_property 47 def _IDCODE_DEVICE(self): 48 return { 49 0x0415: "STM32L47/L48xx", 50 0x0451: "STM32F76xx, STM32F77xx", 51 0x0450: "STM32H742, STM32H743/753, STM32H750", 52 0x0483: "STM32H723/733, STM32H725/735, STM32H730", 53 }.get(self.devid, "Unknown") 54 55 @cached_property 56 def _GPIOS(self): 57 return { 58 0x0415: list(range(0x4800_0000, 0x4800_2000, 0x400)), 59 # FMUv5x/v6x don't use ports J and K 60 0x0451: list(range(0x4002_0000, 0x4002_2001, 0x400)), 61 0x0450: list(range(0x5802_0000, 0x5802_2001, 0x400)), 62 0x0483: list(range(0x5802_0000, 0x5802_2C01, 0x400)), 63 }.get(self.devid, []) 64 65 @cached_property 66 def _SYSTEM_MEMORIES(self): 67 mems = [(0xE000_0000, 0x10_0000)] 68 mems += { 69 0x0415: [(0x1FFF_7500, 0x100)], 70 0x0451: [(0x1FF0_F420, 0x100), (0x1FFF_7B00, 0x100)], 71 **dict.fromkeys([0x0450, 0x0483], 72 [(0x1FF1_E800, 0x100), (0x58000524, 4)]) 73 }.get(self.devid, []) 74 return mems 75 76 @cached_property 77 def _PERIPHERALS(self): 78 if self._SVD_FILE is None: return [] 79 from cmsis_svd.parser import SVDParser 80 device = SVDParser.for_xml_file(self._SVD_FILE).get_device() 81 registers = [(per.base_address + r.address_offset, 82 per.base_address + r.address_offset + r.size//8) 83 for per in device.peripherals for r in per.registers] 84 registers.sort() 85 ranges = [] 86 cluster = 512 87 current = registers[0] 88 for (regl, regh) in registers[1:]: 89 if (current[1] + cluster) > regh: 90 current = (current[0], regh) 91 else: 92 ranges.append(current) 93 current = (regl, regh) 94 ranges.append(current) 95 return [(r[0], r[1] - r[0]) for r in ranges] 96 97 @cached_property 98 def _MEMORIES(self): 99 mems = { 100 0x0415: [ 101 (0x1000_0000, 0x20000), # SRAM2 102 (0x2000_0000, 0x20000), # SRAM1 103 ], 104 0x0451: [ 105 # (0x0000_0000, 0x04000), # ITCM 106 (0x2000_0000, 0x80000), # DTCM, SRAM1, SRAM2 107 ], 108 **dict.fromkeys([0x0450, 0x0483], [ 109 # (0x0000_0000, 0x10000), # ITCM 110 (0x2000_0000, 0x20000), # DTCM 111 (0x2400_0000, 0x80000), # AXI_SRAM 112 (0x3000_0000, 0x48000), # SRAM1, SRAM2, SRAM3 113 (0x3800_0000, 0x10000), # SRAM4 114 (0x3880_0000, 0x01000), # Backup SRAM 115 (0x5C00_1000, 4), # IDCODE 116 ]), 117 }.get(self.devid, []) 118 mems += self._PERIPHERALS 119 mems += self._SYSTEM_MEMORIES 120 return sorted(mems) 121 122 @cached_property 123 def _SVD_FILE(self): 124 return { 125 # 0x0415: Path(__file__).parents[1] / "data/STM32L4x6.svd", 126 0x0451: Path(__file__).parents[1] / "data/STM32F765.svd", 127 0x0450: Path(__file__).parents[1] / "data/STM32H753.svd", 128 # 0x0483: Path(__file__).parents[1] / "data/STM32H7x3.svd", 129 }.get(self.devid) 130 131 @dataclass 132 class Gpio: 133 port: str 134 index: int 135 moder: int 136 otyper: int 137 speedr: int 138 pupdr: int 139 idr: int 140 odr: int 141 lockr: int 142 afr: int 143 144 @dataclass 145 class Irq: 146 index: int 147 """Index of the IRQ starting at zero""" 148 handler: "gdb.Block" 149 """Function handler of the IRQ""" 150 priority: int 151 """Priority of the IRQ""" 152 is_enabled: bool 153 """Is interrupt enabled""" 154 is_pending: bool 155 """Is interrupt pending""" 156 is_active: bool 157 """Is interrupt active""" 158 159 @dataclass 160 class IrqNuttX(Irq): 161 arg: "gdb.Value" 162 """Optional argument passed to the IRQ handler""" 163 164 def __init__(self, gdb): 165 super().__init__(gdb) 166 self.architecture = self._arch.name() 167 self._hrt_counter = self.addr_ptr(self._HRT_CNT, "uint16_t") 168 169 @cached_property 170 def _hrt_base(self): 171 return self.lookup_static_symbol_in_function_ptr("base_time", "hrt_absolute_time") 172 173 def _read_bits(self, base, index, size): 174 shift = ((index * size) % 64) 175 offset = (index * size) // 64 176 mask = ((1 << size) - 1) << shift 177 bits = self.read_memory(base + offset, 8).cast("Q")[0] 178 return (bits & mask) >> shift 179 180 def _priority(self, index): 181 if index == -15: return -3 182 if index == -14: return -2 183 if index == -13: return -1 184 if index < 0: return self._read_bits(self._SCS_SHPR, index + 16, 8) 185 return self._read_bits(self._NVIC_IPR, index, 8) 186 187 def _enabled(self, index): 188 # if index < 0: return self._read_bits1(self._SCS_, index); 189 return self._read_bits(self._NVIC_ISER, index, 1) 190 191 def _pending(self, index): 192 return self._read_bits(self._NVIC_ISPR, index, 1) 193 194 def _active(self, index): 195 return self._read_bits(self._NVIC_IABR, index, 1) 196 197 @cached_property 198 def uptime(self) -> int: 199 """The uptime in microseconds as read from the NuttX high-resolution timer (HRT)""" 200 if self._hrt_base is None: return 0 201 return int(self._hrt_base.dereference() + self._hrt_counter.dereference()) 202 203 @cached_property 204 def max_interrupts(self) -> int: 205 """Maximum number of interrupts implemented on this device""" 206 return 32 * (self.read_uint(self._SCS_ICTR, 4) + 1) 207 208 @cached_property 209 def vector_table(self) -> list[Irq]: 210 """The vector table as stored inside the SCB->VTOR""" 211 # SCS = 0xE000E000, SCB = 0xE000ED00, SCB->VTOR = 0xE000ED08 212 # TODO: Cortex-M0 has no VTOR, Cortex-M0+ has one if implemented 213 vtor = self._gdb.Value(self._SCB_VTOR).cast(self.uint32.pointer()) 214 vtor = vtor.dereference().cast(self.uint32.pointer()) 215 entries = [] 216 for ii in range(self.max_interrupts): 217 ii -= 16 218 entries.append(self.Irq(ii, self.block(vtor[ii+16]), self._priority(ii), 219 self._enabled(ii), self._pending(ii), self._active(ii))) 220 return entries 221 222 @cached_property 223 def vector_table_nuttx(self) -> dict[int, IrqNuttX]: 224 """ 225 The index, handler and argument of the NuttX interrupt table if the 226 handler is not empty and is not `irq_unexpected_isr`. 227 """ 228 g_irqvector = self._gdb.lookup_global_symbol("g_irqvector") 229 vectors = {} 230 for ii, vector in enumerate(utils.gdb_iter(g_irqvector)): 231 if handler := vector["handler"]: 232 block = self.block(handler) 233 if block.function and block.function.name == "irq_unexpected_isr": 234 continue 235 ii -= 16 236 vectors[ii] = self.IrqNuttX(ii, block, self._priority(ii), 237 self._enabled(ii), self._pending(ii), 238 self._active(ii), vector["arg"]) 239 return vectors 240 241 @property 242 def gpios(self) -> list[Gpio]: 243 """List of GPIOs as read from the device.""" 244 result = [] 245 for jj, gper_addr in enumerate(self._GPIOS): 246 port = chr(ord("A") + jj) 247 gper = self.read_memory(gper_addr, 0x28).cast("I") 248 if gper[0] == 0xffffffff: continue 249 for ii in range(16): 250 _1v = lambda index: (gper[index] >> ii) & 0x1 251 _2v = lambda index: (gper[index] >> ii*2) & 0x3 252 moder, speedr, pupdr = _2v(0), _2v(2), _2v(3) 253 otyper, idr, odr, lockr = _1v(1), _1v(4), _1v(5), _1v(7) 254 afr = ((gper[9] << 32 | gper[8]) >> ii*4) & 0xf 255 g = self.Gpio(port, ii, moder, otyper, speedr, pupdr, idr, odr, lockr, afr) 256 result.append(g) 257 return result 258 259 def coredump(self, memories: list[tuple[int, int]] = None, with_flash: bool = False) -> tuple[str, int]: 260 """ 261 Reads the memories and registers and returns them as a formatted string 262 that is compatible with CrashDebug (see `emdbg.debug.crashdebug`). 263 264 :param memories: list of memory ranges (start, size) to dump 265 :param with_flash: also dump the entire non-volatile storage 266 :return: coredump formatted as string and coredump size 267 """ 268 if memories is None: 269 memories = self._MEMORIES 270 if with_flash and self.flash_size: 271 memories += [(0x0800_0000, self.flash_size)] 272 lines = [] 273 total_size = 0 274 for addr, size in memories: 275 try: 276 data = self.read_memory(addr, size).cast("I") 277 total_size += size 278 except Exception as e: 279 print(f"Failed to read whole range [{addr:#x}, {addr+size:#x}]! {e}") 280 data = [] 281 for offset in range(0, size, 4): 282 try: 283 data.append(self.read_memory(addr + offset, 4).cast("I")[0]) 284 total_size += 4 285 except Exception as e: 286 print(f"Failed to read uint32_t {addr+offset:#x}! {e}") 287 data.append(0) 288 continue 289 for ii, values in enumerate(utils.chunks(data, 4, 0)): 290 values = (hex(v & 0xffffffff) for v in values) 291 lines.append(f"{hex(addr + ii * 16)}: {' '.join(values)}") 292 293 for name, value in self.registers.items(): 294 if re.match(r"d\d+", name): 295 lines.append(f"{name:<28} {float(value):<28} (raw {value & 0xffffffffffffffff:#x})") 296 elif re.match(r"s\d+", name): 297 lines.append(f"{name:<28} {float(value):<28} (raw {value & 0xffffffff:#x})") 298 else: 299 lines.append(f"{name:<28} {hex(value & 0xffffffff):<28} {int(value)}") 300 301 return "\n".join(lines), total_size 302 303 @cached_property 304 def cpuid(self) -> int: 305 """The SCB->CPUID value""" 306 return self.read_uint(self._SCB_CPUID, 4) 307 308 @cached_property 309 def idcode(self) -> int: 310 """The STM32-specific DBG->IDCODE value""" 311 for addr in self._DBG_IDCODE: 312 if idcode := (self.read_uint(addr, 4) & 0xffff0fff): 313 return idcode 314 return 0 315 316 @cached_property 317 def flash_size(self) -> int: 318 """The FLASH size in bytes""" 319 addr = { 320 0x0415: 0x1FFF_75E0, 321 0x0451: 0x1FF0_F442, 322 0x0450: 0x1FF1_E880, 323 0x0483: 0x1FF1_E880, 324 }.get(self.devid) 325 if addr is None: return 0 326 return self.read_uint(addr, 2) * 1024 327 328 @cached_property 329 def line(self) -> str: 330 """The device family and name""" 331 if self.devid == 0x0483: 332 return self.read_string(0x1FF1_E8C0, length=4)[::-1] 333 return self._IDCODE_DEVICE 334 335 @cached_property 336 def uid(self) -> int: 337 """The device's unique identifier as a big integer""" 338 addr = { 339 0x0415: 0x1FFF_7590, 340 0x0451: 0x1FF0_F420, 341 0x0450: 0x1FF1_E800, 342 0x0483: 0x1FF1_E800, 343 }.get(self.devid) 344 if addr is None: return 0 345 return int.from_bytes(self.read_memory(addr, 3*4).tobytes(), "little") 346 347 @cached_property 348 def package(self) -> str: 349 """The device package""" 350 351 if self.devid == 0x0415: 352 return { 353 0b00000: "LQFP64", 354 0b00010: "LQFP100", 355 0b00011: "UFBGA132", 356 0b00100: "LQFP144, UFBGA144, WLCSP72, WLCSP81 or WLCSP86", 357 0b10000: "UFBGA169, WLCSP115", 358 0b10001: "WLCSP100", 359 }.get(self.read_uint(0x1FFF_7500, 1) & 0x1f, "Reserved") 360 if self.devid == 0x0451: 361 return { 362 0b111: "LQFP208 or TFBGA216", 363 0b110: "LQFP208 or TFBGA216", 364 0b101: "LQFP176", 365 0b100: "LQFP176", 366 0b011: "WLCSP180", 367 0b010: "LQFP144 ", 368 0b001: "LQFP100", 369 }.get(self.read_uint(0x1FFF_7BF1, 1) & 0x7, "Reserved") 370 if self.devid == 0x0450: 371 return { 372 0b0000: "LQFP100", 373 0b0010: "TQFP144", 374 0b0101: "TQFP176/UFBGA176", 375 0b1000: "LQFP208/TFBGA240", 376 }.get(self.read_uint(0x58000524, 1) & 0xf, "All pads enabled") 377 if self.devid == 0x0483: 378 return { 379 0b0000: "VFQFPN68 Industrial", 380 0b0001: "LQFP100 Legacy / TFBGA100 Legacy", 381 0b0010: "LQFP100 Industrial", 382 0b0011: "TFBGA100 Industrial", 383 0b0100: "WLCSP115 Industrial", 384 0b0101: "LQFP144 Legacy", 385 0b0110: "UFBGA144 Legacy", 386 0b0111: "LQFP144 Industrial", 387 0b1000: "UFBGA169 Industrial", 388 0b1001: "UFBGA176+25 Industrial", 389 0b1010: "LQFP176 Industrial", 390 }.get(self.read_uint(0x58000524, 1) & 0xf, "All pads enabled") 391 return "?" 392 393 @cached_property 394 def devid(self) -> int: 395 """The STM32-specific device id part of DBG->IDCODE""" 396 return self.idcode & 0xfff 397 398 @cached_property 399 def rev(self) -> int: 400 """The STM32-specific revision part of DBG->IDCODE""" 401 return self.idcode >> 16
Accessors for the state of ARM Cortex-M CPU, STM32 identifiers, uptime, and NuttX kernel internals.
197 @cached_property 198 def uptime(self) -> int: 199 """The uptime in microseconds as read from the NuttX high-resolution timer (HRT)""" 200 if self._hrt_base is None: return 0 201 return int(self._hrt_base.dereference() + self._hrt_counter.dereference())
The uptime in microseconds as read from the NuttX high-resolution timer (HRT)
203 @cached_property 204 def max_interrupts(self) -> int: 205 """Maximum number of interrupts implemented on this device""" 206 return 32 * (self.read_uint(self._SCS_ICTR, 4) + 1)
Maximum number of interrupts implemented on this device
208 @cached_property 209 def vector_table(self) -> list[Irq]: 210 """The vector table as stored inside the SCB->VTOR""" 211 # SCS = 0xE000E000, SCB = 0xE000ED00, SCB->VTOR = 0xE000ED08 212 # TODO: Cortex-M0 has no VTOR, Cortex-M0+ has one if implemented 213 vtor = self._gdb.Value(self._SCB_VTOR).cast(self.uint32.pointer()) 214 vtor = vtor.dereference().cast(self.uint32.pointer()) 215 entries = [] 216 for ii in range(self.max_interrupts): 217 ii -= 16 218 entries.append(self.Irq(ii, self.block(vtor[ii+16]), self._priority(ii), 219 self._enabled(ii), self._pending(ii), self._active(ii))) 220 return entries
The vector table as stored inside the SCB->VTOR
222 @cached_property 223 def vector_table_nuttx(self) -> dict[int, IrqNuttX]: 224 """ 225 The index, handler and argument of the NuttX interrupt table if the 226 handler is not empty and is not `irq_unexpected_isr`. 227 """ 228 g_irqvector = self._gdb.lookup_global_symbol("g_irqvector") 229 vectors = {} 230 for ii, vector in enumerate(utils.gdb_iter(g_irqvector)): 231 if handler := vector["handler"]: 232 block = self.block(handler) 233 if block.function and block.function.name == "irq_unexpected_isr": 234 continue 235 ii -= 16 236 vectors[ii] = self.IrqNuttX(ii, block, self._priority(ii), 237 self._enabled(ii), self._pending(ii), 238 self._active(ii), vector["arg"]) 239 return vectors
The index, handler and argument of the NuttX interrupt table if the
handler is not empty and is not irq_unexpected_isr.
241 @property 242 def gpios(self) -> list[Gpio]: 243 """List of GPIOs as read from the device.""" 244 result = [] 245 for jj, gper_addr in enumerate(self._GPIOS): 246 port = chr(ord("A") + jj) 247 gper = self.read_memory(gper_addr, 0x28).cast("I") 248 if gper[0] == 0xffffffff: continue 249 for ii in range(16): 250 _1v = lambda index: (gper[index] >> ii) & 0x1 251 _2v = lambda index: (gper[index] >> ii*2) & 0x3 252 moder, speedr, pupdr = _2v(0), _2v(2), _2v(3) 253 otyper, idr, odr, lockr = _1v(1), _1v(4), _1v(5), _1v(7) 254 afr = ((gper[9] << 32 | gper[8]) >> ii*4) & 0xf 255 g = self.Gpio(port, ii, moder, otyper, speedr, pupdr, idr, odr, lockr, afr) 256 result.append(g) 257 return result
List of GPIOs as read from the device.
259 def coredump(self, memories: list[tuple[int, int]] = None, with_flash: bool = False) -> tuple[str, int]: 260 """ 261 Reads the memories and registers and returns them as a formatted string 262 that is compatible with CrashDebug (see `emdbg.debug.crashdebug`). 263 264 :param memories: list of memory ranges (start, size) to dump 265 :param with_flash: also dump the entire non-volatile storage 266 :return: coredump formatted as string and coredump size 267 """ 268 if memories is None: 269 memories = self._MEMORIES 270 if with_flash and self.flash_size: 271 memories += [(0x0800_0000, self.flash_size)] 272 lines = [] 273 total_size = 0 274 for addr, size in memories: 275 try: 276 data = self.read_memory(addr, size).cast("I") 277 total_size += size 278 except Exception as e: 279 print(f"Failed to read whole range [{addr:#x}, {addr+size:#x}]! {e}") 280 data = [] 281 for offset in range(0, size, 4): 282 try: 283 data.append(self.read_memory(addr + offset, 4).cast("I")[0]) 284 total_size += 4 285 except Exception as e: 286 print(f"Failed to read uint32_t {addr+offset:#x}! {e}") 287 data.append(0) 288 continue 289 for ii, values in enumerate(utils.chunks(data, 4, 0)): 290 values = (hex(v & 0xffffffff) for v in values) 291 lines.append(f"{hex(addr + ii * 16)}: {' '.join(values)}") 292 293 for name, value in self.registers.items(): 294 if re.match(r"d\d+", name): 295 lines.append(f"{name:<28} {float(value):<28} (raw {value & 0xffffffffffffffff:#x})") 296 elif re.match(r"s\d+", name): 297 lines.append(f"{name:<28} {float(value):<28} (raw {value & 0xffffffff:#x})") 298 else: 299 lines.append(f"{name:<28} {hex(value & 0xffffffff):<28} {int(value)}") 300 301 return "\n".join(lines), total_size
Reads the memories and registers and returns them as a formatted string
that is compatible with CrashDebug (see emdbg.debug.crashdebug).
Parameters
- memories: list of memory ranges (start, size) to dump
- with_flash: also dump the entire non-volatile storage
Returns
coredump formatted as string and coredump size
303 @cached_property 304 def cpuid(self) -> int: 305 """The SCB->CPUID value""" 306 return self.read_uint(self._SCB_CPUID, 4)
The SCB->CPUID value
308 @cached_property 309 def idcode(self) -> int: 310 """The STM32-specific DBG->IDCODE value""" 311 for addr in self._DBG_IDCODE: 312 if idcode := (self.read_uint(addr, 4) & 0xffff0fff): 313 return idcode 314 return 0
The STM32-specific DBG->IDCODE value
316 @cached_property 317 def flash_size(self) -> int: 318 """The FLASH size in bytes""" 319 addr = { 320 0x0415: 0x1FFF_75E0, 321 0x0451: 0x1FF0_F442, 322 0x0450: 0x1FF1_E880, 323 0x0483: 0x1FF1_E880, 324 }.get(self.devid) 325 if addr is None: return 0 326 return self.read_uint(addr, 2) * 1024
The FLASH size in bytes
328 @cached_property 329 def line(self) -> str: 330 """The device family and name""" 331 if self.devid == 0x0483: 332 return self.read_string(0x1FF1_E8C0, length=4)[::-1] 333 return self._IDCODE_DEVICE
The device family and name
335 @cached_property 336 def uid(self) -> int: 337 """The device's unique identifier as a big integer""" 338 addr = { 339 0x0415: 0x1FFF_7590, 340 0x0451: 0x1FF0_F420, 341 0x0450: 0x1FF1_E800, 342 0x0483: 0x1FF1_E800, 343 }.get(self.devid) 344 if addr is None: return 0 345 return int.from_bytes(self.read_memory(addr, 3*4).tobytes(), "little")
The device's unique identifier as a big integer
347 @cached_property 348 def package(self) -> str: 349 """The device package""" 350 351 if self.devid == 0x0415: 352 return { 353 0b00000: "LQFP64", 354 0b00010: "LQFP100", 355 0b00011: "UFBGA132", 356 0b00100: "LQFP144, UFBGA144, WLCSP72, WLCSP81 or WLCSP86", 357 0b10000: "UFBGA169, WLCSP115", 358 0b10001: "WLCSP100", 359 }.get(self.read_uint(0x1FFF_7500, 1) & 0x1f, "Reserved") 360 if self.devid == 0x0451: 361 return { 362 0b111: "LQFP208 or TFBGA216", 363 0b110: "LQFP208 or TFBGA216", 364 0b101: "LQFP176", 365 0b100: "LQFP176", 366 0b011: "WLCSP180", 367 0b010: "LQFP144 ", 368 0b001: "LQFP100", 369 }.get(self.read_uint(0x1FFF_7BF1, 1) & 0x7, "Reserved") 370 if self.devid == 0x0450: 371 return { 372 0b0000: "LQFP100", 373 0b0010: "TQFP144", 374 0b0101: "TQFP176/UFBGA176", 375 0b1000: "LQFP208/TFBGA240", 376 }.get(self.read_uint(0x58000524, 1) & 0xf, "All pads enabled") 377 if self.devid == 0x0483: 378 return { 379 0b0000: "VFQFPN68 Industrial", 380 0b0001: "LQFP100 Legacy / TFBGA100 Legacy", 381 0b0010: "LQFP100 Industrial", 382 0b0011: "TFBGA100 Industrial", 383 0b0100: "WLCSP115 Industrial", 384 0b0101: "LQFP144 Legacy", 385 0b0110: "UFBGA144 Legacy", 386 0b0111: "LQFP144 Industrial", 387 0b1000: "UFBGA169 Industrial", 388 0b1001: "UFBGA176+25 Industrial", 389 0b1010: "LQFP176 Industrial", 390 }.get(self.read_uint(0x58000524, 1) & 0xf, "All pads enabled") 391 return "?"
The device package
393 @cached_property 394 def devid(self) -> int: 395 """The STM32-specific device id part of DBG->IDCODE""" 396 return self.idcode & 0xfff
The STM32-specific device id part of DBG->IDCODE
398 @cached_property 399 def rev(self) -> int: 400 """The STM32-specific revision part of DBG->IDCODE""" 401 return self.idcode >> 16
The STM32-specific revision part of DBG->IDCODE
Inherited Members
- emdbg.debug.px4.base.Base
- register_names
- registers
- read_register
- write_register
- write_registers
- fix_nuttx_sp
- lookup_static_symbol_in_function
- lookup_static_symbol_ptr
- lookup_global_symbol_ptr
- lookup_static_symbol_in_function_ptr
- value_ptr
- addr_ptr
- read_memory
- write_memory
- read_uint
- read_int
- read_string
- symtab_line
- block
- description_at
- integer_type
- uint32
- int32
131 @dataclass 132 class Gpio: 133 port: str 134 index: int 135 moder: int 136 otyper: int 137 speedr: int 138 pupdr: int 139 idr: int 140 odr: int 141 lockr: int 142 afr: int
144 @dataclass 145 class Irq: 146 index: int 147 """Index of the IRQ starting at zero""" 148 handler: "gdb.Block" 149 """Function handler of the IRQ""" 150 priority: int 151 """Priority of the IRQ""" 152 is_enabled: bool 153 """Is interrupt enabled""" 154 is_pending: bool 155 """Is interrupt pending""" 156 is_active: bool 157 """Is interrupt active"""
159 @dataclass 160 class IrqNuttX(Irq): 161 arg: "gdb.Value" 162 """Optional argument passed to the IRQ handler"""
Inherited Members
404def discover(gdb, hint=None) -> Table: 405 """ 406 Reads the device identifier registers and outputs a human readable string if possible. 407 :return: description string 408 """ 409 dev = Device(gdb) 410 table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD) 411 table.add_column("Device") 412 table.add_column("Revision") 413 table.add_column("Flash") 414 table.add_column("Package") 415 table.add_column("UID") 416 table.add_column("Hint") 417 table.add_row(f"{dev.devid:#4x}: {dev.line}", 418 f"{dev.rev:#4x}: rev {dev._IDCODE_REVISION}", 419 f"{dev.flash_size//1024}kB", dev.package, 420 f"{dev.uid:x}", 421 hint or "") 422 return table
Reads the device identifier registers and outputs a human readable string if possible.
Returns
description string
425def all_registers(gdb) -> dict[str, int]: 426 """Return a dictionary of register name and values""" 427 return Device(gdb).registers
Return a dictionary of register name and values
430def all_registers_as_table(gdb, columns: int = 3) -> Table: 431 """ 432 Format the Cortex-M CPU+FPU registers and their values into a simple table. 433 434 :param columns: The number of columns to spread the registers across. 435 """ 436 table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD) 437 table.add_column("Name") 438 table.add_column("Hexadecimal", justify="right") 439 table.add_column("Decimal", justify="right") 440 table.add_column("Binary", justify="right") 441 for reg, value in all_registers(gdb).items(): 442 table.add_row(reg, f"{value:x}", str(value), f"{value:b}") 443 return table
Format the Cortex-M CPU+FPU registers and their values into a simple table.
Parameters
- columns: The number of columns to spread the registers across.
446def vector_table(gdb) -> dict[int, Device.IrqNuttX]: 447 """Return a dictionary of NuttX interrupt numbers and their handlers with arguments""" 448 return Device(gdb).vector_table_nuttx
Return a dictionary of NuttX interrupt numbers and their handlers with arguments
451def vector_table_as_table(gdb, columns: int = 1) -> Table: 452 """ 453 Format the NuttX interrupts and their handlers with arguments into a simple table. 454 455 :param columns: The number of columns to spread the interrupts across. 456 """ 457 def _fname(block): 458 count = 0 459 while block and block.function is None: 460 block = block.superblock 461 count += 1 462 if block.function: 463 return f"{block.function.name} {'^' * count}" 464 return f"{block} ?" 465 466 table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD) 467 table.add_column("IRQ", justify="right") 468 table.add_column("EPA") 469 table.add_column("Prio") 470 table.add_column("Address") 471 table.add_column("Function") 472 table.add_column("Argument") 473 for idx, irq in vector_table(gdb).items(): 474 table.add_row(str(idx), 475 ("e" if irq.is_enabled else " ") + 476 ("p" if irq.is_pending else " ") + 477 ("a" if irq.is_active else " "), 478 f"{irq.priority:x}", hex(irq.handler.start), 479 _fname(irq.handler), str(irq.arg) or "", 480 style="bold blue" if irq.is_active else None) 481 return table
Format the NuttX interrupts and their handlers with arguments into a simple table.
Parameters
- columns: The number of columns to spread the interrupts across.
484def coredump(gdb, memories: list[tuple[int, int]] = None, 485 with_flash: bool = False, filename: Path = None): 486 """ 487 Dumps the memories and register state into a file. 488 489 :param memories: List of (addr, size) tuples that describe which memories to dump. 490 :param with_flash: Also dump the entire non-volatile storage. 491 :param filename: Target filename, or `coredump_{datetime}.txt` by default. 492 """ 493 if filename is None: 494 filename = utils.add_datetime("coredump.txt") 495 print("Starting coredump...", flush=True) 496 start = time.perf_counter() 497 output, size = Device(gdb).coredump(memories, with_flash) 498 Path(filename).write_text(output) 499 end = time.perf_counter() 500 print(f"Dumped {size//1000}kB in {(end - start):.1f}s ({int(size/((end - start)*1000))}kB/s)")
Dumps the memories and register state into a file.
Parameters
- memories: List of (addr, size) tuples that describe which memories to dump.
- with_flash: Also dump the entire non-volatile storage.
- filename: Target filename, or
coredump_{datetime}.txtby default.
503def all_gpios_as_table(gdb, pinout: dict[str, tuple[str, str]] = None, 504 fn_filter = None, sort_by = None, columns: int = 2) -> Table: 505 """ 506 Reads the GPIO peripheral space and prints a table of the individual pin 507 configuration, input/output state and alternate function. If a pinout is 508 provided, the pins will be matched with their names and functions. 509 510 Config: Condensed view with omitted defaults. 511 MODER: IN=Input, OUT=Output, ALT=Alternate Function, AN=Analog, 512 OTYPER: +OD=OpenDrain, (+PP=PushPull omitted), 513 PUPDR: +PU=PullUp, +PD=PullDown, (+FL=Floating omitted), 514 SPEEDR: +M=Medium, +H=High, +VH=Very High, (+L=Low omitted). 515 516 Input (IDR), Output (ODR): _=Low, ^=High 517 Input only shown for IN, OUT, and ALT. 518 Output only shown for OUT. 519 520 Alternate Function (AFR): only shown when config is ALT. 521 Consult the datasheet for device-specific mapping. 522 523 :param pinout: A map of port+index -> (name, function). 524 :param pin_filter: A filter function that gets passed the entire row as a 525 list and returns `True` if row is accepted. 526 :param sort_by: The name of the column to sort by: default is `PIN`. 527 :param columns: The number of columns to spread the GPIO table across. 528 """ 529 rows = [] 530 for gpio in Device(gdb).gpios: 531 name = f"{gpio.port}{gpio.index}" 532 if pinout is not None: 533 list(pinout.get(name, [""]*2)) 534 config = "".join([["IN", "OUT", "ALT", "AN"][gpio.moder], 535 ["", "+OD"][gpio.otyper], 536 ["", "+PU", "+PD", "+!!"][gpio.pupdr], 537 ["", "+M", "+H", "+VH"][gpio.speedr], 538 ["", "+L"][gpio.lockr]]) 539 idr = "" if gpio.moder == 3 else ["_", "^"][gpio.idr] 540 odr = ["_", "^"][gpio.odr] if gpio.moder == 1 else "" 541 afr = str(gpio.afr) if gpio.moder == 2 else "" 542 row = [name, config, idr, odr, afr] 543 if pinout is not None: 544 row += list(pinout.get(name, [""]*2)) 545 if fn_filter is None or fn_filter(row): 546 rows.append(row) 547 if sort_by is not None: 548 def _sort(row): 549 pinf = ord(row[0][0]) * 100 + int(row[0][1:]) 550 if sort_by == 0: return pinf 551 if sort_by == 4: return -1 if row[4] == "" else row[4] 552 if sort_by == 5: return (row[5], row[6], pinf) 553 if sort_by == 6: return (row[6], row[5], pinf) 554 return row[sort_by] 555 rows.sort(key=_sort) 556 557 table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD) 558 table.add_column("Pin") 559 table.add_column("Config") 560 table.add_column("I") 561 table.add_column("O") 562 table.add_column("AF", justify="right") 563 if pinout is not None: 564 table.add_column("Name") 565 table.add_column("Function") 566 for row in rows: 567 table.add_row(*row) 568 return table
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, (+PP=PushPull omitted), PUPDR: +PU=PullUp, +PD=PullDown, (+FL=Floating omitted), SPEEDR: +M=Medium, +H=High, +VH=Very High, (+L=Low 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.
Parameters
- pinout: A map of port+index -> (name, function).
- pin_filter: A filter function that gets passed the entire row as a
list and returns
Trueif row is accepted. - sort_by: The name of the column to sort by: default is
PIN. - columns: The number of columns to spread the GPIO table across.