[PATCH 8/8] [gdb/symtab] Support .debug_line with DW_FORM_line_strp

Tom de Vries tdevries@suse.de
Mon Oct 25 10:30:00 GMT 2021


I noticed a new gcc option -gdwarf64 and tried it out (using gcc 11.2.1).

With a test-case hello.c:
...
int
main (void)
{
  printf ("hello\n");
  return 0;
}
...
compiled like this:
...
$ gcc -g -gdwarf64 ~/hello.c
...
I ran into:
...
$ gdb -q -batch a.out
DW_FORM_line_strp pointing outside of .debug_line_str section \
  [in module a.out]
...

Debugging gdb revealed that the string offset is:
...
(gdb) up
    objfile=0x182ab70, str_offset=1378684502312,
    form_name=0xeae9b5 "DW_FORM_line_strp")
    at src/gdb/dwarf2/section.c:208
208         error (_("%s pointing outside of %s section [in module %s]"),
(gdb) p /x str_offset
$1 = 0x14100000128
(gdb)
...
which is read when parsing a .debug_line entry at 0x1e0.

Looking with readelf at the 0x1e0 entry, we have:
...
 The Directory Table (offset 0x202, lines 2, columns 1):
  Entry Name
  0     (indirect line string, offset: 0x128): /data/gdb_versions/devel
  1     (indirect line string, offset: 0x141): /home/vries
...
which in a hexdump looks like:
...
  0x00000200 1f022801 00004101 00000201 1f020f02
...

What happens is the following:
- readelf interprets the DW_FORM_line_strp reference to .debug_line_str as
  a 4 byte value, and sees entries 0x00000128 and 0x00000141.
- gdb instead interprets it as an 8 byte value, and sees as first entry
  0x0000014100000128, which is too big so it bails out.

AFAIU, gdb is wrong.  It assumes DW_FORM_line_strp is 8 bytes on the basis
that the corresponding CU is 64-bit DWARF.  However, the .debug_line
contribution has it's own initial_length field, and encodes there that it's
32-bit DWARF.

Fix this by using the correct offset size for DW_FORM_line_strp references
in .debug_line.

Note: the described test-case does trigger this complaint (both with and
without this patch):
...
$ gdb -q -batch -iex "set complaints 10" a.out
During symbol reading: intermixed 32-bit and 64-bit DWARF sections
...

The reason that the CU has 64-bit dwarf is because -gdwarf64 was passed to
gcc.  The reason that the .debug_line entry has 32-bit dwarf is because that's
what gas generates.  Perhaps this is complaint-worthy, but I don't think it
is wrong.

Tested on x86_64-linux, using native and target board dwarf64.exp.
---
 gdb/dwarf2/line-header.c               | 15 +++---
 gdb/dwarf2/read.c                      | 12 +++++
 gdb/dwarf2/read.h                      |  5 ++
 gdb/testsuite/gdb.dwarf2/dw2-lines.exp | 22 +++++---
 gdb/testsuite/lib/dwarf.exp            | 75 ++++++++++++++++++++++----
 5 files changed, 106 insertions(+), 23 deletions(-)

diff --git a/gdb/dwarf2/line-header.c b/gdb/dwarf2/line-header.c
index 15195764c89..852e2851e99 100644
--- a/gdb/dwarf2/line-header.c
+++ b/gdb/dwarf2/line-header.c
@@ -137,7 +137,7 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
 static void
 read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
 			const gdb_byte **bufp, struct line_header *lh,
-			const struct comp_unit_head *cu_header,
+			unsigned int offset_size,
 			void (*callback) (struct line_header *lh,
 					  const char *name,
 					  dir_index d_index,
@@ -187,9 +187,12 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
 	      break;
 
 	    case DW_FORM_line_strp:
-	      string.emplace
-		(per_objfile->read_line_string (buf, cu_header, &bytes_read));
-	      buf += bytes_read;
+	      {
+		const char *str
+		  = per_objfile->read_line_string (buf, offset_size);
+		string.emplace (str);
+		buf += offset_size;
+	      }
 	      break;
 
 	    case DW_FORM_data1:
@@ -372,7 +375,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
     {
       /* Read directory table.  */
       read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
-			      cu_header,
+			      offset_size,
 			      [] (struct line_header *header, const char *name,
 				  dir_index d_index, unsigned int mod_time,
 				  unsigned int length)
@@ -382,7 +385,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
 
       /* Read file name table.  */
       read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
-			      cu_header,
+			      offset_size,
 			      [] (struct line_header *header, const char *name,
 				  dir_index d_index, unsigned int mod_time,
 				  unsigned int length)
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index e456c37e193..a807f90568f 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -20286,6 +20286,18 @@ read_indirect_string (dwarf2_per_objfile *per_objfile, bfd *abfd,
 
 /* See read.h.  */
 
+const char *
+dwarf2_per_objfile::read_line_string (const gdb_byte *buf,
+				      unsigned int offset_size)
+{
+  bfd *abfd = objfile->obfd;
+  ULONGEST str_offset = read_offset (abfd, buf, offset_size);
+
+  return per_bfd->line_str.read_string (objfile, str_offset, "DW_FORM_line_strp");
+}
+
+/* See read.h.  */
+
 const char *
 dwarf2_per_objfile::read_line_string (const gdb_byte *buf,
 				      const struct comp_unit_head *cu_header,
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 1638d8521c0..fe34e3f95ae 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -517,6 +517,11 @@ struct dwarf2_per_objfile
 				const struct comp_unit_head *cu_header,
 				unsigned int *bytes_read_ptr);
 
+  /* Return pointer to string at .debug_line_str offset as read from BUF.
+     The offset_size is OFFSET_SIZE.  */
+  const char *read_line_string (const gdb_byte *buf,
+				unsigned int offset_size);
+
   /* Return true if the symtab corresponding to PER_CU has been set,
      false otherwise.  */
   bool symtab_set_p (const dwarf2_per_cu_data *per_cu) const;
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-lines.exp b/gdb/testsuite/gdb.dwarf2/dw2-lines.exp
index 27134af8f5d..9cc24955102 100644
--- a/gdb/testsuite/gdb.dwarf2/dw2-lines.exp
+++ b/gdb/testsuite/gdb.dwarf2/dw2-lines.exp
@@ -37,18 +37,19 @@ proc line_for { l } {
 }
 
 # Execute test.
-proc test_1 { _cv _cdw64 _lv _ldw64 } {
+proc test_1 { _cv _cdw64 _lv _ldw64 {_string_form ""}} {
     global srcfile srcfile2 testfile
-    global cv cdw64 lv ldw64
+    global cv cdw64 lv ldw64 string_form
     set cv $_cv
     set cdw64 $_cdw64
     set lv $_lv
     set ldw64 $_ldw64
+    set string_form $_string_form
 
     set asm_file [standard_output_file $srcfile2]
     Dwarf::assemble $asm_file {
 	declare_labels Llines
-	global srcdir subdir srcfile cv cdw64 lv ldw64
+	global srcdir subdir srcfile cv cdw64 lv ldw64 string_form
 	global func_info_vars
 	foreach var $func_info_vars {
 	    global $var
@@ -75,7 +76,7 @@ proc test_1 { _cv _cdw64 _lv _ldw64 } {
 	    }
 	}
 
-	lines [list version $lv is_64 $ldw64] Llines {
+	lines [list version $lv is_64 $ldw64 string_form $string_form] Llines {
 	    include_dir "${srcdir}/${subdir}"
 	    file_name "$srcfile" 1
 
@@ -121,12 +122,18 @@ proc test_1 { _cv _cdw64 _lv _ldw64 } {
 
 
 # Add unique test prefix.
-proc test { cv cdw64 lv ldw64 } {
+proc test { cv cdw64 lv ldw64 {string_form ""}} {
     with_test_prefix cv=$cv {
 	with_test_prefix cdw=[expr $cdw64 ? 64 : 32] {
 	    with_test_prefix lv=$lv {
 		with_test_prefix ldw=[expr $ldw64 ? 64 : 32] {
-		    test_1 $cv $cdw64 $lv $ldw64
+		    if { $string_form == "" } {
+			test_1 $cv $cdw64 $lv $ldw64
+		    } else {
+			with_test_prefix string_form=$string_form {
+			    test_1 $cv $cdw64 $lv $ldw64 $string_form
+			}
+		    }
 		}
 	    }
 	}
@@ -157,6 +164,7 @@ for { set cv $cv_low } { $cv <= $cv_high } { incr cv } {
 
 foreach cdw64 { 0 1 } {
     foreach ldw64 { 0 1 } {
-	test 5 $cdw64 5 $ldw64
+	test 5 $cdw64 5 $ldw64 string
+	test 5 $cdw64 5 $ldw64 line_strp
     }
 }
diff --git a/gdb/testsuite/lib/dwarf.exp b/gdb/testsuite/lib/dwarf.exp
index 76ba65df9fd..4e777e4cceb 100644
--- a/gdb/testsuite/lib/dwarf.exp
+++ b/gdb/testsuite/lib/dwarf.exp
@@ -2143,9 +2143,11 @@ namespace eval Dwarf {
 	variable _line_saw_program
 	variable _line_header_end_label
 	variable _line_unit_version
+	variable _line_is_64
+	variable _line_string_form
 
 	# Establish the defaults.
-	set is_64 0
+	set _line_is_64 0
 	set _line_unit_version 4
 	set _unit_addr_size default
 	set _line_saw_program 0
@@ -2155,14 +2157,17 @@ namespace eval Dwarf {
 	set _line_header_finalized 0
 	set _default_is_stmt 1
 	set _seg_sel_size 0
+	#set _line_string_form string
+	set _line_string_form line_strp
 
 	foreach { name value } $options {
 	    switch -exact -- $name {
-		is_64 { set is_64 $value }
+		is_64 { set _line_is_64 $value }
 		version { set _line_unit_version $value }
 		addr_size { set _unit_addr_size $value }
 		seg_sel_size { set _seg_sel_size $value }
 		default_is_stmt { set _default_is_stmt $value }
+		string_form { set $_line_string_form $value }
 		default { error "unknown option $name" }
 	    }
 	}
@@ -2189,7 +2194,7 @@ namespace eval Dwarf {
 	set header_len_label [_compute_label "line${_line_count}_header_start"]
 	set _line_header_end_label [_compute_label "line${_line_count}_header_end"]
 
-	if {$is_64} {
+	if {$_line_is_64} {
 	    _op .4byte 0xffffffff
 	    _op .8byte "$unit_end_label - $unit_len_label" "unit_length"
 	} else {
@@ -2206,7 +2211,7 @@ namespace eval Dwarf {
 	    _op .byte $_seg_sel_size "seg_sel_size"
 	}
 
-	if {$is_64} {
+	if {$_line_is_64} {
 	    _op .8byte "$_line_header_end_label - $header_len_label" "header_length"
 	} else {
 	    _op .4byte "$_line_header_end_label - $header_len_label" "header_length"
@@ -2264,12 +2269,22 @@ namespace eval Dwarf {
 	    variable _line_file_names
 
 	    variable _line_unit_version
+	    variable _line_is_64
+	    variable _line_string_form
 	    if { $_line_unit_version >= 5 } {
 		_op .byte 1 "directory_entry_format_count"
 		_op .uleb128 1 \
 		    "directory_entry_format (content type code: DW_LNCT_path)"
-		_op .uleb128 0x08 \
-		    "directory_entry_format (form: DW_FORM_string)"
+		switch $_line_string_form {
+		    string {
+			_op .uleb128 0x08 \
+			    "directory_entry_format (form: DW_FORM_string)"
+		    }
+		    line_strp {
+			_op .uleb128 0x1f \
+			    "directory_entry_format (form: DW_FORM_line_strp)"
+		    }
+		}
 
 		set nr_dirs [llength $_line_include_dirs]
 		# For entry 0.
@@ -2282,14 +2297,38 @@ namespace eval Dwarf {
 		    [concat [list $dirname] $_line_include_dirs]
 
 		foreach dirname $_line_include_dirs {
-		    _op .ascii [_quote $dirname]
+		    switch $_line_string_form {
+			string {
+			    _op .ascii [_quote $dirname]
+			}
+			line_strp {
+			    declare_labels string_ptr
+			    _defer_output .debug_line_str {
+				string_ptr:
+				_op .ascii [_quote $dirname]
+			    }
+			    if { $_line_is_64 } {
+				_op .8byte $string_ptr
+			    } else {
+				_op .4byte $string_ptr
+			    }
+			}
+		    }
 		}
 
 		_op .byte 2 "file_name_entry_format_count"
 		_op .uleb128 1 \
 		    "file_name_entry_format (content type code: DW_LNCT_path)"
-		_op .uleb128 0x08 \
-		    "file_name_entry_format (form: DW_FORM_string)"
+		switch $_line_string_form {
+		    string {
+			_op .uleb128 0x08 \
+			    "directory_entry_format (form: DW_FORM_string)"
+		    }
+		    line_strp {
+			_op .uleb128 0x1f \
+			    "directory_entry_format (form: DW_FORM_line_strp)"
+		    }
+		}
 		_op .uleb128 2 \
 		    "file_name_entry_format (content type code: DW_LNCT_directory_index)"
 		_op .uleb128 0x0f \
@@ -2307,7 +2346,23 @@ namespace eval Dwarf {
 		    [concat [list $filename $diridx] $_line_file_names]
 
 		foreach { filename diridx } $_line_file_names {
-		    _op .ascii [_quote $filename]
+		    switch $_line_string_form {
+			string {
+			    _op .ascii [_quote $filename]
+			}
+			line_strp {
+			    declare_labels string_ptr
+			    _defer_output .debug_line_str {
+				string_ptr:
+				_op .ascii [_quote $filename]
+			    }
+			    if { $_line_is_64 } {
+				_op .8byte $string_ptr
+			    } else {
+				_op .4byte $string_ptr
+			    }
+			}
+		    }
 		    _op .uleb128 $diridx
 		}
 	    } else {
-- 
2.26.2



More information about the Gdb-patches mailing list