[gdb/symtab] Fix function range handling in psymtabs

Consider the test-case from this patch.

We run into:
...
(gdb) PASS: gdb.dwarf2/dw2-ranges-psym-warning.exp: continue
bt^M
warning: (Internal error: pc 0x4004b6 in read in psymtab, but not in symtab.)^M
^M
warning: (Internal error: pc 0x4004b6 in read in psymtab, but not in symtab.)^M
^M
warning: (Internal error: pc 0x4004b6 in read in psymtab, but not in symtab.)^M
^M
warning: (Internal error: pc 0x4004b6 in read in psymtab, but not in symtab.)^M
^M
warning: (Internal error: pc 0x4004b6 in read in psymtab, but not in symtab.)^M
^M
warning: (Internal error: pc 0x4004b6 in read in psymtab, but not in symtab.)^M
^M
  read in psymtab, but not in symtab.)^M
^M
)^M
(gdb) FAIL: gdb.dwarf2/dw2-ranges-psym-warning.exp: bt
...

This happens as follows.

The function foo:
...
 <1><31>: Abbrev Number: 4 (DW_TAG_subprogram)
    <33>   DW_AT_name        : foo
    <37>   DW_AT_ranges      : 0x0
...
has these ranges:
...
    00000000 00000000004004c1 00000000004004d2
    00000000 00000000004004ae 00000000004004af
    00000000 <End of list>
...
which have a hole at at [0x4004af,0x4004c1).

However, the address map of the partial symtabs incorrectly maps addresses
in the hole (such as 0x4004b6 in the backtrace) to the foo CU.

The address map of the full symbol table of the foo CU however does not
contain the addresses in the hole, which is what the warning / internal error
complains about.

Fix this by making sure that ranges of functions are read correctly.

The patch adds a bit to struct partial_die_info, in this hole (shown for
x86_64-linux):
...
/*   11: 7   |     4 */    unsigned int canonical_name : 1;
/* XXX  4-byte hole  */
/*   16      |     8 */    const char *raw_name;
...
So there's no increase in size for 64-bit, but AFAIU there will be an increase
for 32-bit.

Tested on x86_64-linux.

gdb/ChangeLog:

2021-08-10  Tom de Vries  <tdevries@suse.de>

	PR symtab/28200
	* dwarf2/read.c (struct partial_die_info): Add has_range_info and
	range_offset field.
	(add_partial_subprogram): Handle pdi->has_range_info.
	(partial_die_info::read): Set pdi->has_range_info.

gdb/testsuite/ChangeLog:

2021-08-10  Tom de Vries  <tdevries@suse.de>

	PR symtab/28200
	* gdb.dwarf2/dw2-ranges-psym-warning-main.c: New test.
	* gdb.dwarf2/dw2-ranges-psym-warning.c: New test.
	* gdb.dwarf2/dw2-ranges-psym-warning.exp: New file.
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 5385a3a..192df2a 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -863,6 +863,7 @@
     unsigned int has_type : 1;
     unsigned int has_specification : 1;
     unsigned int has_pc_info : 1;
+    unsigned int has_range_info : 1;
     unsigned int may_be_inlined : 1;
 
     /* This DIE has been marked DW_AT_main_subprogram.  */
@@ -914,9 +915,17 @@
       sect_offset sect_off;
     } d {};
 
-    /* If HAS_PC_INFO, the PC range associated with this DIE.  */
-    CORE_ADDR lowpc = 0;
-    CORE_ADDR highpc = 0;
+    union
+    {
+      /* If HAS_PC_INFO, the PC range associated with this DIE.  */
+      struct
+      {
+	CORE_ADDR lowpc = 0;
+	CORE_ADDR highpc = 0;
+      };
+      /* If HAS_RANGE_INFO, the ranges offset associated with this DIE.  */
+      ULONGEST ranges_offset;
+    };
 
     /* Pointer into the info_buffer (or types_buffer) pointing at the target of
        DW_AT_sibling, if any.  */
@@ -954,6 +963,7 @@
       has_type = 0;
       has_specification = 0;
       has_pc_info = 0;
+      has_range_info = 0;
       may_be_inlined = 0;
       main_subprogram = 0;
       scope_set = 0;
@@ -8125,6 +8135,10 @@
     scan_partial_symbols (pdi->die_child, lowpc, highpc, set_addrmap, cu);
 }
 
+static int
+dwarf2_ranges_read (unsigned, CORE_ADDR *, CORE_ADDR *, struct dwarf2_cu *,
+		    dwarf2_psymtab *, dwarf_tag);
+
 /* Read a partial die corresponding to a subprogram or an inlined
    subprogram and create a partial symbol for that subprogram.
    When the CU language allows it, this routine also defines a partial
@@ -8174,7 +8188,20 @@
 	    }
 	}
 
-      if (pdi->has_pc_info || (!pdi->is_external && pdi->may_be_inlined))
+      if (pdi->has_range_info
+	  && dwarf2_ranges_read (pdi->ranges_offset, &pdi->lowpc, &pdi->highpc,
+				 cu,
+				 set_addrmap ? cu->per_cu->v.psymtab : nullptr,
+				 pdi->tag))
+	{
+	  if (pdi->lowpc < *lowpc)
+	    *lowpc = pdi->lowpc;
+	  if (pdi->highpc > *highpc)
+	    *highpc = pdi->highpc;
+	}
+
+      if (pdi->has_pc_info || pdi->has_range_info
+	  || (!pdi->is_external && pdi->may_be_inlined))
 	{
 	  if (!pdi->is_declaration)
 	    /* Ignore subprogram DIEs that do not have a name, they are
@@ -19389,16 +19416,14 @@
 	  {
 	    /* Offset in the .debug_ranges or .debug_rnglist section (depending
 	       on DWARF version).  */
-	    ULONGEST ranges_offset = attr.as_unsigned ();
+	    ranges_offset = attr.as_unsigned ();
 
 	    /* See dwarf2_cu::gnu_ranges_base's doc for why we might want to add
 	       this value.  */
 	    if (tag != DW_TAG_compile_unit)
 	      ranges_offset += cu->gnu_ranges_base;
 
-	    if (dwarf2_ranges_read (ranges_offset, &lowpc, &highpc, cu,
-				    nullptr, tag))
-	      has_pc_info = 1;
+	    has_range_info = 1;
 	  }
 	  break;
 
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-ranges-psym-warning-main.c b/gdb/testsuite/gdb.dwarf2/dw2-ranges-psym-warning-main.c
new file mode 100644
index 0000000..dfca2c9
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-ranges-psym-warning-main.c
@@ -0,0 +1,28 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021 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/>.  */
+
+extern void foo (void);
+
+int
+main (void)
+{
+  asm ("main_label: .globl main_label");
+
+  foo ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-ranges-psym-warning.c b/gdb/testsuite/gdb.dwarf2/dw2-ranges-psym-warning.c
new file mode 100644
index 0000000..d6cd2d4
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-ranges-psym-warning.c
@@ -0,0 +1,44 @@
+/* Copyright 2021 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/>.  */
+
+void
+baz (void)
+{
+  asm ("baz_label: .globl baz_label");
+}						/* baz end */
+
+void
+foo_low (void)
+{						/* foo_low prologue */
+  asm ("foo_low_label: .globl foo_low_label");
+  baz ();					/* foo_low baz call */
+  asm ("foo_low_label2: .globl foo_low_label2");
+}						/* foo_low end */
+
+void
+bar (void)
+{
+  asm ("bar_label: .globl bar_label");
+}						/* bar end */
+
+void
+foo (void)
+{						/* foo prologue */
+  asm ("foo_label: .globl foo_label");
+  bar ();					/* foo bar call */
+  asm ("foo_label2: .globl foo_label2");
+  foo_low ();					/* foo foo_low call */
+  asm ("foo_label3: .globl foo_label3");
+}						/* foo end */
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-ranges-psym-warning.exp b/gdb/testsuite/gdb.dwarf2/dw2-ranges-psym-warning.exp
new file mode 100644
index 0000000..0caeb83
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-ranges-psym-warning.exp
@@ -0,0 +1,141 @@
+# Copyright 2021 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 dwarf.exp
+
+# Check psymtabs addrmaps generated from DW_AT_ranges of functions.
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+if {![dwarf2_support]} {
+    unsupported "dwarf2 support required for this test"
+    return 0
+}
+
+standard_testfile -main.c .c -dw.S
+
+# We need to know the size of integer and address types in order to
+# write some of the debugging info we'd like to generate.
+#
+# For that, we ask GDB by debugging our test program.  Any program
+# would do, but since we already have it specifically for this
+# testcase, might as well use that.
+
+if { [prepare_for_testing "failed to prepare" ${testfile} \
+	  [list ${srcfile} ${srcfile2}]] } {
+    return -1
+}
+
+set asm_file [standard_output_file $srcfile3]
+Dwarf::assemble $asm_file {
+    global srcdir subdir srcfile srcfile2
+    declare_labels integer_label func_ranges_label
+    set int_size [get_sizeof "int" 4]
+
+    # Find start address and length for our functions.
+    set sources  [list ${srcdir}/${subdir}/$srcfile ${srcdir}/${subdir}/$srcfile2]
+
+    lassign [function_range foo $sources] \
+	foo_start foo_len
+    set foo_end "$foo_start + $foo_len"
+
+    lassign [function_range foo_low $sources] \
+	foo_low_start foo_low_len
+    set foo_low_end "$foo_low_start + $foo_low_len"
+
+    lassign [function_range bar $sources] \
+	bar_start bar_len
+    set bar_end "$bar_start + $bar_len"
+
+    lassign [function_range baz $sources] \
+	baz_start baz_len
+    set baz_end "$baz_start + $baz_len"
+
+    cu {} {
+	compile_unit {
+	    {language @DW_LANG_C}
+	    {name dw-ranges-psym.c}
+	    {low_pc 0 addr}
+	} {
+	    integer_label: DW_TAG_base_type {
+		{DW_AT_byte_size $int_size DW_FORM_sdata}
+		{DW_AT_encoding  @DW_ATE_signed}
+		{DW_AT_name      integer}
+	    }
+	    subprogram {
+		{external 1 flag}
+		{name foo}
+		{ranges ${func_ranges_label} DW_FORM_sec_offset}
+	    }
+	    subprogram {
+		{external 1 flag}
+		{name bar}
+		{low_pc $bar_start addr}
+		{high_pc $bar_len DW_FORM_data4}
+	    }
+	    subprogram {
+		{external 1 flag}
+		{name baz}
+		{low_pc $baz_start addr}
+		{high_pc $baz_len DW_FORM_data4}
+	    }
+	}
+    }
+
+    # Generate ranges data.  Create a hole at $foo_low_start + 1" .. $foo_low_end.
+    ranges {is_64 [is_64_target]} {
+	func_ranges_label: sequence {
+	    range $foo_start $foo_end
+	    range $foo_low_start "$foo_low_start + 1"
+	    #range "$foo_low_start + 1" $foo_low_end
+	}
+    }
+}
+
+if { [build_executable "failed to prepare" ${testfile} \
+	  [list $srcfile $srcfile2 $asm_file] {nodebug}] } {
+    return -1
+}
+
+clean_restart
+
+gdb_load_no_complaints $binfile
+
+if ![runto_main] {
+    return -1
+}
+
+# Generate backtrace from baz, that visits the hole in the addrmap.  If
+# the hole is there in the symbol table, but not the partial symbol table,
+# we run into:
+# (gdb) bt
+# warning: (Internal error: pc 0x555555554619 in read in psymtab, \
+#    but not in symtab.)
+# ...
+# (gdb) 
+
+gdb_test "break baz" \
+    "Breakpoint.*at.*"
+
+gdb_test "continue"
+
+set re "warning: \\(Internal error: pc $hex in read in psymtab, but not in symtab\\.\\)"
+gdb_test_multiple "bt" "" {
+    -re -wrap "$re.*" {
+	fail $gdb_test_name
+    }
+    -re -wrap "" {
+	pass $gdb_test_name
+    }
+}