# Copyright (C) 2009-2024 Free Software Foundation, Inc. # # This file is part of GDB. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os.path import gdb class TypeFlag: """A class that allows us to store a flag name, its short name, and its value. In the GDB sources, struct type has a component called instance_flags in which the value is the addition of various flags. These flags are defined by the enumerates type_instance_flag_value. This class helps us recreate a list with all these flags that is easy to manipulate and sort. Because all flag names start with TYPE_INSTANCE_FLAG_, a short_name attribute is provided that strips this prefix. ATTRIBUTES name: The enumeration name (eg: "TYPE_INSTANCE_FLAG_CONST"). value: The associated value. short_name: The enumeration name, with the suffix stripped. """ def __init__(self, name, value): self.name = name self.value = value self.short_name = name.replace("TYPE_INSTANCE_FLAG_", "") def __lt__(self, other): """Sort by value order.""" return self.value < other.value # A list of all existing TYPE_INSTANCE_FLAGS_* enumerations, # stored as TypeFlags objects. Lazy-initialized. TYPE_FLAGS = None class TypeFlagsPrinter: """A class that prints a decoded form of an instance_flags value. This class uses a global named TYPE_FLAGS, which is a list of all defined TypeFlag values. Using a global allows us to compute this list only once. This class relies on a couple of enumeration types being defined. If not, then printing of the instance_flag is going to be degraded, but it's not a fatal error. """ def __init__(self, val): self.val = val def __str__(self): global TYPE_FLAGS if TYPE_FLAGS is None: self.init_TYPE_FLAGS() if not self.val: return "0" if TYPE_FLAGS: flag_list = [ flag.short_name for flag in TYPE_FLAGS if self.val & flag.value ] else: flag_list = ["???"] return "0x%x [%s]" % (self.val, "|".join(flag_list)) def init_TYPE_FLAGS(self): """Initialize the TYPE_FLAGS global as a list of TypeFlag objects. This operation requires the search of a couple of enumeration types. If not found, a warning is printed on stdout, and TYPE_FLAGS is set to the empty list. The resulting list is sorted by increasing value, to facilitate printing of the list of flags used in an instance_flags value. """ global TYPE_FLAGS TYPE_FLAGS = [] try: iflags = gdb.lookup_type("enum type_instance_flag_value") except: print("Warning: Cannot find enum type_instance_flag_value type.") print(" `struct type' pretty-printer will be degraded") return TYPE_FLAGS = [TypeFlag(field.name, field.enumval) for field in iflags.fields()] TYPE_FLAGS.sort() class StructTypePrettyPrinter: """Pretty-print an object of type struct type""" def __init__(self, val): self.val = val def to_string(self): fields = [] fields.append("pointer_type = %s" % self.val["pointer_type"]) fields.append("reference_type = %s" % self.val["reference_type"]) fields.append("chain = %s" % self.val["reference_type"]) fields.append( "instance_flags = %s" % TypeFlagsPrinter(self.val["m_instance_flags"]) ) fields.append("length = %d" % self.val["m_length"]) fields.append("main_type = %s" % self.val["main_type"]) return "\n{" + ",\n ".join(fields) + "}" class StructMainTypePrettyPrinter: """Pretty-print an objet of type main_type""" def __init__(self, val): self.val = val def flags_to_string(self): """struct main_type contains a series of components that are one-bit ints whose name start with "flag_". For instance: flag_unsigned, flag_stub, etc. In essence, these components are really boolean flags, and this method prints a short synthetic version of the value of all these flags. For instance, if flag_unsigned and flag_static are the only components set to 1, this function will return "unsigned|static". """ fields = [ field.name.replace("flag_", "") for field in self.val.type.fields() if field.name.startswith("flag_") and self.val[field.name] ] return "|".join(fields) def owner_to_string(self): """Return an image of component "owner".""" if self.val["m_flag_objfile_owned"] != 0: return "%s (objfile)" % self.val["m_owner"]["objfile"] else: return "%s (gdbarch)" % self.val["m_owner"]["gdbarch"] def struct_field_location_img(self, field_val): """Return an image of the loc component inside the given field gdb.Value. """ loc_val = field_val["m_loc"] loc_kind = str(field_val["m_loc_kind"]) if loc_kind == "FIELD_LOC_KIND_BITPOS": return "bitpos = %d" % loc_val["bitpos"] elif loc_kind == "FIELD_LOC_KIND_ENUMVAL": return "enumval = %d" % loc_val["enumval"] elif loc_kind == "FIELD_LOC_KIND_PHYSADDR": return "physaddr = 0x%x" % loc_val["physaddr"] elif loc_kind == "FIELD_LOC_KIND_PHYSNAME": return "physname = %s" % loc_val["physname"] elif loc_kind == "FIELD_LOC_KIND_DWARF_BLOCK": return "dwarf_block = %s" % loc_val["dwarf_block"] else: return "m_loc = ??? (unsupported m_loc_kind value)" def struct_field_img(self, fieldno): """Return an image of the main_type field number FIELDNO.""" f = self.val["flds_bnds"]["fields"][fieldno] label = "flds_bnds.fields[%d]:" % fieldno if f["m_artificial"]: label += " (artificial)" fields = [] fields.append("m_name = %s" % f["m_name"]) fields.append("m_type = %s" % f["m_type"]) fields.append("m_loc_kind = %s" % f["m_loc_kind"]) fields.append("bitsize = %d" % f["m_bitsize"]) fields.append(self.struct_field_location_img(f)) return label + "\n" + " {" + ",\n ".join(fields) + "}" def bound_img(self, bound_name): """Return an image of the given main_type's bound.""" bounds = self.val["flds_bnds"]["bounds"].dereference() b = bounds[bound_name] bnd_kind = str(b["m_kind"]) if bnd_kind == "PROP_CONST": return str(b["m_data"]["const_val"]) elif bnd_kind == "PROP_UNDEFINED": return "(undefined)" else: info = [bnd_kind] if bound_name == "high" and bounds["flag_upper_bound_is_count"]: info.append("upper_bound_is_count") return "{} ({})".format(str(b["m_data"]["baton"]), ",".join(info)) def bounds_img(self): """Return an image of the main_type bounds.""" b = self.val["flds_bnds"]["bounds"].dereference() low = self.bound_img("low") high = self.bound_img("high") img = "flds_bnds.bounds = {%s, %s}" % (low, high) if b["flag_bound_evaluated"]: img += " [evaluated]" return img def type_specific_img(self): """Return a string image of the main_type type_specific union. Only the relevant component of that union is printed (based on the value of the type_specific_kind field. """ type_specific_kind = str(self.val["type_specific_field"]) type_specific = self.val["type_specific"] if type_specific_kind == "TYPE_SPECIFIC_NONE": img = "type_specific_field = %s" % type_specific_kind elif type_specific_kind == "TYPE_SPECIFIC_CPLUS_STUFF": img = "cplus_stuff = %s" % type_specific["cplus_stuff"] elif type_specific_kind == "TYPE_SPECIFIC_GNAT_STUFF": img = ( "gnat_stuff = {descriptive_type = %s}" % type_specific["gnat_stuff"]["descriptive_type"] ) elif type_specific_kind == "TYPE_SPECIFIC_FLOATFORMAT": img = "floatformat[0..1] = %s" % type_specific["floatformat"] elif type_specific_kind == "TYPE_SPECIFIC_FUNC": img = ( "calling_convention = %d" % type_specific["func_stuff"]["calling_convention"] ) # tail_call_list is not printed. elif type_specific_kind == "TYPE_SPECIFIC_SELF_TYPE": img = "self_type = %s" % type_specific["self_type"] elif type_specific_kind == "TYPE_SPECIFIC_FIXED_POINT": # The scaling factor is an opaque structure, so we cannot # decode its value from Python (not without insider knowledge). img = ( "scaling_factor: (call __gmpz_dump with " " _mp_num and _mp_den fields if needed)" ) elif type_specific_kind == "TYPE_SPECIFIC_INT": img = "int_stuff = { bit_size = %d, bit_offset = %d }" % ( type_specific["int_stuff"]["bit_size"], type_specific["int_stuff"]["bit_offset"], ) else: img = ( "type_specific = ??? (unknown type_specific_kind: %s)" % type_specific_kind ) return img def to_string(self): """Return a pretty-printed image of our main_type.""" fields = [] fields.append("name = %s" % self.val["name"]) fields.append("code = %s" % self.val["code"]) fields.append("flags = [%s]" % self.flags_to_string()) fields.append("owner = %s" % self.owner_to_string()) fields.append("target_type = %s" % self.val["m_target_type"]) if self.val["m_nfields"] > 0: for fieldno in range(self.val["m_nfields"]): fields.append(self.struct_field_img(fieldno)) if self.val["code"] == gdb.TYPE_CODE_RANGE: fields.append(self.bounds_img()) fields.append(self.type_specific_img()) return "\n{" + ",\n ".join(fields) + "}" class CoreAddrPrettyPrinter: """Print CORE_ADDR values as hex.""" def __init__(self, val): self._val = val def to_string(self): return hex(int(self._val)) class IntrusiveListPrinter: """Print a struct intrusive_list.""" def __init__(self, val): self._val = val # Type of linked items. self._item_type = self._val.type.template_argument(0) self._node_ptr_type = gdb.lookup_type( "intrusive_list_node<{}>".format(self._item_type.tag) ).pointer() # Type of value -> node converter. self._conv_type = self._val.type.template_argument(1) if self._uses_member_node(): # The second template argument of intrusive_member_node is a member # pointer value. Its value is the offset of the node member in the # enclosing type. member_node_ptr = self._conv_type.template_argument(1) member_node_ptr = member_node_ptr.cast(gdb.lookup_type("int")) self._member_node_offset = int(member_node_ptr) # This is only needed in _as_node_ptr if using a member node. Look it # up here so we only do it once. self._char_ptr_type = gdb.lookup_type("char").pointer() def display_hint(self): return "array" def _uses_member_node(self): """Return True if the list items use a node as a member, False if they use a node as a base class. """ if self._conv_type.name.startswith("intrusive_member_node<"): return True elif self._conv_type.name.startswith("intrusive_base_node<"): return False else: raise RuntimeError( "Unexpected intrusive_list value -> node converter type: {}".format( self._conv_type.name ) ) def to_string(self): s = "intrusive list of {}".format(self._item_type) if self._uses_member_node(): node_member = self._conv_type.template_argument(1) s += ", linked through {}".format(node_member) return s def _as_node_ptr(self, elem_ptr): """Given ELEM_PTR, a pointer to a list element, return a pointer to the corresponding intrusive_list_node. """ assert elem_ptr.type.code == gdb.TYPE_CODE_PTR if self._uses_member_node(): # Node as a member: add the member node offset from to the element's # address to get the member node's address. elem_char_ptr = elem_ptr.cast(self._char_ptr_type) node_char_ptr = elem_char_ptr + self._member_node_offset return node_char_ptr.cast(self._node_ptr_type) else: # Node as a base: just casting from node pointer to item pointer # will adjust the pointer value. return elem_ptr.cast(self._node_ptr_type) def _children_generator(self): """Generator that yields one tuple per list item.""" elem_ptr = self._val["m_front"] idx = 0 while elem_ptr != 0: yield (str(idx), elem_ptr.dereference()) node_ptr = self._as_node_ptr(elem_ptr) elem_ptr = node_ptr["next"] idx += 1 def children(self): return self._children_generator() class HtabPrinter: """Pretty-printer for htab_t hash tables.""" def __init__(self, val): self._val = val def display_hint(self): return "array" def to_string(self): n = int(self._val["n_elements"]) - int(self._val["n_deleted"]) return "htab_t with {} elements".format(n) def children(self): size = int(self._val["size"]) entries = self._val["entries"] child_i = 0 for entries_i in range(size): entry = entries[entries_i] # 0 (NULL pointer) means there's nothing, 1 (HTAB_DELETED_ENTRY) # means there was something, but is now deleted. if int(entry) in (0, 1): continue yield (str(child_i), entry) child_i += 1 def type_lookup_function(val): """A routine that returns the correct pretty printer for VAL if appropriate. Returns None otherwise. """ tag = val.type.tag name = val.type.name if tag == "type": return StructTypePrettyPrinter(val) elif tag == "main_type": return StructMainTypePrettyPrinter(val) elif name == "CORE_ADDR": return CoreAddrPrettyPrinter(val) elif tag is not None and tag.startswith("intrusive_list<"): return IntrusiveListPrinter(val) elif name == "htab_t": return HtabPrinter(val) return None def register_pretty_printer(objfile): """A routine to register a pretty-printer against the given OBJFILE.""" objfile.pretty_printers.append(type_lookup_function) if __name__ == "__main__": if gdb.current_objfile() is not None: # This is the case where this script is being "auto-loaded" # for a given objfile. Register the pretty-printer for that # objfile. register_pretty_printer(gdb.current_objfile()) else: # We need to locate the objfile corresponding to the GDB # executable, and register the pretty-printer for that objfile. # FIXME: The condition used to match the objfile is too simplistic # and will not work on Windows. for objfile in gdb.objfiles(): if os.path.basename(objfile.filename) == "gdb": objfile.pretty_printers.append(type_lookup_function)