Home | History | Annotate | Line # | Download | only in gdb
      1 /* Read MiniDebugInfo data from an objfile.
      2 
      3    Copyright (C) 2012-2024 Free Software Foundation, Inc.
      4 
      5    This file is part of GDB.
      6 
      7    This program is free software; you can redistribute it and/or modify
      8    it under the terms of the GNU General Public License as published by
      9    the Free Software Foundation; either version 3 of the License, or
     10    (at your option) any later version.
     11 
     12    This program is distributed in the hope that it will be useful,
     13    but WITHOUT ANY WARRANTY; without even the implied warranty of
     14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15    GNU General Public License for more details.
     16 
     17    You should have received a copy of the GNU General Public License
     18    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
     19 
     20 #include "gdb_bfd.h"
     21 #include "symfile.h"
     22 #include "objfiles.h"
     23 #include "gdbcore.h"
     24 #include <algorithm>
     25 
     26 #ifdef HAVE_LIBLZMA
     27 
     28 /* We stash a reference to the .gnu_debugdata BFD on the enclosing
     29    BFD.  */
     30 static const registry<bfd>::key<gdb_bfd_ref_ptr> gnu_debug_key;
     31 
     32 #include <lzma.h>
     33 
     34 /* Allocator function for LZMA.  */
     35 
     36 static void *
     37 alloc_lzma (void *opaque, size_t nmemb, size_t size)
     38 {
     39   return xmalloc (nmemb * size);
     40 }
     41 
     42 /* Free function for LZMA.  */
     43 
     44 static void
     45 free_lzma (void *opaque, void *ptr)
     46 {
     47   xfree (ptr);
     48 }
     49 
     50 /* The allocator object for LZMA.  Note that 'gdb_lzma_allocator'
     51    cannot be const due to the lzma library function prototypes.  */
     52 
     53 static lzma_allocator gdb_lzma_allocator = { alloc_lzma, free_lzma, NULL };
     54 
     55 /* Custom bfd_openr_iovec implementation to read compressed data from
     56    a section.  This keeps only the last decompressed block in memory
     57    to allow larger data without using to much memory.  */
     58 
     59 struct gdb_lzma_stream : public gdb_bfd_iovec_base
     60 {
     61   /* Section of input BFD from which we are decoding data.  */
     62   asection *section = nullptr;
     63 
     64   /* lzma library decompression state.  */
     65   lzma_index *index = nullptr;
     66 
     67   /* Currently decoded block.  */
     68   bfd_size_type data_start = 0;
     69   bfd_size_type data_end = 0;
     70   gdb::byte_vector data;
     71 
     72 
     73   ~gdb_lzma_stream ()
     74   {
     75     lzma_index_end (index, &gdb_lzma_allocator);
     76   }
     77 
     78   file_ptr read (bfd *abfd, void *buffer, file_ptr nbytes,
     79 		 file_ptr offset) override;
     80 
     81   int stat (struct bfd *abfd, struct stat *sb) override;
     82 };
     83 
     84 /* bfd_openr_iovec implementation helper for
     85    find_separate_debug_file_in_section.  */
     86 
     87 static gdb_lzma_stream *
     88 lzma_open (struct bfd *nbfd, asection *section)
     89 {
     90   bfd_size_type size, offset;
     91   lzma_stream_flags options;
     92   gdb_byte footer[LZMA_STREAM_HEADER_SIZE];
     93   lzma_index *index;
     94   uint64_t memlimit = UINT64_MAX;
     95   struct gdb_lzma_stream *lstream;
     96   size_t pos;
     97 
     98   size = bfd_section_size (section);
     99   offset = section->filepos + size - LZMA_STREAM_HEADER_SIZE;
    100   if (size < LZMA_STREAM_HEADER_SIZE
    101       || bfd_seek (section->owner, offset, SEEK_SET) != 0
    102       || bfd_read (footer, LZMA_STREAM_HEADER_SIZE, section->owner)
    103 	 != LZMA_STREAM_HEADER_SIZE
    104       || lzma_stream_footer_decode (&options, footer) != LZMA_OK
    105       || offset < options.backward_size)
    106     {
    107       bfd_set_error (bfd_error_wrong_format);
    108       return NULL;
    109     }
    110 
    111   offset -= options.backward_size;
    112   gdb::byte_vector indexdata (options.backward_size);
    113   index = NULL;
    114   pos = 0;
    115   if (bfd_seek (section->owner, offset, SEEK_SET) != 0
    116       || bfd_read (indexdata.data (), options.backward_size, section->owner)
    117 	 != options.backward_size
    118       || lzma_index_buffer_decode (&index, &memlimit, &gdb_lzma_allocator,
    119 				   indexdata.data (), &pos,
    120 				   options.backward_size)
    121 	 != LZMA_OK
    122       || lzma_index_size (index) != options.backward_size)
    123     {
    124       bfd_set_error (bfd_error_wrong_format);
    125       return NULL;
    126     }
    127 
    128   lstream = new struct gdb_lzma_stream;
    129   lstream->section = section;
    130   lstream->index = index;
    131 
    132   return lstream;
    133 }
    134 
    135 /* bfd_openr_iovec read implementation for
    136    find_separate_debug_file_in_section.  */
    137 
    138 file_ptr
    139 gdb_lzma_stream::read (struct bfd *nbfd, void *buf, file_ptr nbytes,
    140 		       file_ptr offset)
    141 {
    142   bfd_size_type chunk_size;
    143   lzma_index_iter iter;
    144   file_ptr block_offset;
    145   lzma_filter filters[LZMA_FILTERS_MAX + 1];
    146   lzma_block block;
    147   size_t compressed_pos, uncompressed_pos;
    148   file_ptr res;
    149 
    150   res = 0;
    151   while (nbytes > 0)
    152     {
    153       if (data.empty () || data_start > offset || offset >= data_end)
    154 	{
    155 	  lzma_index_iter_init (&iter, index);
    156 	  if (lzma_index_iter_locate (&iter, offset))
    157 	    break;
    158 
    159 	  gdb::byte_vector compressed (iter.block.total_size);
    160 	  block_offset = section->filepos + iter.block.compressed_file_offset;
    161 	  if (bfd_seek (section->owner, block_offset, SEEK_SET) != 0
    162 	      || bfd_read (compressed.data (), iter.block.total_size,
    163 			   section->owner) != iter.block.total_size)
    164 	    break;
    165 
    166 	  gdb::byte_vector uncompressed (iter.block.uncompressed_size);
    167 
    168 	  memset (&block, 0, sizeof (block));
    169 	  block.filters = filters;
    170 	  block.header_size = lzma_block_header_size_decode (compressed[0]);
    171 	  if (lzma_block_header_decode (&block, &gdb_lzma_allocator,
    172 					compressed.data ())
    173 	      != LZMA_OK)
    174 	    break;
    175 
    176 	  compressed_pos = block.header_size;
    177 	  uncompressed_pos = 0;
    178 	  if (lzma_block_buffer_decode (&block, &gdb_lzma_allocator,
    179 					compressed.data (), &compressed_pos,
    180 					iter.block.total_size,
    181 					uncompressed.data (),
    182 					&uncompressed_pos,
    183 					iter.block.uncompressed_size)
    184 	      != LZMA_OK)
    185 	    break;
    186 
    187 	  data = std::move (uncompressed);
    188 	  data_start = iter.block.uncompressed_file_offset;
    189 	  data_end = (iter.block.uncompressed_file_offset
    190 		      + iter.block.uncompressed_size);
    191 	}
    192 
    193       chunk_size = std::min (nbytes, (file_ptr) data_end - offset);
    194       memcpy (buf, data.data () + offset - data_start, chunk_size);
    195       buf = (gdb_byte *) buf + chunk_size;
    196       offset += chunk_size;
    197       nbytes -= chunk_size;
    198       res += chunk_size;
    199     }
    200 
    201   return res;
    202 }
    203 
    204 /* bfd_openr_iovec stat implementation for
    205    find_separate_debug_file_in_section.  */
    206 
    207 int
    208 gdb_lzma_stream::stat (struct bfd *abfd, struct stat *sb)
    209 {
    210   memset (sb, 0, sizeof (struct stat));
    211   sb->st_size = lzma_index_uncompressed_size (index);
    212   return 0;
    213 }
    214 
    215 #endif /* HAVE_LIBLZMA  */
    216 
    217 /* This looks for a xz compressed separate debug info object file embedded
    218    in a section called .gnu_debugdata.  See
    219    http://fedoraproject.org/wiki/Features/MiniDebugInfo
    220    or the "Separate Debug Sections" of the manual for details.
    221    If we find one we create a iovec based bfd that decompresses the
    222    object data on demand.  If we don't find one, return NULL.  */
    223 
    224 gdb_bfd_ref_ptr
    225 find_separate_debug_file_in_section (struct objfile *objfile)
    226 {
    227   asection *section;
    228   gdb_bfd_ref_ptr abfd;
    229 
    230   if (objfile->obfd == NULL)
    231     return NULL;
    232 
    233   section = bfd_get_section_by_name (objfile->obfd.get (), ".gnu_debugdata");
    234   if (section == NULL)
    235     return NULL;
    236 
    237 #ifdef HAVE_LIBLZMA
    238   gdb_bfd_ref_ptr *shared = gnu_debug_key.get (objfile->obfd.get ());
    239   if (shared != nullptr)
    240     return *shared;
    241 
    242   std::string filename = string_printf (_(".gnu_debugdata for %s"),
    243 					objfile_name (objfile));
    244 
    245   auto open = [&] (bfd *nbfd) -> gdb_lzma_stream *
    246   {
    247     return lzma_open (nbfd, section);
    248   };
    249 
    250   abfd = gdb_bfd_openr_iovec (filename.c_str (), gnutarget, open);
    251   if (abfd == NULL)
    252     return NULL;
    253 
    254   if (!bfd_check_format (abfd.get (), bfd_object))
    255     {
    256       warning (_("Cannot parse .gnu_debugdata section; not a BFD object"));
    257       return NULL;
    258     }
    259 
    260   gnu_debug_key.emplace (objfile->obfd.get (), abfd);
    261 
    262 #else
    263   warning (_("Cannot parse .gnu_debugdata section; LZMA support was "
    264 	     "disabled at compile time"));
    265 #endif /* !HAVE_LIBLZMA */
    266 
    267   return abfd;
    268 }
    269