binutils-gdb/gdb/gdb-gdb.py.in
Simon Marchi c5c3b90970 gdb-gdb.py: strip typedefs in intrusive_list printer assertion
When debugging gdb itself and trying to print a intrusive_list that has
more than one element, I get:

    File "/home/simark/build/binutils-gdb-all-targets/gdb/gdb-gdb.py", line 365, in _children_generator
      node_ptr = self._as_node_ptr(elem_ptr)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/home/simark/build/binutils-gdb-all-targets/gdb/gdb-gdb.py", line 345, in _as_node_ptr
      assert elem_ptr.type.code == gdb.TYPE_CODE_PTR
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    AssertionError

This is because node_ptr is a typedef
(intrusive_list_base_iterator::pointer).  Add a call to strip_typedefs
to get to the real type.

Enhance gdb.gdb/python-helper.exp with a test that would have caught
this bug.

Change-Id: I3eaca8de5ed06d05756ed979332b6a431e15b700
Approved-By: Andrew Burgess <aburgess@redhat.com>
2024-07-19 16:30:52 -04:00

440 lines
16 KiB
Python

# 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 <http://www.gnu.org/licenses/>.
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: <opaque> (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.strip_typedefs().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)