Handle DWARF 5 separate debug sections

DWARF 5 standardized the .gnu_debugaltlink section that dwz emits in
multi-file mode.  This is handled via some new forms, and a new
.debug_sup section.

This patch adds support for this to gdb.  It is largely
straightforward, I think, though one oddity is that I chose not to
have this code search the system build-id directories for the
supplementary file.  My feeling was that, while it makes sense for a
distro to unify the build-id concept with the hash stored in the
.debug_sup section, there's no intrinsic need to do so.

This in turn means that a few tests -- for example those that test the
index cache -- will not work in this mode.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32808
Acked-By: Simon Marchi <simon.marchi@efficios.com>
This commit is contained in:
Tom Tromey
2025-03-19 14:47:29 -06:00
parent 1e1ef933d1
commit 7b80401da0
15 changed files with 420 additions and 203 deletions

View File

@@ -73,7 +73,8 @@ attribute::form_is_string () const
|| form == DW_FORM_strx3
|| form == DW_FORM_strx4
|| form == DW_FORM_GNU_str_index
|| form == DW_FORM_GNU_strp_alt);
|| form == DW_FORM_GNU_strp_alt
|| form == DW_FORM_strp_sup);
}
/* See attribute.h. */
@@ -190,6 +191,8 @@ attribute::form_is_unsigned () const
{
return (form == DW_FORM_ref_addr
|| form == DW_FORM_GNU_ref_alt
|| form == DW_FORM_ref_sup4
|| form == DW_FORM_ref_sup8
|| form == DW_FORM_data2
|| form == DW_FORM_data4
|| form == DW_FORM_data8

View File

@@ -144,7 +144,9 @@ struct attribute
|| form == DW_FORM_ref4
|| form == DW_FORM_ref8
|| form == DW_FORM_ref_udata
|| form == DW_FORM_GNU_ref_alt);
|| form == DW_FORM_GNU_ref_alt
|| form == DW_FORM_ref_sup4
|| form == DW_FORM_ref_sup8);
}
/* Check if the attribute's form is a DW_FORM_block*
@@ -168,6 +170,16 @@ struct attribute
"reprocessing". */
bool form_requires_reprocessing () const;
/* Check if attribute's form refers to the separate "dwz" file.
This is only useful for references to the .debug_info section,
not to the supplementary .debug_str section. */
bool form_is_alt () const
{
return (form == DW_FORM_GNU_ref_alt
|| form == DW_FORM_ref_sup4
|| form == DW_FORM_ref_sup8);
}
/* Return DIE offset of this attribute. Return 0 with complaint if
the attribute is not of the required kind. */

View File

@@ -222,7 +222,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
case DW_AT_abstract_origin:
case DW_AT_extension:
origin_offset = attr.get_ref_die_offset ();
origin_is_dwz = attr.form == DW_FORM_GNU_ref_alt;
origin_is_dwz = attr.form_is_alt ();
break;
case DW_AT_external:
@@ -423,7 +423,7 @@ cooked_indexer::index_imported_unit (cutu_reader *reader,
if (attr.name == DW_AT_import)
{
sect_off = attr.get_ref_die_offset ();
is_dwz = (attr.form == DW_FORM_GNU_ref_alt
is_dwz = (attr.form_is_alt ()
|| reader->cu ()->per_cu->is_dwz);
}
}

View File

@@ -90,6 +90,8 @@ dump_die_shallow (struct ui_file *f, int indent, struct die_info *die)
gdb_puts (hex_string (die->attrs[i].as_unsigned ()), f);
break;
case DW_FORM_GNU_ref_alt:
case DW_FORM_ref_sup4:
case DW_FORM_ref_sup8:
gdb_printf (f, "alt ref address: ");
gdb_puts (hex_string (die->attrs[i].as_unsigned ()), f);
break;
@@ -123,6 +125,7 @@ dump_die_shallow (struct ui_file *f, int indent, struct die_info *die)
case DW_FORM_strx:
case DW_FORM_GNU_str_index:
case DW_FORM_GNU_strp_alt:
case DW_FORM_strp_sup:
gdb_printf (f, "string: \"%s\" (%s canonicalized)",
die->attrs[i].as_string ()
? die->attrs[i].as_string () : "",

View File

@@ -21,6 +21,7 @@
#include "build-id.h"
#include "debuginfod-support.h"
#include "dwarf2/leb.h"
#include "dwarf2/read.h"
#include "dwarf2/sect-names.h"
#include "filenames.h"
@@ -39,12 +40,12 @@ dwz_file::read_string (struct objfile *objfile, LONGEST str_offset)
gdb_assert (str.readin);
if (str.buffer == NULL)
error (_("DW_FORM_GNU_strp_alt used without .debug_str "
error (_("supplementary DWARF file missing .debug_str "
"section [in module %s]"),
this->filename ());
if (str_offset >= str.size)
error (_("DW_FORM_GNU_strp_alt pointing outside of "
".debug_str section [in module %s]"),
error (_("invalid string reference to supplementary DWARF file "
"[in module %s]"),
this->filename ());
gdb_assert (HOST_CHAR_BIT == 8);
if (str.buffer[str_offset] == '\0')
@@ -87,6 +88,139 @@ locate_dwz_sections (struct objfile *objfile, bfd *abfd, asection *sectp,
}
}
/* Helper that throws an exception when reading the .debug_sup
section. */
static void
debug_sup_failure (const char *text, bfd *abfd)
{
error (_("%s [in modules %s]"), text, bfd_get_filename (abfd));
}
/* Look for the .debug_sup section and read it. If the section does
not exist, this returns false. If the section does exist but fails
to parse for some reason, an exception is thrown. Otherwise, if
everything goes well, this returns true and fills in the out
parameters. */
static bool
get_debug_sup_info (bfd *abfd,
std::string *filename,
size_t *buildid_len,
gdb::unique_xmalloc_ptr<bfd_byte> *buildid)
{
asection *sect = bfd_get_section_by_name (abfd, ".debug_sup");
if (sect == nullptr)
return false;
bfd_byte *contents;
if (!bfd_malloc_and_get_section (abfd, sect, &contents))
debug_sup_failure (_("could not read .debug_sup section"), abfd);
gdb::unique_xmalloc_ptr<bfd_byte> content_holder (contents);
bfd_size_type size = bfd_section_size (sect);
/* Version of this section. */
if (size < 4)
debug_sup_failure (_(".debug_sup section too short"), abfd);
unsigned int version = read_2_bytes (abfd, contents);
contents += 2;
size -= 2;
if (version != 5)
debug_sup_failure (_(".debug_sup has wrong version number"), abfd);
/* Skip the is_supplementary value. We already ensured there were
enough bytes, above. */
++contents;
--size;
/* The spec says that in the supplementary file, this must be \0,
but it doesn't seem very important. */
const char *fname = (const char *) contents;
size_t len = strlen (fname) + 1;
if (filename != nullptr)
*filename = fname;
contents += len;
size -= len;
if (size == 0)
debug_sup_failure (_(".debug_sup section missing ID"), abfd);
unsigned int bytes_read;
*buildid_len = read_unsigned_leb128 (abfd, contents, &bytes_read);
contents += bytes_read;
size -= bytes_read;
if (size < *buildid_len)
debug_sup_failure (_("extra data after .debug_sup section ID"), abfd);
if (*buildid_len != 0)
buildid->reset ((bfd_byte *) xmemdup (contents, *buildid_len,
*buildid_len));
return true;
}
/* Validate that ABFD matches the given BUILDID. If DWARF5 is true,
then this is done by examining the .debug_sup data. */
static bool
verify_id (bfd *abfd, size_t len, const bfd_byte *buildid, bool dwarf5)
{
if (!bfd_check_format (abfd, bfd_object))
return false;
if (dwarf5)
{
size_t new_len;
gdb::unique_xmalloc_ptr<bfd_byte> new_id;
if (!get_debug_sup_info (abfd, nullptr, &new_len, &new_id))
return false;
return (len == new_len
&& memcmp (buildid, new_id.get (), len) == 0);
}
else
return build_id_verify (abfd, len, buildid);
}
/* Find either the .debug_sup or .gnu_debugaltlink section and return
its contents. Returns true on success and sets out parameters, or
false if nothing is found. */
static bool
read_alt_info (bfd *abfd, std::string *filename,
size_t *buildid_len,
gdb::unique_xmalloc_ptr<bfd_byte> *buildid,
bool *dwarf5)
{
if (get_debug_sup_info (abfd, filename, buildid_len, buildid))
{
*dwarf5 = true;
return true;
}
bfd_size_type buildid_len_arg;
bfd_set_error (bfd_error_no_error);
bfd_byte *buildid_out;
gdb::unique_xmalloc_ptr<char> new_filename
(bfd_get_alt_debug_link_info (abfd, &buildid_len_arg,
&buildid_out));
if (new_filename == nullptr)
{
if (bfd_get_error () == bfd_error_no_error)
return false;
error (_("could not read '.gnu_debugaltlink' section: %s"),
bfd_errmsg (bfd_get_error ()));
}
*filename = new_filename.get ();
*buildid_len = buildid_len_arg;
buildid->reset (buildid_out);
*dwarf5 = false;
return true;
}
/* Attempt to find a .dwz file (whose full path is represented by
FILENAME) in all of the specified debug file directories provided.
@@ -95,7 +229,7 @@ locate_dwz_sections (struct objfile *objfile, bfd *abfd, asection *sectp,
static gdb_bfd_ref_ptr
dwz_search_other_debugdirs (std::string &filename, bfd_byte *buildid,
size_t buildid_len)
size_t buildid_len, bool dwarf5)
{
/* Let's assume that the path represented by FILENAME has the
"/.dwz/" subpath in it. This is what (most) GNU/Linux
@@ -163,7 +297,7 @@ dwz_search_other_debugdirs (std::string &filename, bfd_byte *buildid,
if (dwz_bfd == nullptr)
continue;
if (!build_id_verify (dwz_bfd.get (), buildid_len, buildid))
if (!verify_id (dwz_bfd.get (), buildid_len, buildid, dwarf5))
{
dwz_bfd.reset (nullptr);
continue;
@@ -181,9 +315,6 @@ dwz_search_other_debugdirs (std::string &filename, bfd_byte *buildid,
void
dwz_file::read_dwz_file (dwarf2_per_objfile *per_objfile)
{
bfd_size_type buildid_len_arg;
size_t buildid_len;
bfd_byte *buildid;
dwarf2_per_bfd *per_bfd = per_objfile->per_bfd;
/* This may query the user via the debuginfod support, so it may
@@ -195,24 +326,17 @@ dwz_file::read_dwz_file (dwarf2_per_objfile *per_objfile)
/* Set this early, so that on error it remains NULL. */
per_bfd->dwz_file.emplace (nullptr);
bfd_set_error (bfd_error_no_error);
gdb::unique_xmalloc_ptr<char> data
(bfd_get_alt_debug_link_info (per_bfd->obfd,
&buildid_len_arg, &buildid));
if (data == NULL)
size_t buildid_len;
gdb::unique_xmalloc_ptr<bfd_byte> buildid;
std::string filename;
bool dwarf5;
if (!read_alt_info (per_bfd->obfd, &filename, &buildid_len, &buildid,
&dwarf5))
{
if (bfd_get_error () == bfd_error_no_error)
return;
error (_("could not read '.gnu_debugaltlink' section: %s"),
bfd_errmsg (bfd_get_error ()));
/* Nothing found, nothing to do. */
return;
}
gdb::unique_xmalloc_ptr<bfd_byte> buildid_holder (buildid);
buildid_len = (size_t) buildid_len_arg;
std::string filename = data.get ();
if (!IS_ABSOLUTE_PATH (filename.c_str ()))
{
gdb::unique_xmalloc_ptr<char> abs = gdb_realpath (per_bfd->filename ());
@@ -225,25 +349,26 @@ dwz_file::read_dwz_file (dwarf2_per_objfile *per_objfile)
gdb_bfd_ref_ptr dwz_bfd (gdb_bfd_open (filename.c_str (), gnutarget));
if (dwz_bfd != NULL)
{
if (!build_id_verify (dwz_bfd.get (), buildid_len, buildid))
if (!verify_id (dwz_bfd.get (), buildid_len, buildid.get (), dwarf5))
dwz_bfd.reset (nullptr);
}
if (dwz_bfd == NULL)
dwz_bfd = build_id_to_debug_bfd (buildid_len, buildid);
dwz_bfd = build_id_to_debug_bfd (buildid_len, buildid.get ());
if (dwz_bfd == nullptr)
{
/* If the user has provided us with different
debug file directories, we can try them in order. */
dwz_bfd = dwz_search_other_debugdirs (filename, buildid, buildid_len);
dwz_bfd = dwz_search_other_debugdirs (filename, buildid.get (),
buildid_len, dwarf5);
}
if (dwz_bfd == nullptr)
{
gdb::unique_xmalloc_ptr<char> alt_filename;
scoped_fd fd
= debuginfod_debuginfo_query (buildid, buildid_len,
= debuginfod_debuginfo_query (buildid.get (), buildid_len,
per_bfd->filename (), &alt_filename);
if (fd.get () >= 0)
@@ -254,13 +379,15 @@ dwz_file::read_dwz_file (dwarf2_per_objfile *per_objfile)
if (dwz_bfd == nullptr)
warning (_("File \"%s\" from debuginfod cannot be opened as bfd"),
alt_filename.get ());
else if (!build_id_verify (dwz_bfd.get (), buildid_len, buildid))
else if (!verify_id (dwz_bfd.get (), buildid_len, buildid.get (),
dwarf5))
dwz_bfd.reset (nullptr);
}
}
if (dwz_bfd == NULL)
error (_("could not find '.gnu_debugaltlink' file for %s"),
error (_("could not find supplementary DWARF file (%s) for %s"),
filename.c_str (),
per_bfd->filename ());
dwz_file_up result (new dwz_file (std::move (dwz_bfd)));

View File

@@ -34,8 +34,8 @@ struct dwz_file
/* Open the separate '.dwz' debug file, if needed. This will set
the appropriate field in the per-BFD structure. If the DWZ file
exists, the relevant sections are read in as well. Throws an
error if the .gnu_debugaltlink section exists but the file cannot
be found. */
exception if the .gnu_debugaltlink or .debug_sup section exists
but is invalid or if the file cannot be found. */
static void read_dwz_file (dwarf2_per_objfile *per_objfile);
const char *filename () const

View File

@@ -259,6 +259,7 @@ skip_form_bytes (bfd *abfd, const gdb_byte *bytes, const gdb_byte *buffer_end,
case DW_FORM_sec_offset:
case DW_FORM_strp:
case DW_FORM_GNU_strp_alt:
case DW_FORM_strp_sup:
bytes += offset_size;
break;

View File

@@ -3896,11 +3896,13 @@ cutu_reader::skip_one_attribute (dwarf_form form, const gdb_byte *info_ptr)
case DW_FORM_data4:
case DW_FORM_ref4:
case DW_FORM_strx4:
case DW_FORM_ref_sup4:
return info_ptr + 4;
case DW_FORM_data8:
case DW_FORM_ref8:
case DW_FORM_ref_sig8:
case DW_FORM_ref_sup8:
return info_ptr + 8;
case DW_FORM_data16:
@@ -3913,6 +3915,7 @@ cutu_reader::skip_one_attribute (dwarf_form form, const gdb_byte *info_ptr)
case DW_FORM_sec_offset:
case DW_FORM_strp:
case DW_FORM_GNU_strp_alt:
case DW_FORM_strp_sup:
return info_ptr + m_cu->header.offset_size;
case DW_FORM_exprloc:
@@ -5023,7 +5026,7 @@ process_imported_unit_die (struct die_info *die, struct dwarf2_cu *cu)
if (attr != NULL)
{
sect_offset sect_off = attr->get_ref_die_offset ();
bool is_dwz = (attr->form == DW_FORM_GNU_ref_alt || cu->per_cu->is_dwz);
bool is_dwz = attr->form_is_alt () || cu->per_cu->is_dwz;
dwarf2_per_objfile *per_objfile = cu->per_objfile;
dwarf2_per_cu *per_cu
= dwarf2_find_containing_comp_unit (sect_off, is_dwz,
@@ -14914,10 +14917,12 @@ cutu_reader::read_attribute_value (attribute *attr, unsigned form,
info_ptr += 2;
break;
case DW_FORM_data4:
case DW_FORM_ref_sup4:
attr->set_unsigned (read_4_bytes (m_abfd, info_ptr));
info_ptr += 4;
break;
case DW_FORM_data8:
case DW_FORM_ref_sup8:
attr->set_unsigned (read_8_bytes (m_abfd, info_ptr));
info_ptr += 8;
break;
@@ -14969,6 +14974,7 @@ cutu_reader::read_attribute_value (attribute *attr, unsigned form,
}
[[fallthrough]];
case DW_FORM_GNU_strp_alt:
case DW_FORM_strp_sup:
{
dwz_file *dwz = per_objfile->per_bfd->get_dwz_file (true);
LONGEST str_offset
@@ -17251,6 +17257,7 @@ dwarf2_const_value_attr (const struct attribute *attr, struct type *type,
case DW_FORM_strx:
case DW_FORM_GNU_str_index:
case DW_FORM_GNU_strp_alt:
case DW_FORM_strp_sup:
/* The string is already allocated on the objfile obstack, point
directly to it. */
*bytes = (const gdb_byte *) attr->as_string ();
@@ -17457,7 +17464,7 @@ lookup_die_type (struct die_info *die, const struct attribute *attr,
/* First see if we have it cached. */
if (attr->form == DW_FORM_GNU_ref_alt)
if (attr->form_is_alt ())
{
sect_offset sect_off = attr->get_ref_die_offset ();
dwarf2_per_cu *per_cu
@@ -18241,15 +18248,14 @@ follow_die_ref (struct die_info *src_die, const struct attribute *attr,
struct dwarf2_cu *cu = *ref_cu;
struct die_info *die;
if (attr->form != DW_FORM_GNU_ref_alt && src_die->sect_off == sect_off)
if (!attr->form_is_alt () && src_die->sect_off == sect_off)
{
/* Self-reference, we're done. */
return src_die;
}
die = follow_die_offset (sect_off,
(attr->form == DW_FORM_GNU_ref_alt
|| cu->per_cu->is_dwz),
attr->form_is_alt () || cu->per_cu->is_dwz,
ref_cu);
if (!die)
error (_(DWARF_ERROR_PREFIX
@@ -18460,6 +18466,7 @@ dwarf2_fetch_constant_bytes (sect_offset sect_off,
case DW_FORM_strx:
case DW_FORM_GNU_str_index:
case DW_FORM_GNU_strp_alt:
case DW_FORM_strp_sup:
/* The string is already allocated on the objfile obstack, point
directly to it. */
{

View File

@@ -533,9 +533,9 @@ struct dwarf2_per_bfd
}
/* Return the separate '.dwz' debug file. If there is no
.gnu_debugaltlink section in the file, then the result depends on
REQUIRE: if REQUIRE is true, error out; if REQUIRE is false,
return nullptr. */
.gnu_debugaltlink or .debug_sup section in the file, then the
result depends on REQUIRE: if REQUIRE is true, error out; if
REQUIRE is false, return nullptr. */
struct dwz_file *get_dwz_file (bool require = false)
{
gdb_assert (!require || this->dwz_file.has_value ());
@@ -546,7 +546,7 @@ struct dwarf2_per_bfd
{
result = this->dwz_file->get ();
if (require && result == nullptr)
error (_("could not read '.gnu_debugaltlink' section"));
error (_("could not find supplementary DWARF file"));
}
return result;

View File

@@ -13,160 +13,5 @@
# 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
# This test can only be run on targets which support DWARF-2 and use gas.
require dwarf2_support
# No remote host testing either.
require {!is_remote host}
# Lots of source files since we test a few cases and make new files
# for each.
# The tests are:
# ok - the main file refers to a dwz and the buildids match
# mismatch - the buildids do not match
# fallback - the buildids do not match but a match is found via buildid
standard_testfile main.c \
dwzbuildid-ok-base.S dwzbuildid-ok-sep.S \
dwzbuildid-mismatch-base.S dwzbuildid-mismatch-sep.S \
dwzbuildid-fallback-base.S dwzbuildid-fallback-sep.S \
dwzbuildid-fallback-ok.S
# Write some assembly that just has a .gnu_debugaltlink section.
proc write_just_debugaltlink {filename dwzname buildid} {
set asm_file [standard_output_file $filename]
Dwarf::assemble $asm_file {
upvar dwzname dwzname
upvar buildid buildid
gnu_debugaltlink $dwzname $buildid
# Only the DWARF reader checks .gnu_debugaltlink, so make sure
# there is a bit of DWARF in here.
cu { label cu_start } {
compile_unit {{language @DW_LANG_C}} {
}
}
aranges {} cu_start {
arange {} 0 0
}
}
}
# Write some DWARF that also sets the buildid.
proc write_dwarf_file {filename buildid {value 99}} {
set asm_file [standard_output_file $filename]
Dwarf::assemble $asm_file {
declare_labels int_label int_label2
upvar buildid buildid
upvar value value
build_id $buildid
cu { label cu_start } {
compile_unit {{language @DW_LANG_C}} {
int_label2: base_type {
{name int}
{byte_size 4 sdata}
{encoding @DW_ATE_signed}
}
constant {
{name the_int}
{type :$int_label2}
{const_value $value data1}
}
}
}
aranges {} cu_start {
arange {} 0 0
}
}
}
if { [gdb_compile ${srcdir}/${subdir}/${srcfile} ${binfile}1.o \
object {nodebug}] != "" } {
return -1
}
# The values don't really matter, just whether they are equal.
set ok_prefix 01
set ok_suffix 02030405060708091011121314151617181920
set ok_suffix2 020304050607080910111213141516171819ff
set ok_buildid ${ok_prefix}${ok_suffix}
set ok_buildid2 ${ok_prefix}${ok_suffix2}
set bad_buildid [string repeat ff 20]
set debugdir [standard_output_file {}]
set basedir $debugdir/.build-id
file mkdir $basedir $basedir/$ok_prefix
# Test where the separate debuginfo's buildid matches.
write_just_debugaltlink $srcfile2 ${binfile}3.o $ok_buildid
write_dwarf_file $srcfile3 $ok_buildid
# Test where the separate debuginfo's buildid does not match.
write_just_debugaltlink $srcfile4 ${binfile}5.o $ok_buildid
write_dwarf_file $srcfile5 $bad_buildid
# Test where the separate debuginfo's buildid does not match, but then
# we find a match in the .build-id directory.
write_just_debugaltlink $srcfile6 ${binfile}7.o $ok_buildid2
# Use 77 as the value so that if we load the bad debuginfo, we will
# see the wrong result.
write_dwarf_file $srcfile7 $bad_buildid 77
write_dwarf_file $srcfile8 $ok_buildid2
# Compile everything.
for {set i 2} {$i <= 8} {incr i} {
if {[gdb_compile [standard_output_file [set srcfile$i]] \
${binfile}$i.o object nodebug] != ""} {
return -1
}
}
# Copy a file into the .build-id place for the "fallback" test.
file copy -force -- ${binfile}8.o $basedir/$ok_prefix/$ok_suffix2.debug
proc do_test {} {
clean_restart
gdb_test_no_output "set debug-file-directory $::debugdir" \
"set debug-file-directory"
gdb_load ${::binfile}-${::testname}
if {![runto_main]} {
return
}
if {$::testname == "mismatch"} {
gdb_test "print the_int" \
"(No symbol table is loaded|No symbol \"the_int\" in current context).*"
} else {
gdb_test "print the_int" " = 99"
}
}
foreach_with_prefix testname { ok mismatch fallback } {
if { $testname == "ok" } {
set objs [list ${binfile}1.o ${binfile}2.o]
} elseif { $testname == "mismatch" } {
set objs [list ${binfile}1.o ${binfile}4.o]
} elseif { $testname == "fallback" } {
set objs [list ${binfile}1.o ${binfile}6.o]
}
if {[gdb_compile $objs ${binfile}-$testname executable {quiet}] != ""} {
unsupported "compilation failed"
continue
}
do_test
}
set scenario gnu
source $srcdir/$subdir/dwzbuildid.tcl

View File

@@ -0,0 +1,184 @@
# Copyright 2013-2025 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
# This test can only be run on targets which support DWARF-2 and use gas.
require dwarf2_support
# No remote host testing either.
require {!is_remote host}
# Lots of source files since we test a few cases and make new files
# for each.
# The tests are:
# ok - the main file refers to a dwz and the buildids match
# mismatch - the buildids do not match
# fallback - the buildids do not match but a match is found via buildid
standard_testfile main.c \
dwzbuildid-ok-base.S dwzbuildid-ok-sep.S \
dwzbuildid-mismatch-base.S dwzbuildid-mismatch-sep.S \
dwzbuildid-fallback-base.S dwzbuildid-fallback-sep.S \
dwzbuildid-fallback-ok.S
# Write some assembly that just has a .gnu_debugaltlink section.
proc write_just_debugaltlink {filename dwzname buildid} {
set asm_file [standard_output_file $filename]
Dwarf::assemble $asm_file {
upvar dwzname dwzname
upvar buildid buildid
if {$::scenario == "gnu"} {
gnu_debugaltlink $dwzname $buildid
} else {
debug_sup 0 $dwzname $buildid
}
# Only the DWARF reader checks .gnu_debugaltlink, so make sure
# there is a bit of DWARF in here.
cu { label cu_start } {
compile_unit {{language @DW_LANG_C}} {
}
}
aranges {} cu_start {
arange {} 0 0
}
}
}
# Write some DWARF that also sets the buildid.
proc write_dwarf_file {filename buildid {value 99}} {
set asm_file [standard_output_file $filename]
Dwarf::assemble $asm_file {
declare_labels int_label int_label2
upvar buildid buildid
upvar value value
if {$::scenario == "gnu"} {
build_id $buildid
} else {
debug_sup 1 "" $buildid
}
cu { label cu_start } {
compile_unit {{language @DW_LANG_C}} {
int_label2: base_type {
{name int}
{byte_size 4 sdata}
{encoding @DW_ATE_signed}
}
constant {
{name the_int}
{type :$int_label2}
{const_value $value data1}
}
}
}
aranges {} cu_start {
arange {} 0 0
}
}
}
if { [gdb_compile ${srcdir}/${subdir}/${srcfile} ${binfile}1.o \
object {nodebug}] != "" } {
return -1
}
# The values don't really matter, just whether they are equal.
set ok_prefix 01
set ok_suffix 02030405060708091011121314151617181920
set ok_suffix2 020304050607080910111213141516171819ff
set ok_buildid ${ok_prefix}${ok_suffix}
set ok_buildid2 ${ok_prefix}${ok_suffix2}
set bad_buildid [string repeat ff 20]
set debugdir [standard_output_file {}]
set basedir $debugdir/.build-id
file mkdir $basedir $basedir/$ok_prefix
# Test where the separate debuginfo's buildid matches.
write_just_debugaltlink $srcfile2 ${binfile}3.o $ok_buildid
write_dwarf_file $srcfile3 $ok_buildid
# Test where the separate debuginfo's buildid does not match.
write_just_debugaltlink $srcfile4 ${binfile}5.o $ok_buildid
write_dwarf_file $srcfile5 $bad_buildid
# Test where the separate debuginfo's buildid does not match, but then
# we find a match in the .build-id directory.
write_just_debugaltlink $srcfile6 ${binfile}7.o $ok_buildid2
# Use 77 as the value so that if we load the bad debuginfo, we will
# see the wrong result.
write_dwarf_file $srcfile7 $bad_buildid 77
write_dwarf_file $srcfile8 $ok_buildid2
# Compile everything.
for {set i 2} {$i <= 8} {incr i} {
if {[gdb_compile [standard_output_file [set srcfile$i]] \
${binfile}$i.o object nodebug] != ""} {
return -1
}
}
# Copy a file into the .build-id place for the "fallback" test.
file copy -force -- ${binfile}8.o $basedir/$ok_prefix/$ok_suffix2.debug
proc do_test {} {
clean_restart
gdb_test_no_output "set debug-file-directory $::debugdir" \
"set debug-file-directory"
gdb_load ${::binfile}-${::testname}
if {![runto_main]} {
return
}
if {$::testname == "mismatch"} {
gdb_test "print the_int" \
"(No symbol table is loaded|No symbol \"the_int\" in current context).*"
} else {
gdb_test "print the_int" " = 99"
}
}
set tests {ok mismatch}
if {$scenario == "gnu"} {
lappend tests fallback
}
foreach_with_prefix testname $tests {
if { $testname == "ok" } {
set objs [list ${binfile}1.o ${binfile}2.o]
} elseif { $testname == "mismatch" } {
set objs [list ${binfile}1.o ${binfile}4.o]
} elseif { $testname == "fallback" } {
set objs [list ${binfile}1.o ${binfile}6.o]
}
if {[gdb_compile $objs ${binfile}-$testname executable {quiet}] != ""} {
unsupported "compilation failed"
continue
}
do_test
}

View File

@@ -0,0 +1,17 @@
# Copyright 2025 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/>.
set scenario dwarf5
source $srcdir/$subdir/dwzbuildid.tcl

View File

@@ -49,5 +49,5 @@ if {[build_executable $testfile.exp $testfile \
clean_restart
gdb_test "file -readnow $binfile" \
"could not read '.gnu_debugaltlink' section" \
"could not find supplementary DWARF file" \
"file $testfile"

View File

@@ -38,7 +38,7 @@ if { [build_executable $testfile.exp $testfile [list $srcfile $asm_file]] } {
clean_restart
gdb_test_no_output "maint set dwarf synchronous on"
set msg "\r\nwarning: could not find '\.gnu_debugaltlink' file for \[^\r\n\]*"
set msg "\r\nwarning: could not find supplementary DWARF file \[^\r\n\]*"
gdb_test "file $binfile" "$msg" "file command"
set question "Load new symbol table from .*\? .y or n. "

View File

@@ -3068,6 +3068,24 @@ namespace eval Dwarf {
}
}
# Emit a .debug_sup section with the given file name and build-id.
# The buildid should be represented as a hexadecimal string, like
# "ffeeddcc".
proc debug_sup {is_sup filename buildid} {
_defer_output .debug_sup {
# The version.
_op .2byte 0x5
# Supplementary marker.
_op .byte $is_sup
_op .ascii [_quote $filename]
set len [expr {[string length $buildid] / 2}]
_op .uleb128 $len
foreach {a b} [split $buildid {}] {
_op .byte 0x$a$b
}
}
}
proc _note {type name hexdata} {
set namelen [expr [string length $name] + 1]
set datalen [expr [string length $hexdata] / 2]