| /* Read MiniDebugInfo data from an objfile. | |
| Copyright (C) 2012-2024 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 : public gdb_bfd_iovec_base | |
| { | |
| /* Section of input BFD from which we are decoding data. */ | |
| asection *section = nullptr; | |
| /* lzma library decompression state. */ | |
| lzma_index *index = nullptr; | |
| /* Currently decoded block. */ | |
| bfd_size_type data_start = 0; | |
| bfd_size_type data_end = 0; | |
| gdb::byte_vector data; | |
| ~gdb_lzma_stream () | |
| { | |
| lzma_index_end (index, &gdb_lzma_allocator); | |
| } | |
| file_ptr read (bfd *abfd, void *buffer, file_ptr nbytes, | |
| file_ptr offset) override; | |
| int stat (struct bfd *abfd, struct stat *sb) override; | |
| }; | |
| /* bfd_openr_iovec implementation helper for | |
| find_separate_debug_file_in_section. */ | |
| static gdb_lzma_stream * | |
| lzma_open (struct bfd *nbfd, asection *section) | |
| { | |
| bfd_size_type size, offset; | |
| lzma_stream_flags options; | |
| gdb_byte footer[LZMA_STREAM_HEADER_SIZE]; | |
| 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_read (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; | |
| gdb::byte_vector indexdata (options.backward_size); | |
| index = NULL; | |
| pos = 0; | |
| if (bfd_seek (section->owner, offset, SEEK_SET) != 0 | |
| || bfd_read (indexdata.data (), options.backward_size, section->owner) | |
| != options.backward_size | |
| || lzma_index_buffer_decode (&index, &memlimit, &gdb_lzma_allocator, | |
| indexdata.data (), &pos, | |
| options.backward_size) | |
| != LZMA_OK | |
| || lzma_index_size (index) != options.backward_size) | |
| { | |
| bfd_set_error (bfd_error_wrong_format); | |
| return NULL; | |
| } | |
| lstream = new struct gdb_lzma_stream; | |
| lstream->section = section; | |
| lstream->index = index; | |
| return lstream; | |
| } | |
| /* bfd_openr_iovec read implementation for | |
| find_separate_debug_file_in_section. */ | |
| file_ptr | |
| gdb_lzma_stream::read (struct bfd *nbfd, void *buf, file_ptr nbytes, | |
| file_ptr offset) | |
| { | |
| bfd_size_type chunk_size; | |
| lzma_index_iter iter; | |
| 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 (data.empty () || data_start > offset || offset >= data_end) | |
| { | |
| lzma_index_iter_init (&iter, index); | |
| if (lzma_index_iter_locate (&iter, offset)) | |
| break; | |
| gdb::byte_vector compressed (iter.block.total_size); | |
| block_offset = section->filepos + iter.block.compressed_file_offset; | |
| if (bfd_seek (section->owner, block_offset, SEEK_SET) != 0 | |
| || bfd_read (compressed.data (), iter.block.total_size, | |
| section->owner) != iter.block.total_size) | |
| break; | |
| gdb::byte_vector uncompressed (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.data ()) | |
| != LZMA_OK) | |
| break; | |
| compressed_pos = block.header_size; | |
| uncompressed_pos = 0; | |
| if (lzma_block_buffer_decode (&block, &gdb_lzma_allocator, | |
| compressed.data (), &compressed_pos, | |
| iter.block.total_size, | |
| uncompressed.data (), | |
| &uncompressed_pos, | |
| iter.block.uncompressed_size) | |
| != LZMA_OK) | |
| break; | |
| data = std::move (uncompressed); | |
| data_start = iter.block.uncompressed_file_offset; | |
| data_end = (iter.block.uncompressed_file_offset | |
| + iter.block.uncompressed_size); | |
| } | |
| chunk_size = std::min (nbytes, (file_ptr) data_end - offset); | |
| memcpy (buf, data.data () + offset - data_start, chunk_size); | |
| buf = (gdb_byte *) buf + chunk_size; | |
| offset += chunk_size; | |
| nbytes -= chunk_size; | |
| res += chunk_size; | |
| } | |
| return res; | |
| } | |
| /* bfd_openr_iovec stat implementation for | |
| find_separate_debug_file_in_section. */ | |
| int | |
| gdb_lzma_stream::stat (struct bfd *abfd, struct stat *sb) | |
| { | |
| memset (sb, 0, sizeof (struct stat)); | |
| sb->st_size = lzma_index_uncompressed_size (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)); | |
| auto open = [&] (bfd *nbfd) -> gdb_lzma_stream * | |
| { | |
| return lzma_open (nbfd, section); | |
| }; | |
| abfd = gdb_bfd_openr_iovec (filename.c_str (), gnutarget, open); | |
| 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; | |
| } |