Blame SOURCES/gdb-rhbz1639242-fix-dwarf2_find_containing_comp_unit-binary-search.patch

4c2ad1
From FEDORA_PATCHES Mon Sep 17 00:00:00 2001
4c2ad1
From: Sergio Durigan Junior <sergiodj@redhat.com>
4c2ad1
Date: Fri, 30 Nov 2018 15:20:27 -0500
4c2ad1
Subject: 
4c2ad1
 gdb-rhbz1639242-fix-dwarf2_find_containing_comp_unit-binary-search.patch
4c2ad1
4c2ad1
;; Fix for 'py-bt is broken, results in exception'.
4c2ad1
;; RHBZ 1639242
4c2ad1
4c2ad1
Fix dwarf2read.c:dwarf2_find_containing_comp_unit's binary search
4c2ad1
4c2ad1
First of all, I would like to express my gratitude to Keith Seitz, Jan
4c2ad1
Kratochvil and Tom Tromey, who were really kind and helped a lot with
4c2ad1
this bug.  The patch itself was authored by Jan.
4c2ad1
4c2ad1
This all began with:
4c2ad1
4c2ad1
  https://bugzilla.redhat.com/show_bug.cgi?id=1639242
4c2ad1
  py-bt is broken, results in exception
4c2ad1
4c2ad1
In summary, the error reported by the bug above is:
4c2ad1
4c2ad1
  $ gdb -args python3
4c2ad1
  GNU gdb (GDB) Fedora 8.1.1-3.fc28
4c2ad1
  (...)
4c2ad1
  Reading symbols from python3...Reading symbols from /usr/lib/debug/usr/bin/python3.6-3.6.6-1.fc28.x86_64.debug...done.
4c2ad1
  done.
4c2ad1
  Dwarf Error: could not find partial DIE containing offset 0x316 [in module /usr/lib/debug/usr/bin/python3.6-3.6.6-1.fc28.x86_64.debug]
4c2ad1
4c2ad1
After a long investigation, and after thinking that the problem might
4c2ad1
actually be on DWZ's side, we were able to determine that there's
4c2ad1
something wrong going on when
4c2ad1
dwarf2read.c:dwarf2_find_containing_comp_unit performs a binary search
4c2ad1
over all of the CUs belonging to an objfile in order to find the CU
4c2ad1
which contains a DIE at an specific offset.  The current algorithm is:
4c2ad1
4c2ad1
  static struct dwarf2_per_cu_data *
4c2ad1
  dwarf2_find_containing_comp_unit (sect_offset sect_off,
4c2ad1
				    unsigned int offset_in_dwz,
4c2ad1
				    struct dwarf2_per_objfile *dwarf2_per_objfile)
4c2ad1
  {
4c2ad1
    struct dwarf2_per_cu_data *this_cu;
4c2ad1
    int low, high;
4c2ad1
    const sect_offset *cu_off;
4c2ad1
4c2ad1
    low = 0;
4c2ad1
    high = dwarf2_per_objfile->all_comp_units.size () - 1;
4c2ad1
    while (high > low)
4c2ad1
      {
4c2ad1
	struct dwarf2_per_cu_data *mid_cu;
4c2ad1
	int mid = low + (high - low) / 2;
4c2ad1
4c2ad1
	mid_cu = dwarf2_per_objfile->all_comp_units[mid];
4c2ad1
	cu_off = &mid_cu->sect_off;
4c2ad1
	if (mid_cu->is_dwz > offset_in_dwz
4c2ad1
	    || (mid_cu->is_dwz == offset_in_dwz && *cu_off >= sect_off))
4c2ad1
	  high = mid;
4c2ad1
	else
4c2ad1
	  low = mid + 1;
4c2ad1
      }
4c2ad1
4c2ad1
For the sake of this example, let's consider that "sect_off =
4c2ad1
0x7d".
4c2ad1
4c2ad1
There are a few important things going on here.  First,
4c2ad1
"dwarf2_per_objfile->all_comp_units ()" will be sorted first by
4c2ad1
whether the CU is a DWZ CU, and then by cu->sect_off.  In this
4c2ad1
specific bug, "offset_in_dwz" is false, which means that, for the most
4c2ad1
part of the loop, we're going to do "high = mid" (i.e, we'll work with
4c2ad1
the lower part of the vector).
4c2ad1
4c2ad1
In our particular case, when we reach the part where "mid_cu->is_dwz
4c2ad1
== offset_in_dwz" (i.e, both are false), we end up with "high = 2" and
4c2ad1
"mid = 1".  I.e., there are only 2 elements in the vector who are not
4c2ad1
DWZ.  The vector looks like this:
4c2ad1
4c2ad1
  #0: cu->sect_off = 0;   length = 114;  is_dwz = false  <-- low
4c2ad1
  #1: cu->sect_off = 114; length = 7796; is_dwz = false  <-- mid
4c2ad1
  #2: cu->sect_off = 0;   length = 28;   is_dwz = true   <-- high
4c2ad1
  ...
4c2ad1
4c2ad1
The CU we want is #1, which is exactly where "mid" is.  Also, #1 is
4c2ad1
not DWZ, which is also exactly what we want.  So we perform the second
4c2ad1
comparison:
4c2ad1
4c2ad1
  (mid_cu->is_dwz == offset_in_dwz && *cu_off >= sect_off)
4c2ad1
                                      ^^^^^^^^^^^^^^^^^^^
4c2ad1
4c2ad1
Because "*cu_off = 114" and "sect_off = 0x7d", this evaluates to
4c2ad1
false, so we end up with "low = mid + 1 = 2", which actually gives us
4c2ad1
the wrong CU (i.e., a CU that is DWZ).  Next in the code, GDB does:
4c2ad1
4c2ad1
    gdb_assert (low == high);
4c2ad1
    this_cu = dwarf2_per_objfile->all_comp_units[low];
4c2ad1
    cu_off = &this_cu->sect_off;
4c2ad1
    if (this_cu->is_dwz != offset_in_dwz || *cu_off > sect_off)
4c2ad1
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4c2ad1
      {
4c2ad1
	if (low == 0 || this_cu->is_dwz != offset_in_dwz)
4c2ad1
	  error (_("Dwarf Error: could not find partial DIE containing "
4c2ad1
		 "offset %s [in module %s]"),
4c2ad1
		 sect_offset_str (sect_off),
4c2ad1
		 bfd_get_filename (dwarf2_per_objfile->objfile->obfd));
4c2ad1
	...
4c2ad1
4c2ad1
Triggering the error we saw in the original bug report.
4c2ad1
4c2ad1
It's important to notice that we see the error message because the
4c2ad1
selected CU is a DWZ one, but we're looking for a non-DWZ CU here.
4c2ad1
However, even when the selected CU is *not* a DWZ (and we don't see
4c2ad1
any error message), we still end up with the wrong CU.  For example,
4c2ad1
suppose that the vector had:
4c2ad1
4c2ad1
  #0: cu->sect_off = 0;    length = 114;  is_dwz = false
4c2ad1
  #1: cu->sect_off = 114;  length = 7796; is_dwz = false
4c2ad1
  #2: cu->sect_off = 7910; length = 28;   is_dwz = false
4c2ad1
  ...
4c2ad1
4c2ad1
I.e., #2's "is_dwz" is false instead of true.  In this case, we still
4c2ad1
want #1, because that's where the DIE is located.  After the loop ends
4c2ad1
up in #2, we have "is_dwz" as false, which is what we wanted, so we
4c2ad1
compare offsets.  In this case, "7910 >= 0x7d", so we set "mid = high
4c2ad1
= 2".  Next iteration, we have "mid = 0 + (2 - 0) / 2 = 1", and thus
4c2ad1
we examining #1.  "is_dwz" is still false, but "114 >= 0x7d" also
4c2ad1
evaluates to false, so "low = mid + 1 = 2", which makes the loop stop.
4c2ad1
Therefore, we end up choosing #2 as our CU, even though #1 is the
4c2ad1
right one.
4c2ad1
4c2ad1
The problem here is happening because we're comparing "sect_off"
4c2ad1
directly against "*cu_off", while we should actually be comparing
4c2ad1
against "*cu_off + mid_cu->length" (i.e., the end offset):
4c2ad1
4c2ad1
  ...
4c2ad1
  || (mid_cu->is_dwz == offset_in_dwz
4c2ad1
      && *cu_off + mid_cu->length >= sect_off))
4c2ad1
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4c2ad1
  ...
4c2ad1
4c2ad1
And this is what the patch does.  The idea is that if GDB is searching
4c2ad1
for an offset that falls above the *end* of the CU being
4c2ad1
analyzed (i.e., "mid"), then the next iteration should try a
4c2ad1
higher-offset CU next.  The previous algorithm was using
4c2ad1
the *beginning* of the CU.
4c2ad1
4c2ad1
Unfortunately, I could not devise a testcase for this problem, so I am
4c2ad1
proposing a fix with this huge explanation attached to it in the hope
4c2ad1
that it is sufficient.  After talking a bit to Keith (our testcase
4c2ad1
guru), it seems that one would have to create an objfile with both DWZ
4c2ad1
and non-DWZ sections, which may prove very hard to do, I think.
4c2ad1
4c2ad1
I ran this patch on our BuildBot, and no regressions were detected.
4c2ad1
4c2ad1
gdb/ChangeLog:
4c2ad1
2018-11-30  Jan Kratochvil  <jan.kratochvil@redhat.com>
4c2ad1
	    Keith Seitz  <keiths@redhat.com>
4c2ad1
	    Tom Tromey  <tom@tromey.com>
4c2ad1
	    Sergio Durigan Junior  <sergiodj@redhat.com>
4c2ad1
4c2ad1
	https://bugzilla.redhat.com/show_bug.cgi?id=1613614
4c2ad1
	* dwarf2read.c (dwarf2_find_containing_comp_unit): Add
4c2ad1
	'mid_cu->length' to '*cu_off' when checking if 'sect_off' is
4c2ad1
	inside the CU.
4c2ad1
4c2ad1
diff --git a/gdb/dwarf2read.c b/gdb/dwarf2read.c
4c2ad1
--- a/gdb/dwarf2read.c
4c2ad1
+++ b/gdb/dwarf2read.c
4c2ad1
@@ -25039,7 +25039,8 @@ dwarf2_find_containing_comp_unit (sect_offset sect_off,
4c2ad1
       mid_cu = dwarf2_per_objfile->all_comp_units[mid];
4c2ad1
       cu_off = &mid_cu->sect_off;
4c2ad1
       if (mid_cu->is_dwz > offset_in_dwz
4c2ad1
-	  || (mid_cu->is_dwz == offset_in_dwz && *cu_off >= sect_off))
4c2ad1
+	  || (mid_cu->is_dwz == offset_in_dwz
4c2ad1
+	      && *cu_off + mid_cu->length >= sect_off))
4c2ad1
 	high = mid;
4c2ad1
       else
4c2ad1
 	low = mid + 1;