]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Handle dynamic field properties
authorTom Tromey <tromey@adacore.com>
Tue, 15 Apr 2025 15:08:52 +0000 (09:08 -0600)
committerTom Tromey <tromey@adacore.com>
Tue, 6 May 2025 15:01:54 +0000 (09:01 -0600)
I found a situation where gdb could not properly decode an Ada type.
In this first scenario, the discriminant of a type is a bit-field.
PROP_ADDR_OFFSET does not handle this situation, because it only
allows an offset -- not a bit-size.

My original approach to this just added a bit size as well, but after
some discussion with Eric Botcazou, we found another failing case: a
tagged type can have a second discriminant that appears at a variable
offset.

So, this patch changes this code to accept a general 'struct field'
instead of trying to replicate the field-finding machinery by itself.

This is handled at property-evaluation time by simply using a 'field'
and resolving its dynamic properties.  Then the usual field-extraction
function is called to get the value.

Because the baton now just holds a field, I renamed PROP_ADDR_OFFSET
to PROP_FIELD.

The DWARF reader now defers filling in the property baton until the
fields have been attached to the type.

Finally, I noticed that if the discriminant field has a biased
representation, then unpack_field_as_long would not handle this
either.  This bug is also fixed here, and the test case checks this.

Regression tested on x86-64 Fedora 41.

gdb/dwarf2/cu.h
gdb/dwarf2/loc.c
gdb/dwarf2/loc.h
gdb/dwarf2/read.c
gdb/gdbtypes.c
gdb/gdbtypes.h
gdb/testsuite/gdb.ada/packed_record_2.exp [new file with mode: 0644]
gdb/testsuite/gdb.ada/packed_record_2/exam.adb [new file with mode: 0644]
gdb/value.c

index 5338dfe4b45c69bebe6d51856dd106ce45b98535..9f7678936a74227f349fb890de99c9268f149f7b 100644 (file)
@@ -27,6 +27,8 @@
 #include "gdbsupport/unordered_set.h"
 #include "dwarf2/die.h"
 
+struct field_info;
+
 /* Type used for delaying computation of method physnames.
    See comments for compute_delayed_physnames.  */
 struct delayed_method_info
@@ -399,6 +401,15 @@ public:
      .debug_str_offsets section (8 or 4, depending on the address size).  */
   std::optional<ULONGEST> str_offsets_base;
 
+  /* We may encounter a DIE where a property refers to a field in some
+     outer type.  For example, in Ada, an array length may depend on a
+     field in some outer record.  In this case, we need to be able to
+     stash a pointer to the 'struct field' into the appropriate
+     dynamic property baton.  However, the fields aren't created until
+     the type has been fully processed, so we need a temporary
+     back-link to this object.  */
+  struct field_info *field_info = nullptr;
+
   /* Mark used when releasing cached dies.  */
   bool m_mark : 1;
 
index e1a5fdd5b8d266c8c4e6adeffa7d2696370d0495..fa1b4eb3123bbfbbb5e216966651440079b36e3b 100644 (file)
@@ -1732,11 +1732,10 @@ dwarf2_evaluate_property (const dynamic_prop *prop,
       *value = prop->const_val ();
       return true;
 
-    case PROP_ADDR_OFFSET:
+    case PROP_FIELD:
       {
        const dwarf2_property_baton *baton = prop->baton ();
        const struct property_addr_info *pinfo;
-       struct value *val;
 
        for (pinfo = addr_stack; pinfo != NULL; pinfo = pinfo->next)
          {
@@ -1747,14 +1746,40 @@ dwarf2_evaluate_property (const dynamic_prop *prop,
          }
        if (pinfo == NULL)
          error (_("cannot find reference address for offset property"));
-       if (pinfo->valaddr.data () != NULL)
-         val = value_from_contents
-                 (baton->offset_info.type,
-                  pinfo->valaddr.data () + baton->offset_info.offset);
-       else
-         val = value_at (baton->offset_info.type,
-                         pinfo->addr + baton->offset_info.offset);
-       *value = value_as_address (val);
+
+       struct field resolved_field = *baton->field;
+       resolve_dynamic_field (resolved_field, pinfo, initial_frame);
+
+       /* Storage for memory if we need to read it.  */
+       gdb::byte_vector memory;
+       const gdb_byte *bytes = pinfo->valaddr.data ();
+       if (bytes == nullptr)
+         {
+           int bitpos = resolved_field.loc_bitpos ();
+           int bitsize = resolved_field.bitsize ();
+           if (bitsize == 0)
+             bitsize = check_typedef (resolved_field.type ())->length () * 8;
+
+           /* Read just the minimum number of bytes needed to satisfy
+              unpack_field_as_long.  So, update the resolved field's
+              starting offset to remove any unnecessary leading
+              bytes.  */
+           int byte_offset = bitpos / 8;
+
+           bitpos %= 8;
+           resolved_field.set_loc_bitpos (bitpos);
+
+           /* Make sure to include any remaining bit offset in the
+              size computation, in case the value straddles a
+              byte.  */
+           int byte_length = align_up (bitsize + bitpos, 8) / 8;
+           memory.resize (byte_length);
+
+           read_memory (pinfo->addr + byte_offset, memory.data (),
+                        byte_length);
+           bytes = memory.data ();
+         }
+       *value = unpack_field_as_long (bytes, &resolved_field);
        return true;
       }
 
index 02304121a881394805d7d8390fb23b8df1290e9d..be70704e7d7de738206c865ef4f718871228e37c 100644 (file)
@@ -202,23 +202,6 @@ struct dwarf2_loclist_baton
   unsigned char dwarf_version;
 };
 
-/* The baton used when a dynamic property is an offset to a parent
-   type.  This can be used, for instance, then the bound of an array
-   inside a record is determined by the value of another field inside
-   that record.  */
-
-struct dwarf2_offset_baton
-{
-  /* The offset from the parent type where the value of the property
-     is stored.  In the example provided above, this would be the offset
-     of the field being used as the array bound.  */
-  LONGEST offset;
-
-  /* The type of the object whose property is dynamic.  In the example
-     provided above, this would the array's index type.  */
-  struct type *type;
-};
-
 /* A dynamic property is either expressed as a single location expression
    or a location list.  If the property is an indirection, pointing to
    another die, keep track of the targeted type in PROPERTY_TYPE.
@@ -241,8 +224,8 @@ struct dwarf2_property_baton
     /* Location list to be evaluated in the context of PROPERTY_TYPE.  */
     struct dwarf2_loclist_baton loclist;
 
-    /* The location is an offset to PROPERTY_TYPE.  */
-    struct dwarf2_offset_baton offset_info;
+    /* The location is stored in a field of PROPERTY_TYPE.  */
+    struct field *field;
   };
 };
 
index f90b22781e0dd6daf7852a3966b6357f96cc000a..6f380acbd345fbd16d8dfddf3ec230a99ce90874 100644 (file)
@@ -698,6 +698,12 @@ struct field_info
      we're reading.  */
   std::vector<variant_part_builder> variant_parts;
 
+  /* All the field batons that must be updated.  This is only used
+     when a type's property depends on a field of this structure; for
+     example in Ada when an array's length may come from a field of an
+     enclosing record.  */
+  gdb::unordered_map<dwarf2_property_baton *, sect_offset> field_batons;
+
   /* Return the total number of fields (including baseclasses).  */
   int nfields () const
   {
@@ -9861,54 +9867,6 @@ dwarf2_access_attribute (struct die_info *die, struct dwarf2_cu *cu)
     }
 }
 
-/* Look for DW_AT_data_member_location or DW_AT_data_bit_offset.  Set
-   *OFFSET to the byte offset.  If the attribute was not found return
-   0, otherwise return 1.  If it was found but could not properly be
-   handled, set *OFFSET to 0.  */
-
-static int
-handle_member_location (struct die_info *die, struct dwarf2_cu *cu,
-                       LONGEST *offset)
-{
-  struct attribute *attr;
-
-  attr = dwarf2_attr (die, DW_AT_data_member_location, cu);
-  if (attr != NULL)
-    {
-      *offset = 0;
-      CORE_ADDR temp;
-
-      /* Note that we do not check for a section offset first here.
-        This is because DW_AT_data_member_location is new in DWARF 4,
-        so if we see it, we can assume that a constant form is really
-        a constant and not a section offset.  */
-      if (attr->form_is_constant ())
-       *offset = attr->unsigned_constant ().value_or (0);
-      else if (attr->form_is_section_offset ())
-       dwarf2_complex_location_expr_complaint ();
-      else if (attr->form_is_block ()
-              && decode_locdesc (attr->as_block (), cu, &temp))
-       {
-         *offset = temp;
-       }
-      else
-       dwarf2_complex_location_expr_complaint ();
-
-      return 1;
-    }
-  else
-    {
-      attr = dwarf2_attr (die, DW_AT_data_bit_offset, cu);
-      if (attr != nullptr)
-       {
-         *offset = attr->unsigned_constant ().value_or (0);
-         return 1;
-       }
-    }
-
-  return 0;
-}
-
 /* Look for DW_AT_data_member_location or DW_AT_data_bit_offset and
    store the results in FIELD.  */
 
@@ -11269,6 +11227,56 @@ handle_struct_member_die (struct die_info *child_die, struct type *type,
     handle_variant (child_die, type, fi, template_args, cu);
 }
 
+/* Create a property baton for a field of the struct type currently
+   being processed.  OFFSET is the DIE offset of the field in the
+   structure.  If OFFSET is found among the fields that have already
+   been seen, then a new property baton is allocated on the objfile
+   obstack and returned.  The baton isn't fully filled in -- it will
+   be post-processed once the fields are finally created; see
+   update_field_batons.  If OFFSET is not found, NULL is returned.  */
+
+static dwarf2_property_baton *
+find_field_create_baton (dwarf2_cu *cu, sect_offset offset)
+{
+  field_info *fi = cu->field_info;
+  /* Defensive programming in case we see unusual DWARF.  */
+  if (fi == nullptr)
+    return nullptr;
+  for (const auto &fld : fi->fields)
+    if (fld.offset == offset)
+      {
+       dwarf2_property_baton *result
+         = XOBNEW (&cu->per_objfile->objfile->objfile_obstack,
+                   struct dwarf2_property_baton);
+       fi->field_batons[result] = offset;
+       return result;
+      }
+  return nullptr;
+}
+
+/* Update all the stored field property batons.  FI is the field info
+   for the structure being created.  TYPE is the corresponding struct
+   type with its fields already filled in.  This fills in the correct
+   field for each baton that was stored while processing this
+   type.  */
+
+static void
+update_field_batons (field_info *fi, struct type *type)
+{
+  int n_bases = fi->baseclasses.size ();
+  for (auto &[baton, offset] : fi->field_batons)
+    {
+      for (int i = 0; i < fi->fields.size (); ++i)
+       {
+         if (fi->fields[i].offset == offset)
+           {
+             baton->field = &type->field (n_bases + i);
+             break;
+           }
+       }
+    }
+}
+
 /* Finish creating a structure or union type, including filling in its
    members and creating a symbol for it. This function also handles Fortran
    namelist variables, their items or members and creating a symbol for
@@ -11290,6 +11298,9 @@ process_structure_scope (struct die_info *die, struct dwarf2_cu *cu)
       struct field_info fi;
       std::vector<struct symbol *> template_args;
 
+      scoped_restore save_field_info
+       = make_scoped_restore (&cu->field_info, &fi);
+
       for (die_info *child_die : die->children ())
        handle_struct_member_die (child_die, type, &fi, &template_args, cu);
 
@@ -11311,7 +11322,11 @@ process_structure_scope (struct die_info *die, struct dwarf2_cu *cu)
 
       /* Attach fields and member functions to the type.  */
       if (fi.nfields () > 0)
-       dwarf2_attach_fields_to_type (&fi, type, cu);
+       {
+         dwarf2_attach_fields_to_type (&fi, type, cu);
+         update_field_batons (&fi, type);
+       }
+
       if (!fi.fnfieldlists.empty ())
        {
          dwarf2_attach_fn_fields_to_type (&fi, type, cu);
@@ -13925,17 +13940,14 @@ attr_to_dynamic_prop (const struct attribute *attr, struct die_info *die,
          case DW_AT_data_member_location:
          case DW_AT_data_bit_offset:
            {
-             LONGEST offset;
-
-             if (!handle_member_location (target_die, target_cu, &offset))
+             baton = find_field_create_baton (cu, target_die->sect_off);
+             if (baton == nullptr)
                return 0;
 
-             baton = XOBNEW (obstack, struct dwarf2_property_baton);
              baton->property_type = read_type_die (target_die->parent,
-                                                     target_cu);
-             baton->offset_info.offset = offset;
-             baton->offset_info.type = die_type (target_die, target_cu);
-             prop->set_addr_offset (baton);
+                                                   target_cu);
+             baton->field = nullptr;
+             prop->set_field (baton);
              break;
            }
        }
index 0f01c97169ff09c6eda680c0911dfcbc46cc802a..fb1e8cb09637f15876fa7d2b0393955a66a43a0a 100644 (file)
@@ -902,7 +902,7 @@ operator== (const dynamic_prop &l, const dynamic_prop &r)
       return true;
     case PROP_CONST:
       return l.const_val () == r.const_val ();
-    case PROP_ADDR_OFFSET:
+    case PROP_FIELD:
     case PROP_LOCEXPR:
     case PROP_LOCLIST:
       return l.baton () == r.baton ();
index f973ba6e3c851a6f2d256a33519375e2abc6409c..52d03c40466deed229816c1e18376146a0487f80 100644 (file)
@@ -259,7 +259,7 @@ enum dynamic_prop_kind
 {
   PROP_UNDEFINED, /* Not defined.  */
   PROP_CONST,     /* Constant.  */
-  PROP_ADDR_OFFSET, /* Address offset.  */
+  PROP_FIELD,    /* Field of a type.  */
   PROP_LOCEXPR,   /* Location expression.  */
   PROP_LOCLIST,    /* Location list.  */
   PROP_VARIANT_PARTS, /* Variant parts.  */
@@ -347,7 +347,7 @@ struct dynamic_prop
   {
     gdb_assert (m_kind == PROP_LOCEXPR
                || m_kind == PROP_LOCLIST
-               || m_kind == PROP_ADDR_OFFSET);
+               || m_kind == PROP_FIELD);
 
     return m_data.baton;
   }
@@ -364,9 +364,9 @@ struct dynamic_prop
     m_data.baton = baton;
   }
 
-  void set_addr_offset (const dwarf2_property_baton *baton)
+  void set_field (const dwarf2_property_baton *baton)
   {
-    m_kind = PROP_ADDR_OFFSET;
+    m_kind = PROP_FIELD;
     m_data.baton = baton;
   }
 
diff --git a/gdb/testsuite/gdb.ada/packed_record_2.exp b/gdb/testsuite/gdb.ada/packed_record_2.exp
new file mode 100644 (file)
index 0000000..d0bcdbd
--- /dev/null
@@ -0,0 +1,61 @@
+# Copyright 2025 Free Software Foundation, Inc.
+#
+# 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/>.
+
+load_lib "ada.exp"
+
+require allow_ada_tests
+
+standard_ada_testfile exam
+
+set flags {debug}
+if {[ada_minimal_encodings]} {
+    lappend flags additional_flags=-fgnat-encodings=minimal
+}
+
+if {[gdb_compile_ada "${srcfile}" "${binfile}" executable $flags] != ""} {
+    return -1
+}
+
+clean_restart ${testfile}
+
+set bp_location [gdb_get_line_number "STOP" ${testdir}/exam.adb]
+runto "exam.adb:$bp_location"
+
+set spr_contents "discr => 3, field => -4, array_field => \\(-5, -6, -7\\)"
+
+gdb_test "print spr" " = \\($spr_contents\\)"
+
+gdb_test "print spr.discr" " = 3"
+
+# See PR ada/32880 -- gdb should probably print array (1 .. 3) here,
+# but instead shows array (<>).  However as this isn't totally
+# relevant to this test, we just accept it.
+gdb_test "ptype spr" \
+    [multi_line \
+        "type = tagged record" \
+        "    discr: range 1 .. 8;" \
+        "    field: range -7 .. -4;" \
+        "    array_field: array \\(<>\\) of exam.small <packed: 2-bit elements>;" \
+        "end record"]
+
+gdb_test_multiple "print sc" "" {
+    -re " \\($spr_contents, outer => 2, another_array => \\(-7, -6\\)\\)" {
+       pass $gdb_test_name
+    }
+    -re " \\($spr_contents, outer => $decimal, another_array => \\(.*\\)\\)" {
+       # Other output is a known GCC bug.
+       xfail $gdb_test_name
+    }
+}
diff --git a/gdb/testsuite/gdb.ada/packed_record_2/exam.adb b/gdb/testsuite/gdb.ada/packed_record_2/exam.adb
new file mode 100644 (file)
index 0000000..e528ecf
--- /dev/null
@@ -0,0 +1,51 @@
+--  Copyright 2025 Free Software Foundation, Inc.
+--
+--  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/>.
+
+procedure Exam is
+   type Small is range -7 .. -4;
+   for Small'Size use 2;
+
+   type Range_Int is range 1 .. 8;
+   for Range_Int'Size use 3;
+
+   type Packed_Array is array (Range_Int range <>) of Small;
+   pragma pack (Packed_Array);
+
+   type Some_Packed_Record (Discr : Range_Int) is tagged record
+      Field: Small;
+      Array_Field : Packed_Array (1 .. Discr);
+   end record;
+   pragma Pack (Some_Packed_Record);
+
+   type Sub_Class (Inner, Outer : Range_Int)
+   is new Some_Packed_Record (Inner) with
+      record
+         Another_Array : Packed_Array (1 .. Outer);
+      end record;
+   pragma Pack (Sub_Class);
+
+   SPR : Some_Packed_Record := (Discr => 3,
+                                Field => -4,
+                                Array_Field => (-5, -6, -7));
+
+   SC : Sub_Class := (Inner => 3,
+                      Outer => 2,
+                      Field => -4,
+                      Array_Field => (-5, -6, -7),
+                      Another_Array => (-7, -6));
+
+begin
+   null;                        --  STOP
+end Exam;
index d8d57faaba16f652321367789aa31cb84d17d105..41dce7798f6d141665a7e8f5c31d55e04effb894 100644 (file)
@@ -3266,6 +3266,9 @@ unpack_bits_as_long (struct type *field_type, const gdb_byte *valaddr,
        }
     }
 
+  if (field_type->code () == TYPE_CODE_RANGE)
+    val += field_type->bounds ()->bias;
+
   return val;
 }