mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-27 20:14:06 +08:00
98badbfdc2
This changes struct objfile to use a gdb_bfd_ref_ptr. In addition to removing some manual memory management, this fixes a use-after-free that was introduced by the registry rewrite series. The issue there was that, in some cases, registry shutdown could refer to memory that had already been freed. This help fix the bug by delaying the destruction of the BFD reference (and thus the per-bfd object) until after the registry has been shut down.
303 lines
8.4 KiB
C
303 lines
8.4 KiB
C
/* Read MiniDebugInfo data from an objfile.
|
|
|
|
Copyright (C) 2012-2022 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 "defs.h"
|
|
#include "gdb_bfd.h"
|
|
#include "symfile.h"
|
|
#include "objfiles.h"
|
|
#include "gdbcore.h"
|
|
#include <algorithm>
|
|
|
|
#ifdef HAVE_LIBLZMA
|
|
|
|
/* We stash a reference to the .gnu_debugdata BFD on the enclosing
|
|
BFD. */
|
|
static const registry<bfd>::key<gdb_bfd_ref_ptr> gnu_debug_key;
|
|
|
|
#include <lzma.h>
|
|
|
|
/* Allocator function for LZMA. */
|
|
|
|
static void *
|
|
alloc_lzma (void *opaque, size_t nmemb, size_t size)
|
|
{
|
|
return xmalloc (nmemb * size);
|
|
}
|
|
|
|
/* Free function for LZMA. */
|
|
|
|
static void
|
|
free_lzma (void *opaque, void *ptr)
|
|
{
|
|
xfree (ptr);
|
|
}
|
|
|
|
/* The allocator object for LZMA. Note that 'gdb_lzma_allocator'
|
|
cannot be const due to the lzma library function prototypes. */
|
|
|
|
static lzma_allocator gdb_lzma_allocator = { alloc_lzma, free_lzma, NULL };
|
|
|
|
/* Custom bfd_openr_iovec implementation to read compressed data from
|
|
a section. This keeps only the last decompressed block in memory
|
|
to allow larger data without using to much memory. */
|
|
|
|
struct gdb_lzma_stream
|
|
{
|
|
/* Section of input BFD from which we are decoding data. */
|
|
asection *section;
|
|
|
|
/* lzma library decompression state. */
|
|
lzma_index *index;
|
|
|
|
/* Currently decoded block. */
|
|
bfd_size_type data_start;
|
|
bfd_size_type data_end;
|
|
gdb_byte *data;
|
|
};
|
|
|
|
/* bfd_openr_iovec OPEN_P implementation for
|
|
find_separate_debug_file_in_section. OPEN_CLOSURE is 'asection *'
|
|
of the section to decompress.
|
|
|
|
Return 'struct gdb_lzma_stream *' must be freed by caller by xfree,
|
|
together with its INDEX lzma data. */
|
|
|
|
static void *
|
|
lzma_open (struct bfd *nbfd, void *open_closure)
|
|
{
|
|
asection *section = (asection *) open_closure;
|
|
bfd_size_type size, offset;
|
|
lzma_stream_flags options;
|
|
gdb_byte footer[LZMA_STREAM_HEADER_SIZE];
|
|
gdb_byte *indexdata;
|
|
lzma_index *index;
|
|
uint64_t memlimit = UINT64_MAX;
|
|
struct gdb_lzma_stream *lstream;
|
|
size_t pos;
|
|
|
|
size = bfd_section_size (section);
|
|
offset = section->filepos + size - LZMA_STREAM_HEADER_SIZE;
|
|
if (size < LZMA_STREAM_HEADER_SIZE
|
|
|| bfd_seek (section->owner, offset, SEEK_SET) != 0
|
|
|| bfd_bread (footer, LZMA_STREAM_HEADER_SIZE, section->owner)
|
|
!= LZMA_STREAM_HEADER_SIZE
|
|
|| lzma_stream_footer_decode (&options, footer) != LZMA_OK
|
|
|| offset < options.backward_size)
|
|
{
|
|
bfd_set_error (bfd_error_wrong_format);
|
|
return NULL;
|
|
}
|
|
|
|
offset -= options.backward_size;
|
|
indexdata = (gdb_byte *) xmalloc (options.backward_size);
|
|
index = NULL;
|
|
pos = 0;
|
|
if (bfd_seek (section->owner, offset, SEEK_SET) != 0
|
|
|| bfd_bread (indexdata, options.backward_size, section->owner)
|
|
!= options.backward_size
|
|
|| lzma_index_buffer_decode (&index, &memlimit, &gdb_lzma_allocator,
|
|
indexdata, &pos, options.backward_size)
|
|
!= LZMA_OK
|
|
|| lzma_index_size (index) != options.backward_size)
|
|
{
|
|
xfree (indexdata);
|
|
bfd_set_error (bfd_error_wrong_format);
|
|
return NULL;
|
|
}
|
|
xfree (indexdata);
|
|
|
|
lstream = XCNEW (struct gdb_lzma_stream);
|
|
lstream->section = section;
|
|
lstream->index = index;
|
|
|
|
return lstream;
|
|
}
|
|
|
|
/* bfd_openr_iovec PREAD_P implementation for
|
|
find_separate_debug_file_in_section. Passed STREAM
|
|
is 'struct gdb_lzma_stream *'. */
|
|
|
|
static file_ptr
|
|
lzma_pread (struct bfd *nbfd, void *stream, void *buf, file_ptr nbytes,
|
|
file_ptr offset)
|
|
{
|
|
struct gdb_lzma_stream *lstream = (struct gdb_lzma_stream *) stream;
|
|
bfd_size_type chunk_size;
|
|
lzma_index_iter iter;
|
|
gdb_byte *compressed, *uncompressed;
|
|
file_ptr block_offset;
|
|
lzma_filter filters[LZMA_FILTERS_MAX + 1];
|
|
lzma_block block;
|
|
size_t compressed_pos, uncompressed_pos;
|
|
file_ptr res;
|
|
|
|
res = 0;
|
|
while (nbytes > 0)
|
|
{
|
|
if (lstream->data == NULL
|
|
|| lstream->data_start > offset || offset >= lstream->data_end)
|
|
{
|
|
asection *section = lstream->section;
|
|
|
|
lzma_index_iter_init (&iter, lstream->index);
|
|
if (lzma_index_iter_locate (&iter, offset))
|
|
break;
|
|
|
|
compressed = (gdb_byte *) xmalloc (iter.block.total_size);
|
|
block_offset = section->filepos + iter.block.compressed_file_offset;
|
|
if (bfd_seek (section->owner, block_offset, SEEK_SET) != 0
|
|
|| bfd_bread (compressed, iter.block.total_size, section->owner)
|
|
!= iter.block.total_size)
|
|
{
|
|
xfree (compressed);
|
|
break;
|
|
}
|
|
|
|
uncompressed = (gdb_byte *) xmalloc (iter.block.uncompressed_size);
|
|
|
|
memset (&block, 0, sizeof (block));
|
|
block.filters = filters;
|
|
block.header_size = lzma_block_header_size_decode (compressed[0]);
|
|
if (lzma_block_header_decode (&block, &gdb_lzma_allocator, compressed)
|
|
!= LZMA_OK)
|
|
{
|
|
xfree (compressed);
|
|
xfree (uncompressed);
|
|
break;
|
|
}
|
|
|
|
compressed_pos = block.header_size;
|
|
uncompressed_pos = 0;
|
|
if (lzma_block_buffer_decode (&block, &gdb_lzma_allocator,
|
|
compressed, &compressed_pos,
|
|
iter.block.total_size,
|
|
uncompressed, &uncompressed_pos,
|
|
iter.block.uncompressed_size)
|
|
!= LZMA_OK)
|
|
{
|
|
xfree (compressed);
|
|
xfree (uncompressed);
|
|
break;
|
|
}
|
|
|
|
xfree (compressed);
|
|
|
|
xfree (lstream->data);
|
|
lstream->data = uncompressed;
|
|
lstream->data_start = iter.block.uncompressed_file_offset;
|
|
lstream->data_end = (iter.block.uncompressed_file_offset
|
|
+ iter.block.uncompressed_size);
|
|
}
|
|
|
|
chunk_size = std::min (nbytes, (file_ptr) lstream->data_end - offset);
|
|
memcpy (buf, lstream->data + offset - lstream->data_start, chunk_size);
|
|
buf = (gdb_byte *) buf + chunk_size;
|
|
offset += chunk_size;
|
|
nbytes -= chunk_size;
|
|
res += chunk_size;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* bfd_openr_iovec CLOSE_P implementation for
|
|
find_separate_debug_file_in_section. Passed STREAM
|
|
is 'struct gdb_lzma_stream *'. */
|
|
|
|
static int
|
|
lzma_close (struct bfd *nbfd,
|
|
void *stream)
|
|
{
|
|
struct gdb_lzma_stream *lstream = (struct gdb_lzma_stream *) stream;
|
|
|
|
lzma_index_end (lstream->index, &gdb_lzma_allocator);
|
|
xfree (lstream->data);
|
|
xfree (lstream);
|
|
|
|
/* Zero means success. */
|
|
return 0;
|
|
}
|
|
|
|
/* bfd_openr_iovec STAT_P implementation for
|
|
find_separate_debug_file_in_section. Passed STREAM
|
|
is 'struct gdb_lzma_stream *'. */
|
|
|
|
static int
|
|
lzma_stat (struct bfd *abfd,
|
|
void *stream,
|
|
struct stat *sb)
|
|
{
|
|
struct gdb_lzma_stream *lstream = (struct gdb_lzma_stream *) stream;
|
|
|
|
memset (sb, 0, sizeof (struct stat));
|
|
sb->st_size = lzma_index_uncompressed_size (lstream->index);
|
|
return 0;
|
|
}
|
|
|
|
#endif /* HAVE_LIBLZMA */
|
|
|
|
/* This looks for a xz compressed separate debug info object file embedded
|
|
in a section called .gnu_debugdata. See
|
|
http://fedoraproject.org/wiki/Features/MiniDebugInfo
|
|
or the "Separate Debug Sections" of the manual for details.
|
|
If we find one we create a iovec based bfd that decompresses the
|
|
object data on demand. If we don't find one, return NULL. */
|
|
|
|
gdb_bfd_ref_ptr
|
|
find_separate_debug_file_in_section (struct objfile *objfile)
|
|
{
|
|
asection *section;
|
|
gdb_bfd_ref_ptr abfd;
|
|
|
|
if (objfile->obfd == NULL)
|
|
return NULL;
|
|
|
|
section = bfd_get_section_by_name (objfile->obfd.get (), ".gnu_debugdata");
|
|
if (section == NULL)
|
|
return NULL;
|
|
|
|
#ifdef HAVE_LIBLZMA
|
|
gdb_bfd_ref_ptr *shared = gnu_debug_key.get (objfile->obfd.get ());
|
|
if (shared != nullptr)
|
|
return *shared;
|
|
|
|
std::string filename = string_printf (_(".gnu_debugdata for %s"),
|
|
objfile_name (objfile));
|
|
|
|
abfd = gdb_bfd_openr_iovec (filename.c_str (), gnutarget, lzma_open,
|
|
section, lzma_pread, lzma_close, lzma_stat);
|
|
if (abfd == NULL)
|
|
return NULL;
|
|
|
|
if (!bfd_check_format (abfd.get (), bfd_object))
|
|
{
|
|
warning (_("Cannot parse .gnu_debugdata section; not a BFD object"));
|
|
return NULL;
|
|
}
|
|
|
|
gnu_debug_key.emplace (objfile->obfd.get (), abfd);
|
|
|
|
#else
|
|
warning (_("Cannot parse .gnu_debugdata section; LZMA support was "
|
|
"disabled at compile time"));
|
|
#endif /* !HAVE_LIBLZMA */
|
|
|
|
return abfd;
|
|
}
|