]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
[gdb/symtab] Fix segfault on invalid debug info
authorTom de Vries <tdevries@suse.de>
Tue, 24 Sep 2024 08:50:44 +0000 (10:50 +0200)
committerTom de Vries <tdevries@suse.de>
Tue, 24 Sep 2024 08:50:44 +0000 (10:50 +0200)
While looking at PR symtab/31478 (a problem in the cooked indexer with invalid
dwarf) it occurred to me that I could trigger a similar problem using:
...
  Compilation Unit @ offset 0xb2:
   Length:        0x1f (32-bit)
   Version:       4
   Abbrev Offset: 0x6c
   Pointer Size:  8
 <0><bd>: Abbrev Number: 1 (DW_TAG_compile_unit)
    <be>   DW_AT_language    : 2 (non-ANSI C)
 <1><bf>: Abbrev Number: 2 (DW_TAG_subprogram)
    <c0>   DW_AT_low_pc      : 0x4004a7
    <c8>   DW_AT_high_pc     : 0x4004b2
    <d0>   DW_AT_specification: <0xd5>
 <1><d4>: Abbrev Number: 0
  Compilation Unit @ offset 0xd5:
   Length:        0x7 (32-bit)
   Version:       4
   Abbrev Offset: 0x7f
   Pointer Size:  8
...
and indeed I get:
...
$ gdb -q -batch outputs/gdb.dwarf2/dw2-inter-cu-error-2/dw2-inter-cu-error-2

Fatal signal: Segmentation fault
...

The problem is that we're calling prepare_one_comp_unit with cu == nullptr and
comp_unit_die == nullptr here in cooked_indexer::ensure_cu_exists:
...
      cutu_reader new_reader (per_cu, per_objfile, nullptr, nullptr, false,
                              m_index_storage->get_abbrev_cache ());

      prepare_one_comp_unit (new_reader.cu, new_reader.comp_unit_die,
                             language_minimal);
...

Fix this by bailing out for various types of dummy CUs:
...
      if (new_reader.dummy_p || new_reader.comp_unit_die == nullptr
  || !new_reader.comp_unit_die->has_children)
return nullptr;
...

Also make sure in scan_attributes that this triggers a dwarf error:
...
$ gdb -q -batch dw2-inter-cu-error-2
DWARF Error: cannot follow reference to DIE at 0xd5 \
  [in module dw2-inter-cu-error-2]
...

With target board readnow, the test-case triggers an assertion failure in
follow_die_offset, so fix this by throwing the same dwarf error.

While we're at it, make the other check for dummy CUs in
cooked_indexer::ensure_cu_exists more robust by adding an intermediate test
for comp_unit_die:
...
-  if (result->dummy_p || !result->comp_unit_die->has_children)
+  if (result->dummy_p || result->comp_unit_die == nullptr
+      || !result->comp_unit_die->has_children)
     return nullptr;
...

Tested on x86_64-linux.

Approved-By: Tom Tromey <tom@tromey.com>
gdb/dwarf2/read.c
gdb/testsuite/gdb.dwarf2/dw2-inter-cu-error-2.exp [new file with mode: 0644]

index ca27391a6f5eba206aa71dca0b33556770c2a8b3..193a52ef6e07d54b061058f9d0733f6a19f9905d 100644 (file)
@@ -16078,6 +16078,10 @@ cooked_indexer::ensure_cu_exists (cutu_reader *reader,
       cutu_reader new_reader (per_cu, per_objfile, nullptr, nullptr, false,
                              m_index_storage->get_abbrev_cache ());
 
+      if (new_reader.dummy_p || new_reader.comp_unit_die == nullptr
+         || !new_reader.comp_unit_die->has_children)
+       return nullptr;
+
       prepare_one_comp_unit (new_reader.cu, new_reader.comp_unit_die,
                             language_minimal);
       std::unique_ptr<cutu_reader> copy
@@ -16085,7 +16089,8 @@ cooked_indexer::ensure_cu_exists (cutu_reader *reader,
       result = m_index_storage->preserve (std::move (copy));
     }
 
-  if (result->dummy_p || !result->comp_unit_die->has_children)
+  if (result->dummy_p  || result->comp_unit_die == nullptr
+      || !result->comp_unit_die->has_children)
     return nullptr;
 
   if (for_scanning)
@@ -16271,49 +16276,53 @@ cooked_indexer::scan_attributes (dwarf2_per_cu_data *scanning_per_cu,
       cutu_reader *new_reader
        = ensure_cu_exists (reader, reader->cu->per_objfile, origin_offset,
                            origin_is_dwz, false);
-      if (new_reader != nullptr)
-       {
-         const gdb_byte *new_info_ptr = (new_reader->buffer
-                                         + to_underlying (origin_offset));
+      if (new_reader == nullptr)
+       error (_(DWARF_ERROR_PREFIX
+                "cannot follow reference to DIE at %s"
+                " [in module %s]"),
+              sect_offset_str (origin_offset),
+              bfd_get_filename (reader->abfd));
 
-         if (*parent_entry == nullptr)
-           {
-             /* We only perform immediate lookups of parents for DIEs
-                from earlier in this CU.  This avoids any problem
-                with a NULL result when when we see a reference to a
-                DIE in another CU that we may or may not have
-                imported locally.  */
-             parent_map::addr_type addr
-               = parent_map::form_addr (origin_offset, origin_is_dwz);
-             if (new_reader->cu != reader->cu || new_info_ptr > watermark_ptr)
-               *maybe_defer = addr;
-             else
-               *parent_entry = m_die_range_map->find (addr);
-           }
+      const gdb_byte *new_info_ptr = (new_reader->buffer
+                                     + to_underlying (origin_offset));
 
-         unsigned int bytes_read;
-         const abbrev_info *new_abbrev = peek_die_abbrev (*new_reader,
-                                                          new_info_ptr,
-                                                          &bytes_read);
+      if (*parent_entry == nullptr)
+       {
+         /* We only perform immediate lookups of parents for DIEs
+            from earlier in this CU.  This avoids any problem
+            with a NULL result when when we see a reference to a
+            DIE in another CU that we may or may not have
+            imported locally.  */
+         parent_map::addr_type addr
+           = parent_map::form_addr (origin_offset, origin_is_dwz);
+         if (new_reader->cu != reader->cu || new_info_ptr > watermark_ptr)
+           *maybe_defer = addr;
+         else
+           *parent_entry = m_die_range_map->find (addr);
+       }
 
-         if (new_abbrev == nullptr)
-           error (_(DWARF_ERROR_PREFIX
-                    "Unexpected null DIE at offset %s [in module %s]"),
-                  sect_offset_str (origin_offset),
-                  bfd_get_filename (new_reader->abfd));
+      unsigned int bytes_read;
+      const abbrev_info *new_abbrev = peek_die_abbrev (*new_reader,
+                                                      new_info_ptr,
+                                                      &bytes_read);
 
-         new_info_ptr += bytes_read;
+      if (new_abbrev == nullptr)
+       error (_(DWARF_ERROR_PREFIX
+                "Unexpected null DIE at offset %s [in module %s]"),
+              sect_offset_str (origin_offset),
+              bfd_get_filename (new_reader->abfd));
 
-         if (new_reader->cu == reader->cu && new_info_ptr == watermark_ptr)
-           {
-             /* Self-reference, we're done.  */
-           }
-         else
-           scan_attributes (scanning_per_cu, new_reader, new_info_ptr,
-                            new_info_ptr, new_abbrev, name, linkage_name,
-                            flags, nullptr, parent_entry, maybe_defer,
-                            is_enum_class, true);
+      new_info_ptr += bytes_read;
+
+      if (new_reader->cu == reader->cu && new_info_ptr == watermark_ptr)
+       {
+         /* Self-reference, we're done.  */
        }
+      else
+       scan_attributes (scanning_per_cu, new_reader, new_info_ptr,
+                        new_info_ptr, new_abbrev, name, linkage_name,
+                        flags, nullptr, parent_entry, maybe_defer,
+                        is_enum_class, true);
     }
 
   if (!for_specification)
@@ -20415,7 +20424,12 @@ follow_die_offset (sect_offset sect_off, int offset_in_dwz,
                             false, cu->lang ());
 
       target_cu = per_objfile->get_cu (per_cu);
-      gdb_assert (target_cu != nullptr);
+      if (target_cu == nullptr)
+       error (_(DWARF_ERROR_PREFIX
+                "cannot follow reference to DIE at %s"
+                " [in module %s]"),
+              sect_offset_str (sect_off),
+              objfile_name (per_objfile->objfile));
     }
   else if (cu->dies == NULL)
     {
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-inter-cu-error-2.exp b/gdb/testsuite/gdb.dwarf2/dw2-inter-cu-error-2.exp
new file mode 100644 (file)
index 0000000..585fd54
--- /dev/null
@@ -0,0 +1,51 @@
+# Copyright 2024 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/>.
+
+# Check that GDB doesn't crash on invalid dwarf, specifically an inter-CU
+# reference pointing to a dummy CU.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile main.c .S
+
+# Create the DWARF.
+set asm_file [standard_output_file $srcfile2]
+Dwarf::assemble $asm_file {
+    declare_labels label1
+
+    cu {} {
+       compile_unit {{language @DW_LANG_C}} {
+           subprogram {
+               {MACRO_AT_range { main }}
+               {DW_AT_specification %$label1}
+           }
+       }
+    }
+
+    label1: cu {} {
+    }
+}
+
+if [prepare_for_testing "failed to prepare" $testfile \
+       [list $asm_file $srcfile] {nodebug}] {
+    return -1
+}
+
+gdb_assert \
+    { [regexp "DWARF Error: cannot follow reference" $gdb_file_cmd_msg] } \
+    "Error message"