From bab31d145160cd4dec7b9ad0e79346382ebf8385 Mon Sep 17 00:00:00 2001 From: Tom de Vries Date: Mon, 22 Nov 2021 09:14:16 +0100 Subject: [PATCH] [gdb/symtab] Support .debug_line with DW_FORM_line_strp 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 4a963e4a236..a513cc493a7 100644 --- a/gdb/dwarf2/read.c +++ b/gdb/dwarf2/read.c @@ -20304,6 +20304,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 b2a337238c3..cbb1bf6985a 100644 --- a/gdb/testsuite/lib/dwarf.exp +++ b/gdb/testsuite/lib/dwarf.exp @@ -2185,9 +2185,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 @@ -2197,14 +2199,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" } } } @@ -2231,7 +2236,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 { @@ -2248,7 +2253,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" @@ -2306,12 +2311,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. @@ -2324,14 +2339,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 \ @@ -2349,7 +2388,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.30.2