forked from Imagelibrary/binutils-gdb
404 lines
12 KiB
C
404 lines
12 KiB
C
/* DWARF DWZ handling for GDB.
|
|
|
|
Copyright (C) 2003-2025 Free Software Foundation, Inc.
|
|
|
|
This file is part of GDB.
|
|
|
|
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/>. */
|
|
|
|
#include "dwarf2/dwz.h"
|
|
|
|
#include "build-id.h"
|
|
#include "debuginfod-support.h"
|
|
#include "dwarf2/leb.h"
|
|
#include "dwarf2/read.h"
|
|
#include "dwarf2/sect-names.h"
|
|
#include "filenames.h"
|
|
#include "gdb_bfd.h"
|
|
#include "gdbcore.h"
|
|
#include "gdbsupport/gdb_vecs.h"
|
|
#include "gdbsupport/pathstuff.h"
|
|
#include "gdbsupport/scoped_fd.h"
|
|
#include "run-on-main-thread.h"
|
|
|
|
const char *
|
|
dwz_file::read_string (struct objfile *objfile, LONGEST str_offset)
|
|
{
|
|
/* This must be true because the sections are read in when the
|
|
dwz_file is created. */
|
|
gdb_assert (str.readin);
|
|
|
|
if (str.buffer == NULL)
|
|
error (_("supplementary DWARF file missing .debug_str "
|
|
"section [in module %s]"),
|
|
this->filename ());
|
|
if (str_offset >= str.size)
|
|
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')
|
|
return NULL;
|
|
return (const char *) (str.buffer + str_offset);
|
|
}
|
|
|
|
/* A helper function to find the sections for a .dwz file. */
|
|
|
|
static void
|
|
locate_dwz_sections (objfile *objfile, dwz_file &dwz_file)
|
|
{
|
|
for (asection *sec : gdb_bfd_sections (dwz_file.dwz_bfd))
|
|
{
|
|
dwarf2_section_info *sect = nullptr;
|
|
|
|
/* Note that we only support the standard ELF names, because .dwz
|
|
is ELF-only (at the time of writing). */
|
|
if (dwarf2_elf_names.abbrev.matches (sec->name))
|
|
sect = &dwz_file.abbrev;
|
|
else if (dwarf2_elf_names.info.matches (sec->name))
|
|
sect = &dwz_file.info;
|
|
else if (dwarf2_elf_names.str.matches (sec->name))
|
|
sect = &dwz_file.str;
|
|
else if (dwarf2_elf_names.line.matches (sec->name))
|
|
sect = &dwz_file.line;
|
|
else if (dwarf2_elf_names.macro.matches (sec->name))
|
|
sect = &dwz_file.macro;
|
|
else if (dwarf2_elf_names.gdb_index.matches (sec->name))
|
|
sect = &dwz_file.gdb_index;
|
|
else if (dwarf2_elf_names.debug_names.matches (sec->name))
|
|
sect = &dwz_file.debug_names;
|
|
else if (dwarf2_elf_names.types.matches (sec->name))
|
|
sect = &dwz_file.types;
|
|
|
|
if (sect != nullptr)
|
|
{
|
|
sect->s.section = sec;
|
|
sect->size = bfd_section_size (sec);
|
|
sect->read (objfile);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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.
|
|
|
|
Return the equivalent gdb_bfd_ref_ptr of the .dwz file found, or
|
|
nullptr if it could not find anything. */
|
|
|
|
static gdb_bfd_ref_ptr
|
|
dwz_search_other_debugdirs (std::string &filename, bfd_byte *buildid,
|
|
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
|
|
distributions do, anyway. */
|
|
size_t dwz_pos = filename.find ("/.dwz/");
|
|
|
|
if (dwz_pos == std::string::npos)
|
|
return nullptr;
|
|
|
|
/* This is an obvious assertion, but it's here more to educate
|
|
future readers of this code that FILENAME at DWZ_POS *must*
|
|
contain a directory separator. */
|
|
gdb_assert (IS_DIR_SEPARATOR (filename[dwz_pos]));
|
|
|
|
gdb_bfd_ref_ptr dwz_bfd;
|
|
std::vector<gdb::unique_xmalloc_ptr<char>> debugdir_vec
|
|
= dirnames_to_char_ptr_vec (debug_file_directory.c_str ());
|
|
|
|
for (const gdb::unique_xmalloc_ptr<char> &debugdir : debugdir_vec)
|
|
{
|
|
/* The idea is to iterate over the
|
|
debug file directories provided by the user and
|
|
replace the hard-coded path in the "filename" by each
|
|
debug-file-directory.
|
|
|
|
For example, suppose that filename is:
|
|
|
|
/usr/lib/debug/.dwz/foo.dwz
|
|
|
|
And suppose that we have "$HOME/bar" as the
|
|
debug-file-directory. We would then adjust filename
|
|
to look like:
|
|
|
|
$HOME/bar/.dwz/foo.dwz
|
|
|
|
which would hopefully allow us to find the alt debug
|
|
file. */
|
|
std::string ddir = debugdir.get ();
|
|
|
|
if (ddir.empty ())
|
|
continue;
|
|
|
|
/* Make sure the current debug-file-directory ends with a
|
|
directory separator. This is needed because, if FILENAME
|
|
contains something like "/usr/lib/abcde/.dwz/foo.dwz" and
|
|
DDIR is "/usr/lib/abc", then could wrongfully skip it
|
|
below. */
|
|
if (!IS_DIR_SEPARATOR (ddir.back ()))
|
|
ddir += SLASH_STRING;
|
|
|
|
/* Check whether the beginning of FILENAME is DDIR. If it is,
|
|
then we are dealing with a file which we already attempted to
|
|
open before, so we just skip it and continue processing the
|
|
remaining debug file directories. */
|
|
if (filename.size () > ddir.size ()
|
|
&& filename.compare (0, ddir.size (), ddir) == 0)
|
|
continue;
|
|
|
|
/* Replace FILENAME's default debug-file-directory with
|
|
DDIR. */
|
|
std::string new_filename = ddir + &filename[dwz_pos + 1];
|
|
|
|
dwz_bfd = gdb_bfd_open (new_filename.c_str (), gnutarget);
|
|
|
|
if (dwz_bfd == nullptr)
|
|
continue;
|
|
|
|
if (!verify_id (dwz_bfd.get (), buildid_len, buildid, dwarf5))
|
|
{
|
|
dwz_bfd.reset (nullptr);
|
|
continue;
|
|
}
|
|
|
|
/* Found it. */
|
|
break;
|
|
}
|
|
|
|
return dwz_bfd;
|
|
}
|
|
|
|
/* See dwz.h. */
|
|
|
|
void
|
|
dwz_file::read_dwz_file (dwarf2_per_objfile *per_objfile)
|
|
{
|
|
dwarf2_per_bfd *per_bfd = per_objfile->per_bfd;
|
|
|
|
/* This may query the user via the debuginfod support, so it may
|
|
only be run in the main thread. */
|
|
gdb_assert (is_main_thread ());
|
|
|
|
/* This should only be called once. */
|
|
gdb_assert (!per_bfd->dwz_file.has_value ());
|
|
/* Set this early, so that on error it remains NULL. */
|
|
per_bfd->dwz_file.emplace (nullptr);
|
|
|
|
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))
|
|
{
|
|
/* Nothing found, nothing to do. */
|
|
return;
|
|
}
|
|
|
|
if (!IS_ABSOLUTE_PATH (filename.c_str ()))
|
|
{
|
|
gdb::unique_xmalloc_ptr<char> abs = gdb_realpath (per_bfd->filename ());
|
|
|
|
filename = gdb_ldirname (abs.get ()) + SLASH_STRING + filename;
|
|
}
|
|
|
|
/* First try the file name given in the section. If that doesn't
|
|
work, try to use the build-id instead. */
|
|
gdb_bfd_ref_ptr dwz_bfd (gdb_bfd_open (filename.c_str (), gnutarget));
|
|
if (dwz_bfd != NULL)
|
|
{
|
|
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.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.get (),
|
|
buildid_len, dwarf5);
|
|
}
|
|
|
|
if (dwz_bfd == nullptr)
|
|
{
|
|
gdb::unique_xmalloc_ptr<char> alt_filename;
|
|
scoped_fd fd
|
|
= debuginfod_debuginfo_query (buildid.get (), buildid_len,
|
|
per_bfd->filename (), &alt_filename);
|
|
|
|
if (fd.get () >= 0)
|
|
{
|
|
/* File successfully retrieved from server. */
|
|
dwz_bfd = gdb_bfd_open (alt_filename.get (), gnutarget);
|
|
|
|
if (dwz_bfd == nullptr)
|
|
warning (_("File \"%s\" from debuginfod cannot be opened as bfd"),
|
|
alt_filename.get ());
|
|
else if (!verify_id (dwz_bfd.get (), buildid_len, buildid.get (),
|
|
dwarf5))
|
|
dwz_bfd.reset (nullptr);
|
|
}
|
|
}
|
|
|
|
if (dwz_bfd == NULL)
|
|
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)));
|
|
|
|
locate_dwz_sections (per_objfile->objfile, *result);
|
|
|
|
gdb_bfd_record_inclusion (per_bfd->obfd, result->dwz_bfd.get ());
|
|
bfd_cache_close (result->dwz_bfd.get ());
|
|
|
|
per_bfd->dwz_file = std::move (result);
|
|
}
|