| /* Handle ROCm Code Objects for GDB, the GNU Debugger. | 
 |  | 
 |    Copyright (C) 2019-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 "amd-dbgapi-target.h" | 
 | #include "amdgpu-tdep.h" | 
 | #include "arch-utils.h" | 
 | #include "elf-bfd.h" | 
 | #include "elf/amdgpu.h" | 
 | #include "event-top.h" | 
 | #include "gdbsupport/fileio.h" | 
 | #include "inferior.h" | 
 | #include "observable.h" | 
 | #include "solib.h" | 
 | #include "solib-svr4.h" | 
 | #include "solist.h" | 
 | #include "symfile.h" | 
 |  | 
 | #include <unordered_map> | 
 |  | 
 | namespace { | 
 |  | 
 | /* Per inferior cache of opened file descriptors.  */ | 
 | struct rocm_solib_fd_cache | 
 | { | 
 |   explicit rocm_solib_fd_cache (inferior *inf) : m_inferior (inf) {} | 
 |   DISABLE_COPY_AND_ASSIGN (rocm_solib_fd_cache); | 
 |  | 
 |   /* Return a read-only file descriptor to FILENAME and increment the | 
 |      associated reference count. | 
 |  | 
 |      Open the file FILENAME if it is not already opened, reuse the existing file | 
 |      descriptor otherwise. | 
 |  | 
 |      On error -1 is returned, and TARGET_ERRNO is set.  */ | 
 |   int open (const std::string &filename, fileio_error *target_errno); | 
 |  | 
 |   /* Decrement the reference count to FD and close FD if the reference count | 
 |      reaches 0. | 
 |  | 
 |      On success, return 0.  On error, return -1 and set TARGET_ERRNO.  */ | 
 |   int close (int fd, fileio_error *target_errno); | 
 |  | 
 | private: | 
 |   struct refcnt_fd | 
 |   { | 
 |     DISABLE_COPY_AND_ASSIGN (refcnt_fd); | 
 |     refcnt_fd (int fd, int refcnt) : fd (fd), refcnt (refcnt) {} | 
 |  | 
 |     int fd = -1; | 
 |     int refcnt = 0; | 
 |   }; | 
 |  | 
 |   inferior *m_inferior; | 
 |   std::unordered_map<std::string, refcnt_fd> m_cache; | 
 | }; | 
 |  | 
 | int | 
 | rocm_solib_fd_cache::open (const std::string &filename, | 
 | 			   fileio_error *target_errno) | 
 | { | 
 |   auto it = m_cache.find (filename); | 
 |   if (it == m_cache.end ()) | 
 |     { | 
 |       /* The file is not yet opened on the target.  */ | 
 |       int fd | 
 | 	= target_fileio_open (m_inferior, filename.c_str (), FILEIO_O_RDONLY, | 
 | 			      false, 0, target_errno); | 
 |       if (fd != -1) | 
 | 	m_cache.emplace (std::piecewise_construct, | 
 | 			 std::forward_as_tuple (filename), | 
 | 			 std::forward_as_tuple (fd, 1)); | 
 |       return fd; | 
 |     } | 
 |   else | 
 |     { | 
 |       /* The file is already opened.  Increment the refcnt and return the | 
 | 	 already opened FD.  */ | 
 |       it->second.refcnt++; | 
 |       gdb_assert (it->second.fd != -1); | 
 |       return it->second.fd; | 
 |     } | 
 | } | 
 |  | 
 | int | 
 | rocm_solib_fd_cache::close (int fd, fileio_error *target_errno) | 
 | { | 
 |   using cache_val = std::unordered_map<std::string, refcnt_fd>::value_type; | 
 |   auto it | 
 |     = std::find_if (m_cache.begin (), m_cache.end (), | 
 | 		    [fd](const cache_val &s) { return s.second.fd == fd; }); | 
 |  | 
 |   gdb_assert (it != m_cache.end ()); | 
 |  | 
 |   it->second.refcnt--; | 
 |   if (it->second.refcnt == 0) | 
 |     { | 
 |       int ret = target_fileio_close (it->second.fd, target_errno); | 
 |       m_cache.erase (it); | 
 |       return ret; | 
 |     } | 
 |   else | 
 |     { | 
 |       /* Keep the FD open for the other users, return success.  */ | 
 |       return 0; | 
 |     } | 
 | } | 
 |  | 
 | } /* Anonymous namespace.  */ | 
 |  | 
 | /* ROCm-specific inferior data.  */ | 
 |  | 
 | struct rocm_so | 
 | { | 
 |   rocm_so (const char *name, std::string unique_name, lm_info_svr4_up lm_info) | 
 |     : name (name), | 
 |       unique_name (std::move (unique_name)), | 
 |       lm_info (std::move (lm_info)) | 
 |   {} | 
 |  | 
 |   std::string name, unique_name; | 
 |   lm_info_svr4_up lm_info; | 
 | }; | 
 |  | 
 | struct solib_info | 
 | { | 
 |   explicit solib_info (inferior *inf) | 
 |     : fd_cache (inf) | 
 |   {}; | 
 |  | 
 |   /* List of code objects loaded into the inferior.  */ | 
 |   std::vector<rocm_so> solib_list; | 
 |  | 
 |   /* Cache of opened FD in the inferior.  */ | 
 |   rocm_solib_fd_cache fd_cache; | 
 | }; | 
 |  | 
 | /* Per-inferior data key.  */ | 
 | static const registry<inferior>::key<solib_info> rocm_solib_data; | 
 |  | 
 | static solib_ops rocm_solib_ops; | 
 |  | 
 | /* Fetch the solib_info data for INF.  */ | 
 |  | 
 | static struct solib_info * | 
 | get_solib_info (inferior *inf) | 
 | { | 
 |   solib_info *info = rocm_solib_data.get (inf); | 
 |  | 
 |   if (info == nullptr) | 
 |     info = rocm_solib_data.emplace (inf, inf); | 
 |  | 
 |   return info; | 
 | } | 
 |  | 
 | /* Relocate section addresses.  */ | 
 |  | 
 | static void | 
 | rocm_solib_relocate_section_addresses (solib &so, | 
 | 				       struct target_section *sec) | 
 | { | 
 |   if (!is_amdgpu_arch (gdbarch_from_bfd (so.abfd.get ()))) | 
 |     { | 
 |       svr4_so_ops.relocate_section_addresses (so, sec); | 
 |       return; | 
 |     } | 
 |  | 
 |   auto *li = gdb::checked_static_cast<lm_info_svr4 *> (so.lm_info.get ()); | 
 |   sec->addr = sec->addr + li->l_addr; | 
 |   sec->endaddr = sec->endaddr + li->l_addr; | 
 | } | 
 |  | 
 | static void rocm_update_solib_list (); | 
 |  | 
 | static void | 
 | rocm_solib_handle_event () | 
 | { | 
 |   /* Since we sit on top of svr4_so_ops, we might get called following an event | 
 |      concerning host libraries.  We must therefore forward the call.  If the | 
 |      event was for a ROCm code object, it will be a no-op.  On the other hand, | 
 |      if the event was for host libraries, rocm_update_solib_list will be | 
 |      essentially be a no-op (it will reload the same code object list as was | 
 |      previously loaded).  */ | 
 |   svr4_so_ops.handle_event (); | 
 |  | 
 |   rocm_update_solib_list (); | 
 | } | 
 |  | 
 | /* Create so_list objects from rocm_so objects in SOS.  */ | 
 |  | 
 | static owning_intrusive_list<solib> | 
 | so_list_from_rocm_sos (const std::vector<rocm_so> &sos) | 
 | { | 
 |   owning_intrusive_list<solib> dst; | 
 |  | 
 |   for (const rocm_so &so : sos) | 
 |     { | 
 |       auto &newobj = dst.emplace_back (); | 
 |  | 
 |       newobj.lm_info = std::make_unique<lm_info_svr4> (*so.lm_info); | 
 |       newobj.so_name = so.name; | 
 |       newobj.so_original_name = so.unique_name; | 
 |     } | 
 |  | 
 |   return dst; | 
 | } | 
 |  | 
 | /* Build a list of `struct solib' objects describing the shared | 
 |    objects currently loaded in the inferior.  */ | 
 |  | 
 | static owning_intrusive_list<solib> | 
 | rocm_solib_current_sos () | 
 | { | 
 |   /* First, retrieve the host-side shared library list.  */ | 
 |   owning_intrusive_list<solib> sos = svr4_so_ops.current_sos (); | 
 |  | 
 |   /* Then, the device-side shared library list.  */ | 
 |   std::vector<rocm_so> &dev_sos = get_solib_info (current_inferior ())->solib_list; | 
 |  | 
 |   if (dev_sos.empty ()) | 
 |     return sos; | 
 |  | 
 |   owning_intrusive_list<solib> dev_so_list = so_list_from_rocm_sos (dev_sos); | 
 |  | 
 |   if (sos.empty ()) | 
 |     return dev_so_list; | 
 |  | 
 |   /* Append our libraries to the end of the list.  */ | 
 |   sos.splice (std::move (dev_so_list)); | 
 |  | 
 |   return sos; | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | /* Interface to interact with a ROCm code object stream.  */ | 
 |  | 
 | struct rocm_code_object_stream : public gdb_bfd_iovec_base | 
 | { | 
 |   DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream); | 
 |  | 
 |   int stat (bfd *abfd, struct stat *sb) final override; | 
 |  | 
 |   ~rocm_code_object_stream () override = default; | 
 |  | 
 | protected: | 
 |   rocm_code_object_stream () = default; | 
 |  | 
 |   /* Return the size of the object file, or -1 if the size cannot be | 
 |      determined. | 
 |  | 
 |      This is a helper function for stat.  */ | 
 |   virtual LONGEST size () = 0; | 
 | }; | 
 |  | 
 | int | 
 | rocm_code_object_stream::stat (bfd *, struct stat *sb) | 
 | { | 
 |   const LONGEST size = this->size (); | 
 |   if (size == -1) | 
 |     return -1; | 
 |  | 
 |   memset (sb, '\0', sizeof (struct stat)); | 
 |   sb->st_size = size; | 
 |   return 0; | 
 | } | 
 |  | 
 | /* Interface to a ROCm object stream which is embedded in an ELF file | 
 |    accessible to the debugger.  */ | 
 |  | 
 | struct rocm_code_object_stream_file final : rocm_code_object_stream | 
 | { | 
 |   DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream_file); | 
 |  | 
 |   rocm_code_object_stream_file (inferior *inf, int fd, ULONGEST offset, | 
 | 				ULONGEST size); | 
 |  | 
 |   file_ptr read (bfd *abfd, void *buf, file_ptr size, | 
 | 		 file_ptr offset) override; | 
 |  | 
 |   LONGEST size () override; | 
 |  | 
 |   ~rocm_code_object_stream_file () override; | 
 |  | 
 | protected: | 
 |  | 
 |   /* The inferior owning this code object stream.  */ | 
 |   inferior *m_inf; | 
 |  | 
 |   /* The target file descriptor for this stream.  */ | 
 |   int m_fd; | 
 |  | 
 |   /* The offset of the ELF file image in the target file.  */ | 
 |   ULONGEST m_offset; | 
 |  | 
 |   /* The size of the ELF file image.  The value 0 means that it was | 
 |      unspecified in the URI descriptor.  */ | 
 |   ULONGEST m_size; | 
 | }; | 
 |  | 
 | rocm_code_object_stream_file::rocm_code_object_stream_file | 
 |   (inferior *inf, int fd, ULONGEST offset, ULONGEST size) | 
 |   : m_inf (inf), m_fd (fd), m_offset (offset), m_size (size) | 
 | { | 
 | } | 
 |  | 
 | file_ptr | 
 | rocm_code_object_stream_file::read (bfd *, void *buf, file_ptr size, | 
 | 				    file_ptr offset) | 
 | { | 
 |   fileio_error target_errno; | 
 |   file_ptr nbytes = 0; | 
 |   while (size > 0) | 
 |     { | 
 |       QUIT; | 
 |  | 
 |       file_ptr bytes_read | 
 | 	= target_fileio_pread (m_fd, static_cast<gdb_byte *> (buf) + nbytes, | 
 | 			       size, m_offset + offset + nbytes, | 
 | 			       &target_errno); | 
 |  | 
 |       if (bytes_read == 0) | 
 | 	break; | 
 |  | 
 |       if (bytes_read < 0) | 
 | 	{ | 
 | 	  errno = fileio_error_to_host (target_errno); | 
 | 	  bfd_set_error (bfd_error_system_call); | 
 | 	  return -1; | 
 | 	} | 
 |  | 
 |       nbytes += bytes_read; | 
 |       size -= bytes_read; | 
 |     } | 
 |  | 
 |   return nbytes; | 
 | } | 
 |  | 
 | LONGEST | 
 | rocm_code_object_stream_file::size () | 
 | { | 
 |   if (m_size == 0) | 
 |     { | 
 |       fileio_error target_errno; | 
 |       struct stat stat; | 
 |       if (target_fileio_fstat (m_fd, &stat, &target_errno) < 0) | 
 | 	{ | 
 | 	  errno = fileio_error_to_host (target_errno); | 
 | 	  bfd_set_error (bfd_error_system_call); | 
 | 	  return -1; | 
 | 	} | 
 |  | 
 |       /* Check that the offset is valid.  */ | 
 |       if (m_offset >= stat.st_size) | 
 | 	{ | 
 | 	  bfd_set_error (bfd_error_bad_value); | 
 | 	  return -1; | 
 | 	} | 
 |  | 
 |       m_size = stat.st_size - m_offset; | 
 |     } | 
 |  | 
 |   return m_size; | 
 | } | 
 |  | 
 | rocm_code_object_stream_file::~rocm_code_object_stream_file () | 
 | { | 
 |   auto info = get_solib_info (m_inf); | 
 |   fileio_error target_errno; | 
 |   if (info->fd_cache.close (m_fd, &target_errno) != 0) | 
 |     warning (_("Failed to close solib: %s"), | 
 | 	     strerror (fileio_error_to_host (target_errno))); | 
 | } | 
 |  | 
 | /* Interface to a code object which lives in the inferior's memory.  */ | 
 |  | 
 | struct rocm_code_object_stream_memory final : public rocm_code_object_stream | 
 | { | 
 |   DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream_memory); | 
 |  | 
 |   rocm_code_object_stream_memory (gdb::byte_vector buffer); | 
 |  | 
 |   file_ptr read (bfd *abfd, void *buf, file_ptr size, | 
 | 		 file_ptr offset) override; | 
 |  | 
 | protected: | 
 |  | 
 |   /* Snapshot of the original ELF image taken during load.  This is done to | 
 |      support the situation where an inferior uses an in-memory image, and | 
 |      releases or re-uses this memory before GDB is done using it.  */ | 
 |   gdb::byte_vector m_objfile_image; | 
 |  | 
 |   LONGEST size () override | 
 |   { | 
 |     return m_objfile_image.size (); | 
 |   } | 
 | }; | 
 |  | 
 | rocm_code_object_stream_memory::rocm_code_object_stream_memory | 
 |   (gdb::byte_vector buffer) | 
 |   : m_objfile_image (std::move (buffer)) | 
 | { | 
 | } | 
 |  | 
 | file_ptr | 
 | rocm_code_object_stream_memory::read (bfd *, void *buf, file_ptr size, | 
 | 				      file_ptr offset) | 
 | { | 
 |   if (size > m_objfile_image.size () - offset) | 
 |     size = m_objfile_image.size () - offset; | 
 |  | 
 |   memcpy (buf, m_objfile_image.data () + offset, size); | 
 |   return size; | 
 | } | 
 |  | 
 | } /* anonymous namespace */ | 
 |  | 
 | static gdb_bfd_iovec_base * | 
 | rocm_bfd_iovec_open (bfd *abfd, inferior *inferior) | 
 | { | 
 |   std::string_view uri (bfd_get_filename (abfd)); | 
 |   std::string_view protocol_delim = "://"; | 
 |   size_t protocol_end = uri.find (protocol_delim); | 
 |   std::string protocol (uri.substr (0, protocol_end)); | 
 |   protocol_end += protocol_delim.length (); | 
 |  | 
 |   std::transform (protocol.begin (), protocol.end (), protocol.begin (), | 
 | 		  [] (unsigned char c) { return std::tolower (c); }); | 
 |  | 
 |   std::string_view path; | 
 |   size_t path_end = uri.find_first_of ("#?", protocol_end); | 
 |   if (path_end != std::string::npos) | 
 |     path = uri.substr (protocol_end, path_end++ - protocol_end); | 
 |   else | 
 |     path = uri.substr (protocol_end); | 
 |  | 
 |   /* %-decode the string.  */ | 
 |   std::string decoded_path; | 
 |   decoded_path.reserve (path.length ()); | 
 |   for (size_t i = 0; i < path.length (); ++i) | 
 |     if (path[i] == '%' | 
 | 	&& i < path.length () - 2 | 
 | 	&& std::isxdigit (path[i + 1]) | 
 | 	&& std::isxdigit (path[i + 2])) | 
 |       { | 
 | 	std::string_view hex_digits = path.substr (i + 1, 2); | 
 | 	decoded_path += std::stoi (std::string (hex_digits), 0, 16); | 
 | 	i += 2; | 
 |       } | 
 |     else | 
 |       decoded_path += path[i]; | 
 |  | 
 |   /* Tokenize the query/fragment.  */ | 
 |   std::vector<std::string_view> tokens; | 
 |   size_t pos, last = path_end; | 
 |   while ((pos = uri.find ('&', last)) != std::string::npos) | 
 |     { | 
 |       tokens.emplace_back (uri.substr (last, pos - last)); | 
 |       last = pos + 1; | 
 |     } | 
 |  | 
 |   if (last != std::string::npos) | 
 |     tokens.emplace_back (uri.substr (last)); | 
 |  | 
 |   /* Create a tag-value map from the tokenized query/fragment.  */ | 
 |   std::unordered_map<std::string_view, std::string_view, | 
 | 		     gdb::string_view_hash> params; | 
 |   for (std::string_view token : tokens) | 
 |     { | 
 |       size_t delim = token.find ('='); | 
 |       if (delim != std::string::npos) | 
 | 	{ | 
 | 	  std::string_view tag = token.substr (0, delim); | 
 | 	  std::string_view val = token.substr (delim + 1); | 
 | 	  params.emplace (tag, val); | 
 | 	} | 
 |     } | 
 |  | 
 |   try | 
 |     { | 
 |       ULONGEST offset = 0; | 
 |       ULONGEST size = 0; | 
 |  | 
 |       auto try_strtoulst = [] (std::string_view v) | 
 | 	{ | 
 | 	  errno = 0; | 
 | 	  ULONGEST value = strtoulst (v.data (), nullptr, 0); | 
 | 	  if (errno != 0) | 
 | 	    { | 
 | 	      /* The actual message doesn't matter, the exception is caught | 
 | 		 below, transformed in a BFD error, and the message is lost.  */ | 
 | 	      error (_("Failed to parse integer.")); | 
 | 	    } | 
 |  | 
 | 	  return value; | 
 | 	}; | 
 |  | 
 |       auto offset_it = params.find ("offset"); | 
 |       if (offset_it != params.end ()) | 
 | 	offset = try_strtoulst (offset_it->second); | 
 |  | 
 |       auto size_it = params.find ("size"); | 
 |       if (size_it != params.end ()) | 
 | 	{ | 
 | 	  size = try_strtoulst (size_it->second); | 
 | 	  if (size == 0) | 
 | 	    error (_("Invalid size value")); | 
 | 	} | 
 |  | 
 |       if (protocol == "file") | 
 | 	{ | 
 | 	  auto info = get_solib_info (inferior); | 
 | 	  fileio_error target_errno; | 
 | 	  int fd = info->fd_cache.open (decoded_path, &target_errno); | 
 |  | 
 | 	  if (fd == -1) | 
 | 	    { | 
 | 	      errno = fileio_error_to_host (target_errno); | 
 | 	      bfd_set_error (bfd_error_system_call); | 
 | 	      return nullptr; | 
 | 	    } | 
 |  | 
 | 	  return new rocm_code_object_stream_file (inferior, fd, offset, | 
 | 						   size); | 
 | 	} | 
 |  | 
 |       if (protocol == "memory") | 
 | 	{ | 
 | 	  ULONGEST pid = try_strtoulst (path); | 
 | 	  if (pid != inferior->pid) | 
 | 	    { | 
 | 	      warning (_("`%s': code object is from another inferior"), | 
 | 		       std::string (uri).c_str ()); | 
 | 	      bfd_set_error (bfd_error_bad_value); | 
 | 	      return nullptr; | 
 | 	    } | 
 |  | 
 | 	  gdb::byte_vector buffer (size); | 
 | 	  if (target_read_memory (offset, buffer.data (), size) != 0) | 
 | 	    { | 
 | 	      warning (_("Failed to copy the code object from the inferior")); | 
 | 	      bfd_set_error (bfd_error_bad_value); | 
 | 	      return nullptr; | 
 | 	    } | 
 |  | 
 | 	  return new rocm_code_object_stream_memory (std::move (buffer)); | 
 | 	} | 
 |  | 
 |       warning (_("`%s': protocol not supported: %s"), | 
 | 	       std::string (uri).c_str (), protocol.c_str ()); | 
 |       bfd_set_error (bfd_error_bad_value); | 
 |       return nullptr; | 
 |     } | 
 |   catch (const gdb_exception_quit &ex) | 
 |     { | 
 |       set_quit_flag (); | 
 |       bfd_set_error (bfd_error_bad_value); | 
 |       return nullptr; | 
 |     } | 
 |   catch (const gdb_exception &ex) | 
 |     { | 
 |       bfd_set_error (bfd_error_bad_value); | 
 |       return nullptr; | 
 |     } | 
 | } | 
 |  | 
 | static gdb_bfd_ref_ptr | 
 | rocm_solib_bfd_open (const char *pathname) | 
 | { | 
 |   /* Handle regular files with SVR4 open.  */ | 
 |   if (strstr (pathname, "://") == nullptr) | 
 |     return svr4_so_ops.bfd_open (pathname); | 
 |  | 
 |   auto open = [] (bfd *nbfd) -> gdb_bfd_iovec_base * | 
 |   { | 
 |     return rocm_bfd_iovec_open (nbfd, current_inferior ()); | 
 |   }; | 
 |  | 
 |   gdb_bfd_ref_ptr abfd = gdb_bfd_openr_iovec (pathname, "elf64-amdgcn", open); | 
 |  | 
 |   if (abfd == nullptr) | 
 |     error (_("Could not open `%s' as an executable file: %s"), pathname, | 
 | 	   bfd_errmsg (bfd_get_error ())); | 
 |  | 
 |   /* Check bfd format.  */ | 
 |   if (!bfd_check_format (abfd.get (), bfd_object)) | 
 |     error (_("`%s': not in executable format: %s"), | 
 | 	   bfd_get_filename (abfd.get ()), bfd_errmsg (bfd_get_error ())); | 
 |  | 
 |   unsigned char osabi = elf_elfheader (abfd)->e_ident[EI_OSABI]; | 
 |   unsigned char osabiversion = elf_elfheader (abfd)->e_ident[EI_ABIVERSION]; | 
 |  | 
 |   /* Check that the code object is using the HSA OS ABI.  */ | 
 |   if (osabi != ELFOSABI_AMDGPU_HSA) | 
 |     error (_("`%s': ELF file OS ABI is not supported (%d)."), | 
 | 	   bfd_get_filename (abfd.get ()), osabi); | 
 |  | 
 |   /* We support HSA code objects V3 and greater.  */ | 
 |   if (osabiversion < ELFABIVERSION_AMDGPU_HSA_V3) | 
 |     error (_("`%s': ELF file HSA OS ABI version is not supported (%d)."), | 
 | 	   bfd_get_filename (abfd.get ()), osabiversion); | 
 |  | 
 |   /* For GDB to be able to use this solib, the exact AMDGPU processor type | 
 |      must be supported by both BFD and the amd-dbgapi library.  */ | 
 |   const unsigned char gfx_arch | 
 |     = elf_elfheader (abfd)->e_flags & EF_AMDGPU_MACH ; | 
 |   const bfd_arch_info_type *bfd_arch_info | 
 |     = bfd_lookup_arch (bfd_arch_amdgcn, gfx_arch); | 
 |  | 
 |   amd_dbgapi_architecture_id_t architecture_id; | 
 |   amd_dbgapi_status_t dbgapi_query_arch | 
 |     = amd_dbgapi_get_architecture (gfx_arch, &architecture_id); | 
 |  | 
 |   if (dbgapi_query_arch != AMD_DBGAPI_STATUS_SUCCESS | 
 |       || bfd_arch_info ==  nullptr) | 
 |     { | 
 |       if (dbgapi_query_arch != AMD_DBGAPI_STATUS_SUCCESS | 
 | 	  && bfd_arch_info ==  nullptr) | 
 | 	{ | 
 | 	  /* Neither of the libraries knows about this arch, so we cannot | 
 | 	     provide a human readable name for it.  */ | 
 | 	  error (_("'%s': AMDGCN architecture %#02x is not supported."), | 
 | 		 bfd_get_filename (abfd.get ()), gfx_arch); | 
 | 	} | 
 |       else if (dbgapi_query_arch != AMD_DBGAPI_STATUS_SUCCESS) | 
 | 	{ | 
 | 	  gdb_assert (bfd_arch_info != nullptr); | 
 | 	  error (_("'%s': AMDGCN architecture %s not supported by " | 
 | 		   "amd-dbgapi."), | 
 | 		 bfd_get_filename (abfd.get ()), | 
 | 		 bfd_arch_info->printable_name); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  gdb_assert (dbgapi_query_arch == AMD_DBGAPI_STATUS_SUCCESS); | 
 | 	  char *arch_name; | 
 | 	  if (amd_dbgapi_architecture_get_info | 
 | 	      (architecture_id, AMD_DBGAPI_ARCHITECTURE_INFO_NAME, | 
 | 	       sizeof (arch_name), &arch_name) != AMD_DBGAPI_STATUS_SUCCESS) | 
 | 	    error ("amd_dbgapi_architecture_get_info call failed for arch " | 
 | 		   "%#02x.", gfx_arch); | 
 | 	  gdb::unique_xmalloc_ptr<char> arch_name_cleaner (arch_name); | 
 |  | 
 | 	  error (_("'%s': AMDGCN architecture %s not supported."), | 
 | 		 bfd_get_filename (abfd.get ()), | 
 | 		 arch_name); | 
 | 	} | 
 |     } | 
 |  | 
 |   gdb_assert (gdbarch_from_bfd (abfd.get ()) != nullptr); | 
 |   gdb_assert (is_amdgpu_arch (gdbarch_from_bfd (abfd.get ()))); | 
 |  | 
 |   return abfd; | 
 | } | 
 |  | 
 | static void | 
 | rocm_solib_create_inferior_hook (int from_tty) | 
 | { | 
 |   get_solib_info (current_inferior ())->solib_list.clear (); | 
 |  | 
 |   svr4_so_ops.solib_create_inferior_hook (from_tty); | 
 | } | 
 |  | 
 | static void | 
 | rocm_update_solib_list () | 
 | { | 
 |   inferior *inf = current_inferior (); | 
 |  | 
 |   amd_dbgapi_process_id_t process_id = get_amd_dbgapi_process_id (inf); | 
 |   if (process_id.handle == AMD_DBGAPI_PROCESS_NONE.handle) | 
 |     return; | 
 |  | 
 |   solib_info *info = get_solib_info (inf); | 
 |  | 
 |   info->solib_list.clear (); | 
 |   std::vector<rocm_so> &sos = info->solib_list; | 
 |  | 
 |   amd_dbgapi_code_object_id_t *code_object_list; | 
 |   size_t count; | 
 |  | 
 |   amd_dbgapi_status_t status | 
 |     = amd_dbgapi_process_code_object_list (process_id, &count, | 
 | 					   &code_object_list, nullptr); | 
 |   if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
 |     { | 
 |       warning (_("amd_dbgapi_process_code_object_list failed (%s)"), | 
 | 	       get_status_string (status)); | 
 |       return; | 
 |     } | 
 |  | 
 |   for (size_t i = 0; i < count; ++i) | 
 |     { | 
 |       CORE_ADDR l_addr; | 
 |       char *uri_bytes; | 
 |  | 
 |       status = amd_dbgapi_code_object_get_info | 
 | 	(code_object_list[i], AMD_DBGAPI_CODE_OBJECT_INFO_LOAD_ADDRESS, | 
 | 	 sizeof (l_addr), &l_addr); | 
 |       if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
 | 	continue; | 
 |  | 
 |       status = amd_dbgapi_code_object_get_info | 
 | 	(code_object_list[i], AMD_DBGAPI_CODE_OBJECT_INFO_URI_NAME, | 
 | 	 sizeof (uri_bytes), &uri_bytes); | 
 |       if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
 | 	continue; | 
 |  | 
 |       gdb::unique_xmalloc_ptr<char> uri_bytes_holder (uri_bytes); | 
 |  | 
 |       lm_info_svr4_up li = std::make_unique<lm_info_svr4> (); | 
 |       li->l_addr = l_addr; | 
 |  | 
 |       /* Generate a unique name so that code objects with the same URI but | 
 | 	 different load addresses are seen by gdb core as different shared | 
 | 	 objects.  */ | 
 |       std::string unique_name | 
 | 	= string_printf ("code_object_%ld", code_object_list[i].handle); | 
 |  | 
 |       sos.emplace_back (uri_bytes, std::move (unique_name), std::move (li)); | 
 |     } | 
 |  | 
 |   xfree (code_object_list); | 
 |  | 
 |   if (rocm_solib_ops.current_sos == NULL) | 
 |     { | 
 |       /* Override what we need to.  */ | 
 |       rocm_solib_ops = svr4_so_ops; | 
 |       rocm_solib_ops.current_sos = rocm_solib_current_sos; | 
 |       rocm_solib_ops.solib_create_inferior_hook | 
 | 	= rocm_solib_create_inferior_hook; | 
 |       rocm_solib_ops.bfd_open = rocm_solib_bfd_open; | 
 |       rocm_solib_ops.relocate_section_addresses | 
 | 	= rocm_solib_relocate_section_addresses; | 
 |       rocm_solib_ops.handle_event = rocm_solib_handle_event; | 
 |  | 
 |       /* Engage the ROCm so_ops.  */ | 
 |       set_gdbarch_so_ops (current_inferior ()->arch (), &rocm_solib_ops); | 
 |     } | 
 | } | 
 |  | 
 | static void | 
 | rocm_solib_target_inferior_created (inferior *inf) | 
 | { | 
 |   get_solib_info (inf)->solib_list.clear (); | 
 |  | 
 |   rocm_update_solib_list (); | 
 |  | 
 |   /* Force GDB to reload the solibs.  */ | 
 |   current_inferior ()->pspace->clear_solib_cache (); | 
 |   solib_add (nullptr, 0, auto_solib_add); | 
 | } | 
 |  | 
 | /* -Wmissing-prototypes */ | 
 | extern initialize_file_ftype _initialize_rocm_solib; | 
 |  | 
 | void | 
 | _initialize_rocm_solib () | 
 | { | 
 |   /* The dependency on the amd-dbgapi exists because solib-rocm's | 
 |      inferior_created observer needs amd-dbgapi to have attached the process, | 
 |      which happens in amd_dbgapi_target's inferior_created observer.  */ | 
 |   gdb::observers::inferior_created.attach | 
 |     (rocm_solib_target_inferior_created, | 
 |      "solib-rocm", | 
 |      { &get_amd_dbgapi_target_inferior_created_observer_token () }); | 
 | } |