PR25230, addr2line fails on dwz output

Message ID 20191203073951.GG28726@bubble.grove.modra.org
State New
Headers show
Series
  • PR25230, addr2line fails on dwz output
Related show

Commit Message

Alan Modra Dec. 3, 2019, 7:39 a.m.
This patch remedies the following DW_FORM_GNU_ref_alt related problem:
      /* FIXME: Do we need to locate the correct CU, in a similar
        fashion to the code in the DW_FORM_ref_addr case above ?  */
Without the correct CU the wrong abbrevs are used, resulting in
errors and/or wrong file names.

There is scope for further work here.  Parsing of CUs should be a two
step process, with the first stage just finding the bounds of the CU.
This would allow find_abstract_instance to quickly find the CU
referenced by DW_FORM_ref_addr or DW_FORM_GNU_ref_alt, then take the
second stage of CU parsing where abbrevs, ranges and suchlike consume
time and memory.  As it is, we just process CUs from the start of
.debug_info until we find the one of interest.  The testcase in the PR
takes 98G of virtual memory.

Oh, and it took over 1 hour 45 minutes for addr2line to exit after
printing the file/line on my home x86_64 box with only 16G of
physical memory.  Most of the time was in objalloc_free.  It would be
nice if BFD had a flag to say the process was about to exit so no
cleanup is necessary.

	PR 25230
	* dwarf2.c (struct dwarf2_debug_file): Add line_table and
	abbrev_offsets.
	(struct abbrev_offset_entry): New.
	(hash_abbrev, eq_abbrev, del_abbrev): New functions.
	(read_abbrevs): Check whether we have already read abbrevs at
	given offset, and add new offset/abbrev to hash table.
	(decode_line_info): Keep line table at offset zero in file struct.
	Return this for a cu reusing the same dir/file list.
	(find_abstract_instance): Find cu for DW_FORM_GNU_ref_alt.
	(_bfd_dwarf2_slurp_debug_info): Create offset/abbrev hash tables.
	(_bfd_dwarf2_cleanup_debug_info): Adjust deletion of lines and
	abbrevs.


-- 
Alan Modra
Australia Development Lab, IBM

Patch

diff --git a/bfd/dwarf2.c b/bfd/dwarf2.c
index 61c459ea9f..c73665c1d8 100644
--- a/bfd/dwarf2.c
+++ b/bfd/dwarf2.c
@@ -135,6 +135,12 @@  struct dwarf2_debug_file
 
   /* Last comp unit in list above.  */
   struct comp_unit *last_comp_unit;
+
+  /* Line table at line_offset zero.  */
+  struct line_info_table *line_table;
+
+  /* Hash table to map offsets to decoded abbrevs.  */
+  htab_t abbrev_offsets;
 };
 
 struct dwarf2_debug
@@ -929,6 +935,51 @@  lookup_abbrev (unsigned int number, struct abbrev_info **abbrevs)
   return NULL;
 }
 
+/* We keep a hash table to map .debug_abbrev section offsets to the
+   array of abbrevs, so that compilation units using the same set of
+   abbrevs do not waste memory.  */
+
+struct abbrev_offset_entry
+{
+  size_t offset;
+  struct abbrev_info **abbrevs;
+};
+
+static hashval_t
+hash_abbrev (const void *p)
+{
+  const struct abbrev_offset_entry *ent = p;
+  return htab_hash_pointer ((void *) ent->offset);
+}
+
+static int
+eq_abbrev (const void *pa, const void *pb)
+{
+  const struct abbrev_offset_entry *a = pa;
+  const struct abbrev_offset_entry *b = pb;
+  return a->offset == b->offset;
+}
+
+static void
+del_abbrev (void *p)
+{
+  struct abbrev_offset_entry *ent = p;
+  struct abbrev_info **abbrevs = ent->abbrevs;
+  size_t i;
+
+  for (i = 0; i < ABBREV_HASH_SIZE; i++)
+    {
+      struct abbrev_info *abbrev = abbrevs[i];
+
+      while (abbrev)
+	{
+	  free (abbrev->attrs);
+	  abbrev = abbrev->next;
+	}
+    }
+  free (ent);
+}
+
 /* In DWARF version 2, the description of the debugging information is
    stored in a separate .debug_abbrev section.  Before we read any
    dies from a section we read in all abbreviations and install them
@@ -945,6 +996,17 @@  read_abbrevs (bfd *abfd, bfd_uint64_t offset, struct dwarf2_debug *stash,
   unsigned int abbrev_number, bytes_read, abbrev_name;
   unsigned int abbrev_form, hash_number;
   bfd_size_type amt;
+  void **slot;
+  struct abbrev_offset_entry ent = { offset, NULL };
+
+  if (ent.offset != offset)
+    return NULL;
+
+  slot = htab_find_slot (file->abbrev_offsets, &ent, INSERT);
+  if (slot == NULL)
+    return NULL;
+  if (*slot != NULL)
+    return ((struct abbrev_offset_entry *) (*slot))->abbrevs;
 
   if (! read_section (abfd, &stash->debug_sections[debug_abbrev],
 		      file->syms, offset,
@@ -1044,6 +1106,12 @@  read_abbrevs (bfd *abfd, bfd_uint64_t offset, struct dwarf2_debug *stash,
       if (lookup_abbrev (abbrev_number, abbrevs) != NULL)
 	break;
     }
+
+  *slot = bfd_malloc (sizeof ent);
+  if (!*slot)
+    goto fail;
+  ent.abbrevs = abbrevs;
+  memcpy (*slot, &ent, sizeof ent);
   return abbrevs;
 
  fail:
@@ -2025,29 +2093,14 @@  decode_line_info (struct comp_unit *unit)
   unsigned int exop_len;
   bfd_size_type amt;
 
+  if (unit->line_offset == 0 && file->line_table)
+    return file->line_table;
+
   if (! read_section (abfd, &stash->debug_sections[debug_line],
 		      file->syms, unit->line_offset,
 		      &file->dwarf_line_buffer, &file->dwarf_line_size))
     return NULL;
 
-  amt = sizeof (struct line_info_table);
-  table = (struct line_info_table *) bfd_alloc (abfd, amt);
-  if (table == NULL)
-    return NULL;
-  table->abfd = abfd;
-  table->comp_dir = unit->comp_dir;
-
-  table->num_files = 0;
-  table->files = NULL;
-
-  table->num_dirs = 0;
-  table->dirs = NULL;
-
-  table->num_sequences = 0;
-  table->sequences = NULL;
-
-  table->lcl_head = NULL;
-
   if (file->dwarf_line_size < 16)
     {
       _bfd_error_handler
@@ -2184,6 +2237,24 @@  decode_line_info (struct comp_unit *unit)
       line_ptr += 1;
     }
 
+  amt = sizeof (struct line_info_table);
+  table = (struct line_info_table *) bfd_alloc (abfd, amt);
+  if (table == NULL)
+    return NULL;
+  table->abfd = abfd;
+  table->comp_dir = unit->comp_dir;
+
+  table->num_files = 0;
+  table->files = NULL;
+
+  table->num_dirs = 0;
+  table->dirs = NULL;
+
+  table->num_sequences = 0;
+  table->sequences = NULL;
+
+  table->lcl_head = NULL;
+
   if (lh.version >= 5)
     {
       /* Read directory table.  */
@@ -2441,6 +2512,8 @@  decode_line_info (struct comp_unit *unit)
 	free (filename);
     }
 
+  if (unit->line_offset == 0)
+    file->line_table = table;
   if (sort_line_sequences (table))
     return table;
 
@@ -2818,7 +2891,7 @@  find_abstract_instance (struct comp_unit *unit,
 			int *linenumber_ptr)
 {
   bfd *abfd = unit->abfd;
-  bfd_byte *info_ptr;
+  bfd_byte *info_ptr = NULL;
   bfd_byte *info_ptr_end;
   unsigned int abbrev_number, bytes_read, i;
   struct abbrev_info *abbrev;
@@ -2868,14 +2941,38 @@  find_abstract_instance (struct comp_unit *unit,
 	  return FALSE;
 	}
       info_ptr += die_ref;
+    }
+  else if (attr_ptr->form == DW_FORM_GNU_ref_alt)
+    {
+      bfd_boolean first_time = unit->stash->alt.dwarf_info_buffer == NULL;
+
+      info_ptr = read_alt_indirect_ref (unit, die_ref);
+      if (first_time)
+	unit->stash->alt.info_ptr = unit->stash->alt.dwarf_info_buffer;
+      if (info_ptr == NULL)
+	{
+	  _bfd_error_handler
+	    (_("DWARF error: unable to read alt ref %" PRIu64),
+	     (uint64_t) die_ref);
+	  bfd_set_error (bfd_error_bad_value);
+	  return FALSE;
+	}
+      info_ptr_end = (unit->stash->alt.dwarf_info_buffer
+		      + unit->stash->alt.dwarf_info_size);
+      if (unit->stash->alt.all_comp_units)
+	unit = unit->stash->alt.all_comp_units;
+    }
 
+  if (attr_ptr->form == DW_FORM_ref_addr
+      || attr_ptr->form == DW_FORM_GNU_ref_alt)
+    {
       /* Now find the CU containing this pointer.  */
       if (info_ptr >= unit->info_ptr_unit && info_ptr < unit->end_ptr)
 	info_ptr_end = unit->end_ptr;
       else
 	{
 	  /* Check other CUs to see if they contain the abbrev.  */
-	  struct comp_unit * u;
+	  struct comp_unit *u;
 
 	  for (u = unit->prev_unit; u != NULL; u = u->prev_unit)
 	    if (info_ptr >= u->info_ptr_unit && info_ptr < u->end_ptr)
@@ -2886,15 +2983,27 @@  find_abstract_instance (struct comp_unit *unit,
 	      if (info_ptr >= u->info_ptr_unit && info_ptr < u->end_ptr)
 		break;
 
-	  while (u == NULL)
-	    {
-	      u = stash_comp_unit (unit->stash, unit->file);
-	      if (u == NULL)
-		break;
-	      if (info_ptr >= u->info_ptr_unit && info_ptr < u->end_ptr)
-		break;
-	      u = NULL;
-	    }
+	  if (attr_ptr->form == DW_FORM_ref_addr)
+	    while (u == NULL)
+	      {
+		u = stash_comp_unit (unit->stash, &unit->stash->f);
+		if (u == NULL)
+		  break;
+		if (info_ptr >= u->info_ptr_unit && info_ptr < u->end_ptr)
+		  break;
+		u = NULL;
+	      }
+
+	  if (attr_ptr->form == DW_FORM_GNU_ref_alt)
+	    while (u == NULL)
+	      {
+		u = stash_comp_unit (unit->stash, &unit->stash->alt);
+		if (u == NULL)
+		  break;
+		if (info_ptr >= u->info_ptr_unit && info_ptr < u->end_ptr)
+		  break;
+		u = NULL;
+	      }
 
 	  if (u == NULL)
 	    {
@@ -2908,23 +3017,6 @@  find_abstract_instance (struct comp_unit *unit,
 	  info_ptr_end = unit->end_ptr;
 	}
     }
-  else if (attr_ptr->form == DW_FORM_GNU_ref_alt)
-    {
-      info_ptr = read_alt_indirect_ref (unit, die_ref);
-      if (info_ptr == NULL)
-	{
-	  _bfd_error_handler
-	    (_("DWARF error: unable to read alt ref %" PRIu64),
-	     (uint64_t) die_ref);
-	  bfd_set_error (bfd_error_bad_value);
-	  return FALSE;
-	}
-      info_ptr_end = (unit->stash->alt.dwarf_info_buffer
-		      + unit->stash->alt.dwarf_info_size);
-
-      /* FIXME: Do we need to locate the correct CU, in a similar
-	 fashion to the code in the DW_FORM_ref_addr case above ?  */
-    }
   else
     {
       /* DW_FORM_ref1, DW_FORM_ref2, DW_FORM_ref4, DW_FORM_ref8 or
@@ -4396,6 +4488,16 @@  _bfd_dwarf2_slurp_debug_info (bfd *abfd, bfd *debug_bfd,
   if (!save_section_vma (abfd, stash))
     return FALSE;
 
+  stash->f.abbrev_offsets = htab_create_alloc (10, hash_abbrev, eq_abbrev,
+					       del_abbrev, calloc, free);
+  if (!stash->f.abbrev_offsets)
+    return FALSE;
+
+  stash->alt.abbrev_offsets = htab_create_alloc (10, hash_abbrev, eq_abbrev,
+						 del_abbrev, calloc, free);
+  if (!stash->alt.abbrev_offsets)
+    return FALSE;
+
   *pinfo = stash;
 
   if (debug_bfd == NULL)
@@ -4983,23 +5085,10 @@  _bfd_dwarf2_cleanup_debug_info (bfd *abfd, void **pinfo)
     {
       for (each = file->all_comp_units; each; each = each->next_unit)
 	{
-	  struct abbrev_info **abbrevs = each->abbrevs;
 	  struct funcinfo *function_table = each->function_table;
 	  struct varinfo *variable_table = each->variable_table;
-	  size_t i;
-
-	  for (i = 0; i < ABBREV_HASH_SIZE; i++)
-	    {
-	      struct abbrev_info *abbrev = abbrevs[i];
-
-	      while (abbrev)
-		{
-		  free (abbrev->attrs);
-		  abbrev = abbrev->next;
-		}
-	    }
 
-	  if (each->line_table)
+	  if (each->line_table && each->line_table != file->line_table)
 	    {
 	      free (each->line_table->files);
 	      free (each->line_table->dirs);
@@ -5037,6 +5126,13 @@  _bfd_dwarf2_cleanup_debug_info (bfd *abfd, void **pinfo)
 	    }
 	}
 
+      if (file->line_table)
+	{
+	  free (file->line_table->files);
+	  free (file->line_table->dirs);
+	}
+      htab_delete (file->abbrev_offsets);
+
       free (file->dwarf_line_str_buffer);
       free (file->dwarf_str_buffer);
       free (file->dwarf_ranges_buffer);