emdbg.debug.px4.base
1# Copyright (c) 2023, Auterion AG 2# SPDX-License-Identifier: BSD-3-Clause 3 4from __future__ import annotations 5import re 6from . import utils 7from functools import cached_property 8 9class Base: 10 """ 11 This base class provides basic abstractions to simplify usage of the GDB 12 Python API, which can be a little verbose. 13 14 It also provides a mechanism to invalidate cached properties whenever GDB 15 stopped. This allows the use of `@cached_property` when the target is halted 16 to cache expensive operations. 17 """ 18 def __init__(self, gdb): 19 self._gdb = gdb 20 self._inf = gdb.selected_inferior() 21 self._arch = self._inf.architecture() 22 self.register_names = [r.name for r in self._arch.registers()] 23 # Registering a callback for every obj makes GDB *really* slow :( 24 global _CACHED_OBJECTS 25 if _CACHED_OBJECTS is None: 26 gdb.events.stop.connect(_invalidate_cached_properties) 27 _CACHED_OBJECTS = [] 28 _CACHED_OBJECTS.append(self) 29 30 def _invalidate(self): 31 for key, value in self.__class__.__dict__.items(): 32 if isinstance(value, cached_property): 33 self.__dict__.pop(key, None) 34 35 @cached_property 36 def registers(self) -> dict[str, int]: 37 """All register names and unsigned values in the selected frame.""" 38 return {r: self.read_register(r) for r in self.register_names} 39 40 def read_register(self, name: str) -> int: 41 """:return: unsigned value of the named register in the selected frame""" 42 frame = self._gdb.selected_frame() 43 # This is convoluted because we need to get the raw FPU register values! 44 # Otherwise they get cast from double to int, which is wrong. 45 value = int(frame.read_register(name).format_string(format="x"), 16) 46 return value 47 48 def write_register(self, name: str, value: int): 49 """Writes a value into the named register""" 50 try: 51 # casting to (void*) allows setting the FPU registers with raw! 52 self._gdb.execute(f"set ${name} = (void*){int(value):#x}") 53 except Exception as e: 54 print(e) 55 56 def write_registers(self, values: dict[str, int]): 57 """Writes all named registers into the CPU""" 58 for name, value in values.items(): 59 if name in ["control", "faultmask", "primask"]: continue 60 # GDB does not know SP, only MSP and PSP 61 if name in ["sp", "r13"]: 62 name = "msp" 63 if name == "msp": 64 self.write_register("r13", value) 65 # Remove double FP registers 66 if name.startswith("d"): continue 67 self.write_register(name, value) 68 69 def fix_nuttx_sp(self, regs: dict[str, int]): 70 """Fixes stored SP, as NuttX incorrectly stores the SP (is off by 4 bytes)""" 71 regs["msp"] = regs["msp"] + 4 72 regs["sp"] = regs["msp"] 73 regs["r13"] = regs["msp"] 74 return regs 75 76 def lookup_static_symbol_in_function(self, symbol_name: str, function_name: str) -> "gdb.Symbol | None": 77 """ 78 Lookup a static symbol inside a function. GDB makes this complicated 79 since static symbols inside functions are not accessible directly. 80 81 :return: the symbol if found or `None` 82 """ 83 if (function := self._gdb.lookup_global_symbol(function_name)) is None: 84 return None 85 function = function.value() 86 function_block = self._gdb.block_for_pc(int(function.address)) 87 for symbol in function_block: 88 if symbol.addr_class == self._gdb.SYMBOL_LOC_STATIC: 89 if symbol.name == symbol_name: 90 return symbol 91 return None 92 93 def lookup_static_symbol_ptr(self, name: str) -> "gdb.Value": 94 """:return: a Value to a static symbol name""" 95 if symbol := self._gdb.lookup_static_symbol(name): 96 return self.value_ptr(symbol) 97 return None 98 99 def lookup_global_symbol_ptr(self, name) -> "gdb.Value": 100 """:return: a Value to a global symbol name""" 101 if symbol := self._gdb.lookup_global_symbol(name): 102 return self.value_ptr(symbol) 103 return None 104 105 def lookup_static_symbol_in_function_ptr(self, symbol_name: str, function_name: str) -> "gdb.Value | None": 106 """:return: a Value to a global symbol name""" 107 if symbol := self.lookup_static_symbol_in_function(symbol_name, function_name): 108 return self.value_ptr(symbol) 109 return None 110 111 def value_ptr(self, symbol: "gdb.Symbol") -> "gdb.Value": 112 """ 113 Convert a symbol into a value. This can be useful if you want to keep a 114 "pointer" of the symbol, whose content is always up-to-date, rather than 115 a local copy, which will never be updated again. 116 """ 117 return self._gdb.Value(symbol.value().address).cast(symbol.type.pointer()) 118 119 def addr_ptr(self, addr: int, type: str) -> "gdb.Value": 120 """Cast a memory address to a custom type.""" 121 return self._gdb.Value(addr).cast(self._gdb.lookup_type(type).pointer()) 122 123 def read_memory(self, address: int, size: int) -> memoryview: 124 """ 125 Reads a block of memory and returns its content. 126 See [Inferiors](https://sourceware.org/gdb/onlinedocs/gdb/Inferiors-In-Python.html). 127 """ 128 return self._inf.read_memory(address, size) 129 130 def write_memory(self, address: int, buffer, length: int): 131 """ 132 Writes a block of memory to an address. 133 See [Inferiors](https://sourceware.org/gdb/onlinedocs/gdb/Inferiors-In-Python.html). 134 """ 135 self._inf.write_memory(address, buffer, length=length) 136 137 def read_uint(self, addr: int, size: int, default=None) -> int: 138 """Reads an unsigned integer from a memory address""" 139 if (itype := {1: "B", 2: "H", 4: "I", 8: "Q"}.get(size)) is None: 140 raise ValueError("Unsupported unsigned integer size!") 141 try: 142 return self.read_memory(addr, size).cast(itype)[0] 143 except self._gdb.MemoryError: 144 return default 145 146 def read_int(self, addr: int, size: int, default=None) -> int: 147 """Reads a signed integer from a memory address""" 148 if (itype := {1: "b", 2: "h", 4: "i", 8: "q"}.get(size)) is None: 149 raise ValueError("Unsupported signed integer size!") 150 try: 151 return self.read_memory(addr, size).cast(itype)[0] 152 except self._gdb.MemoryError: 153 return default 154 155 def read_string(self, addr: int, encoding: str = None, 156 errors: str = None, length: int = None) -> str: 157 """Reads a string of a fixed length, or with 0 termination""" 158 kwargs = {"encoding": encoding or "ascii", "errors": errors or "ignore"} 159 if length: kwargs["length"] = length 160 return self.addr_ptr(addr, "char").string(**kwargs) 161 162 def symtab_line(self, pc: int) -> "gdb.Symtab_and_line": 163 """:return: the symbol table and line for a program location""" 164 return self._gdb.find_pc_line(int(pc)) 165 166 def block(self, pc: int) -> "gdb.Block": 167 """:return: the block for a program location""" 168 return self._gdb.block_for_pc(int(pc)) 169 170 def description_at(self, addr: int) -> str | None: 171 """:return: the human-readable symbol description at an address""" 172 output = self._gdb.execute(f"info symbol *{int(addr)}", to_string=True) 173 if match := re.search(r"(.*?) in section (.*?)", output): 174 return match.group(1) 175 return None 176 177 def integer_type(self, size: int, signed: bool = True) -> "gdb.Type": 178 """:return: The built-in integer type for the size in bits""" 179 return self._arch.integer_type(size * 8, signed=True) 180 181 @property 182 def uint32(self) -> "gdb.Type": 183 """The built-in unsigned 32-bit integer type""" 184 return self.integer_type(4, False) 185 186 @property 187 def int32(self) -> "gdb.Type": 188 """The built-in signed 32-bit integer type""" 189 return self.integer_type(4, True) 190 191 192# Single callback makes GDB much faster, so we do the loop ourselves 193_CACHED_OBJECTS = None 194def _invalidate_cached_properties(event): 195 global _CACHED_OBJECTS 196 for obj in _CACHED_OBJECTS: 197 if obj: obj._invalidate()
10class Base: 11 """ 12 This base class provides basic abstractions to simplify usage of the GDB 13 Python API, which can be a little verbose. 14 15 It also provides a mechanism to invalidate cached properties whenever GDB 16 stopped. This allows the use of `@cached_property` when the target is halted 17 to cache expensive operations. 18 """ 19 def __init__(self, gdb): 20 self._gdb = gdb 21 self._inf = gdb.selected_inferior() 22 self._arch = self._inf.architecture() 23 self.register_names = [r.name for r in self._arch.registers()] 24 # Registering a callback for every obj makes GDB *really* slow :( 25 global _CACHED_OBJECTS 26 if _CACHED_OBJECTS is None: 27 gdb.events.stop.connect(_invalidate_cached_properties) 28 _CACHED_OBJECTS = [] 29 _CACHED_OBJECTS.append(self) 30 31 def _invalidate(self): 32 for key, value in self.__class__.__dict__.items(): 33 if isinstance(value, cached_property): 34 self.__dict__.pop(key, None) 35 36 @cached_property 37 def registers(self) -> dict[str, int]: 38 """All register names and unsigned values in the selected frame.""" 39 return {r: self.read_register(r) for r in self.register_names} 40 41 def read_register(self, name: str) -> int: 42 """:return: unsigned value of the named register in the selected frame""" 43 frame = self._gdb.selected_frame() 44 # This is convoluted because we need to get the raw FPU register values! 45 # Otherwise they get cast from double to int, which is wrong. 46 value = int(frame.read_register(name).format_string(format="x"), 16) 47 return value 48 49 def write_register(self, name: str, value: int): 50 """Writes a value into the named register""" 51 try: 52 # casting to (void*) allows setting the FPU registers with raw! 53 self._gdb.execute(f"set ${name} = (void*){int(value):#x}") 54 except Exception as e: 55 print(e) 56 57 def write_registers(self, values: dict[str, int]): 58 """Writes all named registers into the CPU""" 59 for name, value in values.items(): 60 if name in ["control", "faultmask", "primask"]: continue 61 # GDB does not know SP, only MSP and PSP 62 if name in ["sp", "r13"]: 63 name = "msp" 64 if name == "msp": 65 self.write_register("r13", value) 66 # Remove double FP registers 67 if name.startswith("d"): continue 68 self.write_register(name, value) 69 70 def fix_nuttx_sp(self, regs: dict[str, int]): 71 """Fixes stored SP, as NuttX incorrectly stores the SP (is off by 4 bytes)""" 72 regs["msp"] = regs["msp"] + 4 73 regs["sp"] = regs["msp"] 74 regs["r13"] = regs["msp"] 75 return regs 76 77 def lookup_static_symbol_in_function(self, symbol_name: str, function_name: str) -> "gdb.Symbol | None": 78 """ 79 Lookup a static symbol inside a function. GDB makes this complicated 80 since static symbols inside functions are not accessible directly. 81 82 :return: the symbol if found or `None` 83 """ 84 if (function := self._gdb.lookup_global_symbol(function_name)) is None: 85 return None 86 function = function.value() 87 function_block = self._gdb.block_for_pc(int(function.address)) 88 for symbol in function_block: 89 if symbol.addr_class == self._gdb.SYMBOL_LOC_STATIC: 90 if symbol.name == symbol_name: 91 return symbol 92 return None 93 94 def lookup_static_symbol_ptr(self, name: str) -> "gdb.Value": 95 """:return: a Value to a static symbol name""" 96 if symbol := self._gdb.lookup_static_symbol(name): 97 return self.value_ptr(symbol) 98 return None 99 100 def lookup_global_symbol_ptr(self, name) -> "gdb.Value": 101 """:return: a Value to a global symbol name""" 102 if symbol := self._gdb.lookup_global_symbol(name): 103 return self.value_ptr(symbol) 104 return None 105 106 def lookup_static_symbol_in_function_ptr(self, symbol_name: str, function_name: str) -> "gdb.Value | None": 107 """:return: a Value to a global symbol name""" 108 if symbol := self.lookup_static_symbol_in_function(symbol_name, function_name): 109 return self.value_ptr(symbol) 110 return None 111 112 def value_ptr(self, symbol: "gdb.Symbol") -> "gdb.Value": 113 """ 114 Convert a symbol into a value. This can be useful if you want to keep a 115 "pointer" of the symbol, whose content is always up-to-date, rather than 116 a local copy, which will never be updated again. 117 """ 118 return self._gdb.Value(symbol.value().address).cast(symbol.type.pointer()) 119 120 def addr_ptr(self, addr: int, type: str) -> "gdb.Value": 121 """Cast a memory address to a custom type.""" 122 return self._gdb.Value(addr).cast(self._gdb.lookup_type(type).pointer()) 123 124 def read_memory(self, address: int, size: int) -> memoryview: 125 """ 126 Reads a block of memory and returns its content. 127 See [Inferiors](https://sourceware.org/gdb/onlinedocs/gdb/Inferiors-In-Python.html). 128 """ 129 return self._inf.read_memory(address, size) 130 131 def write_memory(self, address: int, buffer, length: int): 132 """ 133 Writes a block of memory to an address. 134 See [Inferiors](https://sourceware.org/gdb/onlinedocs/gdb/Inferiors-In-Python.html). 135 """ 136 self._inf.write_memory(address, buffer, length=length) 137 138 def read_uint(self, addr: int, size: int, default=None) -> int: 139 """Reads an unsigned integer from a memory address""" 140 if (itype := {1: "B", 2: "H", 4: "I", 8: "Q"}.get(size)) is None: 141 raise ValueError("Unsupported unsigned integer size!") 142 try: 143 return self.read_memory(addr, size).cast(itype)[0] 144 except self._gdb.MemoryError: 145 return default 146 147 def read_int(self, addr: int, size: int, default=None) -> int: 148 """Reads a signed integer from a memory address""" 149 if (itype := {1: "b", 2: "h", 4: "i", 8: "q"}.get(size)) is None: 150 raise ValueError("Unsupported signed integer size!") 151 try: 152 return self.read_memory(addr, size).cast(itype)[0] 153 except self._gdb.MemoryError: 154 return default 155 156 def read_string(self, addr: int, encoding: str = None, 157 errors: str = None, length: int = None) -> str: 158 """Reads a string of a fixed length, or with 0 termination""" 159 kwargs = {"encoding": encoding or "ascii", "errors": errors or "ignore"} 160 if length: kwargs["length"] = length 161 return self.addr_ptr(addr, "char").string(**kwargs) 162 163 def symtab_line(self, pc: int) -> "gdb.Symtab_and_line": 164 """:return: the symbol table and line for a program location""" 165 return self._gdb.find_pc_line(int(pc)) 166 167 def block(self, pc: int) -> "gdb.Block": 168 """:return: the block for a program location""" 169 return self._gdb.block_for_pc(int(pc)) 170 171 def description_at(self, addr: int) -> str | None: 172 """:return: the human-readable symbol description at an address""" 173 output = self._gdb.execute(f"info symbol *{int(addr)}", to_string=True) 174 if match := re.search(r"(.*?) in section (.*?)", output): 175 return match.group(1) 176 return None 177 178 def integer_type(self, size: int, signed: bool = True) -> "gdb.Type": 179 """:return: The built-in integer type for the size in bits""" 180 return self._arch.integer_type(size * 8, signed=True) 181 182 @property 183 def uint32(self) -> "gdb.Type": 184 """The built-in unsigned 32-bit integer type""" 185 return self.integer_type(4, False) 186 187 @property 188 def int32(self) -> "gdb.Type": 189 """The built-in signed 32-bit integer type""" 190 return self.integer_type(4, True)
This base class provides basic abstractions to simplify usage of the GDB Python API, which can be a little verbose.
It also provides a mechanism to invalidate cached properties whenever GDB
stopped. This allows the use of @cached_property
when the target is halted
to cache expensive operations.
19 def __init__(self, gdb): 20 self._gdb = gdb 21 self._inf = gdb.selected_inferior() 22 self._arch = self._inf.architecture() 23 self.register_names = [r.name for r in self._arch.registers()] 24 # Registering a callback for every obj makes GDB *really* slow :( 25 global _CACHED_OBJECTS 26 if _CACHED_OBJECTS is None: 27 gdb.events.stop.connect(_invalidate_cached_properties) 28 _CACHED_OBJECTS = [] 29 _CACHED_OBJECTS.append(self)
36 @cached_property 37 def registers(self) -> dict[str, int]: 38 """All register names and unsigned values in the selected frame.""" 39 return {r: self.read_register(r) for r in self.register_names}
All register names and unsigned values in the selected frame.
41 def read_register(self, name: str) -> int: 42 """:return: unsigned value of the named register in the selected frame""" 43 frame = self._gdb.selected_frame() 44 # This is convoluted because we need to get the raw FPU register values! 45 # Otherwise they get cast from double to int, which is wrong. 46 value = int(frame.read_register(name).format_string(format="x"), 16) 47 return value
Returns
unsigned value of the named register in the selected frame
49 def write_register(self, name: str, value: int): 50 """Writes a value into the named register""" 51 try: 52 # casting to (void*) allows setting the FPU registers with raw! 53 self._gdb.execute(f"set ${name} = (void*){int(value):#x}") 54 except Exception as e: 55 print(e)
Writes a value into the named register
57 def write_registers(self, values: dict[str, int]): 58 """Writes all named registers into the CPU""" 59 for name, value in values.items(): 60 if name in ["control", "faultmask", "primask"]: continue 61 # GDB does not know SP, only MSP and PSP 62 if name in ["sp", "r13"]: 63 name = "msp" 64 if name == "msp": 65 self.write_register("r13", value) 66 # Remove double FP registers 67 if name.startswith("d"): continue 68 self.write_register(name, value)
Writes all named registers into the CPU
70 def fix_nuttx_sp(self, regs: dict[str, int]): 71 """Fixes stored SP, as NuttX incorrectly stores the SP (is off by 4 bytes)""" 72 regs["msp"] = regs["msp"] + 4 73 regs["sp"] = regs["msp"] 74 regs["r13"] = regs["msp"] 75 return regs
Fixes stored SP, as NuttX incorrectly stores the SP (is off by 4 bytes)
77 def lookup_static_symbol_in_function(self, symbol_name: str, function_name: str) -> "gdb.Symbol | None": 78 """ 79 Lookup a static symbol inside a function. GDB makes this complicated 80 since static symbols inside functions are not accessible directly. 81 82 :return: the symbol if found or `None` 83 """ 84 if (function := self._gdb.lookup_global_symbol(function_name)) is None: 85 return None 86 function = function.value() 87 function_block = self._gdb.block_for_pc(int(function.address)) 88 for symbol in function_block: 89 if symbol.addr_class == self._gdb.SYMBOL_LOC_STATIC: 90 if symbol.name == symbol_name: 91 return symbol 92 return None
Lookup a static symbol inside a function. GDB makes this complicated since static symbols inside functions are not accessible directly.
Returns
the symbol if found or
None
94 def lookup_static_symbol_ptr(self, name: str) -> "gdb.Value": 95 """:return: a Value to a static symbol name""" 96 if symbol := self._gdb.lookup_static_symbol(name): 97 return self.value_ptr(symbol) 98 return None
Returns
a Value to a static symbol name
100 def lookup_global_symbol_ptr(self, name) -> "gdb.Value": 101 """:return: a Value to a global symbol name""" 102 if symbol := self._gdb.lookup_global_symbol(name): 103 return self.value_ptr(symbol) 104 return None
Returns
a Value to a global symbol name
106 def lookup_static_symbol_in_function_ptr(self, symbol_name: str, function_name: str) -> "gdb.Value | None": 107 """:return: a Value to a global symbol name""" 108 if symbol := self.lookup_static_symbol_in_function(symbol_name, function_name): 109 return self.value_ptr(symbol) 110 return None
Returns
a Value to a global symbol name
112 def value_ptr(self, symbol: "gdb.Symbol") -> "gdb.Value": 113 """ 114 Convert a symbol into a value. This can be useful if you want to keep a 115 "pointer" of the symbol, whose content is always up-to-date, rather than 116 a local copy, which will never be updated again. 117 """ 118 return self._gdb.Value(symbol.value().address).cast(symbol.type.pointer())
Convert a symbol into a value. This can be useful if you want to keep a "pointer" of the symbol, whose content is always up-to-date, rather than a local copy, which will never be updated again.
120 def addr_ptr(self, addr: int, type: str) -> "gdb.Value": 121 """Cast a memory address to a custom type.""" 122 return self._gdb.Value(addr).cast(self._gdb.lookup_type(type).pointer())
Cast a memory address to a custom type.
124 def read_memory(self, address: int, size: int) -> memoryview: 125 """ 126 Reads a block of memory and returns its content. 127 See [Inferiors](https://sourceware.org/gdb/onlinedocs/gdb/Inferiors-In-Python.html). 128 """ 129 return self._inf.read_memory(address, size)
Reads a block of memory and returns its content. See Inferiors.
131 def write_memory(self, address: int, buffer, length: int): 132 """ 133 Writes a block of memory to an address. 134 See [Inferiors](https://sourceware.org/gdb/onlinedocs/gdb/Inferiors-In-Python.html). 135 """ 136 self._inf.write_memory(address, buffer, length=length)
Writes a block of memory to an address. See Inferiors.
138 def read_uint(self, addr: int, size: int, default=None) -> int: 139 """Reads an unsigned integer from a memory address""" 140 if (itype := {1: "B", 2: "H", 4: "I", 8: "Q"}.get(size)) is None: 141 raise ValueError("Unsupported unsigned integer size!") 142 try: 143 return self.read_memory(addr, size).cast(itype)[0] 144 except self._gdb.MemoryError: 145 return default
Reads an unsigned integer from a memory address
147 def read_int(self, addr: int, size: int, default=None) -> int: 148 """Reads a signed integer from a memory address""" 149 if (itype := {1: "b", 2: "h", 4: "i", 8: "q"}.get(size)) is None: 150 raise ValueError("Unsupported signed integer size!") 151 try: 152 return self.read_memory(addr, size).cast(itype)[0] 153 except self._gdb.MemoryError: 154 return default
Reads a signed integer from a memory address
156 def read_string(self, addr: int, encoding: str = None, 157 errors: str = None, length: int = None) -> str: 158 """Reads a string of a fixed length, or with 0 termination""" 159 kwargs = {"encoding": encoding or "ascii", "errors": errors or "ignore"} 160 if length: kwargs["length"] = length 161 return self.addr_ptr(addr, "char").string(**kwargs)
Reads a string of a fixed length, or with 0 termination
163 def symtab_line(self, pc: int) -> "gdb.Symtab_and_line": 164 """:return: the symbol table and line for a program location""" 165 return self._gdb.find_pc_line(int(pc))
Returns
the symbol table and line for a program location
167 def block(self, pc: int) -> "gdb.Block": 168 """:return: the block for a program location""" 169 return self._gdb.block_for_pc(int(pc))
Returns
the block for a program location
171 def description_at(self, addr: int) -> str | None: 172 """:return: the human-readable symbol description at an address""" 173 output = self._gdb.execute(f"info symbol *{int(addr)}", to_string=True) 174 if match := re.search(r"(.*?) in section (.*?)", output): 175 return match.group(1) 176 return None
Returns
the human-readable symbol description at an address
178 def integer_type(self, size: int, signed: bool = True) -> "gdb.Type": 179 """:return: The built-in integer type for the size in bits""" 180 return self._arch.integer_type(size * 8, signed=True)
Returns
The built-in integer type for the size in bits