| /* Read MiniDebugInfo data from an objfile. |
| |
| Copyright (C) 2012-2021 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 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, ".gnu_debugdata"); |
| if (section == NULL) |
| return NULL; |
| |
| #ifdef HAVE_LIBLZMA |
| gdb_bfd_ref_ptr *shared = gnu_debug_key.get (objfile->obfd); |
| 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, abfd); |
| |
| #else |
| warning (_("Cannot parse .gnu_debugdata section; LZMA support was " |
| "disabled at compile time")); |
| #endif /* !HAVE_LIBLZMA */ |
| |
| return abfd; |
| } |